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] [Exceptions] Adds options to create a shared exception list and to create a single item from the manage exceptions view #144575

Merged
merged 65 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
fd62185
initial work
dhurley14 Aug 31, 2022
cfebdbe
adds exception list import button + flyout, updates title of page and…
dhurley14 Sep 8, 2022
36fc5b9
working import
dhurley14 Sep 9, 2022
0ac8f00
merge with main
dhurley14 Sep 15, 2022
9647b1f
import list + override or as new list
dhurley14 Sep 15, 2022
2465249
fix type failures (still some warnings present) and general code cleanup
dhurley14 Sep 20, 2022
c2002ea
Merge remote-tracking branch 'upstream/main' into exceptions-all-list…
dhurley14 Sep 20, 2022
2a2fa40
adds delete action to actions menu bar
dhurley14 Sep 21, 2022
5d8b600
WIP - adding overflow actions
dhurley14 Sep 22, 2022
6921cee
Merge branch 'main' into exceptions-all-lists-view
dhurley14 Sep 22, 2022
a244e83
display flyout - not working right now, WIP
dhurley14 Sep 23, 2022
38c2713
properly formatted exceptions cards
dhurley14 Sep 26, 2022
9679dc3
display title of exception list in blue color
dhurley14 Sep 26, 2022
9c1245e
cleanup
dhurley14 Sep 27, 2022
833ff73
cleanup + start usage of pagination hooks
dhurley14 Sep 29, 2022
b3e7d29
working on pagination, sort of working
dhurley14 Sep 29, 2022
7d0cca9
working pagination, finally
dhurley14 Sep 29, 2022
47d95b1
adds comment explaining bug in pagination, also reset default paginat…
dhurley14 Sep 30, 2022
1a2d1b5
undo changes to getNewExceptionItem function
dhurley14 Sep 30, 2022
ae09187
undo changes that made ruleName an optional parameter for ExceptionBu…
dhurley14 Sep 30, 2022
c69f21b
add ternary logic for pageCount in pagination of exception list cards
dhurley14 Sep 30, 2022
002850a
Merge remote-tracking branch 'upstream/main' into interim-exceptions-…
dhurley14 Sep 30, 2022
bdf8045
update snapshots, fix test mocks to include new properties, update te…
dhurley14 Sep 30, 2022
a8dfd88
try to fix cypress, I think I fixed all the jests tests though
dhurley14 Sep 30, 2022
6d87807
fixes jest test expected text in breadcrumbs
dhurley14 Oct 4, 2022
0cc8118
fixes cypress tests
dhurley14 Oct 5, 2022
8139f2d
Merge remote-tracking branch 'upstream/main' into interim-exceptions-…
dhurley14 Oct 5, 2022
316d952
refactors cypress tests
MadameSheema Oct 6, 2022
9206a37
move import flyout to separate file
dhurley14 Oct 6, 2022
c7add87
fix alignItems in the exception list card
dhurley14 Oct 6, 2022
179c272
fix flex boxes
dhurley14 Oct 6, 2022
d88996b
use styled flex item for badges
dhurley14 Oct 10, 2022
28e3af9
add title badge component to clean up code a little bit
dhurley14 Oct 10, 2022
32231ef
fix pagination location
dhurley14 Oct 10, 2022
a7f0897
don't refresh exceptions table
dhurley14 Oct 18, 2022
6e87260
merge with main
dhurley14 Oct 25, 2022
265cfa7
Merge branch 'main' into interim-exceptions-all-lists
dhurley14 Oct 26, 2022
8a4d040
adds i18n
dhurley14 Oct 27, 2022
1e8d0de
cleanup useEffect if statements
dhurley14 Oct 27, 2022
a92fa17
remove ts expect error
dhurley14 Oct 27, 2022
6b3391b
Merge remote-tracking branch 'upstream/main' into interim-exceptions-…
dhurley14 Oct 27, 2022
d25a76a
fix cypress test - delete should be disabled and adds translation fil…
dhurley14 Oct 27, 2022
b715fcf
Merge remote-tracking branch 'upstream/main' into interim-exceptions-…
dhurley14 Oct 28, 2022
dd6995c
move components out of rules exceptions
dhurley14 Oct 28, 2022
d620366
adds exception item flyout
dhurley14 Oct 28, 2022
2c6e961
Merge remote-tracking branch 'upstream/main' into more-exceptions-work
dhurley14 Oct 31, 2022
57198a8
Merge remote-tracking branch 'upstream/main' into more-exceptions-work
dhurley14 Oct 31, 2022
4525e00
add create shared exception list flyout - still need to create suppor…
dhurley14 Oct 31, 2022
fe10daf
working shared exceptions list route and flyout
dhurley14 Nov 3, 2022
100fc45
merge with main
dhurley14 Nov 3, 2022
d612b00
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 3, 2022
2eaa507
Merge remote-tracking branch 'upstream/main' into more-exceptions-work
dhurley14 Nov 4, 2022
b748ff8
fix type errors
dhurley14 Nov 4, 2022
ce5788a
Merge branch 'more-exceptions-work' of github.com:dhurley14/kibana in…
dhurley14 Nov 4, 2022
17ecc55
fix merge conflict
dhurley14 Nov 4, 2022
0c5081c
adds missing translation
dhurley14 Nov 4, 2022
dc7c006
fixes i18n for package
dhurley14 Nov 4, 2022
287a795
cleanup i18n, removes unused files, removes commented out code
dhurley14 Nov 7, 2022
48c2ade
more i18n updates
dhurley14 Nov 7, 2022
2b24a88
Merge branch 'main' into more-exceptions-work
dhurley14 Nov 7, 2022
2396070
fixes misspelling of function
dhurley14 Nov 8, 2022
f5f3ec3
adds constant for api route for creating a shared exception list, upd…
dhurley14 Nov 8, 2022
ba31ece
Merge branch 'main' into more-exceptions-work
dhurley14 Nov 8, 2022
2f2579d
remove the commented out code
dhurley14 Nov 8, 2022
1f44dc8
Merge branch 'more-exceptions-work' of github.com:dhurley14/kibana in…
dhurley14 Nov 8, 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 @@ -8,6 +8,7 @@

