diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts index b3cf8f3ed1675..84349e9142e22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.test.ts @@ -34,10 +34,6 @@ describe('buildAlert', () => { jest.clearAllMocks(); }); - test('it builds an alert composed of a sequence', () => { - expect(true).toEqual(true); - }); - test('it builds an alert as expected without original_event if event does not exist', () => { const completeRule = getCompleteRuleMock(getQueryRuleParams()); const eqlSequence = { @@ -146,6 +142,64 @@ describe('buildAlert', () => { }); describe('recursive intersection between objects', () => { + describe('objectPairIntersection', () => { + test('returns the intersection of fields with identically-valued arrays', () => { + const a = { + field1: [1], + }; + const b = { + field1: [1], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [1], + }; + expect(intersection).toEqual(expected); + }); + + test('returns the intersection of arrays with differing lengths', () => { + const a = { + field1: 1, + }; + const b = { + field1: [1, 2, 3], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [1], + }; + expect(intersection).toEqual(expected); + }); + + test('should work with arrays with same lengths but only one intersecting element', () => { + const a = { + field1: [3, 4, 5], + }; + const b = { + field1: [1, 2, 3], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [3], + }; + expect(intersection).toEqual(expected); + }); + + test('should work with arrays with differing lengths and two intersecting elements', () => { + const a = { + field1: [3, 4, 5], + }; + const b = { + field1: [1, 2, 3, 4], + }; + const intersection = objectPairIntersection(a, b); + const expected = { + field1: [3, 4], + }; + expect(intersection).toEqual(expected); + }); + }); + test('should treat numbers and strings as unequal', () => { const a = { field1: 1, @@ -217,7 +271,7 @@ describe('buildAlert', () => { expect(intersection).toEqual(expected); }); - test('should strip arrays out regardless of whether they are equal', () => { + test('returns the intersection of values for fields containing arrays', () => { const a = { array_field1: [1, 2], array_field2: [1, 2], @@ -227,7 +281,7 @@ describe('buildAlert', () => { array_field2: [3, 4], }; const intersection = objectPairIntersection(a, b); - const expected = undefined; + const expected = { array_field1: [1, 2], array_field2: [] }; expect(intersection).toEqual(expected); }); @@ -287,6 +341,7 @@ describe('buildAlert', () => { const intersection = objectPairIntersection(a, b); const expected = { container_field: { + array_field: [1, 2], field1: 1, field6: null, nested_container_field: { @@ -332,6 +387,7 @@ describe('buildAlert', () => { }; const intersection = objectPairIntersection(a, b); const expected = { + array_field: [1, 2], field1: 1, field6: null, container_field: { @@ -419,6 +475,7 @@ describe('buildAlert', () => { }; const intersection = objectArrayIntersection([a, b]); const expected = { + array_field: [1, 2], field1: 1, field6: null, container_field: { @@ -427,7 +484,6 @@ describe('buildAlert', () => { }; expect(intersection).toEqual(expected); }); - test('should work with 3 or more objects', () => { const a = { field1: 1, @@ -477,6 +533,7 @@ describe('buildAlert', () => { }; const intersection = objectArrayIntersection([a, b, c]); const expected = { + array_field: [1, 2], field1: 1, }; expect(intersection).toEqual(expected); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts index 8db01c11d4de5..2675c3996e865 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts @@ -6,6 +6,7 @@ */ import { ALERT_URL, ALERT_UUID } from '@kbn/rule-data-utils'; +import { intersection as lodashIntersection, isArray } from 'lodash'; import { getAlertDetailsUrl } from '../../../../../common/utils/alert_detail_path'; import { DEFAULT_ALERTS_INDEX } from '../../../../../common/constants'; @@ -180,6 +181,11 @@ export const buildAlertRoot = ( }; }; +/** + * Merges array of alert sources with the first item in the array + * @param objects array of alert _source objects + * @returns singular object + */ export const objectArrayIntersection = (objects: object[]) => { if (objects.length === 0) { return undefined; @@ -195,6 +201,16 @@ export const objectArrayIntersection = (objects: object[]) => { } }; +/** + * Finds the intersection of two objects by recursively + * finding the "intersection" of each of of their common keys' + * values. If an intersection cannot be found between a key's + * values, the value will be undefined in the returned object. + * + * @param a object + * @param b object + * @returns intersection of the two objects + */ export const objectPairIntersection = (a: object | undefined, b: object | undefined) => { if (a === undefined || b === undefined) { return undefined; @@ -214,6 +230,12 @@ export const objectPairIntersection = (a: object | undefined, b: object | undefi intersection[key] = objectPairIntersection(aVal, bVal); } else if (aVal === bVal) { intersection[key] = aVal; + } else if (isArray(aVal) && isArray(bVal)) { + intersection[key] = lodashIntersection(aVal, bVal); + } else if (isArray(aVal) && !isArray(bVal)) { + intersection[key] = lodashIntersection(aVal, [bVal]); + } else if (!isArray(aVal) && isArray(bVal)) { + intersection[key] = lodashIntersection([aVal], bVal); } } });