mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution][Exceptions] Rule exceptions TTL - Expiration (#145180)
This commit is contained in:
parent
4052d18cef
commit
92a1689e95
109 changed files with 1343 additions and 165 deletions
|
@ -15,7 +15,7 @@ import * as i18n from '../../translations';
|
|||
interface MetaInfoDetailsProps {
|
||||
label: string;
|
||||
lastUpdate: JSX.Element | string;
|
||||
lastUpdateValue: string;
|
||||
lastUpdateValue?: string;
|
||||
dataTestSubj?: string;
|
||||
}
|
||||
|
||||
|
@ -42,20 +42,24 @@ export const MetaInfoDetails = memo<MetaInfoDetailsProps>(
|
|||
{lastUpdate}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs" css={euiBadgeFontFamily}>
|
||||
{i18n.EXCEPTION_ITEM_CARD_META_BY}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj={`${dataTestSubj || ''}lastUpdateValue`}>
|
||||
<EuiFlexGroup responsive gutterSize="xs" alignItems="center">
|
||||
{lastUpdateValue != null && (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow" css={euiBadgeFontFamily}>
|
||||
{lastUpdateValue}
|
||||
</EuiBadge>
|
||||
<EuiText size="xs" css={euiBadgeFontFamily}>
|
||||
{i18n.EXCEPTION_ITEM_CARD_META_BY}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj={`${dataTestSubj || ''}lastUpdateValue`}>
|
||||
<EuiFlexGroup responsive gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow" css={euiBadgeFontFamily}>
|
||||
{lastUpdateValue}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,12 @@ export const ExceptionItemCardMetaInfo = memo<ExceptionItemCardMetaInfoProps>(
|
|||
}),
|
||||
[dataTestSubj, rules, securityLinkAnchorComponent]
|
||||
);
|
||||
|
||||
const isExpired = useMemo(
|
||||
() => (item.expire_time ? new Date(item.expire_time) <= new Date() : false),
|
||||
[item]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" responsive gutterSize="s" data-test-subj={dataTestSubj}>
|
||||
{FormattedDateComponent !== null && (
|
||||
|
@ -77,6 +83,27 @@ export const ExceptionItemCardMetaInfo = memo<ExceptionItemCardMetaInfoProps>(
|
|||
dataTestSubj={`${dataTestSubj || ''}UpdatedBy`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{item.expire_time != null && (
|
||||
<>
|
||||
<EuiFlexItem css={itemCss} grow={false}>
|
||||
<MetaInfoDetails
|
||||
label={
|
||||
isExpired
|
||||
? i18n.EXCEPTION_ITEM_CARD_EXPIRED_LABEL
|
||||
: i18n.EXCEPTION_ITEM_CARD_EXPIRES_LABEL
|
||||
}
|
||||
lastUpdate={
|
||||
<FormattedDateComponent
|
||||
data-test-subj={`{dataTestSubj||''}formattedDateComponentExpireTime`}
|
||||
fieldName="expire_time"
|
||||
value={item.expire_time}
|
||||
/>
|
||||
}
|
||||
dataTestSubj={`${dataTestSubj || ''}ExpireTime`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
|
|
|
@ -34,6 +34,20 @@ export const EXCEPTION_ITEM_CARD_UPDATED_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_ITEM_CARD_EXPIRES_LABEL = i18n.translate(
|
||||
'exceptionList-components.exceptions.exceptionItem.card.expiresLabel',
|
||||
{
|
||||
defaultMessage: 'Expires at',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_ITEM_CARD_EXPIRED_LABEL = i18n.translate(
|
||||
'exceptionList-components.exceptions.exceptionItem.card.expiredLabel',
|
||||
{
|
||||
defaultMessage: 'Expired at',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_ITEM_CARD_META_BY = i18n.translate(
|
||||
'exceptionList-components.exceptions.exceptionItem.card.metaDetailsBy',
|
||||
{
|
||||
|
|
|
@ -29,9 +29,9 @@ interface ExceptionListHeaderComponentProps {
|
|||
canUserEditList?: boolean;
|
||||
securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common
|
||||
onEditListDetails: (listDetails: ListDetails) => void;
|
||||
onExportList: () => void;
|
||||
onDeleteList: () => void;
|
||||
onManageRules: () => void;
|
||||
onExportList: () => void;
|
||||
}
|
||||
|
||||
export interface BackOptions {
|
||||
|
@ -51,9 +51,9 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
|
|||
backOptions,
|
||||
canUserEditList = true,
|
||||
onEditListDetails,
|
||||
onExportList,
|
||||
onDeleteList,
|
||||
onManageRules,
|
||||
onExportList,
|
||||
}) => {
|
||||
const { isModalVisible, listDetails, onEdit, onSave, onCancel } = useExceptionListHeader({
|
||||
name,
|
||||
|
@ -97,9 +97,9 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
|
|||
isReadonly={isReadonly}
|
||||
canUserEditList={canUserEditList}
|
||||
securityLinkAnchorComponent={securityLinkAnchorComponent}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={onDeleteList}
|
||||
onManageRules={onManageRules}
|
||||
onExportList={onExportList}
|
||||
/>,
|
||||
]}
|
||||
breadcrumbs={[
|
||||
|
|
|
@ -18,9 +18,9 @@ interface MenuItemsProps {
|
|||
linkedRules: Rule[];
|
||||
canUserEditList?: boolean;
|
||||
securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common
|
||||
onExportList: () => void;
|
||||
onDeleteList: () => void;
|
||||
onManageRules: () => void;
|
||||
onExportList: () => void;
|
||||
}
|
||||
|
||||
const MenuItemsComponent: FC<MenuItemsProps> = ({
|
||||
|
@ -29,9 +29,9 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
|
|||
securityLinkAnchorComponent,
|
||||
isReadonly,
|
||||
canUserEditList = true,
|
||||
onExportList,
|
||||
onDeleteList,
|
||||
onManageRules,
|
||||
onExportList,
|
||||
}) => {
|
||||
const referencedLinks = useMemo(
|
||||
() =>
|
||||
|
@ -78,7 +78,7 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
|
|||
data-test-subj={`${dataTestSubj || ''}ManageRulesButton`}
|
||||
fill
|
||||
onClick={() => {
|
||||
if (typeof onExportList === 'function') onManageRules();
|
||||
if (typeof onManageRules === 'function') onManageRules();
|
||||
}}
|
||||
>
|
||||
{i18n.EXCEPTION_LIST_HEADER_MANAGE_RULES_BUTTON}
|
||||
|
|
|
@ -108,7 +108,7 @@ describe('MenuItems', () => {
|
|||
fireEvent.click(wrapper.getByTestId('ManageRulesButton'));
|
||||
expect(onManageRules).toHaveBeenCalled();
|
||||
});
|
||||
it('should call onExportList', () => {
|
||||
it('should call onExportModalOpen', () => {
|
||||
const wrapper = render(
|
||||
<MenuItems
|
||||
isReadonly={false}
|
||||
|
|
|
@ -26,6 +26,7 @@ export const getExceptionListItemSchemaMock = (
|
|||
},
|
||||
{ field: 'some.not.nested.field', operator: 'included', type: 'match', value: 'some value' },
|
||||
],
|
||||
expire_time: undefined,
|
||||
id: '1',
|
||||
item_id: 'endpoint_list_item',
|
||||
list_id: 'endpoint_list_id',
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { IsoDateString } from '@kbn/securitysolution-io-ts-types';
|
||||
|
||||
export const expireTime = IsoDateString;
|
||||
export const expireTimeOrUndefined = t.union([expireTime, t.undefined]);
|
||||
export type ExpireTimeOrUndefined = t.TypeOf<typeof expireTimeOrUndefined>;
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const include_expired_exceptions = t.keyof({ true: null, false: null });
|
||||
export const includeExpiredExceptionsOrUndefined = t.union([
|
||||
include_expired_exceptions,
|
||||
t.undefined,
|
||||
]);
|
||||
export type IncludeExpiredExceptionsOrUndefined = t.TypeOf<
|
||||
typeof includeExpiredExceptionsOrUndefined
|
||||
>;
|
|
@ -28,6 +28,7 @@ export * from './entry_nested';
|
|||
export * from './exception_export_details';
|
||||
export * from './exception_list';
|
||||
export * from './exception_list_item_type';
|
||||
export * from './expire_time';
|
||||
export * from './filter';
|
||||
export * from './id';
|
||||
export * from './immutable';
|
||||
|
|
|
@ -22,6 +22,7 @@ import { description } from '../../common/description';
|
|||
import { name } from '../../common/name';
|
||||
import { meta } from '../../common/meta';
|
||||
import { tags } from '../../common/tags';
|
||||
import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common';
|
||||
|
||||
export const createEndpointListItemSchema = t.intersection([
|
||||
t.exact(
|
||||
|
@ -39,6 +40,7 @@ export const createEndpointListItemSchema = t.intersection([
|
|||
meta, // defaults to undefined if not set during decode
|
||||
os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode
|
||||
tags, // defaults to empty array if not set during decode
|
||||
expire_time: expireTimeOrUndefined, // defaults to undefined if not set during decode
|
||||
})
|
||||
),
|
||||
]);
|
||||
|
@ -48,11 +50,12 @@ export type CreateEndpointListItemSchema = t.OutputOf<typeof createEndpointListI
|
|||
// This type is used after a decode since some things are defaults after a decode.
|
||||
export type CreateEndpointListItemSchemaDecoded = Omit<
|
||||
RequiredKeepUndefined<t.TypeOf<typeof createEndpointListItemSchema>>,
|
||||
'tags' | 'item_id' | 'entries' | 'comments' | 'os_types'
|
||||
'tags' | 'item_id' | 'entries' | 'comments' | 'os_types' | 'expire_time'
|
||||
> & {
|
||||
comments: CreateCommentsArray;
|
||||
tags: Tags;
|
||||
item_id: ItemId;
|
||||
entries: EntriesArray;
|
||||
os_types: OsTypeArray;
|
||||
expire_time: ExpireTimeOrUndefined;
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ import { meta } from '../../common/meta';
|
|||
import { namespace_type } from '../../common/namespace_type';
|
||||
import { tags } from '../../common/tags';
|
||||
import { nonEmptyEntriesArray } from '../../common/non_empty_entries_array';
|
||||
import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common';
|
||||
|
||||
export const createExceptionListItemSchema = t.intersection([
|
||||
t.exact(
|
||||
|
@ -39,6 +40,7 @@ export const createExceptionListItemSchema = t.intersection([
|
|||
t.exact(
|
||||
t.partial({
|
||||
comments: DefaultCreateCommentsArray, // defaults to empty array if not set during decode
|
||||
expire_time: expireTimeOrUndefined,
|
||||
item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode
|
||||
meta, // defaults to undefined if not set during decode
|
||||
namespace_type, // defaults to 'single' if not set during decode
|
||||
|
@ -53,9 +55,10 @@ export type CreateExceptionListItemSchema = t.OutputOf<typeof createExceptionLis
|
|||
// This type is used after a decode since some things are defaults after a decode.
|
||||
export type CreateExceptionListItemSchemaDecoded = Omit<
|
||||
RequiredKeepUndefined<t.TypeOf<typeof createExceptionListItemSchema>>,
|
||||
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
|
||||
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' | 'expire_time'
|
||||
> & {
|
||||
comments: CreateCommentsArray;
|
||||
expire_time: ExpireTimeOrUndefined;
|
||||
tags: Tags;
|
||||
item_id: ItemId;
|
||||
entries: EntriesArray;
|
||||
|
|
|
@ -25,6 +25,8 @@ import {
|
|||
Tags,
|
||||
tags,
|
||||
name,
|
||||
ExpireTimeOrUndefined,
|
||||
expireTimeOrUndefined,
|
||||
} from '../../common';
|
||||
import { RequiredKeepUndefined } from '../../common/required_keep_undefined';
|
||||
|
||||
|
@ -46,6 +48,7 @@ export const createRuleExceptionListItemSchema = t.intersection([
|
|||
namespace_type: namespaceType, // defaults to 'single' if not set during decode
|
||||
os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode
|
||||
tags, // defaults to empty array if not set during decode
|
||||
expire_time: expireTimeOrUndefined,
|
||||
})
|
||||
),
|
||||
]);
|
||||
|
@ -57,7 +60,7 @@ export type CreateRuleExceptionListItemSchema = t.OutputOf<
|
|||
// This type is used after a decode since some things are defaults after a decode.
|
||||
export type CreateRuleExceptionListItemSchemaDecoded = Omit<
|
||||
RequiredKeepUndefined<t.TypeOf<typeof createRuleExceptionListItemSchema>>,
|
||||
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
|
||||
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' | 'expire_time'
|
||||
> & {
|
||||
comments: CreateCommentsArray;
|
||||
tags: Tags;
|
||||
|
@ -65,4 +68,5 @@ export type CreateRuleExceptionListItemSchemaDecoded = Omit<
|
|||
entries: EntriesArray;
|
||||
namespace_type: NamespaceType;
|
||||
os_types: OsTypeArray;
|
||||
expire_time: ExpireTimeOrUndefined;
|
||||
};
|
||||
|
|
|
@ -14,4 +14,5 @@ export const getExportExceptionListQuerySchemaMock = (): ExportExceptionListQuer
|
|||
id: ID,
|
||||
list_id: LIST_ID,
|
||||
namespace_type: NAMESPACE_TYPE,
|
||||
include_expired_exceptions: 'true',
|
||||
});
|
||||
|
|
|
@ -45,6 +45,7 @@ describe('export_exception_list_schema', () => {
|
|||
|
||||
expect(message.schema).toEqual({
|
||||
id: 'uuid_here',
|
||||
include_expired_exceptions: 'true',
|
||||
list_id: 'some-list-id',
|
||||
namespace_type: 'single',
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import * as t from 'io-ts';
|
||||
|
||||
import { id } from '../../common/id';
|
||||
import { includeExpiredExceptionsOrUndefined } from '../../common/include_expired_exceptions';
|
||||
import { list_id } from '../../common/list_id';
|
||||
import { namespace_type } from '../../common/namespace_type';
|
||||
|
||||
|
@ -17,6 +18,7 @@ export const exportExceptionListQuerySchema = t.exact(
|
|||
id,
|
||||
list_id,
|
||||
namespace_type,
|
||||
include_expired_exceptions: includeExpiredExceptionsOrUndefined,
|
||||
// TODO: Add file_name here with a default value
|
||||
})
|
||||
);
|
||||
|
|
|
@ -31,4 +31,5 @@ export const getImportExceptionsListItemSchemaDecodedMock = (
|
|||
namespace_type: 'single',
|
||||
os_types: [],
|
||||
tags: [],
|
||||
expire_time: undefined,
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ import { exceptionListItemType } from '../../common/exception_list_item_type';
|
|||
import { ItemId } from '../../common/item_id';
|
||||
import { EntriesArray } from '../../common/entries';
|
||||
import { DefaultImportCommentsArray } from '../../common/default_import_comments_array';
|
||||
import { ImportCommentsArray } from '../../common';
|
||||
import { ExpireTimeOrUndefined, expireTimeOrUndefined, ImportCommentsArray } from '../../common';
|
||||
|
||||
/**
|
||||
* Differences from this and the createExceptionsListItemSchema are
|
||||
|
@ -67,6 +67,7 @@ export const importExceptionListItemSchema = t.intersection([
|
|||
namespace_type, // defaults to 'single' if not set during decode
|
||||
os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode
|
||||
tags, // defaults to empty array if not set during decode
|
||||
expire_time: expireTimeOrUndefined,
|
||||
})
|
||||
),
|
||||
]);
|
||||
|
@ -76,7 +77,7 @@ export type ImportExceptionListItemSchema = t.OutputOf<typeof importExceptionLis
|
|||
// This type is used after a decode since some things are defaults after a decode.
|
||||
export type ImportExceptionListItemSchemaDecoded = Omit<
|
||||
ImportExceptionListItemSchema,
|
||||
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
|
||||
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' | 'expire_time'
|
||||
> & {
|
||||
comments: ImportCommentsArray;
|
||||
tags: Tags;
|
||||
|
@ -84,4 +85,5 @@ export type ImportExceptionListItemSchemaDecoded = Omit<
|
|||
entries: EntriesArray;
|
||||
namespace_type: NamespaceType;
|
||||
os_types: OsTypeArray;
|
||||
expire_time: ExpireTimeOrUndefined;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Tags, tags } from '../../common/tags';
|
|||
import { RequiredKeepUndefined } from '../../common/required_keep_undefined';
|
||||
import { UpdateCommentsArray } from '../../common/update_comment';
|
||||
import { EntriesArray } from '../../common/entries';
|
||||
import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common';
|
||||
|
||||
export const updateEndpointListItemSchema = t.intersection([
|
||||
t.exact(
|
||||
|
@ -40,6 +41,7 @@ export const updateEndpointListItemSchema = t.intersection([
|
|||
meta, // defaults to undefined if not set during decode
|
||||
os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode
|
||||
tags, // defaults to empty array if not set during decode
|
||||
expire_time: expireTimeOrUndefined,
|
||||
})
|
||||
),
|
||||
]);
|
||||
|
@ -49,10 +51,11 @@ export type UpdateEndpointListItemSchema = t.OutputOf<typeof updateEndpointListI
|
|||
// This type is used after a decode since some things are defaults after a decode.
|
||||
export type UpdateEndpointListItemSchemaDecoded = Omit<
|
||||
RequiredKeepUndefined<t.TypeOf<typeof updateEndpointListItemSchema>>,
|
||||
'tags' | 'entries' | 'comments'
|
||||
'tags' | 'entries' | 'comments' | 'expire_time'
|
||||
> & {
|
||||
comments: UpdateCommentsArray;
|
||||
tags: Tags;
|
||||
entries: EntriesArray;
|
||||
os_types: OsTypeArray;
|
||||
expire_time: ExpireTimeOrUndefined;
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ import { _version } from '../../common/underscore_version';
|
|||
import { id } from '../../common/id';
|
||||
import { meta } from '../../common/meta';
|
||||
import { namespace_type } from '../../common/namespace_type';
|
||||
import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common';
|
||||
|
||||
export const updateExceptionListItemSchema = t.intersection([
|
||||
t.exact(
|
||||
|
@ -36,6 +37,7 @@ export const updateExceptionListItemSchema = t.intersection([
|
|||
t.partial({
|
||||
_version, // defaults to undefined if not set during decode
|
||||
comments: DefaultUpdateCommentsArray, // defaults to empty array if not set during decode
|
||||
expire_time: expireTimeOrUndefined,
|
||||
id, // defaults to undefined if not set during decode
|
||||
item_id: t.union([t.string, t.undefined]),
|
||||
meta, // defaults to undefined if not set during decode
|
||||
|
@ -51,11 +53,12 @@ export type UpdateExceptionListItemSchema = t.OutputOf<typeof updateExceptionLis
|
|||
// This type is used after a decode since some things are defaults after a decode.
|
||||
export type UpdateExceptionListItemSchemaDecoded = Omit<
|
||||
RequiredKeepUndefined<t.TypeOf<typeof updateExceptionListItemSchema>>,
|
||||
'tags' | 'entries' | 'namespace_type' | 'comments' | 'os_types'
|
||||
'tags' | 'entries' | 'namespace_type' | 'comments' | 'os_types' | 'expire_time'
|
||||
> & {
|
||||
comments: UpdateCommentsArray;
|
||||
tags: Tags;
|
||||
entries: EntriesArray;
|
||||
namespace_type: NamespaceType;
|
||||
os_types: OsTypeArray;
|
||||
expire_time: ExpireTimeOrUndefined;
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ export const getExceptionListItemSchemaMock = (
|
|||
created_by: USER,
|
||||
description: DESCRIPTION,
|
||||
entries: ENTRIES,
|
||||
expire_time: undefined,
|
||||
id: '1',
|
||||
item_id: 'endpoint_list_item',
|
||||
list_id: 'endpoint_list_id',
|
||||
|
|
|
@ -26,6 +26,7 @@ import { commentsArray } from '../../common/comment';
|
|||
import { entriesArray } from '../../common/entries';
|
||||
import { item_id } from '../../common/item_id';
|
||||
import { exceptionListItemType } from '../../common/exception_list_item_type';
|
||||
import { expireTimeOrUndefined } from '../../common/expire_time';
|
||||
|
||||
export const exceptionListItemSchema = t.exact(
|
||||
t.type({
|
||||
|
@ -35,6 +36,7 @@ export const exceptionListItemSchema = t.exact(
|
|||
created_by,
|
||||
description,
|
||||
entries: entriesArray,
|
||||
expire_time: expireTimeOrUndefined,
|
||||
id,
|
||||
item_id,
|
||||
list_id,
|
||||
|
|
|
@ -77,6 +77,7 @@ export interface ApiCallMemoProps {
|
|||
// remove unnecessary validation checks
|
||||
export interface ApiListExportProps {
|
||||
id: string;
|
||||
includeExpiredExceptions: boolean;
|
||||
listId: string;
|
||||
namespaceType: NamespaceType;
|
||||
onError: (err: Error) => void;
|
||||
|
@ -133,6 +134,7 @@ export interface ExportExceptionListProps {
|
|||
id: string;
|
||||
listId: string;
|
||||
namespaceType: NamespaceType;
|
||||
includeExpiredExceptions: boolean;
|
||||
signal: AbortSignal;
|
||||
}
|
||||
|
||||
|
|
|
@ -532,10 +532,11 @@ const addEndpointExceptionListWithValidation = async ({
|
|||
export { addEndpointExceptionListWithValidation as addEndpointExceptionList };
|
||||
|
||||
/**
|
||||
* Fetch an ExceptionList by providing a ExceptionList ID
|
||||
* Export an ExceptionList by providing a ExceptionList ID
|
||||
*
|
||||
* @param http Kibana http service
|
||||
* @param id ExceptionList ID (not list_id)
|
||||
* @param includeExpiredExceptions boolean for including expired exceptions
|
||||
* @param listId ExceptionList LIST_ID (not id)
|
||||
* @param namespaceType ExceptionList namespace_type
|
||||
* @param signal to cancel request
|
||||
|
@ -545,13 +546,19 @@ export { addEndpointExceptionListWithValidation as addEndpointExceptionList };
|
|||
export const exportExceptionList = async ({
|
||||
http,
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
signal,
|
||||
}: ExportExceptionListProps): Promise<Blob> =>
|
||||
http.fetch<Blob>(`${EXCEPTION_LIST_URL}/_export`, {
|
||||
method: 'POST',
|
||||
query: { id, list_id: listId, namespace_type: namespaceType },
|
||||
query: {
|
||||
id,
|
||||
list_id: listId,
|
||||
namespace_type: namespaceType,
|
||||
include_expired_exceptions: includeExpiredExceptions,
|
||||
},
|
||||
signal,
|
||||
});
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ export const getExceptionListItemSchemaMock = (
|
|||
created_by: USER,
|
||||
description: DESCRIPTION,
|
||||
entries: ENTRIES,
|
||||
expire_time: undefined,
|
||||
id: '1',
|
||||
item_id: 'endpoint_list_item',
|
||||
list_id: 'endpoint_list_id',
|
||||
|
|
|
@ -112,6 +112,7 @@ export const useApi = (http: HttpStart): ExceptionsApi => {
|
|||
},
|
||||
async exportExceptionList({
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
onError,
|
||||
|
@ -123,6 +124,7 @@ export const useApi = (http: HttpStart): ExceptionsApi => {
|
|||
const blob = await Api.exportExceptionList({
|
||||
http,
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
signal: abortCtrl.signal,
|
||||
|
|
|
@ -60,6 +60,7 @@ import {
|
|||
ExceptionsBuilderReturnExceptionItem,
|
||||
FormattedBuilderEntry,
|
||||
OperatorOption,
|
||||
SavedObjectType,
|
||||
} from '../types';
|
||||
|
||||
export const isEntryNested = (item: BuilderEntry): item is EntryNested => {
|
||||
|
@ -914,6 +915,21 @@ export const getDefaultNestedEmptyEntry = (): EmptyNestedEntry => ({
|
|||
export const containsValueListEntry = (items: ExceptionsBuilderExceptionItem[]): boolean =>
|
||||
items.some((item) => item.entries.some(({ type }) => type === OperatorTypeEnum.LIST));
|
||||
|
||||
export const buildShowActiveExceptionsFilter = (savedObjectPrefix: SavedObjectType[]): string => {
|
||||
const now = new Date().toISOString();
|
||||
const filters = savedObjectPrefix.map(
|
||||
(prefix) =>
|
||||
`${prefix}.attributes.expire_time > "${now}" OR NOT ${prefix}.attributes.expire_time: *`
|
||||
);
|
||||
return filters.join(',');
|
||||
};
|
||||
|
||||
export const buildShowExpiredExceptionsFilter = (savedObjectPrefix: SavedObjectType[]): string => {
|
||||
const now = new Date().toISOString();
|
||||
const filters = savedObjectPrefix.map((prefix) => `${prefix}.attributes.expire_time <= "${now}"`);
|
||||
return filters.join(',');
|
||||
};
|
||||
|
||||
const getIndexGroupName = (indexName: string): string => {
|
||||
// Check whether it is a Data Stream index
|
||||
const dataStreamExp = /.ds-(.*?)-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-[0-9]{6}/;
|
||||
|
|
|
@ -87,8 +87,8 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"epm-packages": "1922a722ea42ab4953a96037fabb81a9ded8e240",
|
||||
"epm-packages-assets": "00c8b5e5bf059627ffc9fbde920e1ac75926c5f6",
|
||||
"event_loop_delays_daily": "ef49e7f15649b551b458c7ea170f3ed17f89abd0",
|
||||
"exception-list": "aae42e8f19017277d194d37d4898ed6598c03e9a",
|
||||
"exception-list-agnostic": "2634ee4219d27663a5755536fc06cbf3bb4beba5",
|
||||
"exception-list": "38181294f64fc406c15f20d85ca306c8a4feb3c0",
|
||||
"exception-list-agnostic": "d527ce9d12b134cb163150057b87529043a8ec77",
|
||||
"file": "d12998f49bc82da596a9e6c8397999930187ec6a",
|
||||
"file-upload-usage-collection-telemetry": "c6fcb9a7efcf19b2bb66ca6e005bfee8961f6073",
|
||||
"fileShare": "f07d346acbb724eacf139a0fb781c38dc5280115",
|
||||
|
|
|
@ -53,6 +53,7 @@ export const getImportExceptionsListItemSchemaDecodedMock = (
|
|||
): ImportExceptionListItemSchemaDecoded => ({
|
||||
...getImportExceptionsListItemSchemaMock(itemId, listId),
|
||||
comments: [],
|
||||
expire_time: undefined,
|
||||
meta: undefined,
|
||||
namespace_type: 'single',
|
||||
os_types: [],
|
||||
|
|
|
@ -33,6 +33,7 @@ export const getExceptionListItemSchemaMock = (
|
|||
created_by: USER,
|
||||
description: DESCRIPTION,
|
||||
entries: ENTRIES,
|
||||
expire_time: undefined,
|
||||
id: '1',
|
||||
item_id: 'endpoint_list_item',
|
||||
list_id: 'endpoint_list_id',
|
||||
|
|
|
@ -698,6 +698,7 @@ describe('Exceptions Lists API', () => {
|
|||
await exportExceptionList({
|
||||
http: httpMock,
|
||||
id: 'some-id',
|
||||
includeExpiredExceptions: true,
|
||||
listId: 'list-id',
|
||||
namespaceType: 'single',
|
||||
signal: abortCtrl.signal,
|
||||
|
@ -707,6 +708,7 @@ describe('Exceptions Lists API', () => {
|
|||
method: 'POST',
|
||||
query: {
|
||||
id: 'some-id',
|
||||
include_expired_exceptions: true,
|
||||
list_id: 'list-id',
|
||||
namespace_type: 'single',
|
||||
},
|
||||
|
@ -718,6 +720,7 @@ describe('Exceptions Lists API', () => {
|
|||
const exceptionResponse = await exportExceptionList({
|
||||
http: httpMock,
|
||||
id: 'some-id',
|
||||
includeExpiredExceptions: true,
|
||||
listId: 'list-id',
|
||||
namespaceType: 'single',
|
||||
signal: abortCtrl.signal,
|
||||
|
|
|
@ -43,6 +43,7 @@ export const createEndpointListItemRoute = (router: ListsPluginRouter): void =>
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expire_time: expireTime,
|
||||
item_id: itemId,
|
||||
os_types: osTypes,
|
||||
type,
|
||||
|
@ -62,6 +63,7 @@ export const createEndpointListItemRoute = (router: ListsPluginRouter): void =>
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
meta,
|
||||
name,
|
||||
|
|
|
@ -50,6 +50,7 @@ export const createExceptionListItemRoute = (router: ListsPluginRouter): void =>
|
|||
list_id: listId,
|
||||
os_types: osTypes,
|
||||
type,
|
||||
expire_time: expireTime,
|
||||
} = request.body;
|
||||
const exceptionLists = await getExceptionListClient(context);
|
||||
const exceptionList = await exceptionLists.getExceptionList({
|
||||
|
@ -92,6 +93,7 @@ export const createExceptionListItemRoute = (router: ListsPluginRouter): void =>
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
listId,
|
||||
meta,
|
||||
|
|
|
@ -217,6 +217,7 @@ const updateExceptionListItems = async (
|
|||
comments: listItem.comments,
|
||||
description: listItem.description,
|
||||
entries: remainingEntries,
|
||||
expireTime: listItem.expire_time,
|
||||
id: listItem.id,
|
||||
itemId: listItem.item_id,
|
||||
meta: listItem.meta,
|
||||
|
|
|
@ -28,11 +28,22 @@ export const exportExceptionsRoute = (router: ListsPluginRouter): void => {
|
|||
const siemResponse = buildSiemResponse(response);
|
||||
|
||||
try {
|
||||
const { id, list_id: listId, namespace_type: namespaceType } = request.query;
|
||||
const {
|
||||
id,
|
||||
list_id: listId,
|
||||
namespace_type: namespaceType,
|
||||
include_expired_exceptions: includeExpiredExceptionsString,
|
||||
} = request.query;
|
||||
const exceptionListsClient = await getExceptionListClient(context);
|
||||
|
||||
// Defaults to including expired exceptions if query param is not present
|
||||
const includeExpiredExceptions =
|
||||
includeExpiredExceptionsString !== undefined
|
||||
? includeExpiredExceptionsString === 'true'
|
||||
: true;
|
||||
const exportContent = await exceptionListsClient.exportExceptionListAndItems({
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
});
|
||||
|
|
|
@ -52,6 +52,11 @@ export const findExceptionListItemRoute = (router: ListsPluginRouter): void => {
|
|||
body: `list_id and namespace_id need to have the same comma separated number of values. Expected list_id length: ${listId.length} to equal namespace_type length: ${namespaceType.length}`,
|
||||
statusCode: 400,
|
||||
});
|
||||
} else if (listId.length !== filter.length && filter.length !== 0) {
|
||||
return siemResponse.error({
|
||||
body: `list_id and filter need to have the same comma separated number of values. Expected list_id length: ${listId.length} to equal filter length: ${filter.length}`,
|
||||
statusCode: 400,
|
||||
});
|
||||
} else {
|
||||
const exceptionListItems = await exceptionLists.findExceptionListsItem({
|
||||
filter,
|
||||
|
|
|
@ -82,6 +82,7 @@ export const getExceptionFilterRoute = (router: ListsPluginRouter): void => {
|
|||
excludeExceptions,
|
||||
listClient,
|
||||
lists: exceptionItems,
|
||||
startedAt: new Date(),
|
||||
});
|
||||
|
||||
return response.ok({ body: { filter } ?? {} });
|
||||
|
|
|
@ -49,6 +49,7 @@ export const updateEndpointListItemRoute = (router: ListsPluginRouter): void =>
|
|||
entries,
|
||||
item_id: itemId,
|
||||
tags,
|
||||
expire_time: expireTime,
|
||||
} = request.body;
|
||||
const exceptionLists = await getExceptionListClient(context);
|
||||
const exceptionListItem = await exceptionLists.updateEndpointListItem({
|
||||
|
@ -56,6 +57,7 @@ export const updateEndpointListItemRoute = (router: ListsPluginRouter): void =>
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
|
|
|
@ -56,6 +56,7 @@ export const updateExceptionListItemRoute = (router: ListsPluginRouter): void =>
|
|||
namespace_type: namespaceType,
|
||||
os_types: osTypes,
|
||||
tags,
|
||||
expire_time: expireTime,
|
||||
} = request.body;
|
||||
if (id == null && itemId == null) {
|
||||
return siemResponse.error({
|
||||
|
@ -69,6 +70,7 @@ export const updateExceptionListItemRoute = (router: ListsPluginRouter): void =>
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
|
|
|
@ -159,6 +159,9 @@ export const exceptionListItemMapping: SavedObjectsType['mappings'] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
expire_time: {
|
||||
type: 'date',
|
||||
},
|
||||
item_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
|
|
|
@ -22,6 +22,7 @@ const DEFAULT_EXCEPTION_LIST_SO: ExceptionListSoSchema = {
|
|||
created_by: 'user',
|
||||
description: 'description',
|
||||
entries: undefined,
|
||||
expire_time: undefined,
|
||||
immutable: false,
|
||||
item_id: undefined,
|
||||
list_id: 'some_list',
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
entriesArrayOrUndefined,
|
||||
exceptionListItemType,
|
||||
exceptionListType,
|
||||
expireTimeOrUndefined,
|
||||
immutableOrUndefined,
|
||||
itemIdOrUndefined,
|
||||
list_id,
|
||||
|
@ -37,6 +38,7 @@ export const exceptionListSoSchema = t.exact(
|
|||
created_by,
|
||||
description,
|
||||
entries: entriesArrayOrUndefined,
|
||||
expire_time: expireTimeOrUndefined,
|
||||
immutable: immutableOrUndefined,
|
||||
item_id: itemIdOrUndefined,
|
||||
list_id,
|
||||
|
|
|
@ -11,6 +11,7 @@ import type {
|
|||
ExceptionListItemSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
import { ENTRIES } from '../../../common/constants.mock';
|
||||
import {
|
||||
getEntryMatchAnyExcludeMock,
|
||||
getEntryMatchAnyMock,
|
||||
|
@ -51,6 +52,7 @@ import {
|
|||
buildNestedClause,
|
||||
createOrClauses,
|
||||
filterOutUnprocessableValueLists,
|
||||
removeExpiredExceptions,
|
||||
} from './build_exception_filter';
|
||||
|
||||
const modifiedGetEntryMatchAnyMock = (): EntryMatchAny => ({
|
||||
|
@ -70,6 +72,7 @@ describe('build_exceptions_filter', () => {
|
|||
excludeExceptions: false,
|
||||
listClient,
|
||||
lists: [],
|
||||
startedAt: new Date(),
|
||||
});
|
||||
expect(filter).toBeUndefined();
|
||||
});
|
||||
|
@ -81,6 +84,7 @@ describe('build_exceptions_filter', () => {
|
|||
excludeExceptions: false,
|
||||
listClient,
|
||||
lists: [getExceptionListItemSchemaMock()],
|
||||
startedAt: new Date(),
|
||||
});
|
||||
|
||||
expect(filter).toMatchInlineSnapshot(`
|
||||
|
@ -154,6 +158,7 @@ describe('build_exceptions_filter', () => {
|
|||
excludeExceptions: true,
|
||||
listClient,
|
||||
lists: [exceptionItem1, exceptionItem2],
|
||||
startedAt: new Date(),
|
||||
});
|
||||
expect(filter).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -236,6 +241,7 @@ describe('build_exceptions_filter', () => {
|
|||
excludeExceptions: true,
|
||||
listClient,
|
||||
lists: [exceptionItem1, exceptionItem2, exceptionItem3],
|
||||
startedAt: new Date(),
|
||||
});
|
||||
|
||||
expect(filter).toMatchInlineSnapshot(`
|
||||
|
@ -325,6 +331,7 @@ describe('build_exceptions_filter', () => {
|
|||
excludeExceptions: true,
|
||||
listClient,
|
||||
lists: exceptions,
|
||||
startedAt: new Date(),
|
||||
});
|
||||
|
||||
expect(filter).toMatchInlineSnapshot(`
|
||||
|
@ -479,6 +486,95 @@ describe('build_exceptions_filter', () => {
|
|||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('it should remove all exception items that are expired', async () => {
|
||||
const futureDate = new Date(Date.now() + 1000000).toISOString();
|
||||
const expiredDate = new Date(Date.now() - 1000000).toISOString();
|
||||
const exceptions = [
|
||||
{ ...getExceptionListItemSchemaMock(), entries: [ENTRIES[0]], expire_time: futureDate },
|
||||
{ ...getExceptionListItemSchemaMock(), entries: [ENTRIES[1]], expire_time: expiredDate },
|
||||
getExceptionListItemSchemaMock(),
|
||||
];
|
||||
|
||||
const { filter } = await buildExceptionFilter({
|
||||
alias: null,
|
||||
chunkSize: 1,
|
||||
excludeExceptions: true,
|
||||
listClient,
|
||||
lists: exceptions,
|
||||
startedAt: new Date(),
|
||||
});
|
||||
|
||||
expect(filter).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"meta": Object {
|
||||
"alias": null,
|
||||
"disabled": false,
|
||||
"negate": true,
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"should": Array [
|
||||
Object {
|
||||
"nested": Object {
|
||||
"path": "some.parentField",
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"some.parentField.nested.field": "some value",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"score_mode": "none",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"nested": Object {
|
||||
"path": "some.parentField",
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"some.parentField.nested.field": "some value",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"score_mode": "none",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"some.not.nested.field": "some value",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createOrClauses', () => {
|
||||
|
@ -1297,4 +1393,22 @@ describe('build_exceptions_filter', () => {
|
|||
expect(unprocessableValueListExceptions).toEqual([listExceptionItem]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeExpiredExceptions', () => {
|
||||
test('it should filter out expired exceptions', () => {
|
||||
const futureDate = new Date(Date.now() + 1000000).toISOString();
|
||||
const expiredDate = new Date(Date.now() - 1000000).toISOString();
|
||||
const exceptions = [
|
||||
{ ...getExceptionListItemSchemaMock(), expire_time: futureDate },
|
||||
{ ...getExceptionListItemSchemaMock(), expire_time: expiredDate },
|
||||
getExceptionListItemSchemaMock(),
|
||||
];
|
||||
const filteredExceptions = removeExpiredExceptions(exceptions, new Date());
|
||||
|
||||
expect(filteredExceptions).toEqual([
|
||||
{ ...getExceptionListItemSchemaMock(), expire_time: futureDate },
|
||||
getExceptionListItemSchemaMock(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -273,6 +273,19 @@ export const filterOutUnprocessableValueLists = async <
|
|||
return { filteredExceptions, unprocessableValueListExceptions };
|
||||
};
|
||||
|
||||
export const removeExpiredExceptions = <
|
||||
T extends ExceptionListItemSchema | CreateExceptionListItemSchema
|
||||
>(
|
||||
lists: T[],
|
||||
startedAt: Date
|
||||
): T[] =>
|
||||
lists.filter((listItem) => {
|
||||
if (listItem.expire_time && new Date(listItem.expire_time) < startedAt) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
export const buildExceptionFilter = async <
|
||||
T extends ExceptionListItemSchema | CreateExceptionListItemSchema
|
||||
>({
|
||||
|
@ -281,17 +294,21 @@ export const buildExceptionFilter = async <
|
|||
chunkSize,
|
||||
alias = null,
|
||||
listClient,
|
||||
startedAt,
|
||||
}: {
|
||||
lists: T[];
|
||||
excludeExceptions: boolean;
|
||||
chunkSize: number;
|
||||
alias: string | null;
|
||||
listClient: ListClient;
|
||||
startedAt: Date;
|
||||
}): Promise<{ filter: Filter | undefined; unprocessedExceptions: T[] }> => {
|
||||
const filteredLists = removeExpiredExceptions<T>(lists, startedAt);
|
||||
|
||||
// Remove exception items with large value lists. These are evaluated
|
||||
// elsewhere for the moment being.
|
||||
const [exceptionsWithoutValueLists, valueListExceptions] = partition(
|
||||
lists,
|
||||
filteredLists,
|
||||
(item): item is T => !hasLargeValueList(item.entries)
|
||||
);
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ export const bulkCreateExceptionListItems = async ({
|
|||
created_by: user,
|
||||
description: item.description,
|
||||
entries: item.entries,
|
||||
expire_time: item.expire_time,
|
||||
immutable: false,
|
||||
item_id: item.item_id,
|
||||
list_id: item.list_id,
|
||||
|
|
|
@ -44,6 +44,7 @@ export const createEndpointList = async ({
|
|||
created_by: user,
|
||||
description: ENDPOINT_LIST_DESCRIPTION,
|
||||
entries: undefined,
|
||||
expire_time: undefined,
|
||||
immutable: false,
|
||||
item_id: undefined,
|
||||
list_id: ENDPOINT_LIST_ID,
|
||||
|
|
|
@ -52,6 +52,7 @@ export const createEndpointTrustedAppsList = async ({
|
|||
created_by: user,
|
||||
description: ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION,
|
||||
entries: undefined,
|
||||
expire_time: undefined,
|
||||
immutable: false,
|
||||
item_id: undefined,
|
||||
list_id: ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
|
|
|
@ -62,6 +62,7 @@ export const createExceptionList = async ({
|
|||
created_by: user,
|
||||
description,
|
||||
entries: undefined,
|
||||
expire_time: undefined,
|
||||
immutable,
|
||||
item_id: undefined,
|
||||
list_id: listId,
|
||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
|||
EntriesArray,
|
||||
ExceptionListItemSchema,
|
||||
ExceptionListItemType,
|
||||
ExpireTimeOrUndefined,
|
||||
ItemId,
|
||||
ListId,
|
||||
MetaOrUndefined,
|
||||
|
@ -45,11 +46,13 @@ interface CreateExceptionListItemOptions {
|
|||
tieBreaker?: string;
|
||||
type: ExceptionListItemType;
|
||||
osTypes: OsTypeArray;
|
||||
expireTime: ExpireTimeOrUndefined;
|
||||
}
|
||||
|
||||
export const createExceptionListItem = async ({
|
||||
comments,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
listId,
|
||||
savedObjectsClient,
|
||||
|
@ -75,6 +78,7 @@ export const createExceptionListItem = async ({
|
|||
created_by: user,
|
||||
description,
|
||||
entries,
|
||||
expire_time: expireTime,
|
||||
immutable: undefined,
|
||||
item_id: itemId,
|
||||
list_id: listId,
|
||||
|
|
|
@ -88,6 +88,7 @@ export const getCreateExceptionListItemOptionsMock = (): CreateExceptionListItem
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expire_time: expireTime,
|
||||
item_id: itemId,
|
||||
list_id: listId,
|
||||
meta,
|
||||
|
@ -102,6 +103,7 @@ export const getCreateExceptionListItemOptionsMock = (): CreateExceptionListItem
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
listId,
|
||||
meta,
|
||||
|
@ -114,14 +116,26 @@ export const getCreateExceptionListItemOptionsMock = (): CreateExceptionListItem
|
|||
};
|
||||
|
||||
export const getUpdateExceptionListItemOptionsMock = (): UpdateExceptionListItemOptions => {
|
||||
const { comments, entries, itemId, namespaceType, name, osTypes, description, meta, tags, type } =
|
||||
getCreateExceptionListItemOptionsMock();
|
||||
const {
|
||||
comments,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
namespaceType,
|
||||
name,
|
||||
osTypes,
|
||||
description,
|
||||
meta,
|
||||
tags,
|
||||
type,
|
||||
} = getCreateExceptionListItemOptionsMock();
|
||||
|
||||
return {
|
||||
_version: undefined,
|
||||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
id: ID,
|
||||
itemId,
|
||||
meta,
|
||||
|
@ -161,6 +175,7 @@ export const getExceptionListSoSchemaMock = (
|
|||
created_by,
|
||||
description,
|
||||
entries,
|
||||
expire_time: undefined,
|
||||
immutable: undefined,
|
||||
item_id,
|
||||
list_id,
|
||||
|
|
|
@ -247,6 +247,7 @@ describe('exception_list_client', () => {
|
|||
(): ReturnType<ExceptionListClient['exportExceptionListAndItems']> => {
|
||||
return exceptionListClient.exportExceptionListAndItems({
|
||||
id: '1',
|
||||
includeExpiredExceptions: true,
|
||||
listId: '1',
|
||||
namespaceType: 'agnostic',
|
||||
});
|
||||
|
|
|
@ -287,6 +287,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
meta,
|
||||
name,
|
||||
|
@ -300,6 +301,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
listId: ENDPOINT_LIST_ID,
|
||||
meta,
|
||||
|
@ -356,6 +358,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
|
@ -371,6 +374,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
|
@ -526,6 +530,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
listId,
|
||||
meta,
|
||||
|
@ -540,6 +545,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
itemId,
|
||||
listId,
|
||||
meta,
|
||||
|
@ -593,6 +599,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
|
@ -608,6 +615,7 @@ export class ExceptionListClient {
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expireTime,
|
||||
id,
|
||||
itemId,
|
||||
meta,
|
||||
|
@ -970,6 +978,7 @@ export class ExceptionListClient {
|
|||
listId,
|
||||
id,
|
||||
namespaceType,
|
||||
includeExpiredExceptions,
|
||||
}: ExportExceptionListAndItemsOptions): Promise<ExportExceptionListAndItemsReturn | null> => {
|
||||
const { savedObjectsClient } = this;
|
||||
|
||||
|
@ -978,6 +987,7 @@ export class ExceptionListClient {
|
|||
'exceptionsListPreExport',
|
||||
{
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
},
|
||||
|
@ -987,6 +997,7 @@ export class ExceptionListClient {
|
|||
|
||||
return exportExceptionListAndItems({
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
savedObjectsClient,
|
||||
|
|
|
@ -21,6 +21,7 @@ import type {
|
|||
ExceptionListItemTypeOrUndefined,
|
||||
ExceptionListType,
|
||||
ExceptionListTypeOrUndefined,
|
||||
ExpireTimeOrUndefined,
|
||||
ExportExceptionDetails,
|
||||
FilterOrUndefined,
|
||||
FoundExceptionListItemSchema,
|
||||
|
@ -242,6 +243,8 @@ export interface CreateExceptionListItemOptions {
|
|||
comments: CreateCommentsArray;
|
||||
/** an array with the exception list item entries */
|
||||
entries: EntriesArray;
|
||||
/** an optional datetime string with an expiration time */
|
||||
expireTime: ExpireTimeOrUndefined;
|
||||
/** the "item_id" of the exception list item */
|
||||
itemId: ItemId;
|
||||
/** the "list_id" of the parent exception list */
|
||||
|
@ -271,6 +274,8 @@ export interface CreateEndpointListItemOptions {
|
|||
comments: CreateCommentsArray;
|
||||
/** The entries of the endpoint list item */
|
||||
entries: EntriesArray;
|
||||
/** an optional datetime string with an expiration time */
|
||||
expireTime: ExpireTimeOrUndefined;
|
||||
/** The item id of the list item */
|
||||
itemId: ItemId;
|
||||
/** The name of the list item */
|
||||
|
@ -309,6 +314,8 @@ export interface UpdateExceptionListItemOptions {
|
|||
comments: UpdateCommentsArray;
|
||||
/** item exception entries logic */
|
||||
entries: EntriesArray;
|
||||
/** an optional datetime string with an expiration time */
|
||||
expireTime: ExpireTimeOrUndefined;
|
||||
/** the "id" of the exception list item */
|
||||
id: IdOrUndefined;
|
||||
/** the "item_id" of the exception list item */
|
||||
|
@ -340,6 +347,8 @@ export interface UpdateEndpointListItemOptions {
|
|||
comments: UpdateCommentsArray;
|
||||
/** The entries of the endpoint list item */
|
||||
entries: EntriesArray;
|
||||
/** an optional datetime string with an expiration time */
|
||||
expireTime: ExpireTimeOrUndefined;
|
||||
/** The id of the list item (Either this or itemId has to be defined) */
|
||||
id: IdOrUndefined;
|
||||
/** The item id of the list item (Either this or id has to be defined) */
|
||||
|
@ -490,6 +499,8 @@ export interface ExportExceptionListAndItemsOptions {
|
|||
id: IdOrUndefined;
|
||||
/** saved object namespace (single | agnostic) */
|
||||
namespaceType: NamespaceType;
|
||||
/** whether or not to include expired exceptions */
|
||||
includeExpiredExceptions: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,6 +29,7 @@ describe('export_exception_list_and_items', () => {
|
|||
|
||||
const result = await exportExceptionListAndItems({
|
||||
id: '123',
|
||||
includeExpiredExceptions: true,
|
||||
listId: 'non-existent',
|
||||
namespaceType: 'single',
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
|
@ -45,6 +46,7 @@ describe('export_exception_list_and_items', () => {
|
|||
);
|
||||
const result = await exportExceptionListAndItems({
|
||||
id: '123',
|
||||
includeExpiredExceptions: true,
|
||||
listId: 'non-existent',
|
||||
namespaceType: 'single',
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
|
|
|
@ -15,6 +15,7 @@ import type {
|
|||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { transformDataToNdjson } from '@kbn/securitysolution-utils';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { getSavedObjectType } from '@kbn/securitysolution-list-utils';
|
||||
|
||||
import { findExceptionListItemPointInTimeFinder } from './find_exception_list_item_point_in_time_finder';
|
||||
import { getExceptionList } from './get_exception_list';
|
||||
|
@ -24,6 +25,7 @@ interface ExportExceptionListAndItemsOptions {
|
|||
listId: ListIdOrUndefined;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
namespaceType: NamespaceType;
|
||||
includeExpiredExceptions: boolean;
|
||||
}
|
||||
|
||||
export interface ExportExceptionListAndItemsReturn {
|
||||
|
@ -35,6 +37,7 @@ export const exportExceptionListAndItems = async ({
|
|||
id,
|
||||
listId,
|
||||
namespaceType,
|
||||
includeExpiredExceptions,
|
||||
savedObjectsClient,
|
||||
}: ExportExceptionListAndItemsOptions): Promise<ExportExceptionListAndItemsReturn | null> => {
|
||||
const exceptionList = await getExceptionList({
|
||||
|
@ -52,10 +55,14 @@ export const exportExceptionListAndItems = async ({
|
|||
const executeFunctionOnStream = (response: FoundExceptionListItemSchema): void => {
|
||||
exceptionItems = [...exceptionItems, ...response.data];
|
||||
};
|
||||
const savedObjectPrefix = getSavedObjectType({ namespaceType });
|
||||
const filter = includeExpiredExceptions
|
||||
? undefined
|
||||
: `(${savedObjectPrefix}.attributes.expire_time > "${new Date().toISOString()}" OR NOT ${savedObjectPrefix}.attributes.expire_time: *)`;
|
||||
|
||||
await findExceptionListItemPointInTimeFinder({
|
||||
executeFunctionOnStream,
|
||||
filter: undefined,
|
||||
filter,
|
||||
listId: exceptionList.list_id,
|
||||
maxSize: undefined, // NOTE: This is unbounded when it is "undefined"
|
||||
namespaceType: exceptionList.namespace_type,
|
||||
|
|
|
@ -11,6 +11,7 @@ import type {
|
|||
EntriesArray,
|
||||
ExceptionListItemSchema,
|
||||
ExceptionListItemTypeOrUndefined,
|
||||
ExpireTimeOrUndefined,
|
||||
IdOrUndefined,
|
||||
ItemIdOrUndefined,
|
||||
MetaOrUndefined,
|
||||
|
@ -38,6 +39,7 @@ interface UpdateExceptionListItemOptions {
|
|||
name: NameOrUndefined;
|
||||
description: DescriptionOrUndefined;
|
||||
entries: EntriesArray;
|
||||
expireTime: ExpireTimeOrUndefined;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
namespaceType: NamespaceType;
|
||||
osTypes: OsTypeArray;
|
||||
|
@ -53,6 +55,7 @@ export const updateExceptionListItem = async ({
|
|||
_version,
|
||||
comments,
|
||||
entries,
|
||||
expireTime,
|
||||
id,
|
||||
savedObjectsClient,
|
||||
namespaceType,
|
||||
|
@ -87,6 +90,7 @@ export const updateExceptionListItem = async ({
|
|||
comments: transformedComments,
|
||||
description,
|
||||
entries,
|
||||
expire_time: expireTime,
|
||||
meta,
|
||||
name,
|
||||
os_types: osTypes,
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('getExceptionListsItemFilter', () => {
|
|||
savedObjectType: ['exception-list'],
|
||||
});
|
||||
expect(filter).toEqual(
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "some-list-id") AND exception-list.attributes.name: "Sample Endpoint Exception List")'
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "some-list-id") AND (exception-list.attributes.name: "Sample Endpoint Exception List"))'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -61,7 +61,7 @@ describe('getExceptionListsItemFilter', () => {
|
|||
savedObjectType: ['exception-list', 'exception-list-agnostic'],
|
||||
});
|
||||
expect(filter).toEqual(
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "list-1") AND exception-list.attributes.name: "Sample Endpoint Exception List") OR (exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-2")'
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "list-1") AND (exception-list.attributes.name: "Sample Endpoint Exception List")) OR (exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-2")'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -83,7 +83,7 @@ describe('getExceptionListsItemFilter', () => {
|
|||
savedObjectType: ['exception-list', 'exception-list-agnostic', 'exception-list-agnostic'],
|
||||
});
|
||||
expect(filter).toEqual(
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "list-1") AND exception-list.attributes.name: "Sample Endpoint Exception List") OR (exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-2") OR (exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-3")'
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "list-1") AND (exception-list.attributes.name: "Sample Endpoint Exception List")) OR (exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-2") OR (exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-3")'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -98,7 +98,7 @@ describe('getExceptionListsItemFilter', () => {
|
|||
savedObjectType: ['exception-list', 'exception-list-agnostic', 'exception-list-agnostic'],
|
||||
});
|
||||
expect(filter).toEqual(
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "list-1") AND exception-list.attributes.name: "Sample Endpoint Exception List 1") OR ((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-2") AND exception-list.attributes.name: "Sample Endpoint Exception List 2") OR ((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-3") AND exception-list.attributes.name: "Sample Endpoint Exception List 3")'
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "list-1") AND (exception-list.attributes.name: "Sample Endpoint Exception List 1")) OR ((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-2") AND (exception-list.attributes.name: "Sample Endpoint Exception List 2")) OR ((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "list-3") AND (exception-list.attributes.name: "Sample Endpoint Exception List 3"))'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ export const getExceptionListsItemFilter = ({
|
|||
const escapedListId = escapeQuotes(singleListId);
|
||||
const listItemAppend = `(${savedObjectType[index]}.attributes.list_type: item AND ${savedObjectType[index]}.attributes.list_id: "${escapedListId}")`;
|
||||
const listItemAppendWithFilter =
|
||||
filter[index] != null ? `(${listItemAppend} AND ${filter[index]})` : listItemAppend;
|
||||
filter[index] != null ? `(${listItemAppend} AND (${filter[index]}))` : listItemAppend;
|
||||
if (accum === '') {
|
||||
return listItemAppendWithFilter;
|
||||
} else {
|
||||
|
|
|
@ -22,6 +22,7 @@ describe('bulkCreateImportedItems', () => {
|
|||
created_by: 'elastic',
|
||||
description: 'description here',
|
||||
entries: ENTRIES,
|
||||
expire_time: undefined,
|
||||
immutable: undefined,
|
||||
item_id: 'item-id',
|
||||
list_id: 'list-id',
|
||||
|
|
|
@ -21,6 +21,7 @@ describe('bulkCreateImportedLists', () => {
|
|||
created_by: 'elastic',
|
||||
description: 'some description',
|
||||
entries: undefined,
|
||||
expire_time: undefined,
|
||||
immutable: false,
|
||||
item_id: undefined,
|
||||
list_id: 'list-id',
|
||||
|
|
|
@ -65,7 +65,7 @@ describe('find_all_exception_list_item_types', () => {
|
|||
|
||||
expect(savedObjectsClient.find).toHaveBeenCalledWith({
|
||||
filter:
|
||||
'((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "detection_list_id") AND exception-list-agnostic.attributes.item_id:(1))',
|
||||
'((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "detection_list_id") AND (exception-list-agnostic.attributes.item_id:(1)))',
|
||||
page: undefined,
|
||||
perPage: 100,
|
||||
sortField: undefined,
|
||||
|
@ -83,7 +83,7 @@ describe('find_all_exception_list_item_types', () => {
|
|||
|
||||
expect(savedObjectsClient.find).toHaveBeenCalledWith({
|
||||
filter:
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "detection_list_id") AND exception-list.attributes.item_id:(1))',
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "detection_list_id") AND (exception-list.attributes.item_id:(1)))',
|
||||
page: undefined,
|
||||
perPage: 100,
|
||||
sortField: undefined,
|
||||
|
@ -101,7 +101,7 @@ describe('find_all_exception_list_item_types', () => {
|
|||
|
||||
expect(savedObjectsClient.find).toHaveBeenCalledWith({
|
||||
filter:
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "detection_list_id") AND exception-list.attributes.item_id:(2)) OR ((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "detection_list_id") AND exception-list-agnostic.attributes.item_id:(1))',
|
||||
'((exception-list.attributes.list_type: item AND exception-list.attributes.list_id: "detection_list_id") AND (exception-list.attributes.item_id:(2))) OR ((exception-list-agnostic.attributes.list_type: item AND exception-list-agnostic.attributes.list_id: "detection_list_id") AND (exception-list-agnostic.attributes.item_id:(1)))',
|
||||
page: undefined,
|
||||
perPage: 100,
|
||||
sortField: undefined,
|
||||
|
|
|
@ -50,6 +50,7 @@ export const sortExceptionItemsToUpdateOrCreate = ({
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expire_time: expireTime,
|
||||
item_id: itemId,
|
||||
meta,
|
||||
list_id: listId,
|
||||
|
@ -89,6 +90,7 @@ export const sortExceptionItemsToUpdateOrCreate = ({
|
|||
created_by: user,
|
||||
description,
|
||||
entries,
|
||||
expire_time: expireTime,
|
||||
immutable: undefined,
|
||||
item_id: itemId,
|
||||
list_id: listId,
|
||||
|
|
|
@ -71,6 +71,7 @@ export const sortExceptionListsToUpdateOrCreate = ({
|
|||
created_by: user,
|
||||
description,
|
||||
entries: undefined,
|
||||
expire_time: undefined,
|
||||
immutable: false,
|
||||
item_id: undefined,
|
||||
list_id: listId,
|
||||
|
@ -112,6 +113,7 @@ export const sortExceptionListsToUpdateOrCreate = ({
|
|||
created_by: user,
|
||||
description,
|
||||
entries: undefined,
|
||||
expire_time: undefined,
|
||||
immutable: false,
|
||||
item_id: undefined,
|
||||
list_type: 'list',
|
||||
|
|
|
@ -150,6 +150,7 @@ export const transformSavedObjectToExceptionListItem = ({
|
|||
created_by,
|
||||
description,
|
||||
entries,
|
||||
expire_time,
|
||||
item_id: itemId,
|
||||
list_id,
|
||||
meta,
|
||||
|
@ -174,6 +175,7 @@ export const transformSavedObjectToExceptionListItem = ({
|
|||
created_by,
|
||||
description,
|
||||
entries: entries ?? [],
|
||||
expire_time,
|
||||
id,
|
||||
item_id: itemId ?? '(unknown)',
|
||||
list_id,
|
||||
|
@ -203,6 +205,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
|
|||
comments,
|
||||
description,
|
||||
entries,
|
||||
expire_time: expireTime,
|
||||
meta,
|
||||
name,
|
||||
os_types: osTypes,
|
||||
|
@ -225,6 +228,7 @@ export const transformSavedObjectUpdateToExceptionListItem = ({
|
|||
created_by: exceptionListItem.created_by,
|
||||
description: description ?? exceptionListItem.description,
|
||||
entries: entries ?? exceptionListItem.entries,
|
||||
expire_time: expireTime ?? exceptionListItem.expire_time,
|
||||
id,
|
||||
item_id: exceptionListItem.item_id,
|
||||
list_id: exceptionListItem.list_id,
|
||||
|
@ -308,6 +312,7 @@ export const transformCreateCommentsToComments = ({
|
|||
};
|
||||
|
||||
export const transformCreateExceptionListItemOptionsToCreateExceptionListItemSchema = ({
|
||||
expireTime,
|
||||
listId,
|
||||
itemId,
|
||||
namespaceType,
|
||||
|
@ -316,6 +321,7 @@ export const transformCreateExceptionListItemOptionsToCreateExceptionListItemSch
|
|||
}: CreateExceptionListItemOptions): CreateExceptionListItemSchema => {
|
||||
return {
|
||||
...rest,
|
||||
expire_time: expireTime,
|
||||
item_id: itemId,
|
||||
list_id: listId,
|
||||
namespace_type: namespaceType,
|
||||
|
@ -327,6 +333,7 @@ export const transformUpdateExceptionListItemOptionsToUpdateExceptionListItemSch
|
|||
itemId,
|
||||
namespaceType,
|
||||
osTypes,
|
||||
expireTime,
|
||||
// The `UpdateExceptionListItemOptions` type differs from the schema in that some properties are
|
||||
// marked as having `undefined` as a valid value, where the schema, however, requires it.
|
||||
// So we assign defaults here
|
||||
|
@ -338,6 +345,7 @@ export const transformUpdateExceptionListItemOptionsToUpdateExceptionListItemSch
|
|||
return {
|
||||
...rest,
|
||||
description,
|
||||
expire_time: expireTime,
|
||||
item_id: itemId,
|
||||
name,
|
||||
namespace_type: namespaceType,
|
||||
|
|
|
@ -30,14 +30,14 @@ type NonNullableTypeProperties<T> = {
|
|||
* create a value for (almost) all properties
|
||||
*/
|
||||
type CreateExceptionListItemSchemaWithNonNullProps = NonNullableTypeProperties<
|
||||
Omit<CreateExceptionListItemSchema, 'meta'>
|
||||
Omit<CreateExceptionListItemSchema, 'meta' | 'expire_time'>
|
||||
> &
|
||||
Pick<CreateExceptionListItemSchema, 'meta'>;
|
||||
Pick<CreateExceptionListItemSchema, 'meta' | 'expire_time'>;
|
||||
|
||||
type UpdateExceptionListItemSchemaWithNonNullProps = NonNullableTypeProperties<
|
||||
Omit<UpdateExceptionListItemSchema, 'meta'>
|
||||
Omit<UpdateExceptionListItemSchema, 'meta' | 'expire_time'>
|
||||
> &
|
||||
Pick<UpdateExceptionListItemSchema, 'meta'>;
|
||||
Pick<UpdateExceptionListItemSchema, 'meta' | 'expire_time'>;
|
||||
|
||||
const exceptionItemToCreateExceptionItem = (
|
||||
exceptionItem: ExceptionListItemSchema
|
||||
|
@ -46,6 +46,7 @@ const exceptionItemToCreateExceptionItem = (
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
description,
|
||||
entries,
|
||||
expire_time,
|
||||
list_id,
|
||||
name,
|
||||
type,
|
||||
|
@ -61,6 +62,7 @@ const exceptionItemToCreateExceptionItem = (
|
|||
return {
|
||||
description,
|
||||
entries,
|
||||
expire_time,
|
||||
list_id,
|
||||
name,
|
||||
type,
|
||||
|
@ -109,6 +111,7 @@ export class ExceptionsListItemGenerator extends BaseDataGenerator<ExceptionList
|
|||
value: '741462ab431a22233C787BAAB9B653C7',
|
||||
},
|
||||
],
|
||||
expire_time: undefined,
|
||||
id: this.seededUUIDv4(),
|
||||
item_id: this.seededUUIDv4(),
|
||||
list_id: 'endpoint_list_id',
|
||||
|
|
|
@ -46,9 +46,11 @@ export const EXCEPTIONS_TABLE_SHOWING_LISTS = '[data-test-subj="showingException
|
|||
export const EXCEPTIONS_TABLE_DELETE_BTN =
|
||||
'[data-test-subj="sharedListOverflowCardActionItemDelete"]';
|
||||
|
||||
export const EXCEPTIONS_TABLE_EXPORT_BTN =
|
||||
export const EXCEPTIONS_TABLE_EXPORT_MODAL_BTN =
|
||||
'[data-test-subj="sharedListOverflowCardActionItemExport"]';
|
||||
|
||||
export const EXCEPTIONS_TABLE_EXPORT_CONFIRM_BTN = '[data-test-subj="confirmModalConfirmButton"]';
|
||||
|
||||
export const EXCEPTIONS_TABLE_SEARCH_CLEAR =
|
||||
'[data-test-subj="allExceptionListsPanel"] button.euiFormControlLayoutClearButton';
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@ import {
|
|||
EXCEPTIONS_TABLE_SEARCH_CLEAR,
|
||||
EXCEPTIONS_TABLE_MODAL,
|
||||
EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN,
|
||||
EXCEPTIONS_TABLE_EXPORT_BTN,
|
||||
EXCEPTIONS_TABLE_EXPORT_MODAL_BTN,
|
||||
EXCEPTIONS_OVERFLOW_ACTIONS_BTN,
|
||||
EXCEPTIONS_TABLE_EXPORT_CONFIRM_BTN,
|
||||
} from '../screens/exceptions';
|
||||
|
||||
export const clearSearchSelection = () => {
|
||||
|
@ -26,7 +27,8 @@ export const expandExceptionActions = () => {
|
|||
|
||||
export const exportExceptionList = () => {
|
||||
cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click();
|
||||
cy.get(EXCEPTIONS_TABLE_EXPORT_BTN).first().click();
|
||||
cy.get(EXCEPTIONS_TABLE_EXPORT_MODAL_BTN).first().click();
|
||||
cy.get(EXCEPTIONS_TABLE_EXPORT_CONFIRM_BTN).first().click();
|
||||
};
|
||||
|
||||
export const deleteExceptionListWithoutRuleReference = () => {
|
||||
|
|
|
@ -112,9 +112,9 @@ export const BarGroup = styled.div.attrs({
|
|||
`;
|
||||
BarGroup.displayName = 'BarGroup';
|
||||
|
||||
export const BarText = styled.p.attrs({
|
||||
className: 'siemUtilityBar__text',
|
||||
})<{ shouldWrap: boolean }>`
|
||||
export const BarText = styled.p.attrs(({ className }) => ({
|
||||
className: className || 'siemUtilityBar__text',
|
||||
}))<{ shouldWrap: boolean }>`
|
||||
${({ shouldWrap, theme }) => css`
|
||||
color: ${theme.eui.euiTextSubduedColor};
|
||||
font-size: ${theme.eui.euiFontSizeXS};
|
||||
|
|
|
@ -13,11 +13,12 @@ export interface UtilityBarTextProps {
|
|||
children: string | JSX.Element;
|
||||
dataTestSubj?: string;
|
||||
shouldWrap?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const UtilityBarText = React.memo<UtilityBarTextProps>(
|
||||
({ children, dataTestSubj, shouldWrap = false }) => (
|
||||
<BarText data-test-subj={dataTestSubj} shouldWrap={shouldWrap}>
|
||||
({ children, dataTestSubj, shouldWrap = false, className }) => (
|
||||
<BarText data-test-subj={dataTestSubj} shouldWrap={shouldWrap} className={className}>
|
||||
{children}
|
||||
</BarText>
|
||||
)
|
||||
|
|
|
@ -33,6 +33,7 @@ import type {
|
|||
ExceptionsBuilderReturnExceptionItem,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
|
||||
import type { Moment } from 'moment';
|
||||
import type { Status } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||
import * as i18n from './translations';
|
||||
import { ExceptionItemComments } from '../item_comments';
|
||||
|
@ -54,6 +55,7 @@ import { enrichNewExceptionItems } from '../flyout_components/utils';
|
|||
import { useCloseAlertsFromExceptions } from '../../logic/use_close_alerts';
|
||||
import { ruleTypesThatAllowLargeValueLists } from '../../utils/constants';
|
||||
import { useInvalidateFetchRuleByIdQuery } from '../../../rule_management/api/hooks/use_fetch_rule_by_id_query';
|
||||
import { ExceptionsExpireTime } from '../flyout_components/expire_time';
|
||||
|
||||
const SectionHeader = styled(EuiTitle)`
|
||||
${() => css`
|
||||
|
@ -153,6 +155,8 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
|
|||
newComment,
|
||||
itemConditionValidationErrorExists,
|
||||
errorSubmitting,
|
||||
expireTime,
|
||||
expireErrorExists,
|
||||
},
|
||||
dispatch,
|
||||
] = useReducer(createExceptionItemsReducer(), {
|
||||
|
@ -312,6 +316,26 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
const setExpireTime = useCallback(
|
||||
(exceptionExpireTime: Moment | undefined): void => {
|
||||
dispatch({
|
||||
type: 'setExpireTime',
|
||||
expireTime: exceptionExpireTime,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setExpireError = useCallback(
|
||||
(errorExists: boolean): void => {
|
||||
dispatch({
|
||||
type: 'setExpireError',
|
||||
errorExists,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
if (listType === ExceptionListTypeEnum.ENDPOINT && alertData != null) {
|
||||
setInitialExceptionItems(
|
||||
|
@ -343,6 +367,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
|
|||
sharedLists,
|
||||
listType,
|
||||
selectedOs: osTypesSelection,
|
||||
expireTime,
|
||||
items: exceptionItems,
|
||||
});
|
||||
|
||||
|
@ -391,6 +416,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
|
|||
bulkCloseIndex,
|
||||
setErrorSubmitting,
|
||||
invalidateFetchRuleByIdQuery,
|
||||
expireTime,
|
||||
]);
|
||||
|
||||
const isSubmitButtonDisabled = useMemo(
|
||||
|
@ -401,6 +427,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
|
|||
exceptionItemName.trim() === '' ||
|
||||
exceptionItems.every((item) => item.entries.length === 0) ||
|
||||
itemConditionValidationErrorExists ||
|
||||
expireErrorExists ||
|
||||
(addExceptionToRadioSelection === 'add_to_lists' && isEmpty(exceptionListsToAddTo)),
|
||||
[
|
||||
isSubmitting,
|
||||
|
@ -411,6 +438,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
|
|||
itemConditionValidationErrorExists,
|
||||
addExceptionToRadioSelection,
|
||||
exceptionListsToAddTo,
|
||||
expireErrorExists,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -502,6 +530,12 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
|
|||
newCommentValue={newComment}
|
||||
newCommentOnChange={setComment}
|
||||
/>
|
||||
<EuiHorizontalRule />
|
||||
<ExceptionsExpireTime
|
||||
expireTime={expireTime}
|
||||
setExpireTime={setExpireTime}
|
||||
setExpireError={setExpireError}
|
||||
/>
|
||||
{showAlertCloseOptions && (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
|
|
|
@ -11,6 +11,7 @@ import type {
|
|||
ExceptionsBuilderExceptionItem,
|
||||
ExceptionsBuilderReturnExceptionItem,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
import type { Moment } from 'moment';
|
||||
|
||||
import type { Rule } from '../../../rule_management/logic/types';
|
||||
|
||||
|
@ -30,6 +31,8 @@ export interface State {
|
|||
exceptionListsToAddTo: ExceptionListSchema[];
|
||||
selectedRulesToAddTo: Rule[];
|
||||
errorSubmitting: Error | null;
|
||||
expireTime: Moment | undefined;
|
||||
expireErrorExists: boolean;
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
|
@ -48,6 +51,8 @@ export const initialState: State = {
|
|||
selectedRulesToAddTo: [],
|
||||
listType: ExceptionListTypeEnum.RULE_DEFAULT,
|
||||
errorSubmitting: null,
|
||||
expireTime: undefined,
|
||||
expireErrorExists: false,
|
||||
};
|
||||
|
||||
export type Action =
|
||||
|
@ -110,6 +115,14 @@ export type Action =
|
|||
| {
|
||||
type: 'setErrorSubmitting';
|
||||
err: Error | null;
|
||||
}
|
||||
| {
|
||||
type: 'setExpireTime';
|
||||
expireTime: Moment | undefined;
|
||||
}
|
||||
| {
|
||||
type: 'setExpireError';
|
||||
errorExists: boolean;
|
||||
};
|
||||
|
||||
export const createExceptionItemsReducer =
|
||||
|
@ -244,6 +257,22 @@ export const createExceptionItemsReducer =
|
|||
errorSubmitting: err,
|
||||
};
|
||||
}
|
||||
case 'setExpireTime': {
|
||||
const { expireTime } = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
expireTime,
|
||||
};
|
||||
}
|
||||
case 'setExpireError': {
|
||||
const { errorExists } = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
expireErrorExists: errorExists,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import { mount, shallow } from 'enzyme';
|
|||
|
||||
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
import { fetchExceptionListsItemsByListIds } from '@kbn/securitysolution-list-api';
|
||||
|
||||
import { ExceptionsViewer } from '.';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
|
@ -20,6 +22,7 @@ import * as i18n from './translations';
|
|||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
jest.mock('@kbn/securitysolution-list-hooks');
|
||||
jest.mock('@kbn/securitysolution-list-api');
|
||||
jest.mock('../../logic/use_find_references');
|
||||
jest.mock('react', () => {
|
||||
const r = jest.requireActual('react');
|
||||
|
@ -78,6 +81,8 @@ describe('ExceptionsViewer', () => {
|
|||
},
|
||||
});
|
||||
|
||||
(fetchExceptionListsItemsByListIds as jest.Mock).mockReturnValue({ total: 0 });
|
||||
|
||||
(useFindExceptionListReferences as jest.Mock).mockReturnValue([
|
||||
false,
|
||||
false,
|
||||
|
@ -130,6 +135,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionToEdit: null,
|
||||
viewerState: 'loading',
|
||||
exceptionLists: [],
|
||||
exceptionsToShow: { active: true },
|
||||
},
|
||||
jest.fn(),
|
||||
]);
|
||||
|
@ -168,6 +174,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionToEdit: null,
|
||||
viewerState: 'empty_search',
|
||||
exceptionLists: [],
|
||||
exceptionsToShow: { active: true },
|
||||
},
|
||||
jest.fn(),
|
||||
]);
|
||||
|
@ -206,6 +213,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionToEdit: null,
|
||||
viewerState: 'empty',
|
||||
exceptionLists: [],
|
||||
exceptionsToShow: { active: true },
|
||||
},
|
||||
jest.fn(),
|
||||
]);
|
||||
|
@ -250,6 +258,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionToEdit: null,
|
||||
viewerState: 'empty',
|
||||
exceptionLists: [],
|
||||
exceptionsToShow: { active: true },
|
||||
},
|
||||
jest.fn(),
|
||||
]);
|
||||
|
@ -294,6 +303,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionToEdit: null,
|
||||
viewerState: null,
|
||||
exceptionLists: [],
|
||||
exceptionsToShow: { active: true },
|
||||
},
|
||||
jest.fn(),
|
||||
]);
|
||||
|
@ -328,6 +338,7 @@ describe('ExceptionsViewer', () => {
|
|||
exceptionToEdit: sampleExceptionItem,
|
||||
viewerState: null,
|
||||
exceptionLists: [],
|
||||
exceptionsToShow: { active: true },
|
||||
},
|
||||
jest.fn(),
|
||||
]);
|
||||
|
|
|
@ -24,6 +24,11 @@ import {
|
|||
fetchExceptionListsItemsByListIds,
|
||||
} from '@kbn/securitysolution-list-api';
|
||||
|
||||
import {
|
||||
buildShowActiveExceptionsFilter,
|
||||
buildShowExpiredExceptionsFilter,
|
||||
getSavedObjectTypes,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
import { useUserData } from '../../../../detections/components/user_info';
|
||||
import { useKibana, useToasts } from '../../../../common/lib/kibana';
|
||||
import { ExceptionsViewerSearchBar } from './search_bar';
|
||||
|
@ -44,7 +49,7 @@ const StyledText = styled(EuiText)`
|
|||
font-style: italic;
|
||||
`;
|
||||
|
||||
const STATES_SEARCH_HIDDEN: ViewerState[] = ['error', 'empty'];
|
||||
const STATES_FILTERS_HIDDEN: ViewerState[] = ['error'];
|
||||
const STATES_PAGINATION_UTILITY_HIDDEN: ViewerState[] = [
|
||||
'loading',
|
||||
'empty_search',
|
||||
|
@ -66,6 +71,7 @@ const initialState: State = {
|
|||
viewerState: 'loading',
|
||||
isReadOnly: true,
|
||||
lastUpdated: Date.now(),
|
||||
exceptionsToShow: { active: true },
|
||||
};
|
||||
|
||||
export interface GetExceptionItemProps {
|
||||
|
@ -116,7 +122,16 @@ const ExceptionsViewerComponent = ({
|
|||
|
||||
// Reducer state
|
||||
const [
|
||||
{ exceptions, pagination, currenFlyout, exceptionToEdit, viewerState, isReadOnly, lastUpdated },
|
||||
{
|
||||
exceptions,
|
||||
pagination,
|
||||
currenFlyout,
|
||||
exceptionToEdit,
|
||||
viewerState,
|
||||
isReadOnly,
|
||||
lastUpdated,
|
||||
exceptionsToShow,
|
||||
},
|
||||
dispatch,
|
||||
] = useReducer(allExceptionItemsReducer(), {
|
||||
...initialState,
|
||||
|
@ -179,6 +194,16 @@ const ExceptionsViewerComponent = ({
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
const setExceptionsToShow = useCallback(
|
||||
(optionId: string): void => {
|
||||
dispatch({
|
||||
type: 'setExceptionsToShow',
|
||||
optionId,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const [isLoadingReferences, isFetchReferencesError, allReferences, fetchReferences] =
|
||||
useFindExceptionListReferences();
|
||||
|
||||
|
@ -198,6 +223,26 @@ const ExceptionsViewerComponent = ({
|
|||
}
|
||||
}, [isLoadingReferences, isFetchReferencesError, setViewerState, viewerState]);
|
||||
|
||||
const namespaceTypes = useMemo(
|
||||
() => exceptionListsToQuery.map((list) => list.namespace_type),
|
||||
[exceptionListsToQuery]
|
||||
);
|
||||
|
||||
const exceptionListFilter = useMemo(() => {
|
||||
if (exceptionsToShow.active && exceptionsToShow.expired) {
|
||||
return undefined;
|
||||
}
|
||||
const savedObjectPrefixes = getSavedObjectTypes({
|
||||
namespaceType: namespaceTypes,
|
||||
});
|
||||
if (exceptionsToShow.active) {
|
||||
return buildShowActiveExceptionsFilter(savedObjectPrefixes);
|
||||
}
|
||||
if (exceptionsToShow.expired) {
|
||||
return buildShowExpiredExceptionsFilter(savedObjectPrefixes);
|
||||
}
|
||||
}, [exceptionsToShow, namespaceTypes]);
|
||||
|
||||
const handleFetchItems = useCallback(
|
||||
async (options?: GetExceptionItemProps) => {
|
||||
const abortCtrl = new AbortController();
|
||||
|
@ -228,10 +273,10 @@ const ExceptionsViewerComponent = ({
|
|||
total,
|
||||
data,
|
||||
} = await fetchExceptionListsItemsByListIds({
|
||||
filter: undefined,
|
||||
filter: exceptionListFilter,
|
||||
http: services.http,
|
||||
listIds: exceptionListsToQuery.map((list) => list.list_id),
|
||||
namespaceTypes: exceptionListsToQuery.map((list) => list.namespace_type),
|
||||
namespaceTypes,
|
||||
search: options?.search,
|
||||
pagination: newPagination,
|
||||
signal: abortCtrl.signal,
|
||||
|
@ -248,9 +293,34 @@ const ExceptionsViewerComponent = ({
|
|||
total,
|
||||
};
|
||||
},
|
||||
[pagination.pageIndex, pagination.pageSize, exceptionListsToQuery, services.http]
|
||||
[
|
||||
pagination.pageIndex,
|
||||
pagination.pageSize,
|
||||
exceptionListsToQuery,
|
||||
services.http,
|
||||
exceptionListFilter,
|
||||
namespaceTypes,
|
||||
]
|
||||
);
|
||||
|
||||
const getTotalExceptionCount = useCallback(async () => {
|
||||
const abortCtrl = new AbortController();
|
||||
|
||||
if (exceptionListsToQuery.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const { total } = await fetchExceptionListsItemsByListIds({
|
||||
filter: undefined,
|
||||
http: services.http,
|
||||
listIds: exceptionListsToQuery.map((list) => list.list_id),
|
||||
namespaceTypes,
|
||||
pagination: {},
|
||||
signal: abortCtrl.signal,
|
||||
});
|
||||
return total;
|
||||
}, [exceptionListsToQuery, namespaceTypes, services.http]);
|
||||
|
||||
const handleGetExceptionListItems = useCallback(
|
||||
async (options?: GetExceptionItemProps) => {
|
||||
try {
|
||||
|
@ -266,7 +336,9 @@ const ExceptionsViewerComponent = ({
|
|||
},
|
||||
});
|
||||
|
||||
setViewerState(total > 0 ? null : 'empty');
|
||||
setViewerState(
|
||||
total > 0 ? null : (await getTotalExceptionCount()) > 0 ? 'empty_search' : 'empty'
|
||||
);
|
||||
} catch (e) {
|
||||
setViewerState('error');
|
||||
|
||||
|
@ -276,7 +348,7 @@ const ExceptionsViewerComponent = ({
|
|||
});
|
||||
}
|
||||
},
|
||||
[handleFetchItems, setExceptions, setViewerState, toasts]
|
||||
[handleFetchItems, setExceptions, setViewerState, toasts, getTotalExceptionCount]
|
||||
);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
|
@ -306,6 +378,13 @@ const ExceptionsViewerComponent = ({
|
|||
[handleFetchItems, setExceptions, setViewerState, toasts]
|
||||
);
|
||||
|
||||
const handleExceptionsToShow = useCallback(
|
||||
(optionId: string): void => {
|
||||
setExceptionsToShow(optionId);
|
||||
},
|
||||
[setExceptionsToShow]
|
||||
);
|
||||
|
||||
const handleAddException = useCallback((): void => {
|
||||
setFlyoutType('addException');
|
||||
}, [setFlyoutType]);
|
||||
|
@ -430,22 +509,25 @@ const ExceptionsViewerComponent = ({
|
|||
{isEndpointSpecified ? i18n.ENDPOINT_EXCEPTIONS_TAB_ABOUT : i18n.EXCEPTIONS_TAB_ABOUT}
|
||||
</StyledText>
|
||||
<EuiSpacer size="l" />
|
||||
{!STATES_SEARCH_HIDDEN.includes(viewerState) && (
|
||||
<ExceptionsViewerSearchBar
|
||||
canAddException={isReadOnly}
|
||||
isEndpoint={isEndpointSpecified}
|
||||
isSearching={viewerState === 'searching'}
|
||||
onSearch={handleSearch}
|
||||
onAddExceptionClick={handleAddException}
|
||||
/>
|
||||
)}
|
||||
{!STATES_PAGINATION_UTILITY_HIDDEN.includes(viewerState) && (
|
||||
{!STATES_FILTERS_HIDDEN.includes(viewerState) && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<ExceptionsViewerUtility pagination={pagination} lastUpdated={lastUpdated} />
|
||||
<ExceptionsViewerUtility
|
||||
pagination={pagination}
|
||||
exceptionsToShow={exceptionsToShow}
|
||||
onChangeExceptionsToShow={handleExceptionsToShow}
|
||||
lastUpdated={lastUpdated}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<ExceptionsViewerSearchBar
|
||||
canAddException={isReadOnly}
|
||||
isEndpoint={isEndpointSpecified}
|
||||
isSearching={viewerState === 'searching'}
|
||||
onSearch={handleSearch}
|
||||
onAddExceptionClick={handleAddException}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<ExceptionsViewerItems
|
||||
isReadOnly={isReadOnly}
|
||||
|
|
|
@ -29,6 +29,7 @@ export interface State {
|
|||
viewerState: ViewerState;
|
||||
isReadOnly: boolean;
|
||||
lastUpdated: string | number;
|
||||
exceptionsToShow: { [id: string]: boolean };
|
||||
}
|
||||
|
||||
export type Action =
|
||||
|
@ -53,6 +54,10 @@ export type Action =
|
|||
| {
|
||||
type: 'setLastUpdateTime';
|
||||
lastUpdate: string | number;
|
||||
}
|
||||
| {
|
||||
type: 'setExceptionsToShow';
|
||||
optionId: string;
|
||||
};
|
||||
|
||||
export const allExceptionItemsReducer =
|
||||
|
@ -104,6 +109,21 @@ export const allExceptionItemsReducer =
|
|||
lastUpdated: action.lastUpdate,
|
||||
};
|
||||
}
|
||||
case 'setExceptionsToShow': {
|
||||
const newExceptionsToShow = {
|
||||
...state.exceptionsToShow,
|
||||
...{ [action.optionId]: !state.exceptionsToShow[action.optionId] },
|
||||
};
|
||||
|
||||
// At least one button must be selected
|
||||
if (!newExceptionsToShow.active && !newExceptionsToShow.expired) {
|
||||
return { ...state, exceptionsToShow: { active: true } };
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
exceptionsToShow: newExceptionsToShow,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -150,3 +150,17 @@ export const ADD_TO_DETECTIONS_LIST = i18n.translate(
|
|||
defaultMessage: 'Add rule exception',
|
||||
}
|
||||
);
|
||||
|
||||
export const ACTIVE_EXCEPTIONS = i18n.translate(
|
||||
'xpack.securitySolution.ruleExceptions.allExceptionItems.activeDetectionsLabel',
|
||||
{
|
||||
defaultMessage: 'Active exceptions',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPIRED_EXCEPTIONS = i18n.translate(
|
||||
'xpack.securitySolution.ruleExceptions.allExceptionItems.expiredDetectionsLabel',
|
||||
{
|
||||
defaultMessage: 'Expired exceptions',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -22,6 +22,8 @@ describe('ExceptionsViewerUtility', () => {
|
|||
totalItemCount: 105,
|
||||
pageSizeOptions: [5, 10, 20, 50, 100],
|
||||
}}
|
||||
exceptionsToShow={{ active: true }}
|
||||
onChangeExceptionsToShow={(optionId: string) => {}}
|
||||
lastUpdated={1660534202}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -42,6 +44,8 @@ describe('ExceptionsViewerUtility', () => {
|
|||
totalItemCount: 1,
|
||||
pageSizeOptions: [5, 10, 20, 50, 100],
|
||||
}}
|
||||
exceptionsToShow={{ active: true }}
|
||||
onChangeExceptionsToShow={(optionId: string) => {}}
|
||||
lastUpdated={Date.now()}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiText, EuiButtonGroup, EuiFlexGroup } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
@ -18,24 +18,30 @@ import {
|
|||
UtilityBarText,
|
||||
} from '../../../../common/components/utility_bar';
|
||||
import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const StyledText = styled.span`
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const MyUtilities = styled(EuiFlexGroup)`
|
||||
const MyUtilities = styled.div`
|
||||
height: 50px;
|
||||
`;
|
||||
|
||||
const StyledCondition = styled.span`
|
||||
display: inline-block !important;
|
||||
vertical-align: middle !important;
|
||||
const StyledBarGroup = styled(EuiFlexGroup)`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const PaginationUtilityBarText = styled(UtilityBarText)`
|
||||
align-self: center;
|
||||
`;
|
||||
|
||||
interface ExceptionsViewerUtilityProps {
|
||||
pagination: ExceptionsPagination;
|
||||
// Corresponds to last time exception items were fetched
|
||||
lastUpdated: string | number;
|
||||
exceptionsToShow: { [id: string]: boolean };
|
||||
onChangeExceptionsToShow: (optionId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,19 +50,21 @@ interface ExceptionsViewerUtilityProps {
|
|||
const ExceptionsViewerUtilityComponent: React.FC<ExceptionsViewerUtilityProps> = ({
|
||||
pagination,
|
||||
lastUpdated,
|
||||
}): JSX.Element => (
|
||||
<MyUtilities alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
exceptionsToShow,
|
||||
onChangeExceptionsToShow,
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<MyUtilities>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText dataTestSubj="exceptionsShowing">
|
||||
<PaginationUtilityBarText dataTestSubj="exceptionsShowing">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.exceptions.viewer.paginationDetails"
|
||||
defaultMessage="Showing {partOne} of {partTwo}"
|
||||
values={{
|
||||
partOne: (
|
||||
<StyledText>{`1-${Math.min(
|
||||
<StyledText>{`${pagination.totalItemCount === 0 ? '0' : '1'}-${Math.min(
|
||||
pagination.pageSize,
|
||||
pagination.totalItemCount
|
||||
)}`}</StyledText>
|
||||
|
@ -64,31 +72,44 @@ const ExceptionsViewerUtilityComponent: React.FC<ExceptionsViewerUtilityProps> =
|
|||
partTwo: <StyledText>{`${pagination.totalItemCount}`}</StyledText>,
|
||||
}}
|
||||
/>
|
||||
</UtilityBarText>
|
||||
</PaginationUtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" data-test-subj="exceptionsViewerLastUpdated">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.exceptions.viewer.lastUpdated"
|
||||
defaultMessage="Updated {updated}"
|
||||
values={{
|
||||
updated: (
|
||||
<StyledCondition>
|
||||
<FormattedRelativePreferenceDate
|
||||
value={lastUpdated}
|
||||
tooltipAnchorClassName="eui-textTruncate"
|
||||
<UtilityBarSection>
|
||||
<StyledBarGroup>
|
||||
<UtilityBarText dataTestSubj="lastUpdated">
|
||||
<EuiText size="s" data-test-subj="exceptionsViewerLastUpdated">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.exceptions.viewer.lastUpdated"
|
||||
defaultMessage="Updated {updated}"
|
||||
values={{
|
||||
updated: <FormattedRelativePreferenceDate value={lastUpdated} />,
|
||||
}}
|
||||
/>
|
||||
</StyledCondition>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</MyUtilities>
|
||||
);
|
||||
</EuiText>
|
||||
</UtilityBarText>
|
||||
<EuiButtonGroup
|
||||
legend="Displayed exceptions button group"
|
||||
options={[
|
||||
{
|
||||
id: `active`,
|
||||
label: i18n.ACTIVE_EXCEPTIONS,
|
||||
},
|
||||
{
|
||||
id: `expired`,
|
||||
label: i18n.EXPIRED_EXCEPTIONS,
|
||||
},
|
||||
]}
|
||||
idToSelectedMap={exceptionsToShow}
|
||||
onChange={onChangeExceptionsToShow}
|
||||
type="multi"
|
||||
/>
|
||||
</StyledBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
</MyUtilities>
|
||||
);
|
||||
};
|
||||
|
||||
ExceptionsViewerUtilityComponent.displayName = 'ExceptionsViewerUtilityComponent';
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ import {
|
|||
|
||||
import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution-list-utils';
|
||||
|
||||
import type { Moment } from 'moment';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
isEqlRule,
|
||||
isNewTermsRule,
|
||||
|
@ -56,6 +58,7 @@ import { createExceptionItemsReducer } from './reducer';
|
|||
import { useEditExceptionItems } from './use_edit_exception';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { ExceptionsExpireTime } from '../flyout_components/expire_time';
|
||||
|
||||
interface EditExceptionFlyoutProps {
|
||||
list: ExceptionListSchema;
|
||||
|
@ -119,6 +122,8 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
disableBulkClose,
|
||||
bulkCloseIndex,
|
||||
entryErrorExists,
|
||||
expireTime,
|
||||
expireErrorExists,
|
||||
},
|
||||
dispatch,
|
||||
] = useReducer(createExceptionItemsReducer(), {
|
||||
|
@ -129,6 +134,8 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
disableBulkClose: true,
|
||||
bulkCloseIndex: undefined,
|
||||
entryErrorExists: false,
|
||||
expireTime: itemToEdit.expire_time !== undefined ? moment(itemToEdit.expire_time) : undefined,
|
||||
expireErrorExists: false,
|
||||
});
|
||||
|
||||
const allowLargeValueLists = useMemo((): boolean => {
|
||||
|
@ -231,6 +238,26 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
const setExpireTime = useCallback(
|
||||
(exceptionExpireTime: Moment | undefined): void => {
|
||||
dispatch({
|
||||
type: 'setExpireTime',
|
||||
expireTime: exceptionExpireTime,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setExpireError = useCallback(
|
||||
(errorExists: boolean): void => {
|
||||
dispatch({
|
||||
type: 'setExpireError',
|
||||
errorExists,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleCloseFlyout = useCallback((): void => {
|
||||
onCancel(false);
|
||||
}, [onCancel]);
|
||||
|
@ -251,6 +278,7 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
commentToAdd: newComment,
|
||||
listType,
|
||||
selectedOs: itemToEdit.os_types,
|
||||
expireTime,
|
||||
items: exceptionItems,
|
||||
});
|
||||
|
||||
|
@ -292,6 +320,7 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
onConfirm,
|
||||
bulkCloseIndex,
|
||||
onCancel,
|
||||
expireTime,
|
||||
]);
|
||||
|
||||
const editExceptionMessage = useMemo(
|
||||
|
@ -308,8 +337,9 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
isClosingAlerts ||
|
||||
exceptionItems.every((item) => item.entries.length === 0) ||
|
||||
isLoading ||
|
||||
entryErrorExists,
|
||||
[isLoading, entryErrorExists, exceptionItems, isSubmitting, isClosingAlerts]
|
||||
entryErrorExists ||
|
||||
expireErrorExists,
|
||||
[isLoading, entryErrorExists, exceptionItems, isSubmitting, isClosingAlerts, expireErrorExists]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -370,6 +400,12 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
newCommentValue={newComment}
|
||||
newCommentOnChange={setComment}
|
||||
/>
|
||||
<EuiHorizontalRule />
|
||||
<ExceptionsExpireTime
|
||||
expireTime={expireTime}
|
||||
setExpireTime={setExpireTime}
|
||||
setExpireError={setExpireError}
|
||||
/>
|
||||
{showAlertCloseOptions && (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution-list-utils';
|
||||
import type { Moment } from 'moment';
|
||||
|
||||
export interface State {
|
||||
exceptionItems: ExceptionsBuilderReturnExceptionItem[];
|
||||
|
@ -15,6 +16,8 @@ export interface State {
|
|||
disableBulkClose: boolean;
|
||||
bulkCloseIndex: string[] | undefined;
|
||||
entryErrorExists: boolean;
|
||||
expireTime: Moment | undefined;
|
||||
expireErrorExists: boolean;
|
||||
}
|
||||
|
||||
export type Action =
|
||||
|
@ -45,6 +48,14 @@ export type Action =
|
|||
| {
|
||||
type: 'setConditionValidationErrorExists';
|
||||
errorExists: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'setExpireTime';
|
||||
expireTime: Moment | undefined;
|
||||
}
|
||||
| {
|
||||
type: 'setExpireError';
|
||||
errorExists: boolean;
|
||||
};
|
||||
|
||||
export const createExceptionItemsReducer =
|
||||
|
@ -110,6 +121,22 @@ export const createExceptionItemsReducer =
|
|||
entryErrorExists: errorExists,
|
||||
};
|
||||
}
|
||||
case 'setExpireTime': {
|
||||
const { expireTime } = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
expireTime,
|
||||
};
|
||||
}
|
||||
case 'setExpireError': {
|
||||
const { errorExists } = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
expireErrorExists: errorExists,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,11 @@ export const ExceptionItemCardMetaInfo = memo<ExceptionItemCardMetaInfoProps>(
|
|||
const onCloseRulesPopover = () => setIsRulesPopoverOpen(false);
|
||||
const onClosListsPopover = () => setIsListsPopoverOpen(false);
|
||||
|
||||
const isExpired = useMemo(
|
||||
() => (item.expire_time ? new Date(item.expire_time) <= new Date() : false),
|
||||
[item]
|
||||
);
|
||||
|
||||
const itemActions = useMemo((): EuiContextMenuPanelProps['items'] => {
|
||||
if (listAndReferences == null) {
|
||||
return [];
|
||||
|
@ -177,6 +182,20 @@ export const ExceptionItemCardMetaInfo = memo<ExceptionItemCardMetaInfoProps>(
|
|||
dataTestSubj={`${dataTestSubj}-updatedBy`}
|
||||
/>
|
||||
</StyledFlexItem>
|
||||
{item.expire_time != null && (
|
||||
<>
|
||||
<StyledFlexItem grow={false}>
|
||||
<MetaInfoDetails
|
||||
fieldName="expire_time"
|
||||
label={
|
||||
isExpired ? i18n.EXCEPTION_ITEM_EXPIRED_LABEL : i18n.EXCEPTION_ITEM_EXPIRES_LABEL
|
||||
}
|
||||
value1={<FormattedDate fieldName="expire_time" value={item.expire_time} />}
|
||||
dataTestSubj={`${dataTestSubj}-expireTime`}
|
||||
/>
|
||||
</StyledFlexItem>
|
||||
</>
|
||||
)}
|
||||
{listAndReferences != null && (
|
||||
<>
|
||||
{rulesAffected}
|
||||
|
@ -193,7 +212,7 @@ interface MetaInfoDetailsProps {
|
|||
fieldName: string;
|
||||
label: string;
|
||||
value1: JSX.Element | string;
|
||||
value2: string;
|
||||
value2?: string;
|
||||
dataTestSubj: string;
|
||||
}
|
||||
|
||||
|
@ -210,20 +229,24 @@ const MetaInfoDetails = memo<MetaInfoDetailsProps>(({ label, value1, value2, dat
|
|||
{value1}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs" style={{ fontFamily: 'Inter' }}>
|
||||
{i18n.EXCEPTION_ITEM_META_BY}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj={`${dataTestSubj}-value2`}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center" wrap={false}>
|
||||
{value2 != null && (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow" style={{ fontFamily: 'Inter' }}>
|
||||
{value2}
|
||||
</EuiBadge>
|
||||
<EuiText size="xs" style={{ fontFamily: 'Inter' }}>
|
||||
{i18n.EXCEPTION_ITEM_META_BY}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj={`${dataTestSubj}-value2`}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center" wrap={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow" style={{ fontFamily: 'Inter' }}>
|
||||
{value2}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -49,6 +49,20 @@ export const EXCEPTION_ITEM_UPDATED_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_ITEM_EXPIRES_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.ruleExceptions.exceptionItem.expiresLabel',
|
||||
{
|
||||
defaultMessage: 'Expires at',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_ITEM_EXPIRED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.ruleExceptions.exceptionItem.expiredLabel',
|
||||
{
|
||||
defaultMessage: 'Expired at',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_ITEM_META_BY = i18n.translate(
|
||||
'xpack.securitySolution.ruleExceptions.exceptionItem.metaDetailsBy',
|
||||
{
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { EuiDatePicker, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import type { Moment } from 'moment';
|
||||
import moment from 'moment';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface ExceptionItmeExpireTimeProps {
|
||||
expireTime: Moment | undefined;
|
||||
setExpireTime: (date: Moment | undefined) => void;
|
||||
setExpireError: (errorExists: boolean) => void;
|
||||
}
|
||||
|
||||
const SectionHeader = styled(EuiTitle)`
|
||||
${() => css`
|
||||
font-weight: ${({ theme }) => theme.eui.euiFontWeightSemiBold};
|
||||
`}
|
||||
`;
|
||||
|
||||
const ExceptionItemExpireTime: React.FC<ExceptionItmeExpireTimeProps> = ({
|
||||
expireTime,
|
||||
setExpireTime,
|
||||
setExpireError,
|
||||
}): JSX.Element => {
|
||||
const [dateTime, setDateTime] = useState<Moment | undefined>(expireTime);
|
||||
const [isInvalid, setIsInvalid] = useState(false);
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(date: Moment | null) => {
|
||||
setDateTime(date ?? undefined);
|
||||
setExpireTime(date ?? undefined);
|
||||
if (date?.isBefore()) {
|
||||
setIsInvalid(true);
|
||||
setErrors([i18n.EXCEPTION_EXPIRE_TIME_ERROR]);
|
||||
setExpireError(true);
|
||||
} else {
|
||||
setIsInvalid(false);
|
||||
setErrors([]);
|
||||
setExpireError(false);
|
||||
}
|
||||
},
|
||||
[setDateTime, setExpireTime, setExpireError]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SectionHeader size="xs">
|
||||
<h3>{i18n.EXCEPTION_EXPIRE_TIME_HEADER}</h3>
|
||||
</SectionHeader>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow error={errors} isInvalid={isInvalid} label={i18n.EXPIRE_TIME_LABEL}>
|
||||
<EuiDatePicker
|
||||
showTimeSelect
|
||||
selected={dateTime}
|
||||
isInvalid={isInvalid}
|
||||
onChange={handleChange}
|
||||
onClear={() => handleChange(null)}
|
||||
minDate={moment()}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExceptionsExpireTime = React.memo(ExceptionItemExpireTime);
|
||||
|
||||
ExceptionsExpireTime.displayName = 'ExceptionsExpireTime';
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const EXPIRE_TIME_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.rule_exceptions.flyoutComponents.expireTime.expireTimeLabel',
|
||||
{
|
||||
defaultMessage: 'Exception will expire at',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_EXPIRE_TIME_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.rule_exceptions.flyoutComponents.expireTime.exceptionExpireTime',
|
||||
{
|
||||
defaultMessage: 'Exception Expiration',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXCEPTION_EXPIRE_TIME_ERROR = i18n.translate(
|
||||
'xpack.securitySolution.rule_exceptions.flyoutComponents.expireTime.exceptionExpireTimeError',
|
||||
{
|
||||
defaultMessage: 'Selected date and time must be in the future.',
|
||||
}
|
||||
);
|
|
@ -39,6 +39,7 @@ describe('add_exception_flyout#utils', () => {
|
|||
selectedOs: [],
|
||||
listType: ExceptionListTypeEnum.RULE_DEFAULT,
|
||||
items,
|
||||
expireTime: undefined,
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
|
@ -75,6 +76,7 @@ describe('add_exception_flyout#utils', () => {
|
|||
selectedOs: [],
|
||||
listType: ExceptionListTypeEnum.DETECTION,
|
||||
items,
|
||||
expireTime: undefined,
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
|
@ -114,6 +116,7 @@ describe('add_exception_flyout#utils', () => {
|
|||
selectedOs: ['windows'],
|
||||
listType: ExceptionListTypeEnum.ENDPOINT,
|
||||
items,
|
||||
expireTime: undefined,
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
|||
import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution-list-utils';
|
||||
|
||||
import type { HorizontalAlignment } from '@elastic/eui';
|
||||
import type { Moment } from 'moment';
|
||||
import {
|
||||
HeaderMenu,
|
||||
generateLinkedRulesMenuItems,
|
||||
|
@ -22,6 +23,7 @@ import { ListDetailsLinkAnchor } from '../../../../exceptions/components';
|
|||
import {
|
||||
enrichExceptionItemsWithOS,
|
||||
enrichNewExceptionItemsWithComments,
|
||||
enrichNewExceptionItemsWithExpireTime,
|
||||
enrichNewExceptionItemsWithName,
|
||||
enrichRuleExceptions,
|
||||
enrichSharedExceptions,
|
||||
|
@ -58,6 +60,18 @@ export const enrichItemWithName =
|
|||
return itemName.trim() !== '' ? enrichNewExceptionItemsWithName(items, itemName) : items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds expiration datetime to all new exceptionItems
|
||||
* @param expireTimeToAdd new expireTime to add to item
|
||||
*/
|
||||
export const enrichItemWithExpireTime =
|
||||
(expireTimeToAdd: Moment | undefined) =>
|
||||
(items: ExceptionsBuilderReturnExceptionItem[]): ExceptionsBuilderReturnExceptionItem[] => {
|
||||
return expireTimeToAdd != null
|
||||
? enrichNewExceptionItemsWithExpireTime(items, expireTimeToAdd)
|
||||
: items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Modifies item entries to be in correct format and adds os selection to items
|
||||
* @param listType exception list type
|
||||
|
@ -114,6 +128,7 @@ export const enrichItemsForSharedLists =
|
|||
* @param sharedLists shared exception lists that were selected to add items to
|
||||
* @param selectedOs os selection
|
||||
* @param listType exception list type
|
||||
* @param expireTime exception item expire time
|
||||
* @param items exception items to be modified
|
||||
*/
|
||||
export const enrichNewExceptionItems = ({
|
||||
|
@ -124,6 +139,7 @@ export const enrichNewExceptionItems = ({
|
|||
sharedLists,
|
||||
selectedOs,
|
||||
listType,
|
||||
expireTime,
|
||||
items,
|
||||
}: {
|
||||
itemName: string;
|
||||
|
@ -133,10 +149,12 @@ export const enrichNewExceptionItems = ({
|
|||
addToSharedLists: boolean;
|
||||
sharedLists: ExceptionListSchema[];
|
||||
listType: ExceptionListTypeEnum;
|
||||
expireTime: Moment | undefined;
|
||||
items: ExceptionsBuilderReturnExceptionItem[];
|
||||
}): ExceptionsBuilderReturnExceptionItem[] => {
|
||||
const enriched: ExceptionsBuilderReturnExceptionItem[] = pipe(
|
||||
enrichItemWithComment(commentToAdd),
|
||||
enrichItemWithExpireTime(expireTime),
|
||||
enrichItemWithName(itemName),
|
||||
enrichEndpointItems(listType, selectedOs),
|
||||
enrichItemsForDefaultRuleList(listType, addToRules),
|
||||
|
@ -155,6 +173,7 @@ export const enrichNewExceptionItems = ({
|
|||
* @param sharedLists shared exception lists that were selected to add items to
|
||||
* @param selectedOs os selection
|
||||
* @param listType exception list type
|
||||
* @param expireTime exception item expire time
|
||||
* @param items exception items to be modified
|
||||
*/
|
||||
export const enrichExceptionItemsForUpdate = ({
|
||||
|
@ -162,16 +181,19 @@ export const enrichExceptionItemsForUpdate = ({
|
|||
commentToAdd,
|
||||
selectedOs,
|
||||
listType,
|
||||
expireTime,
|
||||
items,
|
||||
}: {
|
||||
itemName: string;
|
||||
commentToAdd: string;
|
||||
selectedOs: OsType[];
|
||||
listType: ExceptionListTypeEnum;
|
||||
expireTime: Moment | undefined;
|
||||
items: ExceptionsBuilderReturnExceptionItem[];
|
||||
}): ExceptionsBuilderReturnExceptionItem[] => {
|
||||
const enriched: ExceptionsBuilderReturnExceptionItem[] = pipe(
|
||||
enrichItemWithComment(commentToAdd),
|
||||
enrichItemWithExpireTime(expireTime),
|
||||
enrichItemWithName(itemName),
|
||||
enrichEndpointItems(listType, selectedOs)
|
||||
)(items);
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import type { EuiCommentProps } from '@elastic/eui';
|
||||
import { EuiText, EuiAvatar } from '@elastic/eui';
|
||||
import { capitalize, omit } from 'lodash';
|
||||
import type { Moment } from 'moment';
|
||||
import moment from 'moment';
|
||||
|
||||
import type {
|
||||
|
@ -177,6 +178,23 @@ export const enrichNewExceptionItemsWithComments = (
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds expireTime to all new exceptionItems if not present already
|
||||
* @param exceptionItems new or existing ExceptionItem[]
|
||||
* @param expireTime new expireTime
|
||||
*/
|
||||
export const enrichNewExceptionItemsWithExpireTime = (
|
||||
exceptionItems: ExceptionsBuilderReturnExceptionItem[],
|
||||
expireTime: Moment
|
||||
): ExceptionsBuilderReturnExceptionItem[] => {
|
||||
return exceptionItems.map((item: ExceptionsBuilderReturnExceptionItem) => {
|
||||
return {
|
||||
...item,
|
||||
expire_time: expireTime.toISOString(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const buildGetAlertByIdQuery = (id: string | undefined) => ({
|
||||
query: {
|
||||
match: {
|
||||
|
|
|
@ -78,6 +78,7 @@ export const getExceptionListItemSchemaMock = (
|
|||
created_by: USER,
|
||||
description: DESCRIPTION,
|
||||
entries: ENTRIES,
|
||||
expire_time: undefined,
|
||||
id: '1',
|
||||
item_id: 'endpoint_list_item',
|
||||
list_id: 'endpoint_list_id',
|
||||
|
|
|
@ -33,6 +33,7 @@ import { ListExceptionItems } from '../list_exception_items';
|
|||
import { useListDetailsView } from '../../hooks';
|
||||
import { useExceptionsListCard } from '../../hooks/use_exceptions_list.card';
|
||||
import { ManageRules } from '../manage_rules';
|
||||
import { ExportExceptionsListModal } from '../export_exceptions_list_modal';
|
||||
|
||||
interface ExceptionsListCardProps {
|
||||
exceptionsList: ExceptionListInfo;
|
||||
|
@ -47,10 +48,12 @@ interface ExceptionsListCardProps {
|
|||
}) => () => Promise<void>;
|
||||
handleExport: ({
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
}: {
|
||||
id: string;
|
||||
includeExpiredExceptions: boolean;
|
||||
listId: string;
|
||||
namespaceType: NamespaceType;
|
||||
}) => () => Promise<void>;
|
||||
|
@ -115,6 +118,9 @@ export const ExceptionsListCard = memo<ExceptionsListCardProps>(
|
|||
emptyViewerTitle,
|
||||
emptyViewerBody,
|
||||
emptyViewerButtonText,
|
||||
handleCancelExportModal,
|
||||
handleConfirmExportModal,
|
||||
showExportModal,
|
||||
} = useExceptionsListCard({
|
||||
exceptionsList,
|
||||
handleExport,
|
||||
|
@ -248,6 +254,12 @@ export const ExceptionsListCard = memo<ExceptionsListCardProps>(
|
|||
onRuleSelectionChange={onRuleSelectionChange}
|
||||
/>
|
||||
) : null}
|
||||
{showExportModal ? (
|
||||
<ExportExceptionsListModal
|
||||
handleCloseModal={handleCancelExportModal}
|
||||
onModalConfirm={handleConfirmExportModal}
|
||||
/>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 React, { memo, useCallback, useState } from 'react';
|
||||
|
||||
import { EuiConfirmModal, EuiSwitch } from '@elastic/eui';
|
||||
import * as i18n from '../../translations';
|
||||
|
||||
interface ExportExceptionsListModalProps {
|
||||
handleCloseModal: () => void;
|
||||
onModalConfirm: (includeExpired: boolean) => void;
|
||||
}
|
||||
|
||||
export const ExportExceptionsListModal = memo<ExportExceptionsListModalProps>(
|
||||
({ handleCloseModal, onModalConfirm }) => {
|
||||
const [exportExpired, setExportExpired] = useState(true);
|
||||
|
||||
const handleSwitchChange = useCallback(() => {
|
||||
setExportExpired(!exportExpired);
|
||||
}, [setExportExpired, exportExpired]);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
onModalConfirm(exportExpired);
|
||||
handleCloseModal();
|
||||
}, [exportExpired, handleCloseModal, onModalConfirm]);
|
||||
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
title={i18n.EXPORT_MODAL_TITLE}
|
||||
onCancel={handleCloseModal}
|
||||
onConfirm={handleConfirm}
|
||||
cancelButtonText={i18n.EXPORT_MODAL_CANCEL_BUTTON}
|
||||
confirmButtonText={i18n.EXPORT_MODAL_CONFIRM_BUTTON}
|
||||
defaultFocusedButton="confirm"
|
||||
>
|
||||
<EuiSwitch
|
||||
label={i18n.EXPORT_MODAL_INCLUDE_SWITCH_LABEL}
|
||||
checked={exportExpired}
|
||||
onChange={handleSwitchChange}
|
||||
/>
|
||||
</EuiConfirmModal>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ExportExceptionsListModal.displayName = 'ExportExceptionsListModal';
|
|
@ -21,6 +21,12 @@ import { useListExceptionItems } from '../use_list_exception_items';
|
|||
import * as i18n from '../../translations';
|
||||
import { checkIfListCannotBeEdited } from '../../utils/list.utils';
|
||||
|
||||
interface ExportListAction {
|
||||
id: string;
|
||||
listId: string;
|
||||
namespaceType: NamespaceType;
|
||||
includeExpiredExceptions: boolean;
|
||||
}
|
||||
interface ListAction {
|
||||
id: string;
|
||||
listId: string;
|
||||
|
@ -33,7 +39,12 @@ export const useExceptionsListCard = ({
|
|||
handleManageRules,
|
||||
}: {
|
||||
exceptionsList: ExceptionListInfo;
|
||||
handleExport: ({ id, listId, namespaceType }: ListAction) => () => Promise<void>;
|
||||
handleExport: ({
|
||||
id,
|
||||
listId,
|
||||
namespaceType,
|
||||
includeExpiredExceptions,
|
||||
}: ExportListAction) => () => Promise<void>;
|
||||
handleDelete: ({ id, listId, namespaceType }: ListAction) => () => Promise<void>;
|
||||
handleManageRules: () => void;
|
||||
}) => {
|
||||
|
@ -41,6 +52,7 @@ export const useExceptionsListCard = ({
|
|||
const [exceptionToEdit, setExceptionToEdit] = useState<ExceptionListItemSchema>();
|
||||
const [showAddExceptionFlyout, setShowAddExceptionFlyout] = useState(false);
|
||||
const [showEditExceptionFlyout, setShowEditExceptionFlyout] = useState(false);
|
||||
const [showExportModal, setShowExportModal] = useState(false);
|
||||
|
||||
const {
|
||||
name: listName,
|
||||
|
@ -111,13 +123,7 @@ export const useExceptionsListCard = ({
|
|||
key: 'Export',
|
||||
icon: 'exportAction',
|
||||
label: i18n.EXPORT_EXCEPTION_LIST,
|
||||
onClick: (e: React.MouseEvent<Element, MouseEvent>) => {
|
||||
handleExport({
|
||||
id: exceptionsList.id,
|
||||
listId: exceptionsList.list_id,
|
||||
namespaceType: exceptionsList.namespace_type,
|
||||
})();
|
||||
},
|
||||
onClick: (e: React.MouseEvent<Element, MouseEvent>) => setShowExportModal(true),
|
||||
},
|
||||
{
|
||||
key: 'Delete',
|
||||
|
@ -147,7 +153,7 @@ export const useExceptionsListCard = ({
|
|||
exceptionsList.list_id,
|
||||
exceptionsList.namespace_type,
|
||||
handleDelete,
|
||||
handleExport,
|
||||
setShowExportModal,
|
||||
listCannotBeEdited,
|
||||
handleManageRules,
|
||||
]
|
||||
|
@ -173,6 +179,26 @@ export const useExceptionsListCard = ({
|
|||
[fetchItems, setShowAddExceptionFlyout, setShowEditExceptionFlyout]
|
||||
);
|
||||
|
||||
const onExportListClick = useCallback(() => {
|
||||
setShowExportModal(true);
|
||||
}, [setShowExportModal]);
|
||||
|
||||
const handleCancelExportModal = () => {
|
||||
setShowExportModal(false);
|
||||
};
|
||||
|
||||
const handleConfirmExportModal = useCallback(
|
||||
(includeExpiredExceptions: boolean): void => {
|
||||
handleExport({
|
||||
id: exceptionsList.id,
|
||||
listId: exceptionsList.list_id,
|
||||
namespaceType: exceptionsList.namespace_type,
|
||||
includeExpiredExceptions,
|
||||
})();
|
||||
},
|
||||
[handleExport, exceptionsList]
|
||||
);
|
||||
|
||||
// routes to x-pack/plugins/security_solution/public/exceptions/routes.tsx
|
||||
// details component is here: x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx
|
||||
const { onClick: goToExceptionDetail } = useGetSecuritySolutionLinkProps()({
|
||||
|
@ -211,5 +237,9 @@ export const useExceptionsListCard = ({
|
|||
emptyViewerTitle,
|
||||
emptyViewerBody,
|
||||
emptyViewerButtonText,
|
||||
showExportModal,
|
||||
onExportListClick,
|
||||
handleCancelExportModal,
|
||||
handleConfirmExportModal,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -163,28 +163,32 @@ export const useListDetailsView = (exceptionListId: string) => {
|
|||
},
|
||||
[exceptionListId, handleErrorStatus, http, list]
|
||||
);
|
||||
const onExportList = useCallback(async () => {
|
||||
try {
|
||||
if (!list) return;
|
||||
await exportExceptionList({
|
||||
id: list.id,
|
||||
listId: list.list_id,
|
||||
namespaceType: list.namespace_type,
|
||||
onError: (error: Error) => handleErrorStatus(error),
|
||||
onSuccess: (blob) => {
|
||||
setExportedList(blob);
|
||||
toasts?.addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(list.list_id));
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
handleErrorStatus(
|
||||
error,
|
||||
undefined,
|
||||
i18n.EXCEPTION_EXPORT_ERROR,
|
||||
i18n.EXCEPTION_EXPORT_ERROR_DESCRIPTION
|
||||
);
|
||||
}
|
||||
}, [list, exportExceptionList, handleErrorStatus, toasts]);
|
||||
const onExportList = useCallback(
|
||||
async (includeExpiredExceptions: boolean) => {
|
||||
try {
|
||||
if (!list) return;
|
||||
await exportExceptionList({
|
||||
id: list.id,
|
||||
listId: list.list_id,
|
||||
includeExpiredExceptions,
|
||||
namespaceType: list.namespace_type,
|
||||
onError: (error: Error) => handleErrorStatus(error),
|
||||
onSuccess: (blob) => {
|
||||
setExportedList(blob);
|
||||
toasts?.addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(list.list_id));
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
handleErrorStatus(
|
||||
error,
|
||||
undefined,
|
||||
i18n.EXCEPTION_EXPORT_ERROR,
|
||||
i18n.EXCEPTION_EXPORT_ERROR_DESCRIPTION
|
||||
);
|
||||
}
|
||||
},
|
||||
[list, exportExceptionList, handleErrorStatus, toasts]
|
||||
);
|
||||
|
||||
// #region DeleteList
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import {
|
||||
|
@ -24,6 +24,7 @@ import { AutoDownload } from '../../../common/components/auto_download/auto_down
|
|||
import { ListWithSearch, ManageRules, ListDetailsLinkAnchor } from '../../components';
|
||||
import { useListDetailsView } from '../../hooks';
|
||||
import * as i18n from '../../translations';
|
||||
import { ExportExceptionsListModal } from '../../components/export_exceptions_list_modal';
|
||||
|
||||
export const ListsDetailViewComponent: FC = () => {
|
||||
const { detailName: exceptionListId } = useParams<{
|
||||
|
@ -59,6 +60,12 @@ export const ListsDetailViewComponent: FC = () => {
|
|||
handleReferenceDelete,
|
||||
} = useListDetailsView(exceptionListId);
|
||||
|
||||
const [showExportModal, setShowExportModal] = useState(false);
|
||||
|
||||
const onModalClose = useCallback(() => setShowExportModal(false), [setShowExportModal]);
|
||||
|
||||
const onModalOpen = useCallback(() => setShowExportModal(true), [setShowExportModal]);
|
||||
|
||||
const detailsViewContent = useMemo(() => {
|
||||
if (viewerStatus === ViewerStatus.ERROR)
|
||||
return <EmptyViewerState isReadOnly={isReadOnly} viewerStatus={viewerStatus} />;
|
||||
|
@ -79,7 +86,7 @@ export const ListsDetailViewComponent: FC = () => {
|
|||
backOptions={headerBackOptions}
|
||||
securityLinkAnchorComponent={ListDetailsLinkAnchor}
|
||||
onEditListDetails={onEditListDetails}
|
||||
onExportList={onExportList}
|
||||
onExportList={onModalOpen}
|
||||
onDeleteList={handleDelete}
|
||||
onManageRules={onManageRules}
|
||||
/>
|
||||
|
@ -107,6 +114,12 @@ export const ListsDetailViewComponent: FC = () => {
|
|||
onRuleSelectionChange={onRuleSelectionChange}
|
||||
/>
|
||||
) : null}
|
||||
{showExportModal && (
|
||||
<ExportExceptionsListModal
|
||||
onModalConfirm={onExportList}
|
||||
handleCloseModal={onModalClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
|
@ -128,6 +141,7 @@ export const ListsDetailViewComponent: FC = () => {
|
|||
showManageButtonLoader,
|
||||
showManageRulesFlyout,
|
||||
showReferenceErrorModal,
|
||||
showExportModal,
|
||||
viewerStatus,
|
||||
onCancelManageRules,
|
||||
onEditListDetails,
|
||||
|
@ -138,6 +152,8 @@ export const ListsDetailViewComponent: FC = () => {
|
|||
handleCloseReferenceErrorModal,
|
||||
handleDelete,
|
||||
handleReferenceDelete,
|
||||
onModalClose,
|
||||
onModalOpen,
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -192,10 +192,21 @@ export const SharedLists = React.memo(() => {
|
|||
);
|
||||
|
||||
const handleExport = useCallback(
|
||||
({ id, listId, namespaceType }: { id: string; listId: string; namespaceType: NamespaceType }) =>
|
||||
({
|
||||
id,
|
||||
listId,
|
||||
namespaceType,
|
||||
includeExpiredExceptions,
|
||||
}: {
|
||||
id: string;
|
||||
listId: string;
|
||||
namespaceType: NamespaceType;
|
||||
includeExpiredExceptions: boolean;
|
||||
}) =>
|
||||
async () => {
|
||||
await exportExceptionList({
|
||||
id,
|
||||
includeExpiredExceptions,
|
||||
listId,
|
||||
namespaceType,
|
||||
onError: handleExportError,
|
||||
|
|
|
@ -358,3 +358,31 @@ export const SORT_BY_CREATE_AT = i18n.translate(
|
|||
defaultMessage: 'Created At',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPORT_MODAL_CANCEL_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.exportModalCancelButton',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPORT_MODAL_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.exportModalTitle',
|
||||
{
|
||||
defaultMessage: 'Export exception list',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPORT_MODAL_INCLUDE_SWITCH_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.exportModalIncludeSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Include expired exceptions',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPORT_MODAL_CONFIRM_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.exportModalConfirmButton',
|
||||
{
|
||||
defaultMessage: 'Export',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -97,6 +97,7 @@ export const createNonInteractiveSessionEventFilter = (
|
|||
itemId: uuidv4(),
|
||||
meta: [],
|
||||
comments: [],
|
||||
expireTime: undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`Error creating Event Filter: ${wrapErrorIfNeeded(err)}`);
|
||||
|
|
|
@ -57,6 +57,7 @@ export const removePolicyFromArtifacts = async (
|
|||
namespaceType: artifact.namespace_type,
|
||||
osTypes: artifact.os_types,
|
||||
tags: artifact.tags.filter((currentPolicy) => currentPolicy !== `policy:${policy.id}`),
|
||||
expireTime: artifact.expire_time,
|
||||
}),
|
||||
{
|
||||
/** Number of concurrent executions till the end of the artifacts array */
|
||||
|
|
|
@ -190,6 +190,7 @@ export const createExceptionListItems = async ({
|
|||
comments: item.comments,
|
||||
description: item.description,
|
||||
entries: item.entries,
|
||||
expireTime: item.expire_time,
|
||||
itemId: item.item_id,
|
||||
listId: defaultList.list_id,
|
||||
meta: item.meta,
|
||||
|
|
|
@ -94,6 +94,7 @@ export const createPromises = (
|
|||
id,
|
||||
listId,
|
||||
namespaceType,
|
||||
includeExpiredExceptions: true, // TODO: pass this arg in via the rule export api
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -313,6 +313,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
});
|
||||
|
||||
const { filter: exceptionFilter, unprocessedExceptions } = await buildExceptionFilter({
|
||||
startedAt,
|
||||
alias: null,
|
||||
excludeExceptions: true,
|
||||
chunkSize: 10,
|
||||
|
|
|
@ -789,6 +789,7 @@ describe('create_signals', () => {
|
|||
alias: null,
|
||||
chunkSize: 1024,
|
||||
excludeExceptions: true,
|
||||
startedAt: new Date(),
|
||||
});
|
||||
const request = buildEqlSearchRequest({
|
||||
query: 'process where true',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue