[SecuritySolution][Case] Disable cases on detections in read-only mode (#93010)
* Disable cases on detetions on read-only mode * Add cypress tests
This commit is contained in:
parent
4739eab490
commit
aa62a130ee
6 changed files with 170 additions and 18 deletions
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { newRule } from '../../objects/rule';
|
||||
import { ROLES } from '../../../common/test';
|
||||
|
||||
import { waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded } from '../../tasks/alerts';
|
||||
import { createCustomRuleActivated } from '../../tasks/api_calls/rules';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
|
||||
import { login, loginAndWaitForPage, waitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ATTACH_ALERT_TO_CASE_BUTTON } from '../../screens/alerts_detection_rules';
|
||||
|
||||
const loadDetectionsPage = (role: ROLES) => {
|
||||
waitForPageWithoutDateRange(DETECTIONS_URL, role);
|
||||
waitForAlertsToPopulate();
|
||||
};
|
||||
|
||||
describe('Alerts timeline', () => {
|
||||
before(() => {
|
||||
// First we login as a privileged user to create alerts.
|
||||
cleanKibana();
|
||||
loginAndWaitForPage(DETECTIONS_URL, ROLES.platform_engineer);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(newRule);
|
||||
refreshPage();
|
||||
waitForAlertsToPopulate();
|
||||
|
||||
// Then we login as read-only user to test.
|
||||
login(ROLES.reader);
|
||||
});
|
||||
|
||||
context('Privileges: read only', () => {
|
||||
beforeEach(() => {
|
||||
loadDetectionsPage(ROLES.reader);
|
||||
});
|
||||
|
||||
it('should not allow user with read only privileges to attach alerts to cases', () => {
|
||||
cy.get(ATTACH_ALERT_TO_CASE_BUTTON).first().should('be.disabled');
|
||||
});
|
||||
});
|
||||
|
||||
context('Privileges: can crud', () => {
|
||||
beforeEach(() => {
|
||||
loadDetectionsPage(ROLES.platform_engineer);
|
||||
});
|
||||
|
||||
it('should allow a user with crud privileges to attach alerts to cases', () => {
|
||||
cy.get(ATTACH_ALERT_TO_CASE_BUTTON).first().should('not.be.disabled');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const ATTACH_ALERT_TO_CASE_BUTTON = '[data-test-subj="attach-alert-to-case-button"]';
|
||||
|
||||
export const BULK_ACTIONS_BTN = '[data-test-subj="bulkActions"] span';
|
||||
|
||||
export const CREATE_NEW_RULE_BTN = '[data-test-subj="create-new-rule"]';
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { ReactNode } from 'react';
|
|||
import { mount } from 'enzyme';
|
||||
import { EuiGlobalToastList } from '@elastic/eui';
|
||||
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { useKibana, useGetUserSavedObjectPermissions } from '../../../common/lib/kibana';
|
||||
import { useStateToaster } from '../../../common/components/toasters';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { usePostComment } from '../../containers/use_post_comment';
|
||||
|
@ -113,8 +113,8 @@ describe('AddToCaseAction', () => {
|
|||
ecsRowData: {
|
||||
_id: 'test-id',
|
||||
_index: 'test-index',
|
||||
signal: { rule: { id: ['rule-id'], name: ['rule-name'], false_positives: [] } },
|
||||
},
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
const mockDispatchToaster = jest.fn();
|
||||
|
@ -127,6 +127,10 @@ describe('AddToCaseAction', () => {
|
|||
(useKibana as jest.Mock).mockReturnValue({
|
||||
services: { application: { navigateToApp: mockNavigateToApp } },
|
||||
});
|
||||
(useGetUserSavedObjectPermissions as jest.Mock).mockReturnValue({
|
||||
crud: true,
|
||||
read: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('it renders', async () => {
|
||||
|
@ -181,8 +185,8 @@ describe('AddToCaseAction', () => {
|
|||
alertId: 'test-id',
|
||||
index: 'test-index',
|
||||
rule: {
|
||||
id: null,
|
||||
name: null,
|
||||
id: 'rule-id',
|
||||
name: 'rule-name',
|
||||
},
|
||||
type: 'alert',
|
||||
});
|
||||
|
@ -218,7 +222,38 @@ describe('AddToCaseAction', () => {
|
|||
alertId: 'test-id',
|
||||
index: 'test-index',
|
||||
rule: {
|
||||
id: null,
|
||||
id: 'rule-id',
|
||||
name: 'rule-name',
|
||||
},
|
||||
type: 'alert',
|
||||
});
|
||||
});
|
||||
|
||||
it('it set rule information as null when missing', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AddToCaseAction
|
||||
{...props}
|
||||
ecsRowData={{
|
||||
_id: 'test-id',
|
||||
_index: 'test-index',
|
||||
signal: { rule: { id: ['rule-id'], false_positives: [] } },
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click');
|
||||
wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click');
|
||||
|
||||
wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click');
|
||||
|
||||
expect(postComment.mock.calls[0][0].caseId).toBe('new-case');
|
||||
expect(postComment.mock.calls[0][0].data).toEqual({
|
||||
alertId: 'test-id',
|
||||
index: 'test-index',
|
||||
rule: {
|
||||
id: 'rule-id',
|
||||
name: null,
|
||||
},
|
||||
type: 'alert',
|
||||
|
@ -291,4 +326,39 @@ describe('AddToCaseAction', () => {
|
|||
path: '/selected-case',
|
||||
});
|
||||
});
|
||||
|
||||
it('disabled when event type is not supported', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AddToCaseAction
|
||||
{...props}
|
||||
ecsRowData={{
|
||||
_id: 'test-id',
|
||||
_index: 'test-index',
|
||||
}}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().prop('disabled')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('disabled when user does not have crud permissions', async () => {
|
||||
(useGetUserSavedObjectPermissions as jest.Mock).mockReturnValue({
|
||||
crud: false,
|
||||
read: true,
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AddToCaseAction {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().prop('disabled')
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { memo, useState, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
EuiPopover,
|
||||
|
@ -22,7 +23,7 @@ import { usePostComment } from '../../containers/use_post_comment';
|
|||
import { Case } from '../../containers/types';
|
||||
import { useStateToaster } from '../../../common/components/toasters';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { useGetUserSavedObjectPermissions, useKibana } from '../../../common/lib/kibana';
|
||||
import { getCaseDetailsUrl } from '../../../common/components/link_to';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { useAllCasesModal } from '../use_all_cases_modal';
|
||||
|
@ -34,13 +35,11 @@ import { CreateCaseFlyout } from '../create/flyout';
|
|||
interface AddToCaseActionProps {
|
||||
ariaLabel?: string;
|
||||
ecsRowData: Ecs;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
||||
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
|
||||
ecsRowData,
|
||||
disabled,
|
||||
}) => {
|
||||
const eventId = ecsRowData._id;
|
||||
const eventIndex = ecsRowData._index;
|
||||
|
@ -51,6 +50,16 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const openPopover = useCallback(() => setIsPopoverOpen(true), []);
|
||||
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
|
||||
const userPermissions = useGetUserSavedObjectPermissions();
|
||||
|
||||
const isEventSupported = !isEmpty(ecsRowData.signal?.rule?.id);
|
||||
const userCanCrud = userPermissions?.crud ?? false;
|
||||
const isDisabled = !userCanCrud || !isEventSupported;
|
||||
const tooltipContext = userCanCrud
|
||||
? isEventSupported
|
||||
? i18n.ACTION_ADD_TO_CASE_TOOLTIP
|
||||
: i18n.UNSUPPORTED_EVENTS_MSG
|
||||
: i18n.PERMISSIONS_MSG;
|
||||
|
||||
const { postComment } = usePostComment();
|
||||
|
||||
|
@ -137,7 +146,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
onClick={addNewCaseClick}
|
||||
aria-label={i18n.ACTION_ADD_NEW_CASE}
|
||||
data-test-subj="add-new-case-item"
|
||||
disabled={disabled}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<EuiText size="m">{i18n.ACTION_ADD_NEW_CASE}</EuiText>
|
||||
</EuiContextMenuItem>,
|
||||
|
@ -146,31 +155,28 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
onClick={addExistingCaseClick}
|
||||
aria-label={i18n.ACTION_ADD_EXISTING_CASE}
|
||||
data-test-subj="add-existing-case-menu-item"
|
||||
disabled={disabled}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<EuiText size="m">{i18n.ACTION_ADD_EXISTING_CASE}</EuiText>
|
||||
</EuiContextMenuItem>,
|
||||
],
|
||||
[addExistingCaseClick, addNewCaseClick, disabled]
|
||||
[addExistingCaseClick, addNewCaseClick, isDisabled]
|
||||
);
|
||||
|
||||
const button = useMemo(
|
||||
() => (
|
||||
<EuiToolTip
|
||||
data-test-subj="attach-alert-to-case-tooltip"
|
||||
content={i18n.ACTION_ADD_TO_CASE_TOOLTIP}
|
||||
>
|
||||
<EuiToolTip data-test-subj="attach-alert-to-case-tooltip" content={tooltipContext}>
|
||||
<EuiButtonIcon
|
||||
aria-label={ariaLabel}
|
||||
data-test-subj="attach-alert-to-case-button"
|
||||
size="s"
|
||||
iconType="folderClosed"
|
||||
onClick={openPopover}
|
||||
disabled={disabled}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
),
|
||||
[ariaLabel, disabled, openPopover]
|
||||
[ariaLabel, isDisabled, openPopover, tooltipContext]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -61,3 +61,18 @@ export const VIEW_CASE = i18n.translate(
|
|||
defaultMessage: 'View Case',
|
||||
}
|
||||
);
|
||||
|
||||
export const PERMISSIONS_MSG = i18n.translate(
|
||||
'xpack.securitySolution.case.timeline.actions.permissionsMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'You are currently missing the required permissions to attach alerts to cases. Please contact your administrator for further assistance.',
|
||||
}
|
||||
);
|
||||
|
||||
export const UNSUPPORTED_EVENTS_MSG = i18n.translate(
|
||||
'xpack.securitySolution.case.timeline.actions.unsupportedEventsMessage',
|
||||
{
|
||||
defaultMessage: 'This event cannot be attached to a case',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -175,7 +175,6 @@ export const EventColumnView = React.memo<Props>(
|
|||
ariaLabel={i18n.ATTACH_ALERT_TO_CASE_FOR_ROW({ ariaRowindex, columnValues })}
|
||||
key="attach-to-case"
|
||||
ecsRowData={ecsData}
|
||||
disabled={eventType !== 'signal'}
|
||||
/>,
|
||||
]
|
||||
: []),
|
||||
|
|
Loading…
Reference in a new issue