import React from 'react';
import { EuiComboBox } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { FieldProps } from './types';
import { useField } from './use_field';
Expand Down Expand Up @@ -62,7 +63,10 @@ export const FieldComponent: React.FC<FieldProps> = ({
data-test-subj="fieldAutocompleteComboBox"
style={fieldWidth}
onCreateOption={handleCreateCustomOption}
customOptionText="Add {searchValue} as your occupation"
customOptionText={i18n.translate('autocomplete.customOptionText', {
defaultMessage: 'Add {searchValuePlaceholder} as a custom field',
values: { searchValuePlaceholder: '{searchValue}' },
})}
fullWidth
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export const exceptionListTypeOrUndefined = t.union([exceptionListType, t.undefi
export type ExceptionListType = t.TypeOf<typeof exceptionListType>;
export type ExceptionListTypeOrUndefined = t.TypeOf<typeof exceptionListTypeOrUndefined>;
export enum ExceptionListTypeEnum {
DETECTION = 'detection',
RULE_DEFAULT = 'rule_default',
DETECTION = 'detection', // shared exception list type
RULE_DEFAULT = 'rule_default', // rule default, cannot be shared
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

ENDPOINT = 'endpoint',
ENDPOINT_TRUSTED_APPS = 'endpoint',
ENDPOINT_EVENTS = 'endpoint_events',
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ export const LEGACY_NOTIFICATIONS_ID = `siem.notifications` as const;
*/
export const UPDATE_OR_CREATE_LEGACY_ACTIONS = '/internal/api/detection/legacy/notifications';

/**
* Exceptions management routes
*/

export const SHARED_EXCEPTION_LIST_URL = `/api${EXCEPTIONS_PATH}/shared` as const;

/**
* Detection engine routes
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { ChangeEvent } from 'react';
import React, { memo, useState, useCallback, useRef, useEffect } from 'react';
import {
EuiFlyout,
EuiTitle,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiText,
EuiFieldText,
EuiSpacer,
EuiTextArea,
EuiFlyoutFooter,
EuiFlexGroup,
EuiButtonEmpty,
EuiButton,
EuiFlexItem,
} from '@elastic/eui';
import type { HttpSetup } from '@kbn/core-http-browser';
import type { ErrorToastOptions, Toast, ToastInput } from '@kbn/core-notifications-browser';
import { i18n as translate } from '@kbn/i18n';
import type { ListDetails } from '@kbn/securitysolution-exception-list-components';

import { useCreateSharedExceptionListWithOptionalSignal } from './use_create_shared_list';
import {
CREATE_SHARED_LIST_TITLE,
CREATE_SHARED_LIST_NAME_FIELD,
CREATE_SHARED_LIST_DESCRIPTION,
CREATE_BUTTON,
CLOSE_FLYOUT,
CREATE_SHARED_LIST_DESCRIPTION_PLACEHOLDER,
CREATE_SHARED_LIST_NAME_FIELD_PLACEHOLDER,
SUCCESS_TITLE,
getSuccessText,
} from './translations';

export const CreateSharedListFlyout = memo(
({
handleRefresh,
http,
handleCloseFlyout,
addSuccess,
addError,
}: {
handleRefresh: () => void;
http: HttpSetup;
addSuccess: (toastOrTitle: ToastInput, options?: unknown) => Toast;
addError: (error: unknown, options: ErrorToastOptions) => Toast;
handleCloseFlyout: () => void;
}) => {
const { start: createSharedExceptionList, ...createSharedExceptionListState } =
useCreateSharedExceptionListWithOptionalSignal();
const ctrl = useRef(new AbortController());

enum DetailProperty {
name = 'name',
description = 'description',
}

const [newListDetails, setNewListDetails] = useState<ListDetails>({
name: '',
description: '',
});
const onChange = (
{ target }: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
detailProperty: DetailProperty.name | DetailProperty.description
) => {
const { value } = target;
setNewListDetails({ ...newListDetails, [detailProperty]: value });
};

const handleCreateSharedExceptionList = useCallback(() => {
if (!createSharedExceptionListState.loading && newListDetails.name !== '') {
ctrl.current = new AbortController();

createSharedExceptionList({
http,
signal: ctrl.current.signal,
name: newListDetails.name,
description: newListDetails.description ?? '',
});
}
}, [createSharedExceptionList, createSharedExceptionListState.loading, newListDetails, http]);

const handleCreateSuccess = useCallback(
(response) => {
addSuccess({
text: getSuccessText(newListDetails.name),
title: SUCCESS_TITLE,
});
handleRefresh();

handleCloseFlyout();
},
[addSuccess, handleCloseFlyout, handleRefresh, newListDetails]
);

const handleCreateError = useCallback(
(error) => {
if (!error.message.includes('AbortError') && !error?.body?.message.includes('AbortError')) {
addError(error, {
title: translate.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListErrorTitle',
{
defaultMessage: 'creation error',
}
),
});
}
},
[addError]
);

useEffect(() => {
if (!createSharedExceptionListState.loading) {
if (createSharedExceptionListState?.result) {
handleCreateSuccess(createSharedExceptionListState.result);
} else if (createSharedExceptionListState?.error) {
handleCreateError(createSharedExceptionListState?.error);
}
}
}, [
createSharedExceptionListState?.error,
createSharedExceptionListState.loading,
createSharedExceptionListState.result,
handleCreateError,
handleCreateSuccess,
]);

return (
<EuiFlyout
ownFocus
size="s"
onClose={handleCloseFlyout}
data-test-subj="createSharedExceptionListFlyout"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 data-test-subj="createSharedExceptionListTitle">{CREATE_SHARED_LIST_TITLE}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiText>{CREATE_SHARED_LIST_NAME_FIELD}</EuiText>
<EuiFieldText
placeholder={CREATE_SHARED_LIST_NAME_FIELD_PLACEHOLDER}
value={newListDetails.name}
onChange={(e) => onChange(e, DetailProperty.name)}
aria-label="Use aria labels when no actual label is in use"
/>
<EuiSpacer />
<EuiText>{CREATE_SHARED_LIST_DESCRIPTION}</EuiText>
<EuiTextArea
placeholder={CREATE_SHARED_LIST_DESCRIPTION_PLACEHOLDER}
value={newListDetails.description}
onChange={(e) => onChange(e, DetailProperty.description)}
aria-label="Stop the hackers"
/>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={handleCloseFlyout} flush="left">
{CLOSE_FLYOUT}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="exception-lists-form-create-shared"
onClick={handleCreateSharedExceptionList}
disabled={newListDetails.name === ''}
>
{CREATE_BUTTON}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
);
}
);

CreateSharedListFlyout.displayName = 'CreateSharedListFlyout';
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../common/endpoint/service
import { ExceptionsListCard } from './exceptions_list_card';

import { ImportExceptionListFlyout } from './import_exceptions_list_flyout';
import { CreateSharedListFlyout } from './create_shared_exception_list';

import { AddExceptionFlyout } from '../../detection_engine/rule_exceptions/components/add_exception_flyout';

export type Func = () => Promise<void>;

Expand Down Expand Up @@ -355,6 +358,16 @@ export const ExceptionListsTable = React.memo(() => {

const goToPage = (pageNumber: number) => setActivePage(pageNumber);

const [isCreatePopoverOpen, setIsCreatePopoverOpen] = useState(false);
const [displayAddExceptionItemFlyout, setDisplayAddExceptionItemFlyout] = useState(false);
const [displayCreateSharedListFlyout, setDisplayCreateSharedListFlyout] = useState(false);

const onCreateButtonClick = () => setIsCreatePopoverOpen((isOpen) => !isOpen);
const onCloseCreatePopover = () => {
setDisplayAddExceptionItemFlyout(false);
setIsCreatePopoverOpen(false);
};

return (
<>
<MissingPrivilegesCallOut />
Expand All @@ -370,11 +383,67 @@ export const ExceptionListsTable = React.memo(() => {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton iconType={'importAction'} onClick={() => setDisplayImportListFlyout(true)}>
{i18n.IMPORT_EXCEPTION_LIST}
{i18n.IMPORT_EXCEPTION_LIST_BUTTON}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
data-test-subj="manageExceptionListCreateButton"
button={
<EuiButton iconType={'arrowDown'} onClick={onCreateButtonClick}>
{i18n.CREATE_BUTTON}
</EuiButton>
}
isOpen={isCreatePopoverOpen}
closePopover={onCloseCreatePopover}
>
<EuiContextMenuPanel
items={[
<EuiContextMenuItem
key={'createList'}
onClick={() => {
onCloseCreatePopover();
setDisplayCreateSharedListFlyout(true);
}}
>
{i18n.CREATE_SHARED_LIST_BUTTON}
</EuiContextMenuItem>,
<EuiContextMenuItem
key={'createItem'}
onClick={() => {
onCloseCreatePopover();
setDisplayAddExceptionItemFlyout(true);
}}
>
{i18n.CREATE_BUTTON_ITEM_BUTTON}
</EuiContextMenuItem>,
]}
/>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>

{displayCreateSharedListFlyout && (
<CreateSharedListFlyout
handleRefresh={handleRefresh}
http={http}
addSuccess={addSuccess}
addError={addError}
handleCloseFlyout={() => setDisplayCreateSharedListFlyout(false)}
/>
)}

{displayAddExceptionItemFlyout && (
<AddExceptionFlyout
rules={null}
isEndpointItem={false}
isBulkAction={false}
showAlertCloseOptions
onCancel={(didRuleChange: boolean) => setDisplayAddExceptionItemFlyout(false)}
onConfirm={(didRuleChange: boolean) => setDisplayAddExceptionItemFlyout(false)}
/>
)}

{displayImportListFlyout && (
<ImportExceptionListFlyout
handleRefresh={handleRefresh}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const EXPORT_EXCEPTION_LIST = i18n.translate(
);

export const IMPORT_EXCEPTION_LIST_HEADER = i18n.translate(
'xpack.securitySolution.exceptionsTable.importExceptionListFlyoutHeader',
'xpack.securitySolution.exceptionsTable.importExceptionListHeader',
{
defaultMessage: 'Import shared exception list',
}
Expand Down Expand Up @@ -104,3 +104,58 @@ export const IMPORT_PROMPT = i18n.translate(
defaultMessage: 'Select or drag and drop multiple files',
}
);

export const CREATE_SHARED_LIST_TITLE = i18n.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListTitle',
{
defaultMessage: 'Create shared exception list',
}
);

export const CREATE_SHARED_LIST_NAME_FIELD = i18n.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListFlyoutNameField',
{
defaultMessage: 'Shared exception list name',
}
);

export const CREATE_SHARED_LIST_NAME_FIELD_PLACEHOLDER = i18n.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListFlyoutNameFieldPlaceholder',
{
defaultMessage: 'New exception list',
}
);

export const CREATE_SHARED_LIST_DESCRIPTION = i18n.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListFlyoutDescription',
{
defaultMessage: 'Description (optional)',
}
);

export const CREATE_SHARED_LIST_DESCRIPTION_PLACEHOLDER = i18n.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListFlyoutDescriptionPlaceholder',
{
defaultMessage: 'New exception list',
}
);

export const CREATE_BUTTON = i18n.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListFlyoutCreateButton',
{
defaultMessage: 'Create shared exception list',
}
);

export const getSuccessText = (listName: string) =>
i18n.translate('xpack.securitySolution.exceptions.createSharedExceptionListSuccessDescription', {
defaultMessage: 'list with name ${listName} was created!',
values: { listName },
});

export const SUCCESS_TITLE = i18n.translate(
'xpack.securitySolution.exceptions.createSharedExceptionListSuccessTitle',
{
defaultMessage: 'created list',
}
);
Loading