Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Value list exception support for all rule types #133254

Merged
merged 64 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
372a57b
first pass
dplumlee May 31, 2022
aae255f
adds more stuff
dplumlee Jun 1, 2022
aba9529
adds backend versions of existing logic
dplumlee Jun 27, 2022
f950db6
finishes exceptions route file
dplumlee Jun 28, 2022
ff92e13
updates frontend to use api
dplumlee Jun 29, 2022
9c994e2
fixes promise bug
dplumlee Jul 5, 2022
3235519
fixes some bugs
dplumlee Jul 13, 2022
05688a6
fixes merge conflicts
dplumlee Jul 20, 2022
c79d18e
adds ip_range list clause
dplumlee Aug 3, 2022
1e3793a
fixes merge stuff
dplumlee Aug 8, 2022
cbad017
modifies listClient and adds findAll api
dplumlee Aug 16, 2022
c59b42d
fix some stuff
dplumlee Aug 16, 2022
ec0e2aa
fix merge stuff
dplumlee Aug 16, 2022
32b2c3b
updates tests
dplumlee Aug 16, 2022
1a6f4cd
gets rid of unused files
dplumlee Aug 16, 2022
4c2f115
fixes some errors
dplumlee Aug 16, 2022
1ed2549
fix types
dplumlee Aug 16, 2022
57a5c24
fixes some tests
dplumlee Aug 18, 2022
866dffd
fixes more tests
dplumlee Aug 18, 2022
cd58786
fixes some more tests
dplumlee Aug 18, 2022
9f70d89
fixes logic on findAllListItems
dplumlee Aug 22, 2022
aaa816c
fixes some tests
dplumlee Aug 22, 2022
44c9549
fixes rest of jest tests
dplumlee Aug 22, 2022
599e634
just kidding now fixes rest of jest tests
dplumlee Aug 23, 2022
c9ecde6
fixes functional tests
dplumlee Aug 23, 2022
501d05c
adds unprocessable logic to build_exception_filter
dplumlee Aug 23, 2022
8c92237
makes unprocessedExceptions logic a bit cleaner
dplumlee Aug 24, 2022
e8d7dc3
fixes jest tests
dplumlee Aug 24, 2022
7c3c930
fixes other list types
dplumlee Aug 24, 2022
be6fd15
fix async problems
dplumlee Aug 24, 2022
f9a499d
omits text type list
dplumlee Aug 25, 2022
356dc95
changes some api stuff
dplumlee Aug 29, 2022
ea639f0
gets rid of consolelog
dplumlee Aug 29, 2022
cecffbd
removes timeout
dplumlee Aug 30, 2022
ea77f01
factors out exceptions logic to security wrapper
dplumlee Aug 30, 2022
9e4832d
fix merge stuff
dplumlee Aug 30, 2022
73c2f16
fix types
dplumlee Aug 30, 2022
0c1e91d
adds and fixes executor tests
dplumlee Aug 30, 2022
cf2d414
addresses comments
dplumlee Sep 7, 2022
13a804b
fix import
dplumlee Sep 7, 2022
99687ca
updates translations
dplumlee Sep 7, 2022
9bc36eb
updates names
dplumlee Sep 7, 2022
717f326
addresses other comments
dplumlee Sep 7, 2022
1164c79
fixes route init
dplumlee Sep 8, 2022
1a0709b
Merge remote-tracking branch 'upstream/main' into value-list-exceptions
dplumlee Sep 8, 2022
c0345f7
fixes text list bug
dplumlee Sep 10, 2022
b0c645f
fixes order
dplumlee Sep 10, 2022
6d6ca3c
Merge remote-tracking branch 'upstream/main' into value-list-exceptions
dplumlee Sep 12, 2022
185a479
addresses some comments
dplumlee Sep 12, 2022
eb9fc15
remove console log
dplumlee Sep 12, 2022
2f3c95b
fix type
dplumlee Sep 12, 2022
8e4d9c9
changes up chunking processes in build exception filter
dplumlee Sep 13, 2022
a81f3eb
Merge remote-tracking branch 'upstream/main' into value-list-exceptions
dplumlee Sep 13, 2022
b40936d
adds runtime mapping fields
dplumlee Sep 14, 2022
3644117
adds tests and changes api name
dplumlee Sep 14, 2022
e01d5a5
fixes types and imports
dplumlee Sep 14, 2022
48071ea
fixes types for sure this time
dplumlee Sep 14, 2022
1c3811c
Merge remote-tracking branch 'upstream/main' into value-list-exceptions
dplumlee Sep 15, 2022
28ea835
adds tests
dplumlee Sep 15, 2022
257c3f6
Merge remote-tracking branch 'upstream/main' into value-list-exceptions
dplumlee Sep 16, 2022
58a4829
Merge remote-tracking branch 'upstream/main' into value-list-exceptions
dplumlee Sep 19, 2022
235a099
Merge remote-tracking branch 'upstream/main' into value-list-exceptions
dplumlee Sep 19, 2022
903bfbd
fixes merge problems
dplumlee Sep 19, 2022
170cc51
fixes merge types
dplumlee Sep 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { getField } from '../fields/index.mock';
import { AutocompleteFieldListsComponent } from '.';
import {
getListResponseMock,
getFoundListSchemaMock,
getFoundListsBySizeSchemaMock,
DATE_NOW,
IMMUTABLE,
VERSION,
Expand All @@ -34,14 +34,15 @@ const mockKeywordList: ListSchema = {
name: 'keyword list',
type: 'keyword',
};
const mockResult = { ...getFoundListSchemaMock() };
mockResult.data = [...mockResult.data, mockKeywordList];
const mockResult = { ...getFoundListsBySizeSchemaMock() };
mockResult.smallLists = [...mockResult.smallLists, mockKeywordList];
mockResult.largeLists = [];
jest.mock('@kbn/securitysolution-list-hooks', () => {
const originalModule = jest.requireActual('@kbn/securitysolution-list-hooks');

return {
...originalModule,
useFindLists: () => ({
useFindListsBySize: () => ({
error: undefined,
loading: false,
result: mockResult,
Expand Down Expand Up @@ -116,7 +117,7 @@ describe('AutocompleteFieldListsComponent', () => {
wrapper
.find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
.prop('options')
).toEqual([{ label: 'some name' }]);
).toEqual([{ label: 'some name', disabled: false }]);
});

test('it correctly displays lists that match the selected "keyword" field esType', () => {
Expand All @@ -139,7 +140,7 @@ describe('AutocompleteFieldListsComponent', () => {
wrapper
.find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
.prop('options')
).toEqual([{ label: 'keyword list' }]);
).toEqual([{ label: 'keyword list', disabled: false }]);
});

test('it correctly displays lists that match the selected "ip" field esType', () => {
Expand All @@ -162,7 +163,7 @@ describe('AutocompleteFieldListsComponent', () => {
wrapper
.find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
.prop('options')
).toEqual([{ label: 'some name' }]);
).toEqual([{ label: 'some name', disabled: false }]);
});

test('it correctly displays selected list', async () => {
Expand Down Expand Up @@ -206,7 +207,7 @@ describe('AutocompleteFieldListsComponent', () => {
wrapper.find(EuiComboBox).props() as unknown as {
onChange: (a: EuiComboBoxOptionOption[]) => void;
}
).onChange([{ label: 'some name' }]);
).onChange([{ label: 'some name', disabled: false }]);

await waitFor(() => {
expect(mockOnChange).toHaveBeenCalledWith({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
*/

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiLink, EuiText } from '@elastic/eui';
import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
import { useFindLists } from '@kbn/securitysolution-list-hooks';
import { useFindListsBySize } from '@kbn/securitysolution-list-hooks';
import { DataViewFieldBase } from '@kbn/es-query';

import { filterFieldToList } from '../filter_field_to_list';
Expand All @@ -33,6 +33,12 @@ interface AutocompleteFieldListsProps {
rowLabel?: string;
selectedField: DataViewFieldBase | undefined;
selectedValue: string | undefined;
allowLargeValueLists?: boolean;
}

export interface AutocompleteListsData {
smallLists: ListSchema[];
largeLists: ListSchema[];
}

export const AutocompleteFieldListsComponent: React.FC<AutocompleteFieldListsProps> = ({
Expand All @@ -45,37 +51,44 @@ export const AutocompleteFieldListsComponent: React.FC<AutocompleteFieldListsPro
rowLabel,
selectedField,
selectedValue,
allowLargeValueLists = false,
}): JSX.Element => {
const [error, setError] = useState<string | undefined>(undefined);
const [lists, setLists] = useState<ListSchema[]>([]);
const { loading, result, start } = useFindLists();
const [listData, setListData] = useState<AutocompleteListsData>({
smallLists: [],
largeLists: [],
});
const { loading, result, start } = useFindListsBySize();
const getLabel = useCallback(({ name }) => name, []);

const optionsMemo = useMemo(
() => filterFieldToList(lists, selectedField),
[lists, selectedField]
() => filterFieldToList(listData, selectedField),
[listData, selectedField]
);
const selectedOptionsMemo = useMemo(() => {
if (selectedValue != null) {
const list = lists.filter(({ id }) => id === selectedValue);
const combinedLists = [...listData.smallLists, ...listData.largeLists];
const list = combinedLists.filter(({ id }) => id === selectedValue);
return list ?? [];
} else {
return [];
}
}, [selectedValue, lists]);
}, [selectedValue, listData]);
const { comboOptions, labels, selectedComboOptions } = useMemo(
() =>
getGenericComboBoxProps<ListSchema>({
getLabel,
options: optionsMemo,
options: [...optionsMemo.smallLists, ...optionsMemo.largeLists],
selectedOptions: selectedOptionsMemo,
disabledOptions: allowLargeValueLists ? undefined : optionsMemo.largeLists, // Disable large lists if the rule type doesn't allow it
}),
[optionsMemo, selectedOptionsMemo, getLabel]
[optionsMemo, selectedOptionsMemo, getLabel, allowLargeValueLists]
);

const handleValuesChange = useCallback(
(newOptions: EuiComboBoxOptionOption[]) => {
const [newValue] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]);
const combinedLists = [...optionsMemo.smallLists, ...optionsMemo.largeLists];
const [newValue] = newOptions.map(({ label }) => combinedLists[labels.indexOf(label)]);
onChange(newValue ?? '');
},
[labels, optionsMemo, onChange]
Expand All @@ -87,7 +100,7 @@ export const AutocompleteFieldListsComponent: React.FC<AutocompleteFieldListsPro

useEffect(() => {
if (result != null) {
setLists(result.data);
setListData(result);
}
}, [result]);

Expand All @@ -103,8 +116,27 @@ export const AutocompleteFieldListsComponent: React.FC<AutocompleteFieldListsPro

const isLoadingState = useMemo((): boolean => isLoading || loading, [isLoading, loading]);

const helpText = useMemo(() => {
return (
!allowLargeValueLists && (
<EuiText size="xs">
{i18n.LISTS_TOOLTIP_INFO}{' '}
<EuiLink external target="_blank" href="https://www.elastic.co/">
{i18n.SEE_DOCUMENTATION}
</EuiLink>
</EuiText>
)
);
}, [allowLargeValueLists]);

return (
<EuiFormRow label={rowLabel} error={error} isInvalid={error != null} fullWidth>
<EuiFormRow
label={rowLabel}
error={error}
isInvalid={error != null}
helpText={helpText}
fullWidth
>
<EuiComboBox
async
data-test-subj="valuesAutocompleteComboBox listsComboxBox"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,83 +8,99 @@

import { filterFieldToList } from '.';

import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
import { getListResponseMock } from '../list_schema/index.mock';
import { DataViewFieldBase } from '@kbn/es-query';
import { AutocompleteListsData } from '../field_value_lists';

const emptyListData: AutocompleteListsData = { smallLists: [], largeLists: [] };

describe('#filterFieldToList', () => {
test('it returns empty array if given a undefined for field', () => {
const filter = filterFieldToList([], undefined);
expect(filter).toEqual([]);
test('it returns empty list data object if given a undefined for field', () => {
const filter = filterFieldToList(emptyListData, undefined);
expect(filter).toEqual(emptyListData);
});

test('it returns empty array if filed does not contain esTypes', () => {
test('it returns empty list data object if filed does not contain esTypes', () => {
const field: DataViewFieldBase = {
name: 'some-name',
type: 'some-type',
};
const filter = filterFieldToList([], field);
expect(filter).toEqual([]);
const filter = filterFieldToList(emptyListData, field);
expect(filter).toEqual(emptyListData);
});

test('it returns single filtered list of ip_range -> ip', () => {
test('it returns filtered lists of ip_range -> ip', () => {
const field: DataViewFieldBase & { esTypes: string[] } = {
esTypes: ['ip'],
name: 'some-name',
type: 'ip',
};
const listItem: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
const filter = filterFieldToList([listItem], field);
const expected: ListSchema[] = [listItem];
const listData: AutocompleteListsData = {
smallLists: [{ ...getListResponseMock(), type: 'ip_range' }],
largeLists: [],
};
const filter = filterFieldToList(listData, field);
const expected = listData;
expect(filter).toEqual(expected);
});

test('it returns single filtered list of ip -> ip', () => {
test('it returns filtered lists of ip -> ip', () => {
const field: DataViewFieldBase & { esTypes: string[] } = {
esTypes: ['ip'],
name: 'some-name',
type: 'ip',
};
const listItem: ListSchema = { ...getListResponseMock(), type: 'ip' };
const filter = filterFieldToList([listItem], field);
const expected: ListSchema[] = [listItem];
const listData: AutocompleteListsData = {
smallLists: [{ ...getListResponseMock(), type: 'ip' }],
largeLists: [],
};
const filter = filterFieldToList(listData, field);
const expected = listData;
expect(filter).toEqual(expected);
});

test('it returns single filtered list of keyword -> keyword', () => {
test('it returns filtered lists of keyword -> keyword', () => {
const field: DataViewFieldBase & { esTypes: string[] } = {
esTypes: ['keyword'],
name: 'some-name',
type: 'keyword',
};
const listItem: ListSchema = { ...getListResponseMock(), type: 'keyword' };
const filter = filterFieldToList([listItem], field);
const expected: ListSchema[] = [listItem];
const listData: AutocompleteListsData = {
smallLists: [{ ...getListResponseMock(), type: 'keyword' }],
largeLists: [],
};
const filter = filterFieldToList(listData, field);
const expected = listData;
expect(filter).toEqual(expected);
});

test('it returns single filtered list of text -> text', () => {
test('it returns filtered lists of text -> text', () => {
const field: DataViewFieldBase & { esTypes: string[] } = {
esTypes: ['text'],
name: 'some-name',
type: 'text',
};
const listItem: ListSchema = { ...getListResponseMock(), type: 'text' };
const filter = filterFieldToList([listItem], field);
const expected: ListSchema[] = [listItem];
const listData: AutocompleteListsData = {
smallLists: [{ ...getListResponseMock(), type: 'text' }],
largeLists: [],
};
const filter = filterFieldToList(listData, field);
const expected = listData;
expect(filter).toEqual(expected);
});

test('it returns 2 filtered lists of ip_range -> ip', () => {
test('it returns small and large filtered lists of ip_range -> ip', () => {
const field: DataViewFieldBase & { esTypes: string[] } = {
esTypes: ['ip'],
name: 'some-name',
type: 'ip',
};
const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
const listItem2: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
const filter = filterFieldToList([listItem1, listItem2], field);
const expected: ListSchema[] = [listItem1, listItem2];
const listData: AutocompleteListsData = {
smallLists: [{ ...getListResponseMock(), type: 'ip_range' }],
largeLists: [{ ...getListResponseMock(), type: 'ip_range' }],
};
const filter = filterFieldToList(listData, field);
const expected = listData;
expect(filter).toEqual(expected);
});

Expand All @@ -94,10 +110,15 @@ describe('#filterFieldToList', () => {
name: 'some-name',
type: 'ip',
};
const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
const listItem2: ListSchema = { ...getListResponseMock(), type: 'text' };
const filter = filterFieldToList([listItem1, listItem2], field);
const expected: ListSchema[] = [listItem1];
const listData: AutocompleteListsData = {
smallLists: [{ ...getListResponseMock(), type: 'ip_range' }],
largeLists: [{ ...getListResponseMock(), type: 'text' }],
};
const filter = filterFieldToList(listData, field);
const expected: AutocompleteListsData = {
smallLists: [{ ...getListResponseMock(), type: 'ip_range' }],
largeLists: [],
};
expect(filter).toEqual(expected);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* Side Public License, v 1.
*/

import { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
import { DataViewFieldBase } from '@kbn/es-query';
import { typeMatch } from '../type_match';
import { AutocompleteListsData } from '../field_value_lists';

/**
* Given an array of lists and optionally a field this will return all
Expand All @@ -21,13 +21,20 @@ import { typeMatch } from '../type_match';
* @param field The field to check against the list to see if they are compatible
*/
export const filterFieldToList = (
lists: ListSchema[],
lists: AutocompleteListsData,
field?: DataViewFieldBase & { esTypes?: string[] }
): ListSchema[] => {
): AutocompleteListsData => {
if (field != null) {
const { esTypes = [] } = field;
return lists.filter(({ type }) => esTypes.some((esType: string) => typeMatch(type, esType)));
return {
smallLists: lists.smallLists.filter(({ type }) =>
esTypes.some((esType: string) => typeMatch(type, esType))
),
largeLists: lists.largeLists.filter(({ type }) =>
esTypes.some((esType: string) => typeMatch(type, esType))
),
};
} else {
return [];
return { smallLists: [], largeLists: [] };
}
};
Loading