Skip to content

Commit

Permalink
[Security Solution] Display warning banner when removing a host isola…
Browse files Browse the repository at this point in the history
…tion exception (#121729)
  • Loading branch information
academo authored Dec 21, 2021
1 parent ea24d16 commit 9ae1ffe
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { EuiButton, EuiButtonProps, PropsForButton } from '@elastic/eui';
import React, { FC, memo, useEffect, useRef } from 'react';

export const AutoFocusButton: FC<PropsForButton<EuiButtonProps>> = memo((props) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const button = <EuiButton buttonRef={buttonRef} {...props} />;

useEffect(() => {
if (buttonRef.current) {
buttonRef.current.focus();
}
}, []);

return button;
});

AutoFocusButton.displayName = 'AutoFocusButton';
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,12 @@ export function getEffectedPolicySelectionByTags(
export function isGlobalPolicyEffected(tags?: string[]): boolean {
return tags !== undefined && tags.find((tag) => tag === GLOBAL_POLICY_TAG) !== undefined;
}

/**
* Given an array of an artifact tags, return the ids of policies inside
* those tags. It will only return tags starting with `policy:` and it will
* return them without the suffix
*/
export function getArtifactPoliciesIdByTag(tags: string[] = []): string[] {
return tags.filter((tag) => tag.startsWith('policy:')).map((tag) => tag.substring(7));
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
* 2.0.
*/

import React, { memo, useCallback, useEffect } from 'react';
import {
EuiButton,
EuiButtonEmpty,
EuiCallOut,
EuiModal,
Expand All @@ -18,20 +16,25 @@ import {
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { memo, useCallback, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import { i18n } from '@kbn/i18n';
import { useEventFiltersSelector } from '../hooks';
import { AutoFocusButton } from '../../../../../common/components/autofocus_button/autofocus_button';
import { useToasts } from '../../../../../common/lib/kibana';
import { AppAction } from '../../../../../common/store/actions';
import {
getArtifactPoliciesIdByTag,
isGlobalPolicyEffected,
} from '../../../../components/effected_policy_select/utils';
import {
getDeleteError,
getItemToDelete,
isDeletionInProgress,
wasDeletionSuccessful,
} from '../../store/selector';
import { AppAction } from '../../../../../common/store/actions';
import { useToasts } from '../../../../../common/lib/kibana';
import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils';
import { useEventFiltersSelector } from '../hooks';

export const EventFilterDeleteModal = memo<{}>(() => {
const dispatch = useDispatch<Dispatch<AppAction>>();
Expand Down Expand Up @@ -109,7 +112,7 @@ export const EventFilterDeleteModal = memo<{}>(() => {
values={{
count: isGlobalPolicyEffected(Array.from(eventFilter?.tags || []))
? 'all'
: eventFilter?.tags?.length || 0,
: getArtifactPoliciesIdByTag(eventFilter?.tags as string[]).length,
}}
/>
</p>
Expand All @@ -136,7 +139,7 @@ export const EventFilterDeleteModal = memo<{}>(() => {
/>
</EuiButtonEmpty>

<EuiButton
<AutoFocusButton
fill
color="danger"
onClick={onConfirm}
Expand All @@ -147,7 +150,7 @@ export const EventFilterDeleteModal = memo<{}>(() => {
id="xpack.securitySolution.eventFilters.deletionDialog.confirmButton"
defaultMessage="Delete"
/>
</EuiButton>
</AutoFocusButton>
</EuiModalFooter>
</EuiModal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { waitFor } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import React from 'react';
import uuid from 'uuid';
import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import {
AppContextTestRender,
createAppRootMockRenderer,
} from '../../../../../common/mock/endpoint';
import { HostIsolationExceptionDeleteModal } from './delete_modal';
import { deleteOneHostIsolationExceptionItem } from '../../service';
import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { waitFor } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { HostIsolationExceptionDeleteModal } from './delete_modal';

jest.mock('../../service');
const deleteOneHostIsolationExceptionItemMock = deleteOneHostIsolationExceptionItem as jest.Mock;
Expand All @@ -23,10 +25,11 @@ describe('When on the host isolation exceptions delete modal', () => {
let renderResult: ReturnType<typeof render>;
let coreStart: AppContextTestRender['coreStart'];
let onCancel: (forceRefresh?: boolean) => void;
let itemToDelete: ExceptionListItemSchema;

beforeEach(() => {
const mockedContext = createAppRootMockRenderer();
const itemToDelete = getExceptionListItemSchemaMock();
itemToDelete = getExceptionListItemSchemaMock();
deleteOneHostIsolationExceptionItemMock.mockReset();
onCancel = jest.fn();
render = () =>
Expand All @@ -44,6 +47,24 @@ describe('When on the host isolation exceptions delete modal', () => {
).toBeTruthy();
});

it.each(['all', '0', '1', '2', '5'])(
'should display a warning banner with how many policies (%s) will be affected. skipping non-policy-tags',
(amount) => {
if (amount === 'all') {
itemToDelete.tags = ['policy:all', 'non-policy-tag'];
} else {
itemToDelete.tags = [
...Array.from({ length: +amount }, (_) => `policy:${uuid.v4()}`),
'non-policy-tag',
];
}
render();
expect(
renderResult.getByTestId('hostIsolationExceptionsDeleteModalCalloutMessage')
).toHaveTextContent(`Deleting this entry will remove it from ${amount} associated`);
}
);

it('should disable the buttons when confirm is pressed and show loading', async () => {
render();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,27 @@
* 2.0.
*/

import React, { memo } from 'react';
import {
EuiButton,
EuiButtonEmpty,
EuiCallOut,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import React, { memo } from 'react';
import { useMutation } from 'react-query';
import { AutoFocusButton } from '../../../../../common/components/autofocus_button/autofocus_button';
import { useHttp, useToasts } from '../../../../../common/lib/kibana';
import {
getArtifactPoliciesIdByTag,
isGlobalPolicyEffected,
} from '../../../../components/effected_policy_select/utils';
import { deleteOneHostIsolationExceptionItem } from '../../service';

export const HostIsolationExceptionDeleteModal = memo(
Expand Down Expand Up @@ -89,13 +94,29 @@ export const HostIsolationExceptionDeleteModal = memo(

<EuiModalBody data-test-subj="hostIsolationExceptionsFilterDeleteModalBody">
<EuiText>
<p>
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.subtitle"
defaultMessage='You are deleting exception "{name}".'
values={{ name: <b className="eui-textBreakWord">{item?.name}</b> }}
/>
</p>
<EuiCallOut
data-test-subj="hostIsolationExceptionsDeleteModalCallout"
title={i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.deletionDialog.calloutTitle',
{
defaultMessage: 'Warning',
}
)}
color="danger"
iconType="alert"
>
<p data-test-subj="hostIsolationExceptionsDeleteModalCalloutMessage">
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.calloutMessage"
defaultMessage="Deleting this entry will remove it from {count} associated {count, plural, one {policy} other {policies}}."
values={{
count: isGlobalPolicyEffected(Array.from(item.tags || []))
? 'all'
: getArtifactPoliciesIdByTag(item.tags).length,
}}
/>
</p>
</EuiCallOut>
<p>
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmation"
Expand All @@ -117,7 +138,7 @@ export const HostIsolationExceptionDeleteModal = memo(
/>
</EuiButtonEmpty>

<EuiButton
<AutoFocusButton
fill
color="danger"
onClick={handleConfirmButton}
Expand All @@ -126,9 +147,9 @@ export const HostIsolationExceptionDeleteModal = memo(
>
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmButton"
defaultMessage="Remove exception"
defaultMessage="Delete"
/>
</EuiButton>
</AutoFocusButton>
</EuiModalFooter>
</EuiModal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,31 @@
* 2.0.
*/

import React, { FC, memo, useCallback, useEffect, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiButton,
EuiButtonEmpty,
EuiButtonProps,
PropsForButton,
EuiCallOut,
EuiSpacer,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiSpacer,
EuiText,
} from '@elastic/eui';

import { FormattedMessage } from '@kbn/i18n-react';
import React, { memo, useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import { AutoFocusButton } from '../../../../common/components/autofocus_button/autofocus_button';
import { Immutable, TrustedApp } from '../../../../../common/endpoint/types';
import { AppAction } from '../../../../common/store/actions';
import { useTrustedAppsSelector } from './hooks';
import { isPolicyEffectScope } from '../state/type_guards';
import {
getDeletionDialogEntry,
isDeletionDialogOpen,
isDeletionInProgress,
} from '../store/selectors';
import { isPolicyEffectScope } from '../state/type_guards';
import { useTrustedAppsSelector } from './hooks';

const CANCEL_SUBJ = 'trustedAppDeletionCancel';
const CONFIRM_SUBJ = 'trustedAppDeletionConfirm';
Expand Down Expand Up @@ -83,21 +80,6 @@ const getTranslations = (entry: Immutable<TrustedApp> | undefined) => ({
),
});

const AutoFocusButton: FC<PropsForButton<EuiButtonProps>> = memo((props) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const button = <EuiButton buttonRef={buttonRef} {...props} />;

useEffect(() => {
if (buttonRef.current) {
buttonRef.current.focus();
}
}, []);

return button;
});

AutoFocusButton.displayName = 'AutoFocusButton';

export const TrustedAppDeletionDialog = memo(() => {
const dispatch = useDispatch<Dispatch<AppAction>>();
const isBusy = useTrustedAppsSelector(isDeletionInProgress);
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -23007,10 +23007,8 @@
"xpack.securitySolution.hostIsolation.agentStatuses.empty": "-",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.cancel": "キャンセル",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmation": "この操作は元に戻すことができません。続行していいですか?",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmButton": "例外を削除",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteFailure": "ホスト分離例外リストから\"{name}\"を削除できません。理由:{message}",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteSuccess": "\"{name}\"はホスト分離例外リストから削除されました。",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.subtitle": "例外\"{name}\"を削除しています。",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.title": "ホスト分離例外を削除",
"xpack.securitySolution.hostIsolationExceptions.flyout.cancel": "キャンセル",
"xpack.securitySolution.hostIsolationExceptions.flyout.createButton": "ホスト分離例外を追加",
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -23374,10 +23374,8 @@
"xpack.securitySolution.hostIsolation.agentStatuses.empty": "-",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.cancel": "取消",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmation": "此操作无法撤消。是否确定要继续?",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmButton": "移除例外",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteFailure": "无法从主机隔离例外列表中移除“{name}”。原因:{message}",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteSuccess": "已从主机隔离例外列表中移除“{name}”。",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.subtitle": "正在删除例外“{name}”。",
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.title": "删除主机隔离例外",
"xpack.securitySolution.hostIsolationExceptions.flyout.cancel": "取消",
"xpack.securitySolution.hostIsolationExceptions.flyout.createButton": "添加主机隔离例外",
Expand Down

0 comments on commit 9ae1ffe

Please sign in to comment.