From 168ef90e8c9f260e1efccb5a56f326822ee2bc94 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Mon, 1 Mar 2021 14:38:13 -0800 Subject: [PATCH 1/9] [Security Solution][Endpoint][Admin] Fixes 7.12 ransomware migration and mac bug (#92639) (#93105) --- .../common/endpoint/models/policy_config.ts | 14 ----------- .../policy/migrations/to_v7_12_0.test.ts | 24 ++----------------- .../endpoint/policy/migrations/to_v7_12_0.ts | 9 +++---- .../common/endpoint/types/index.ts | 7 +----- .../common/license/policy_config.test.ts | 19 --------------- .../common/license/policy_config.ts | 16 ++++--------- .../policy/store/policy_details/index.test.ts | 5 ---- .../policy/store/policy_details/middleware.ts | 1 - .../policy/store/policy_details/selectors.ts | 1 - .../policy_forms/protections/ransomware.tsx | 6 ++--- .../apps/endpoint/policy_details.ts | 15 ------------ 11 files changed, 14 insertions(+), 103 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts index 04fa8e08785ed..cbac2d03cfb97 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts @@ -54,18 +54,11 @@ export const policyFactory = (): PolicyConfig => { malware: { mode: ProtectionModes.prevent, }, - ransomware: { - mode: ProtectionModes.prevent, - }, popup: { malware: { message: '', enabled: true, }, - ransomware: { - message: '', - enabled: true, - }, }, logging: { file: 'info', @@ -111,19 +104,12 @@ export const policyFactoryWithoutPaidFeatures = ( }, mac: { ...policy.mac, - ransomware: { - mode: ProtectionModes.off, - }, popup: { ...policy.mac.popup, malware: { message: '', enabled: true, }, - ransomware: { - message: '', - enabled: false, - }, }, }, }; diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts index 6c583d60b7b15..936d90cc1aa9c 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts @@ -49,15 +49,6 @@ describe('7.12.0 Endpoint Package Policy migration', () => { }, }, }, - mac: { - // @ts-expect-error - popup: { - malware: { - message: '', - enabled: false, - }, - }, - }, }, }, }, @@ -96,20 +87,9 @@ describe('7.12.0 Endpoint Package Policy migration', () => { policy: { value: { windows: { - ransomware: ProtectionModes.off, - popup: { - malware: { - message: '', - enabled: false, - }, - ransomware: { - message: '', - enabled: false, - }, + ransomware: { + mode: ProtectionModes.off, }, - }, - mac: { - ransomware: ProtectionModes.off, popup: { malware: { message: '', diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts index 5c0bf8e965026..06d505a71025f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts +++ b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts @@ -19,16 +19,17 @@ export const migratePackagePolicyToV7120: SavedObjectMigrationFn; + mac: Pick; /** * Linux-specific policy configuration that is supported via the UI */ diff --git a/x-pack/plugins/security_solution/common/license/policy_config.test.ts b/x-pack/plugins/security_solution/common/license/policy_config.test.ts index fcfb1acfa85b4..e8637e43ce1c7 100644 --- a/x-pack/plugins/security_solution/common/license/policy_config.test.ts +++ b/x-pack/plugins/security_solution/common/license/policy_config.test.ts @@ -77,7 +77,6 @@ describe('policy_config and licenses', () => { it('allows ransomware to be turned on for Platinum licenses', () => { const policy = policyFactoryWithoutPaidFeatures(); policy.windows.ransomware.mode = ProtectionModes.prevent; - policy.mac.ransomware.mode = ProtectionModes.prevent; const valid = isEndpointPolicyValidForLicense(policy, Platinum); expect(valid).toBeTruthy(); @@ -85,7 +84,6 @@ describe('policy_config and licenses', () => { it('blocks ransomware to be turned on for Gold and below licenses', () => { const policy = policyFactoryWithoutPaidFeatures(); policy.windows.ransomware.mode = ProtectionModes.prevent; - policy.mac.ransomware.mode = ProtectionModes.prevent; let valid = isEndpointPolicyValidForLicense(policy, Gold); expect(valid).toBeFalsy(); @@ -96,14 +94,12 @@ describe('policy_config and licenses', () => { it('allows ransomware notification to be turned on with a Platinum license', () => { const policy = policyFactoryWithoutPaidFeatures(); policy.windows.popup.ransomware.enabled = true; - policy.mac.popup.ransomware.enabled = true; const valid = isEndpointPolicyValidForLicense(policy, Platinum); expect(valid).toBeTruthy(); }); it('blocks ransomware notification to be turned on for Gold and below licenses', () => { const policy = policyFactoryWithoutPaidFeatures(); policy.windows.popup.ransomware.enabled = true; - policy.mac.popup.ransomware.enabled = true; let valid = isEndpointPolicyValidForLicense(policy, Gold); expect(valid).toBeFalsy(); @@ -114,14 +110,12 @@ describe('policy_config and licenses', () => { it('allows ransomware notification message changes with a Platinum license', () => { const policy = policyFactory(); policy.windows.popup.ransomware.message = 'BOOM'; - policy.mac.popup.ransomware.message = 'BOOM'; const valid = isEndpointPolicyValidForLicense(policy, Platinum); expect(valid).toBeTruthy(); }); it('blocks ransomware notification message changes for Gold and below licenses', () => { const policy = policyFactory(); policy.windows.popup.ransomware.message = 'BOOM'; - policy.mac.popup.ransomware.message = 'BOOM'; let valid = isEndpointPolicyValidForLicense(policy, Gold); expect(valid).toBeFalsy(); @@ -154,19 +148,13 @@ describe('policy_config and licenses', () => { const policy = policyFactory(); const popupMessage = 'WOOP WOOP'; policy.windows.ransomware.mode = ProtectionModes.detect; - policy.mac.ransomware.mode = ProtectionModes.detect; policy.windows.popup.ransomware.enabled = false; - policy.mac.popup.ransomware.enabled = false; policy.windows.popup.ransomware.message = popupMessage; - policy.mac.popup.ransomware.message = popupMessage; const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Platinum); expect(retPolicy.windows.ransomware.mode).toEqual(ProtectionModes.detect); - expect(retPolicy.mac.ransomware.mode).toEqual(ProtectionModes.detect); expect(retPolicy.windows.popup.ransomware.enabled).toBeFalsy(); - expect(retPolicy.mac.popup.ransomware.enabled).toBeFalsy(); expect(retPolicy.windows.popup.ransomware.message).toEqual(popupMessage); - expect(retPolicy.mac.popup.ransomware.message).toEqual(popupMessage); }); it('resets Platinum-paid malware fields for lower license tiers', () => { @@ -178,14 +166,12 @@ describe('policy_config and licenses', () => { policy.windows.popup.malware.enabled = false; policy.windows.popup.ransomware.message = popupMessage; - policy.mac.popup.ransomware.message = popupMessage; policy.windows.popup.ransomware.enabled = false; const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Gold); expect(retPolicy.windows.popup.malware.enabled).toEqual( defaults.windows.popup.malware.enabled ); expect(retPolicy.windows.popup.malware.message).not.toEqual(popupMessage); - expect(retPolicy.mac.popup.malware.message).not.toEqual(popupMessage); // need to invert the test, since it could be either value expect(['', DefaultMalwareMessage]).toContain(retPolicy.windows.popup.malware.message); @@ -196,22 +182,17 @@ describe('policy_config and licenses', () => { const policy = policyFactory(); // what we will modify, and should be reset const popupMessage = 'WOOP WOOP'; policy.windows.popup.ransomware.message = popupMessage; - policy.mac.popup.ransomware.message = popupMessage; const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Gold); expect(retPolicy.windows.ransomware.mode).toEqual(defaults.windows.ransomware.mode); - expect(retPolicy.mac.ransomware.mode).toEqual(defaults.mac.ransomware.mode); expect(retPolicy.windows.popup.ransomware.enabled).toEqual( defaults.windows.popup.ransomware.enabled ); - expect(retPolicy.mac.popup.ransomware.enabled).toEqual(defaults.mac.popup.ransomware.enabled); expect(retPolicy.windows.popup.ransomware.message).not.toEqual(popupMessage); - expect(retPolicy.mac.popup.ransomware.message).not.toEqual(popupMessage); // need to invert the test, since it could be either value expect(['', DefaultMalwareMessage]).toContain(retPolicy.windows.popup.ransomware.message); - expect(['', DefaultMalwareMessage]).toContain(retPolicy.mac.popup.ransomware.message); }); }); diff --git a/x-pack/plugins/security_solution/common/license/policy_config.ts b/x-pack/plugins/security_solution/common/license/policy_config.ts index fd5105ca03502..903e241b1b490 100644 --- a/x-pack/plugins/security_solution/common/license/policy_config.ts +++ b/x-pack/plugins/security_solution/common/license/policy_config.ts @@ -45,27 +45,19 @@ export const isEndpointPolicyValidForLicense = ( } // only platinum or higher may enable ransomware - if ( - policy.windows.ransomware.mode !== defaults.windows.ransomware.mode || - policy.mac.ransomware.mode !== defaults.mac.ransomware.mode - ) { + if (policy.windows.ransomware.mode !== defaults.windows.ransomware.mode) { return false; } // only platinum or higher may enable ransomware notification - if ( - policy.windows.popup.ransomware.enabled !== defaults.windows.popup.ransomware.enabled || - policy.mac.popup.ransomware.enabled !== defaults.mac.popup.ransomware.enabled - ) { + if (policy.windows.popup.ransomware.enabled !== defaults.windows.popup.ransomware.enabled) { return false; } // Only Platinum or higher may change the ransomware message (which can be blank or what Endpoint defaults) if ( - [policy.windows, policy.mac].some( - (p) => - p.popup.ransomware.message !== '' && p.popup.ransomware.message !== DefaultMalwareMessage - ) + policy.windows.popup.ransomware.message !== '' && + policy.windows.popup.ransomware.message !== DefaultMalwareMessage ) { return false; } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts index 14c3ecd591631..74dfbe4dec3ba 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts @@ -303,16 +303,11 @@ describe('policy details: ', () => { mac: { events: { process: true, file: true, network: true }, malware: { mode: 'prevent' }, - ransomware: { mode: 'off' }, popup: { malware: { enabled: true, message: '', }, - ransomware: { - enabled: false, - message: '', - }, }, logging: { file: 'info' }, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts index 8cd712cdb21fd..2e44021a79126 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts @@ -44,7 +44,6 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory UIPolicyConfig = createSel advanced: mac.advanced, events: mac.events, malware: mac.malware, - ransomware: mac.ransomware, popup: mac.popup, }, linux: { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx index 7a672474029e3..ccc056d81e912 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx @@ -42,7 +42,7 @@ import { AppAction } from '../../../../../../common/store/actions'; import { SupportedVersionNotice } from './supported_version'; import { RadioFlexGroup } from './malware'; -const OSes: Immutable = [OS.windows, OS.mac]; +const OSes: Immutable = [OS.windows]; const protection = 'ransomware'; const ProtectionRadio = React.memo( @@ -50,7 +50,6 @@ const ProtectionRadio = React.memo( const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); const dispatch = useDispatch<(action: AppAction) => void>(); const radioButtonId = useMemo(() => htmlIdGenerator()(), []); - // currently just taking windows.ransomware, but both windows.ransomware and mac.ransomware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.ransomware.mode; const handleRadioChange: EuiRadioProps['onChange'] = useCallback(() => { @@ -97,7 +96,6 @@ ProtectionRadio.displayName = 'ProtectionRadio'; export const Ransomware = React.memo(() => { const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); const dispatch = useDispatch<(action: AppAction) => void>(); - // currently just taking windows.ransomware, but both windows.ransomware and mac.ransomware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.ransomware.mode; const userNotificationSelected = policyDetailsConfig && policyDetailsConfig.windows.popup.ransomware.enabled; @@ -318,7 +316,7 @@ export const Ransomware = React.memo(() => { type={i18n.translate('xpack.securitySolution.endpoint.policy.details.ransomware', { defaultMessage: 'Ransomware', })} - supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC]} + supportedOss={[OperatingSystem.WINDOWS]} dataTestSubj="ransomwareProtectionsForm" rightCorner={protectionSwitch} > diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 40972da0044bc..298f3df58d6ef 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -256,16 +256,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { events: { file: false, network: true, process: true }, logging: { file: 'info' }, malware: { mode: 'prevent' }, - ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, }, }, windows: { @@ -410,16 +405,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { events: { file: true, network: true, process: true }, logging: { file: 'info' }, malware: { mode: 'prevent' }, - ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, }, }, windows: { @@ -557,16 +547,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { events: { file: true, network: true, process: true }, logging: { file: 'info' }, malware: { mode: 'prevent' }, - ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, }, }, windows: { From 8c7281236699873b8125a38a4326f4a6553133b5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 1 Mar 2021 15:59:28 -0700 Subject: [PATCH 2/9] [Maps] fix fit to data on heatmap not working (#92697) (#93113) * [Maps] fix fit to data on heatmap not working * tslint Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../layers/heatmap_layer/heatmap_layer.ts | 33 +++++++++++- .../classes/layers/vector_layer/index.ts | 2 +- .../classes/layers/vector_layer/utils.tsx | 44 +++++++++++++++- .../layers/vector_layer/vector_layer.tsx | 52 ++++--------------- 4 files changed, 86 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts index 8eebd7c57afd7..96c7fcedaf3d9 100644 --- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts @@ -12,7 +12,7 @@ import { HeatmapStyle } from '../../styles/heatmap/heatmap_style'; import { EMPTY_FEATURE_COLLECTION, LAYER_TYPE } from '../../../../common/constants'; import { HeatmapLayerDescriptor, MapQuery } from '../../../../common/descriptor_types'; import { ESGeoGridSource } from '../../sources/es_geo_grid_source'; -import { addGeoJsonMbSource, syncVectorSource } from '../vector_layer'; +import { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from '../vector_layer'; import { DataRequestContext } from '../../../actions'; import { DataRequestAbortError } from '../../util/data_request'; @@ -46,6 +46,12 @@ export class HeatmapLayer extends AbstractLayer { } } + destroy() { + if (this.getSource()) { + this.getSource().destroy(); + } + } + getSource(): ESGeoGridSource { return super.getSource() as ESGeoGridSource; } @@ -179,4 +185,29 @@ export class HeatmapLayer extends AbstractLayer { const metricFields = this.getSource().getMetricFields(); return this.getCurrentStyle().renderLegendDetails(metricFields[0]); } + + async getBounds(syncContext: DataRequestContext) { + return await getVectorSourceBounds({ + layerId: this.getId(), + syncContext, + source: this.getSource(), + sourceQuery: this.getQuery() as MapQuery, + }); + } + + async isFilteredByGlobalTime(): Promise { + return this.getSource().getApplyGlobalTime() && (await this.getSource().isTimeAware()); + } + + getIndexPatternIds() { + return this.getSource().getIndexPatternIds(); + } + + getQueryableIndexPatternIds() { + return this.getSource().getQueryableIndexPatternIds(); + } + + async getLicensedFeatures() { + return await this.getSource().getLicensedFeatures(); + } } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts index 4b509ba5dff00..b6777f8a5e454 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { addGeoJsonMbSource, syncVectorSource } from './utils'; +export { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; export { IVectorLayer, VectorLayer, VectorLayerArguments } from './vector_layer'; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx index a3754b20de818..91bdd74c158f9 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx @@ -9,10 +9,11 @@ import { FeatureCollection } from 'geojson'; import { Map as MbMap } from 'mapbox-gl'; import { EMPTY_FEATURE_COLLECTION, + SOURCE_BOUNDS_DATA_REQUEST_ID, SOURCE_DATA_REQUEST_ID, VECTOR_SHAPE_TYPE, } from '../../../../common/constants'; -import { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; +import { MapExtent, MapQuery, VectorSourceRequestMeta } from '../../../../common/descriptor_types'; import { DataRequestContext } from '../../../actions'; import { IVectorSource } from '../../sources/vector_source'; import { DataRequestAbortError } from '../../util/data_request'; @@ -112,3 +113,44 @@ export async function syncVectorSource({ throw error; } } + +export async function getVectorSourceBounds({ + layerId, + syncContext, + source, + sourceQuery, +}: { + layerId: string; + syncContext: DataRequestContext; + source: IVectorSource; + sourceQuery: MapQuery | null; +}): Promise { + const { startLoading, stopLoading, registerCancelCallback, dataFilters } = syncContext; + + const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${layerId}`); + + // Do not pass all searchFilters to source.getBoundsForFilters(). + // For example, do not want to filter bounds request by extent and buffer. + const boundsFilters = { + sourceQuery: sourceQuery ? sourceQuery : undefined, + query: dataFilters.query, + timeFilters: dataFilters.timeFilters, + filters: dataFilters.filters, + applyGlobalQuery: source.getApplyGlobalQuery(), + applyGlobalTime: source.getApplyGlobalTime(), + }; + + let bounds = null; + try { + startLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, boundsFilters); + bounds = await source.getBoundsForFilters( + boundsFilters, + registerCancelCallback.bind(null, requestToken) + ); + } finally { + // Use stopLoading callback instead of onLoadError callback. + // Function is loading bounds and not feature data. + stopLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, bounds ? bounds : {}); + } + return bounds; +} diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 2373ed3ba2062..b21bff9922671 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -17,7 +17,6 @@ import { FEATURE_ID_PROPERTY_NAME, SOURCE_META_DATA_REQUEST_ID, SOURCE_FORMATTERS_DATA_REQUEST_ID, - SOURCE_BOUNDS_DATA_REQUEST_ID, FEATURE_VISIBLE_PROPERTY_NAME, EMPTY_FEATURE_COLLECTION, KBN_TOO_MANY_FEATURES_PROPERTY, @@ -60,7 +59,7 @@ import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_st import { IESSource } from '../../sources/es_source'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { ITermJoinSource } from '../../sources/term_join_source'; -import { addGeoJsonMbSource, syncVectorSource } from './utils'; +import { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; interface SourceResult { refreshed: boolean; @@ -241,47 +240,16 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this.getCurrentStyle().renderLegendDetails(); } - async getBounds({ - startLoading, - stopLoading, - registerCancelCallback, - dataFilters, - }: DataRequestContext) { + async getBounds(syncContext: DataRequestContext) { const isStaticLayer = !this.getSource().isBoundsAware(); - if (isStaticLayer || this.hasJoins()) { - return getFeatureCollectionBounds(this._getSourceFeatureCollection(), this.hasJoins()); - } - - const requestToken = Symbol(`${SOURCE_BOUNDS_DATA_REQUEST_ID}-${this.getId()}`); - const searchFilters: VectorSourceRequestMeta = this._getSearchFilters( - dataFilters, - this.getSource(), - this.getCurrentStyle() - ); - // Do not pass all searchFilters to source.getBoundsForFilters(). - // For example, do not want to filter bounds request by extent and buffer. - const boundsFilters = { - sourceQuery: searchFilters.sourceQuery, - query: searchFilters.query, - timeFilters: searchFilters.timeFilters, - filters: searchFilters.filters, - applyGlobalQuery: searchFilters.applyGlobalQuery, - applyGlobalTime: searchFilters.applyGlobalTime, - }; - - let bounds = null; - try { - startLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, boundsFilters); - bounds = await this.getSource().getBoundsForFilters( - boundsFilters, - registerCancelCallback.bind(null, requestToken) - ); - } finally { - // Use stopLoading callback instead of onLoadError callback. - // Function is loading bounds and not feature data. - stopLoading(SOURCE_BOUNDS_DATA_REQUEST_ID, requestToken, bounds ? bounds : {}, boundsFilters); - } - return bounds; + return isStaticLayer || this.hasJoins() + ? getFeatureCollectionBounds(this._getSourceFeatureCollection(), this.hasJoins()) + : getVectorSourceBounds({ + layerId: this.getId(), + syncContext, + source: this.getSource(), + sourceQuery: this.getQuery() as MapQuery, + }); } async getLeftJoinFields() { From 60d44cf31b793983792a675f55abc4bb7ea76e16 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Mon, 1 Mar 2021 18:02:36 -0500 Subject: [PATCH 3/9] [Dashboard] Refactor Initial View Mode (#92747) (#93107) * refactored dashboard initial view mode --- .../public/application/dashboard_app.tsx | 14 +++- .../application/dashboard_state.test.ts | 70 +++++++++++++++++++ .../application/dashboard_state_manager.ts | 35 +++++++--- .../hooks/use_dashboard_container.test.tsx | 50 +++++++++---- .../hooks/use_dashboard_container.ts | 12 ++-- .../hooks/use_dashboard_state_manager.ts | 11 ++- .../application/lib/get_app_state_defaults.ts | 4 +- .../lib/session_restoration.test.ts | 3 +- .../application/top_nav/dashboard_top_nav.tsx | 1 - .../application/top_nav/get_top_nav_config.ts | 2 +- 10 files changed, 162 insertions(+), 40 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 8466cf009db9d..fd73741cef8cb 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -63,14 +63,26 @@ export function DashboardApp({ const [indexPatterns, setIndexPatterns] = useState([]); const savedDashboard = useSavedDashboard(savedDashboardId, history); + + const getIncomingEmbeddable = useCallback( + (removeAfterFetch?: boolean) => { + return embeddable + .getStateTransfer() + .getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, removeAfterFetch); + }, + [embeddable] + ); + const { dashboardStateManager, viewMode, setViewMode } = useDashboardStateManager( savedDashboard, - history + history, + getIncomingEmbeddable ); const [unsavedChanges, setUnsavedChanges] = useState(false); const dashboardContainer = useDashboardContainer({ timeFilter: data.query.timefilter.timefilter, dashboardStateManager, + getIncomingEmbeddable, setUnsavedChanges, history, }); diff --git a/src/plugins/dashboard/public/application/dashboard_state.test.ts b/src/plugins/dashboard/public/application/dashboard_state.test.ts index c5bda98c31b70..ffe5c80febe02 100644 --- a/src/plugins/dashboard/public/application/dashboard_state.test.ts +++ b/src/plugins/dashboard/public/application/dashboard_state.test.ts @@ -43,6 +43,7 @@ describe('DashboardState', function () { savedDashboard, hideWriteControls: false, allowByValueEmbeddables: false, + hasPendingEmbeddable: () => false, kibanaVersion: '7.0.0', kbnUrlStateStorage: createKbnUrlStateStorage(), history: createBrowserHistory(), @@ -199,4 +200,73 @@ describe('DashboardState', function () { expect(dashboardState.getIsDirty()).toBeFalsy(); }); }); + + describe('initial view mode', () => { + test('initial view mode set to view when hideWriteControls is true', () => { + const initialViewModeDashboardState = new DashboardStateManager({ + savedDashboard, + hideWriteControls: true, + allowByValueEmbeddables: false, + hasPendingEmbeddable: () => false, + kibanaVersion: '7.0.0', + kbnUrlStateStorage: createKbnUrlStateStorage(), + history: createBrowserHistory(), + toasts: coreMock.createStart().notifications.toasts, + hasTaggingCapabilities: mockHasTaggingCapabilities, + }); + expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.VIEW); + }); + + test('initial view mode set to edit if edit mode specified in URL', () => { + const kbnUrlStateStorage = createKbnUrlStateStorage(); + kbnUrlStateStorage.set('_a', { viewMode: ViewMode.EDIT }); + + const initialViewModeDashboardState = new DashboardStateManager({ + savedDashboard, + kbnUrlStateStorage, + kibanaVersion: '7.0.0', + hideWriteControls: false, + allowByValueEmbeddables: false, + history: createBrowserHistory(), + hasPendingEmbeddable: () => false, + toasts: coreMock.createStart().notifications.toasts, + hasTaggingCapabilities: mockHasTaggingCapabilities, + }); + expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.EDIT); + }); + + test('initial view mode set to edit if the dashboard is new', () => { + const newDashboard = getSavedDashboardMock(); + newDashboard.id = undefined; + const initialViewModeDashboardState = new DashboardStateManager({ + savedDashboard: newDashboard, + kibanaVersion: '7.0.0', + hideWriteControls: false, + allowByValueEmbeddables: false, + history: createBrowserHistory(), + hasPendingEmbeddable: () => false, + kbnUrlStateStorage: createKbnUrlStateStorage(), + toasts: coreMock.createStart().notifications.toasts, + hasTaggingCapabilities: mockHasTaggingCapabilities, + }); + expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.EDIT); + }); + + test('initial view mode set to edit if there is a pending embeddable', () => { + const newDashboard = getSavedDashboardMock(); + newDashboard.id = undefined; + const initialViewModeDashboardState = new DashboardStateManager({ + savedDashboard: newDashboard, + kibanaVersion: '7.0.0', + hideWriteControls: false, + allowByValueEmbeddables: false, + history: createBrowserHistory(), + hasPendingEmbeddable: () => true, + kbnUrlStateStorage: createKbnUrlStateStorage(), + toasts: coreMock.createStart().notifications.toasts, + hasTaggingCapabilities: mockHasTaggingCapabilities, + }); + expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.EDIT); + }); + }); }); diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index 51b12baad4769..d11bdd0399d41 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -88,6 +88,7 @@ export class DashboardStateManager { private readonly usageCollection: UsageCollectionSetup | undefined; public readonly hasTaggingCapabilities: SavedObjectTagDecoratorTypeGuard; + private hasPendingEmbeddable: () => boolean; /** * @@ -104,6 +105,7 @@ export class DashboardStateManager { usageCollection, hideWriteControls, kbnUrlStateStorage, + hasPendingEmbeddable, dashboardPanelStorage, hasTaggingCapabilities, allowByValueEmbeddables, @@ -111,6 +113,7 @@ export class DashboardStateManager { history: History; kibanaVersion: string; hideWriteControls: boolean; + hasPendingEmbeddable: () => boolean; allowByValueEmbeddables: boolean; savedDashboard: DashboardSavedObject; toasts: NotificationsStart['toasts']; @@ -126,15 +129,17 @@ export class DashboardStateManager { this.usageCollection = usageCollection; this.hasTaggingCapabilities = hasTaggingCapabilities; this.allowByValueEmbeddables = allowByValueEmbeddables; + this.hasPendingEmbeddable = hasPendingEmbeddable; + this.dashboardPanelStorage = dashboardPanelStorage; + this.kbnUrlStateStorage = kbnUrlStateStorage; // get state defaults from saved dashboard, make sure it is migrated + const viewMode = this.getInitialViewMode(); this.stateDefaults = migrateAppState( - getAppStateDefaults(this.savedDashboard, this.hideWriteControls, this.hasTaggingCapabilities), + getAppStateDefaults(viewMode, this.savedDashboard, this.hasTaggingCapabilities), kibanaVersion, usageCollection ); - this.dashboardPanelStorage = dashboardPanelStorage; - this.kbnUrlStateStorage = kbnUrlStateStorage; // setup initial state by merging defaults with state from url & panels storage // also run migration, as state in url could be of older version @@ -357,8 +362,9 @@ export class DashboardStateManager { // The right way to fix this might be to ensure the defaults object stored on state is a deep // clone, but given how much code uses the state object, I determined that to be too risky of a change for // now. TODO: revisit this! + const currentViewMode = this.stateContainer.get().viewMode; this.stateDefaults = migrateAppState( - getAppStateDefaults(this.savedDashboard, this.hideWriteControls, this.hasTaggingCapabilities), + getAppStateDefaults(currentViewMode, this.savedDashboard, this.hasTaggingCapabilities), this.kibanaVersion, this.usageCollection ); @@ -369,8 +375,7 @@ export class DashboardStateManager { this.stateDefaults.filters = [...this.getLastSavedFilterBars()]; this.isDirty = false; - const currentViewMode = this.stateContainer.get().viewMode; - this.stateContainer.set({ ...this.stateDefaults, viewMode: currentViewMode }); + this.stateContainer.set(this.stateDefaults); } /** @@ -534,9 +539,7 @@ export class DashboardStateManager { return this.appState.viewMode; } // get viewMode should work properly even before the state container is created - return this.savedDashboard.id - ? this.kbnUrlStateStorage.get(STATE_STORAGE_KEY)?.viewMode ?? ViewMode.VIEW - : ViewMode.EDIT; + return this.getInitialViewMode(); } public getIsViewMode() { @@ -668,6 +671,7 @@ export class DashboardStateManager { public switchViewMode(newMode: ViewMode) { this.stateContainer.transitions.set('viewMode', newMode); + this.restorePanels(); } /** @@ -692,6 +696,7 @@ export class DashboardStateManager { ...this.stateDefaults, ...unsavedState, ...this.kbnUrlStateStorage.get(STATE_STORAGE_KEY), + viewMode: this.getViewMode(), }, this.kibanaVersion, this.usageCollection @@ -739,6 +744,18 @@ export class DashboardStateManager { return stateWithoutPanels; } + private getInitialViewMode() { + if (this.hideWriteControls) { + return ViewMode.VIEW; + } + const viewModeFromUrl = this.kbnUrlStateStorage.get(STATE_STORAGE_KEY) + ?.viewMode; + if (viewModeFromUrl) { + return viewModeFromUrl; + } + return !this.savedDashboard.id || this.hasPendingEmbeddable() ? ViewMode.EDIT : ViewMode.VIEW; + } + private checkIsDirty() { // Filters need to be compared manually because they sometimes have a $$hashkey stored on the object. // Query needs to be compared manually because saved legacy queries get migrated in app state automatically diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx index 6a6dc58db7815..b2fc7c917d285 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx @@ -37,6 +37,7 @@ const createDashboardState = () => hideWriteControls: false, allowByValueEmbeddables: false, history: createBrowserHistory(), + hasPendingEmbeddable: () => false, kbnUrlStateStorage: createKbnUrlStateStorage(), hasTaggingCapabilities: mockHasTaggingCapabilities, toasts: coreMock.createStart().notifications.toasts, @@ -53,6 +54,8 @@ const defaultCapabilities: DashboardCapabilities = { storeSearchSession: true, }; +const getIncomingEmbeddable = () => undefined; + const services = { dashboardCapabilities: defaultCapabilities, data: dataPluginMock.createStartContract(), @@ -87,7 +90,12 @@ test('container is destroyed on unmount', async () => { const dashboardStateManager = createDashboardState(); const { result, unmount, waitForNextUpdate } = renderHook( - () => useDashboardContainer({ dashboardStateManager, history }), + () => + useDashboardContainer({ + getIncomingEmbeddable, + dashboardStateManager, + history, + }), { wrapper: ({ children }) => ( {children} @@ -115,12 +123,20 @@ test('old container is destroyed on new dashboardStateManager', async () => { const { result, waitForNextUpdate, rerender } = renderHook< DashboardStateManager, DashboardContainer | null - >((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), { - wrapper: ({ children }) => ( - {children} - ), - initialProps: createDashboardState(), - }); + >( + (dashboardStateManager) => + useDashboardContainer({ + getIncomingEmbeddable, + dashboardStateManager, + history, + }), + { + wrapper: ({ children }) => ( + {children} + ), + initialProps: createDashboardState(), + } + ); expect(result.current).toBeNull(); // null on initial render @@ -150,12 +166,20 @@ test('destroyed if rerendered before resolved', async () => { const { result, waitForNextUpdate, rerender } = renderHook< DashboardStateManager, DashboardContainer | null - >((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), { - wrapper: ({ children }) => ( - {children} - ), - initialProps: createDashboardState(), - }); + >( + (dashboardStateManager) => + useDashboardContainer({ + getIncomingEmbeddable, + dashboardStateManager, + history, + }), + { + wrapper: ({ children }) => ( + {children} + ), + initialProps: createDashboardState(), + } + ); expect(result.current).toBeNull(); // null on initial render diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts index f4fe55f877400..109380ed0d89b 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts @@ -14,6 +14,7 @@ import { ContainerOutput, EmbeddableFactoryNotFoundError, EmbeddableInput, + EmbeddablePackageState, ErrorEmbeddable, isErrorEmbeddable, ViewMode, @@ -21,7 +22,7 @@ import { import { DashboardStateManager } from '../dashboard_state_manager'; import { getDashboardContainerInput, getSearchSessionIdFromURL } from '../dashboard_app_functions'; -import { DashboardConstants, DashboardContainer, DashboardContainerInput } from '../..'; +import { DashboardContainer, DashboardContainerInput } from '../..'; import { DashboardAppServices } from '../types'; import { DASHBOARD_CONTAINER_TYPE } from '..'; import { TimefilterContract } from '../../services/data'; @@ -30,6 +31,7 @@ export const useDashboardContainer = ({ history, timeFilter, setUnsavedChanges, + getIncomingEmbeddable, dashboardStateManager, isEmbeddedExternally, }: { @@ -38,6 +40,7 @@ export const useDashboardContainer = ({ timeFilter?: TimefilterContract; setUnsavedChanges?: (dirty: boolean) => void; dashboardStateManager: DashboardStateManager | null; + getIncomingEmbeddable: (removeAfterFetch?: boolean) => EmbeddablePackageState | undefined; }) => { const { dashboardCapabilities, @@ -77,11 +80,8 @@ export const useDashboardContainer = ({ searchSession.restore(searchSessionIdFromURL); } - const incomingEmbeddable = embeddable - .getStateTransfer() - .getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, true); - // when dashboard state manager initially loads, determine whether or not there are unsaved changes + const incomingEmbeddable = getIncomingEmbeddable(true); setUnsavedChanges?.( Boolean(incomingEmbeddable) || dashboardStateManager.hasUnsavedPanelState() ); @@ -131,7 +131,6 @@ export const useDashboardContainer = ({ (incomingEmbeddable.embeddableId && !pendingContainer.getInput().panels[incomingEmbeddable.embeddableId])) ) { - dashboardStateManager.switchViewMode(ViewMode.EDIT); pendingContainer.addNewEmbeddable( incomingEmbeddable.type, incomingEmbeddable.input @@ -154,6 +153,7 @@ export const useDashboardContainer = ({ }, [ dashboardCapabilities, dashboardStateManager, + getIncomingEmbeddable, isEmbeddedExternally, setUnsavedChanges, searchSession, diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts index effd598cc3ee8..72b43723f07fb 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts @@ -29,7 +29,7 @@ import { createSessionRestorationDataProvider } from '../lib/session_restoration import { DashboardStateManager } from '../dashboard_state_manager'; import { getDashboardTitle } from '../../dashboard_strings'; import { DashboardAppServices } from '../types'; -import { ViewMode } from '../../services/embeddable'; +import { EmbeddablePackageState, ViewMode } from '../../services/embeddable'; // TS is picky with type guards, we can't just inline `() => false` function defaultTaggingGuard(_obj: SavedObject): _obj is TagDecoratedSavedObject { @@ -44,7 +44,8 @@ interface DashboardStateManagerReturn { export const useDashboardStateManager = ( savedDashboard: DashboardSavedObject | null, - history: History + history: History, + getIncomingEmbeddable: () => EmbeddablePackageState | undefined ): DashboardStateManagerReturn => { const { data: dataPlugin, @@ -87,6 +88,7 @@ export const useDashboardStateManager = ( }); const stateManager = new DashboardStateManager({ + hasPendingEmbeddable: () => Boolean(getIncomingEmbeddable()), toasts: core.notifications.toasts, hasTaggingCapabilities, dashboardPanelStorage, @@ -182,10 +184,6 @@ export const useDashboardStateManager = ( } ); - if (stateManager.getIsEditMode()) { - stateManager.restorePanels(); - } - setDashboardStateManager(stateManager); setViewMode(stateManager.getViewMode()); @@ -201,6 +199,7 @@ export const useDashboardStateManager = ( hasTaggingCapabilities, initializerContext.config, dashboardPanelStorage, + getIncomingEmbeddable, hideWriteControls, history, kibanaVersion, diff --git a/src/plugins/dashboard/public/application/lib/get_app_state_defaults.ts b/src/plugins/dashboard/public/application/lib/get_app_state_defaults.ts index a184ad527f9a3..d8d335317c2b2 100644 --- a/src/plugins/dashboard/public/application/lib/get_app_state_defaults.ts +++ b/src/plugins/dashboard/public/application/lib/get_app_state_defaults.ts @@ -12,8 +12,8 @@ import { DashboardSavedObject } from '../../saved_dashboards'; import { DashboardAppStateDefaults } from '../../types'; export function getAppStateDefaults( + viewMode: ViewMode, savedDashboard: DashboardSavedObject, - hideWriteControls: boolean, hasTaggingCapabilities: SavedObjectTagDecoratorTypeGuard ): DashboardAppStateDefaults { return { @@ -26,6 +26,6 @@ export function getAppStateDefaults( options: savedDashboard.optionsJSON ? JSON.parse(savedDashboard.optionsJSON) : {}, query: savedDashboard.getQuery(), filters: savedDashboard.getFilters(), - viewMode: savedDashboard.id || hideWriteControls ? ViewMode.VIEW : ViewMode.EDIT, + viewMode, }; } diff --git a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts b/src/plugins/dashboard/public/application/lib/session_restoration.test.ts index e74108ed5559f..a6740d8647825 100644 --- a/src/plugins/dashboard/public/application/lib/session_restoration.test.ts +++ b/src/plugins/dashboard/public/application/lib/session_restoration.test.ts @@ -11,6 +11,7 @@ import { createSessionRestorationDataProvider } from './session_restoration'; import { getAppStateDefaults } from './get_app_state_defaults'; import { getSavedDashboardMock } from '../test_helpers'; import { SavedObjectTagDecoratorTypeGuard } from '../../../../saved_objects_tagging_oss/public'; +import { ViewMode } from '../../services/embeddable'; describe('createSessionRestorationDataProvider', () => { const mockDataPlugin = dataPluginMock.createStartContract(); @@ -18,8 +19,8 @@ describe('createSessionRestorationDataProvider', () => { data: mockDataPlugin, getAppState: () => getAppStateDefaults( + ViewMode.VIEW, getSavedDashboardMock(), - false, ((() => false) as unknown) as SavedObjectTagDecoratorTypeGuard ), getDashboardTitle: () => 'Dashboard', diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index b51382347b952..65cb4db5ad543 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -166,7 +166,6 @@ export function DashboardTopNav({ function switchViewMode() { dashboardStateManager.switchViewMode(newMode); - dashboardStateManager.restorePanels(); if (savedDashboard?.id && allowByValueEmbeddables) { const { getFullEditPath, title, id } = savedDashboard; diff --git a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts index 801ab54eb9839..b1b1386c00b19 100644 --- a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts +++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts @@ -151,7 +151,7 @@ function getViewConfig(action: NavAction, disableButton?: boolean) { disableButton, id: 'cancel', label: i18n.translate('dashboard.topNave.cancelButtonAriaLabel', { - defaultMessage: 'Return', + defaultMessage: 'Cancel', }), description: i18n.translate('dashboard.topNave.viewConfigDescription', { defaultMessage: 'Switch to view-only mode', From 20a0d82e372cfda14a9e2d58eb97ff4d838e4075 Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Mon, 1 Mar 2021 17:18:42 -0600 Subject: [PATCH 4/9] [Security Solution][Detecttions] Indicator enrichment tweaks (#92989) (#93121) * Update copy of rule config * Encode threat index as part of our named query * Add index to named query, and enrich both id and index We still need mappings and to fix integration tests, but this generates the correct data. * Update integration tests with new enrichment fields Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../rules/step_about_rule/schema.tsx | 4 +- .../build_threat_mapping_filter.ts | 1 + .../enrich_signal_threat_matches.mock.ts | 1 + .../enrich_signal_threat_matches.test.ts | 94 +++++++++++++++++-- .../enrich_signal_threat_matches.ts | 2 +- .../signals/threat_mapping/types.ts | 1 + .../signals/threat_mapping/utils.test.ts | 5 +- .../signals/threat_mapping/utils.ts | 11 ++- .../tests/create_threat_matching.ts | 46 ++++++--- 9 files changed, 136 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx index 07012af17e734..3467b34d47135 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx @@ -198,14 +198,14 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathLabel', { - defaultMessage: 'Threat Indicator Path', + defaultMessage: 'Indicator prefix override', } ), helpText: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathHelpText', { defaultMessage: - 'Specify the document path containing your threat indicator fields. Used for enrichment of indicator match alerts.', + 'Specify the document prefix containing your indicator fields. Used for enrichment of indicator match alerts.', } ), labelAppend: OptionalFieldLabel, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts index 0a2789ec2f1d0..18204bb678a47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts @@ -84,6 +84,7 @@ export const createInnerAndClauses = ({ query: value[0], _name: encodeThreatMatchNamedQuery({ id: threatListItem._id, + index: threatListItem._index, field: threatMappingEntry.field, value: threatMappingEntry.value, }), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.mock.ts index a3ff932e97886..f66dc461c6d40 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.mock.ts @@ -12,6 +12,7 @@ export const getNamedQueryMock = ( overrides: Partial = {} ): ThreatMatchNamedQuery => ({ id: 'id', + index: 'index', field: 'field', value: 'value', ...overrides, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts index b77e8228e72d8..7b3ca099cc93c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts @@ -88,7 +88,12 @@ describe('buildMatchedIndicator', () => { }), ]; queries = [ - getNamedQueryMock({ id: '123', field: 'event.field', value: 'threat.indicator.domain' }), + getNamedQueryMock({ + id: '123', + index: 'threat-index', + field: 'event.field', + value: 'threat.indicator.domain', + }), ]; }); @@ -112,6 +117,26 @@ describe('buildMatchedIndicator', () => { expect(get(indicator, 'matched.atomic')).toEqual('domain_1'); }); + it('returns the _id of the matched indicator as matched.id', () => { + const [indicator] = buildMatchedIndicator({ + queries, + threats, + indicatorPath, + }); + + expect(get(indicator, 'matched.id')).toEqual('123'); + }); + + it('returns the _index of the matched indicator as matched.index', () => { + const [indicator] = buildMatchedIndicator({ + queries, + threats, + indicatorPath, + }); + + expect(get(indicator, 'matched.index')).toEqual('threat-index'); + }); + it('returns the field of the matched indicator as matched.field', () => { const [indicator] = buildMatchedIndicator({ queries, @@ -173,6 +198,8 @@ describe('buildMatchedIndicator', () => { domain: 'domain_1', matched: { atomic: 'domain_1', + id: '123', + index: 'threat-index', field: 'event.field', type: 'type_1', }, @@ -211,6 +238,8 @@ describe('buildMatchedIndicator', () => { indicator_field: 'indicator_field_1', matched: { atomic: 'domain_1', + id: '123', + index: 'threat-index', field: 'event.field', type: 'indicator_type', }, @@ -237,6 +266,8 @@ describe('buildMatchedIndicator', () => { { matched: { atomic: undefined, + id: '123', + index: 'threat-index', field: 'event.field', type: undefined, }, @@ -262,6 +293,8 @@ describe('buildMatchedIndicator', () => { { matched: { atomic: undefined, + id: '123', + index: 'threat-index', field: 'event.field', type: undefined, }, @@ -295,6 +328,8 @@ describe('buildMatchedIndicator', () => { domain: 'foo', matched: { atomic: undefined, + id: '123', + index: 'threat-index', field: 'event.field', type: 'first', }, @@ -362,7 +397,12 @@ describe('enrichSignalThreatMatches', () => { }), ]; matchedQuery = encodeThreatMatchNamedQuery( - getNamedQueryMock({ id: '123', field: 'event.field', value: 'threat.indicator.domain' }) + getNamedQueryMock({ + id: '123', + index: 'indicator_index', + field: 'event.field', + value: 'threat.indicator.domain', + }) ); }); @@ -395,7 +435,13 @@ describe('enrichSignalThreatMatches', () => { { existing: 'indicator' }, { domain: 'domain_1', - matched: { atomic: 'domain_1', field: 'event.field', type: 'type_1' }, + matched: { + atomic: 'domain_1', + id: '123', + index: 'indicator_index', + field: 'event.field', + type: 'type_1', + }, other: 'other_1', type: 'type_1', }, @@ -418,7 +464,13 @@ describe('enrichSignalThreatMatches', () => { expect(indicators).toEqual([ { - matched: { atomic: undefined, field: 'event.field', type: undefined }, + matched: { + atomic: undefined, + id: '123', + index: 'indicator_index', + field: 'event.field', + type: undefined, + }, }, ]); }); @@ -441,7 +493,13 @@ describe('enrichSignalThreatMatches', () => { { existing: 'indicator' }, { domain: 'domain_1', - matched: { atomic: 'domain_1', field: 'event.field', type: 'type_1' }, + matched: { + atomic: 'domain_1', + id: '123', + index: 'indicator_index', + field: 'event.field', + type: 'type_1', + }, other: 'other_1', type: 'type_1', }, @@ -477,6 +535,7 @@ describe('enrichSignalThreatMatches', () => { matchedQuery = encodeThreatMatchNamedQuery( getNamedQueryMock({ id: '123', + index: 'custom_index', field: 'event.field', value: 'custom_threat.custom_indicator.domain', }) @@ -496,7 +555,13 @@ describe('enrichSignalThreatMatches', () => { expect(indicators).toEqual([ { domain: 'custom_domain', - matched: { atomic: 'custom_domain', field: 'event.field', type: 'custom_type' }, + matched: { + atomic: 'custom_domain', + id: '123', + index: 'custom_index', + field: 'event.field', + type: 'custom_type', + }, other: 'custom_other', type: 'custom_type', }, @@ -526,7 +591,12 @@ describe('enrichSignalThreatMatches', () => { _id: 'signal123', matched_queries: [ encodeThreatMatchNamedQuery( - getNamedQueryMock({ id: '456', field: 'event.other', value: 'threat.indicator.domain' }) + getNamedQueryMock({ + id: '456', + index: 'other_custom_index', + field: 'event.other', + value: 'threat.indicator.domain', + }) ), ], }); @@ -545,7 +615,13 @@ describe('enrichSignalThreatMatches', () => { expect(indicators).toEqual([ { domain: 'domain_1', - matched: { atomic: 'domain_1', field: 'event.field', type: 'type_1' }, + matched: { + atomic: 'domain_1', + id: '123', + index: 'indicator_index', + field: 'event.field', + type: 'type_1', + }, other: 'other_1', type: 'type_1', }, @@ -553,6 +629,8 @@ describe('enrichSignalThreatMatches', () => { domain: 'domain_2', matched: { atomic: 'domain_2', + id: '456', + index: 'other_custom_index', field: 'event.other', type: 'type_2', }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts index 3c8b80886cabe..bc2be4ecaab32 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts @@ -60,7 +60,7 @@ export const buildMatchedIndicator = ({ return { ...indicator, - matched: { atomic, field: query.field, type }, + matched: { atomic, field: query.field, id: query.id, index: query.index, type }, }; }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index a022cbbdd4042..38a8840550e7b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -202,6 +202,7 @@ export interface SortWithTieBreaker { export interface ThreatMatchNamedQuery { id: string; + index: string; field: string; value: string; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts index 897143f9ae574..6219da93036ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts @@ -589,6 +589,7 @@ describe('utils', () => { it('generates a string that can be later decoded', () => { const encoded = encodeThreatMatchNamedQuery({ id: 'id', + index: 'index', field: 'field', value: 'value', }); @@ -601,6 +602,7 @@ describe('utils', () => { it('can decode an encoded query', () => { const query: ThreatMatchNamedQuery = { id: 'my_id', + index: 'index', field: 'threat.indicator.domain', value: 'host.name', }; @@ -623,6 +625,7 @@ describe('utils', () => { it('raises an error if the query is missing a value', () => { const badQuery: ThreatMatchNamedQuery = { id: 'my_id', + index: 'index', // @ts-expect-error field intentionally undefined field: undefined, value: 'host.name', @@ -630,7 +633,7 @@ describe('utils', () => { const badInput = encodeThreatMatchNamedQuery(badQuery); expect(() => decodeThreatMatchNamedQuery(badInput)).toThrowError( - 'Decoded query is invalid. Decoded value: {"id":"my_id","field":"","value":"host.name"}' + 'Decoded query is invalid. Decoded value: {"id":"my_id","index":"index","field":"","value":"host.name"}' ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts index 72d9257798e1c..805aca563701c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts @@ -115,21 +115,22 @@ export const combineConcurrentResults = ( return combineResults(currentResult, maxedNewResult); }; -const separator = '___SEPARATOR___'; +const separator = '__SEP__'; export const encodeThreatMatchNamedQuery = ({ id, + index, field, value, }: ThreatMatchNamedQuery): string => { - return [id, field, value].join(separator); + return [id, index, field, value].join(separator); }; export const decodeThreatMatchNamedQuery = (encoded: string): ThreatMatchNamedQuery => { const queryValues = encoded.split(separator); - const [id, field, value] = queryValues; - const query = { id, field, value }; + const [id, index, field, value] = queryValues; + const query = { id, index, field, value }; - if (queryValues.length !== 3 || !queryValues.every(Boolean)) { + if (queryValues.length !== 4 || !queryValues.every(Boolean)) { const queryString = JSON.stringify(query); throw new Error(`Decoded query is invalid. Decoded value: ${queryString}`); } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 20d2b107dc2cc..9da98e316315d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -307,6 +307,8 @@ export default ({ getService }: FtrProviderContext) => { first_seen: '2021-01-26T11:09:04.000Z', matched: { atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', type: 'url', }, @@ -327,6 +329,8 @@ export default ({ getService }: FtrProviderContext) => { first_seen: '2021-01-26T11:09:04.000Z', matched: { atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', type: 'url', }, @@ -388,6 +392,8 @@ export default ({ getService }: FtrProviderContext) => { ip: '45.115.45.3', matched: { atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', type: 'url', }, @@ -401,6 +407,8 @@ export default ({ getService }: FtrProviderContext) => { ip: '45.115.45.3', matched: { atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', type: 'ip', }, @@ -468,6 +476,8 @@ export default ({ getService }: FtrProviderContext) => { ip: '45.115.45.3', matched: { atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', type: 'url', }, @@ -475,18 +485,6 @@ export default ({ getService }: FtrProviderContext) => { provider: 'geenensp', type: 'url', }, - { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - matched: { - atomic: '45.115.45.3', - field: 'source.ip', - type: 'ip', - }, - provider: 'other_provider', - type: 'ip', - }, // We do not merge matched indicators during enrichment, so in // certain circumstances a given indicator document could appear // multiple times in an enriched alert (albeit with different @@ -498,6 +496,8 @@ export default ({ getService }: FtrProviderContext) => { ip: '45.115.45.3', matched: { atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.port', type: 'url', }, @@ -505,6 +505,20 @@ export default ({ getService }: FtrProviderContext) => { provider: 'geenensp', type: 'url', }, + { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: 'ip', + }, + provider: 'other_provider', + type: 'ip', + }, ], }, ]); @@ -570,6 +584,8 @@ export default ({ getService }: FtrProviderContext) => { first_seen: '2021-01-26T11:09:04.000Z', matched: { atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', type: 'url', }, @@ -590,6 +606,8 @@ export default ({ getService }: FtrProviderContext) => { first_seen: '2021-01-26T11:09:04.000Z', matched: { atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', type: 'url', }, @@ -606,6 +624,8 @@ export default ({ getService }: FtrProviderContext) => { ip: '45.115.45.3', matched: { atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', type: 'url', }, @@ -619,6 +639,8 @@ export default ({ getService }: FtrProviderContext) => { ip: '45.115.45.3', matched: { atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.port', type: 'url', }, From fb1c83a3092598acb0acec575cc00bc90452d334 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 1 Mar 2021 16:27:06 -0700 Subject: [PATCH 5/9] [Maps] fix results trimmed tooltip message doubles feature count for line and polygon features (#92932) (#93125) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../data_request_descriptor_types.ts | 1 + .../maps/public/actions/data_request_actions.ts | 12 ++++++++++-- .../sources/es_search_source/es_search_source.tsx | 12 +++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index 5394d00ba16eb..dd01b7b596c30 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -75,6 +75,7 @@ export type VectorStyleRequestMeta = MapFilters & { export type ESSearchSourceResponseMeta = { areResultsTrimmed?: boolean; + resultsCount?: number; // top hits meta areEntitiesTrimmed?: boolean; diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 5e8a18348ac5a..6689a0229c921 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -14,7 +14,12 @@ import uuid from 'uuid/v4'; import { multiPoint } from '@turf/helpers'; import { FeatureCollection } from 'geojson'; import { MapStoreState } from '../reducers/store'; -import { LAYER_STYLE_TYPE, LAYER_TYPE, SOURCE_DATA_REQUEST_ID } from '../../common/constants'; +import { + KBN_IS_CENTROID_FEATURE, + LAYER_STYLE_TYPE, + LAYER_TYPE, + SOURCE_DATA_REQUEST_ID, +} from '../../common/constants'; import { getDataFilters, getDataRequestDescriptor, @@ -246,7 +251,10 @@ function endDataLoad( const layer = getLayerById(layerId, getState()); const resultMeta: ResultMeta = {}; if (layer && layer.getType() === LAYER_TYPE.VECTOR) { - resultMeta.featuresCount = features.length; + const featuresWithoutCentroids = features.filter((feature) => { + return feature.properties ? !feature.properties[KBN_IS_CENTROID_FEATURE] : true; + }); + resultMeta.featuresCount = featuresWithoutCentroids.length; } eventHandlers.onDataLoadEnd({ diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index d5087664b399c..785b00c06dd54 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -11,7 +11,7 @@ import rison from 'rison-node'; import { i18n } from '@kbn/i18n'; import { IFieldType, IndexPattern } from 'src/plugins/data/public'; -import { FeatureCollection, GeoJsonProperties } from 'geojson'; +import { GeoJsonProperties } from 'geojson'; import { AbstractESSource } from '../es_source'; import { getHttp, getSearchService } from '../../../kibana_services'; import { addFieldToDSL, getField, hitsToGeoJson } from '../../../../common/elasticsearch_util'; @@ -399,6 +399,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return { hits: resp.hits.hits.reverse(), // Reverse hits so top documents by sort are drawn on top meta: { + resultsCount: resp.hits.hits.length, areResultsTrimmed: resp.hits.total > resp.hits.hits.length, }, }; @@ -589,11 +590,8 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye } getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig { - const featureCollection: FeatureCollection | null = sourceDataRequest - ? (sourceDataRequest.getData() as FeatureCollection) - : null; const meta = sourceDataRequest ? sourceDataRequest.getMeta() : null; - if (!featureCollection || !meta) { + if (!meta) { // no tooltip content needed when there is no feature collection or meta return { tooltipContent: null, @@ -631,7 +629,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return { tooltipContent: i18n.translate('xpack.maps.esSearch.resultsTrimmedMsg', { defaultMessage: `Results limited to first {count} documents.`, - values: { count: featureCollection.features.length }, + values: { count: meta.resultsCount }, }), areResultsTrimmed: true, }; @@ -640,7 +638,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return { tooltipContent: i18n.translate('xpack.maps.esSearch.featureCountMsg', { defaultMessage: `Found {count} documents.`, - values: { count: featureCollection.features.length }, + values: { count: meta.resultsCount }, }), areResultsTrimmed: false, }; From df3076b35ec6994da4642e1ad6d4505f82680dd4 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 1 Mar 2021 18:34:03 -0500 Subject: [PATCH 6/9] [Security Solution][Case] Migrate category & subcategory fields of ServiceNow ITSM connector (#93092) (#93128) Co-authored-by: Christos Nasikas --- .../server/saved_object_types/migrations.ts | 22 +++++++++++++--- .../basic/tests/cases/migrations.ts | 24 +++++++++++++++++- .../cases/migrations/7.11.1/data.json.gz | Bin 679 -> 1039 bytes 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/case/server/saved_object_types/migrations.ts b/x-pack/plugins/case/server/saved_object_types/migrations.ts index 117e2eaeea4d8..bf9694d7e6bb0 100644 --- a/x-pack/plugins/case/server/saved_object_types/migrations.ts +++ b/x-pack/plugins/case/server/saved_object_types/migrations.ts @@ -8,7 +8,13 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '../../../../../src/core/server'; -import { ConnectorTypes, CommentType, CaseType, AssociationType } from '../../common/api'; +import { + ConnectorTypes, + CommentType, + CaseType, + AssociationType, + ESConnectorFields, +} from '../../common/api'; interface UnsanitizedCaseConnector { connector_id: string; @@ -24,7 +30,7 @@ interface SanitizedCaseConnector { id: string; name: string | null; type: string | null; - fields: null; + fields: null | ESConnectorFields; }; } @@ -88,13 +94,21 @@ export const caseMigrations = { }; }, '7.12.0': ( - doc: SavedObjectUnsanitizedDoc> - ): SavedObjectSanitizedDoc => { + doc: SavedObjectUnsanitizedDoc + ): SavedObjectSanitizedDoc => { + const { fields, type } = doc.attributes.connector; return { ...doc, attributes: { ...doc.attributes, type: CaseType.individual, + connector: { + ...doc.attributes.connector, + fields: + Array.isArray(fields) && fields.length > 0 && type === ConnectorTypes.serviceNowITSM + ? [...fields, { key: 'category', value: null }, { key: 'subcategory', value: null }] + : fields, + }, }, references: doc.references || [], }; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts b/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts index e75ea0f8a89c9..e66b623138e60 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts @@ -58,7 +58,6 @@ export default function createGetTests({ getService }: FtrProviderContext) { // tests upgrading a 7.11.1 saved object to the latest version describe('7.11.1 -> latest stack version', () => { - const caseID = '2ea28c10-7855-11eb-9ca6-83ec5acb735f'; before(async () => { await esArchiver.load('cases/migrations/7.11.1'); }); @@ -68,6 +67,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); it('adds rule info to only alert comments for 7.12', async () => { + const caseID = '2ea28c10-7855-11eb-9ca6-83ec5acb735f'; // user comment let { body } = await supertest .get(`${CASES_URL}/${caseID}/comments/34a20a00-7855-11eb-9ca6-83ec5acb735f`) @@ -84,6 +84,28 @@ export default function createGetTests({ getService }: FtrProviderContext) { expect(body).key('rule'); expect(body.rule).to.eql({ id: null, name: null }); }); + + it('adds category and subcategory to the ITSM connector', async () => { + const { body } = await supertest + .get(`${CASES_URL}/6f973440-7abd-11eb-9ca6-83ec5acb735f`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body).key('connector'); + expect(body.connector).to.eql({ + id: '444ebab0-7abd-11eb-9ca6-83ec5acb735f', + name: 'SN', + type: '.servicenow', + fields: { + impact: '2', + severity: '2', + urgency: '2', + category: null, + subcategory: null, + }, + }); + }); }); }); } diff --git a/x-pack/test/functional/es_archives/cases/migrations/7.11.1/data.json.gz b/x-pack/test/functional/es_archives/cases/migrations/7.11.1/data.json.gz index 219f58b7e9d216faa6b20e2ea4d7e6a5a5c0a47c..efb62cba5675de208dc0af5a77b2f07566218af8 100644 GIT binary patch literal 1039 zcmV+q1n~PGiwFq7JUw6l17u-zVJ>QOZ*BnXSWR=AJP^M7S1_Cn1LD)1-pqUIC6{!1 zNS*Nrv37Wig$IeGo{ax{B>}b|xW#?rw4G^AVOP7-emsw~ewi7D$@e=j6T{3(V$P-D z2^Y1Lyoe9+nP*Z1wNg8>!O&yQlc~M z9nVWDmt#X#Og}Yz<B6TRoy{p7;v7`4I4(q_LSTdP;sf@RSlyDU>x&Wt}x3T zP<>~PH^aJ`_!(Ha)k^Z98|1uDK}WXh+OF~MAe4HSoh=zc2?}iFd-r1&zrJL%LFUYD zzNxS*+b5`4Q#~DIE$*6B937p^D_BDXlz^V%V=|8bX#5<-6EYYC_swqYUPo6e$wM<4 z%3(HpxsaeNQf{|EIZHerBL_RzVXEUcgKRcZ>FLI3o2!#ervzaZ1b+Y}>}vLShM-&3 z7(sW%lslaT-N2>?FRA;PF4t>zS-TtklsD~m!A;oOb$P{%l_Zw|S$){_^3$kZ0s9Q} zPQgvWVB2NkwK3>M@fxKnN6|ZB5X+n7gF1~*Qg4AFK;RKTB*Zw1aTE~;1yQ(IK#;Dp zzzHMAbNn>Q+|(7NByoV<5Ef1tPsQx`o$y;?_LOCo!`5Q?MqJs<%3zJ_f?JOKbImsc zxhlqAl>Z`>cQ-A%2FjCgy@)*D7pcK%_UDfR{qn1IUtq6ps%j&8W#22H(s#)*k=aun z=9By~c!DbDZ|^wY?l9pOxYfSKCL-VWA;syGO1E|$2<~pr%uDT%K+EUpQ+xMckq}x|YomE@roirKUsL9B9&iSKpcWa!Q@b{dRel!;u*4Zeo&gJhjmkin} zyb4=dvYjk}N)ahbMSqiLe%F6sn_gId+( zuJLkWH1RDN?Qqb$tSqu)Du4P-I6#D~g7g30g%>=2x}Lb^yRaAFWVVw(Jx-YbzX3QQ JuzMyJ008kR`@;YL literal 679 zcmV;Y0$BYYiwFokH924a17u-zVJ>QOZ*BnXRZVZ4Fc7`(uRxpwvdtRUJvHj7_o|0f z(P}WW8@FIq*rbV~{P$wM76>bCs-|hBa%0Bx@#guBpH`0Jn#Uu$TgOcc;a*w8J!F-& zJk>w-uS~61fJVJ#2rL$qcu~x`M=2)WMnLGr0SXQxi2}ZFlGr$v;)4zKzsm#^>}c=N zMOBJZ1*gkCGFVeqXiRrtYO|`c?COOpd0cQ6N}(`CIZ0MK_7bzymiRid-d>H5v_Ms% z(4E*7!(0aMu%ZgE64u592i=hcND zsEd3=$?Z5mnydjG`IP$9`8b189n-UvF}7vgXY1f*DdPPpTg@it=JHU2rR@urx@B)H zvQBsHD27C*@Dle}q7pbB@i5Fm0BxSZe8B`cziF#=wT4tXyN6a5%JXXV^vVV0i6YNY z8NChGzzC4H*;GTB*)&_L&2;T_m#ep3z7py%&i{dxaIEy*nFZasmKJnRrmb{VbhDU= zysYn6ZuywA*IjpKI`a0qz2YV8;`+IQ)`gH3kT;dhWInCyg|N>^?*#554R*IId}|HT zDBiQAnNjqIGzj)3`P6i?7D=!H@t0055?PPza9 From 7003e0212bbd64ae9e7d01adfbacb156704c043c Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 1 Mar 2021 18:39:52 -0500 Subject: [PATCH 7/9] Hide instances latency distribution chart (#92869) (#93127) ...until #88852 and #92631 are resolved. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Nathan L Smith --- .../service_overview_instances_chart_and_table.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index 2f2aaf3156b93..32b684c9a0757 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -10,9 +10,15 @@ import React from 'react'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { InstancesLatencyDistributionChart } from '../../shared/charts/instances_latency_distribution_chart'; import { ServiceOverviewInstancesTable } from './service_overview_instances_table'; +// We're hiding this chart until these issues are resolved in the 7.13 timeframe: +// +// * [[APM] Tooltips for instances latency distribution chart](https://github.com/elastic/kibana/issues/88852) +// * [[APM] x-axis on the instance bubble chart is broken](https://github.com/elastic/kibana/issues/92631) +// +// import { InstancesLatencyDistributionChart } from '../../shared/charts/instances_latency_distribution_chart'; + interface ServiceOverviewInstancesChartAndTableProps { chartHeight: number; serviceName: string; @@ -67,13 +73,13 @@ export function ServiceOverviewInstancesChartAndTable({ return ( <> - + {/* - + */} Date: Mon, 1 Mar 2021 15:57:20 -0800 Subject: [PATCH 8/9] [Security Solution][Endpoint][Admin] Fixes policy sticky footer save test (#92919) (#93134) * commented code to close out toast # Conflicts: # x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts --- .../apps/endpoint/policy_details.ts | 4 ++++ .../security_solution_endpoint/page_objects/policy_page.ts | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 298f3df58d6ef..75f266e29a91e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -448,6 +448,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // Clear the value await advancedPolicyField.click(); await advancedPolicyField.clearValueWithKeyboard(); + + // Make sure the toast button closes so the save button on the sticky footer is visible + await (await testSubjects.find('toastCloseButton')).click(); + await testSubjects.waitForHidden('toastCloseButton'); await pageObjects.policy.confirmAndSave(); await testSubjects.existOrFail('policyDetailsSuccessMessage'); diff --git a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts index 719e8d345bf44..d1a037a47ff08 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'header']); const testSubjects = getService('testSubjects'); - const browser = getService('browser'); return { /** @@ -70,7 +69,6 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr */ async confirmAndSave() { await this.ensureIsOnDetailsPage(); - await browser.scrollTop(); await (await this.findSaveButton()).click(); await testSubjects.existOrFail('policyDetailsConfirmModal'); await pageObjects.common.clickConfirmOnModal(); From afa6da7cd492f852541485e1976f6213e6c9577f Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Mon, 1 Mar 2021 18:59:17 -0500 Subject: [PATCH 9/9] [actions] for simplistic email servers, set rejectUnauthorized to false (#91760) (#93132) resolves https://github.com/elastic/kibana/issues/91686 The poor email action has not had great success in setting TLS options correctly. Prior to 7.11, it was basically always setting `rejectUnauthorized` to false, so was never validating certificates. Starting in 7.11.0, it started respecting TLS certificates, but there are some simple/test servers in use that use self-signed certificates. The real fix for this will be the resolution of issue https://github.com/elastic/kibana/issues/80120 , but until then, this PR does a special-case check if the `secure` option is off (so the email client connects with a plain socket and then upgrades to TLS via STARTTLS) and both the user and password for the server are not set, then it will use `rejectUnauthorized: false`. Otherwise, it uses the global configured value of this setting. This also changes some other cases, where `secure: true` often did not set any `rejectUnauthorized` property at all, and so did not get verified. Now in all cases, `rejectUnauthorized` will be set, and the value will correspond to the globally configured value, except for the special case checked here, and when a proxy is in use (that logic did not change). So it is possible this would break customers, who were using insecure servers and email action worked, but with this fix the connections will be rejected. They should have been rejected all this time though. The work-around for this problem, if we don't implement a fix like this, is that customers will need to set the global `rejectUnauthorized` to `false`, which means NONE of their TLS connections for any actions will be verified. Which seems extreme. --- .../builtin_action_types/lib/send_email.test.ts | 5 ++++- .../server/builtin_action_types/lib/send_email.ts | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index c6317a6a980bb..cc3f03f50c36f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -138,7 +138,7 @@ describe('send_email module', () => { "port": 1025, "secure": false, "tls": Object { - "rejectUnauthorized": true, + "rejectUnauthorized": false, }, }, ] @@ -187,6 +187,9 @@ describe('send_email module', () => { "host": "example.com", "port": 1025, "secure": true, + "tls": Object { + "rejectUnauthorized": true, + }, }, ] `); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index 79842f4aec02b..d4905015f7663 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -80,10 +80,13 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom }; transportConfig.proxy = proxySettings.proxyUrl; transportConfig.headers = proxySettings.proxyHeaders; - } else if (!transportConfig.secure) { - transportConfig.tls = { - rejectUnauthorized, - }; + } else if (!transportConfig.secure && user == null && password == null) { + // special case - if secure:false && user:null && password:null set + // rejectUnauthorized false, because simple/test servers that don't even + // authenticate rarely have valid certs; eg cloud proxy, and npm maildev + transportConfig.tls = { rejectUnauthorized: false }; + } else { + transportConfig.tls = { rejectUnauthorized }; } }