[Security Solution][Investigations] - Disable adding notes when read only (#133905) (#134964)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit b41bc6643d)

Co-authored-by: Michael Olorunnisola <michael.olorunnisola@elastic.co>
This commit is contained in:
Kibana Machine 2022-06-23 09:05:58 -04:00 committed by GitHub
parent 9ee96d7b64
commit 6824d89493
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 196 additions and 21 deletions

View file

@ -0,0 +1,68 @@
/*
* 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 { render, screen } from '@testing-library/react';
import React from 'react';
import { AddEventNoteAction } from './add_note_icon_item';
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
import { getEndpointPrivilegesInitialStateMock } from '../../../../../common/components/user_privileges/endpoint/mocks';
import { TestProviders } from '../../../../../common/mock';
import { TimelineType } from '../../../../../../common/types';
jest.mock('../../../../../common/components/user_privileges');
const useUserPrivilegesMock = useUserPrivileges as jest.Mock;
describe('AddEventNoteAction', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('isDisabled', () => {
test('it disables the add note button when the user does NOT have crud privileges', () => {
useUserPrivilegesMock.mockReturnValue({
kibanaSecuritySolutionsPrivileges: { crud: false, read: true },
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
});
render(
<TestProviders>
<AddEventNoteAction
showNotes={false}
timelineType={TimelineType.default}
toggleShowNotes={jest.fn}
/>
</TestProviders>
);
expect(screen.getByTestId('timeline-notes-button-small')).toHaveClass(
'euiButtonIcon-isDisabled'
);
});
test('it enables the add note button when the user has crud privileges', () => {
useUserPrivilegesMock.mockReturnValue({
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
});
render(
<TestProviders>
<AddEventNoteAction
showNotes={false}
timelineType={TimelineType.default}
toggleShowNotes={jest.fn}
/>
</TestProviders>
);
expect(screen.getByTestId('timeline-notes-button-small')).not.toHaveClass(
'euiButtonIcon-isDisabled'
);
});
});
});

View file

@ -11,6 +11,7 @@ import { TimelineType } from '../../../../../../common/types/timeline';
import * as i18n from '../translations';
import { NotesButton } from '../../properties/helpers';
import { ActionIconItem } from './action_icon_item';
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
interface AddEventNoteActionProps {
ariaLabel?: string;
@ -24,20 +25,25 @@ const AddEventNoteActionComponent: React.FC<AddEventNoteActionProps> = ({
showNotes,
timelineType,
toggleShowNotes,
}) => (
<ActionIconItem>
<NotesButton
ariaLabel={ariaLabel}
data-test-subj="add-note"
showNotes={showNotes}
timelineType={timelineType}
toggleShowNotes={toggleShowNotes}
toolTip={
timelineType === TimelineType.template ? i18n.NOTES_DISABLE_TOOLTIP : i18n.NOTES_TOOLTIP
}
/>
</ActionIconItem>
);
}) => {
const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges();
return (
<ActionIconItem>
<NotesButton
ariaLabel={ariaLabel}
data-test-subj="add-note"
isDisabled={kibanaSecuritySolutionsPrivileges.crud === false}
showNotes={showNotes}
timelineType={timelineType}
toggleShowNotes={toggleShowNotes}
toolTip={
timelineType === TimelineType.template ? i18n.NOTES_DISABLE_TOOLTIP : i18n.NOTES_TOOLTIP
}
/>
</ActionIconItem>
);
};
AddEventNoteActionComponent.displayName = 'AddEventNoteActionComponent';

View file

@ -0,0 +1,68 @@
/*
* 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 { render, screen } from '@testing-library/react';
import React from 'react';
import { PinEventAction } from './pin_event_action';
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
import { getEndpointPrivilegesInitialStateMock } from '../../../../../common/components/user_privileges/endpoint/mocks';
import { TestProviders } from '../../../../../common/mock';
import { TimelineType } from '../../../../../../common/types';
jest.mock('../../../../../common/components/user_privileges');
const useUserPrivilegesMock = useUserPrivileges as jest.Mock;
describe('PinEventAction', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('isDisabled', () => {
test('it disables the pin event button when the user does NOT have crud privileges', () => {
useUserPrivilegesMock.mockReturnValue({
kibanaSecuritySolutionsPrivileges: { crud: false, read: true },
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
});
render(
<TestProviders>
<PinEventAction
isAlert={false}
noteIds={[]}
onPinClicked={jest.fn}
eventIsPinned={false}
timelineType={TimelineType.default}
/>
</TestProviders>
);
expect(screen.getByTestId('pin')).toHaveClass('euiButtonIcon-isDisabled');
});
test('it enables the pin event button when the user has crud privileges', () => {
useUserPrivilegesMock.mockReturnValue({
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
});
render(
<TestProviders>
<PinEventAction
isAlert={false}
noteIds={[]}
onPinClicked={jest.fn}
eventIsPinned={false}
timelineType={TimelineType.default}
/>
</TestProviders>
);
expect(screen.getByTestId('pin')).not.toHaveClass('euiButtonIcon-isDisabled');
});
});
});

View file

@ -13,6 +13,7 @@ import { EventsTdContent } from '../../styles';
import { eventHasNotes, getPinTooltip } from '../helpers';
import { Pin } from '../../pin';
import { TimelineType } from '../../../../../../common/types/timeline';
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
interface PinEventActionProps {
ariaLabel?: string;
@ -31,6 +32,7 @@ const PinEventActionComponent: React.FC<PinEventActionProps> = ({
eventIsPinned,
timelineType,
}) => {
const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges();
const tooltipContent = useMemo(
() =>
getPinTooltip({
@ -50,6 +52,7 @@ const PinEventActionComponent: React.FC<PinEventActionProps> = ({
ariaLabel={ariaLabel}
allowUnpinning={!eventHasNotes(noteIds)}
data-test-subj="pin-event"
isDisabled={kibanaSecuritySolutionsPrivileges.crud === false}
isAlert={isAlert}
onClick={onPinClicked}
pinned={eventIsPinned}

View file

@ -28,6 +28,17 @@ jest.mock('../../../../../common/hooks/use_selector', () => ({
useShallowEqualSelector: jest.fn(),
useDeepEqualSelector: jest.fn(),
}));
jest.mock('../../../../../common/components/user_privileges', () => {
return {
useUserPrivileges: () => ({
listPrivileges: { loading: false, error: undefined, result: undefined },
detectionEnginePrivileges: { loading: false, error: undefined, result: undefined },
endpointPrivileges: {},
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
}),
};
});
jest.mock('../../../../../common/lib/kibana', () => ({
useKibana: () => ({
services: {

View file

@ -36,6 +36,17 @@ import { createStore, State } from '../../../../common/store';
jest.mock('../../../../common/lib/kibana/hooks');
jest.mock('../../../../common/hooks/use_app_toasts');
jest.mock('../../../../common/components/user_privileges', () => {
return {
useUserPrivileges: () => ({
listPrivileges: { loading: false, error: undefined, result: undefined },
detectionEnginePrivileges: { loading: false, error: undefined, result: undefined },
endpointPrivileges: {},
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
}),
};
});
jest.mock('../../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../../common/lib/kibana');
const mockCasesContract = jest.requireActual('@kbn/cases-plugin/public/mocks');
@ -225,7 +236,7 @@ describe('Body', () => {
mockDispatch.mockClear();
});
test('Add a Note to an event', () => {
test('Add a note to an event', () => {
const wrapper = mount(
<TestProviders>
<StatefulBody {...props} />
@ -257,7 +268,7 @@ describe('Body', () => {
);
});
test('Add two Note to an event', () => {
test('Add two notes to an event', () => {
const { storage } = createSecuritySolutionStorageMock();
const state: State = {
...mockGlobalState,

View file

@ -39,6 +39,7 @@ import { getTimelineNoteSelector } from './selectors';
import { DetailsPanel } from '../../side_panel';
import { getScrollToTopSelector } from '../tabs_content/selectors';
import { useScrollToTop } from '../../../../common/components/scroll_to_top';
import { useUserPrivileges } from '../../../../common/components/user_privileges';
const FullWidthFlexGroup = styled(EuiFlexGroup)`
width: 100%;
@ -131,6 +132,7 @@ interface NotesTabContentProps {
const NotesTabContentComponent: React.FC<NotesTabContentProps> = ({ timelineId }) => {
const dispatch = useDispatch();
const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges();
const getScrollToTop = useMemo(() => getScrollToTopSelector(), []);
const scrollToTop = useShallowEqualSelector((state) => getScrollToTop(state, timelineId));
@ -239,7 +241,7 @@ const NotesTabContentComponent: React.FC<NotesTabContentProps> = ({ timelineId }
showTimelineDescription
/>
<EuiSpacer size="s" />
{!isImmutable && (
{!isImmutable && kibanaSecuritySolutionsPrivileges.crud === true && (
<AddNote
associateNote={associateNote}
newNote={newNote}

View file

@ -21,6 +21,7 @@ interface Props {
ariaLabel?: string;
allowUnpinning: boolean;
isAlert: boolean;
isDisabled?: boolean;
timelineType?: TimelineTypeLiteral;
onClick?: () => void;
pinned: boolean;
@ -45,7 +46,7 @@ export const getDefaultAriaLabel = ({
};
export const Pin = React.memo<Props>(
({ ariaLabel, allowUnpinning, isAlert, onClick = noop, pinned, timelineType }) => {
({ ariaLabel, allowUnpinning, isAlert, isDisabled, onClick = noop, pinned, timelineType }) => {
const isTemplate = timelineType === TimelineType.template;
const defaultAriaLabel = getDefaultAriaLabel({
isAlert,
@ -60,7 +61,7 @@ export const Pin = React.memo<Props>(
data-test-subj="pin"
iconType={getPinIcon(pinned)}
onClick={onClick}
isDisabled={isTemplate || !allowUnpinning}
isDisabled={isDisabled || isTemplate || !allowUnpinning}
size="s"
/>
);

View file

@ -91,6 +91,7 @@ NewTimeline.displayName = 'NewTimeline';
interface NotesButtonProps {
ariaLabel?: string;
isDisabled?: boolean;
showNotes: boolean;
toggleShowNotes: () => void;
toolTip?: string;
@ -99,6 +100,7 @@ interface NotesButtonProps {
interface SmallNotesButtonProps {
ariaLabel?: string;
isDisabled?: boolean;
toggleShowNotes: () => void;
timelineType: TimelineTypeLiteral;
}
@ -106,7 +108,7 @@ interface SmallNotesButtonProps {
export const NOTES_BUTTON_CLASS_NAME = 'notes-button';
const SmallNotesButton = React.memo<SmallNotesButtonProps>(
({ ariaLabel = i18n.NOTES, toggleShowNotes, timelineType }) => {
({ ariaLabel = i18n.NOTES, isDisabled, toggleShowNotes, timelineType }) => {
const isTemplate = timelineType === TimelineType.template;
return (
@ -114,6 +116,7 @@ const SmallNotesButton = React.memo<SmallNotesButtonProps>(
aria-label={ariaLabel}
className={NOTES_BUTTON_CLASS_NAME}
data-test-subj="timeline-notes-button-small"
disabled={isDisabled}
iconType="editorComment"
onClick={toggleShowNotes}
size="s"
@ -125,10 +128,11 @@ const SmallNotesButton = React.memo<SmallNotesButtonProps>(
SmallNotesButton.displayName = 'SmallNotesButton';
export const NotesButton = React.memo<NotesButtonProps>(
({ ariaLabel, showNotes, timelineType, toggleShowNotes, toolTip }) =>
({ ariaLabel, isDisabled, showNotes, timelineType, toggleShowNotes, toolTip }) =>
showNotes ? (
<SmallNotesButton
ariaLabel={ariaLabel}
isDisabled={isDisabled}
toggleShowNotes={toggleShowNotes}
timelineType={timelineType}
/>
@ -136,6 +140,7 @@ export const NotesButton = React.memo<NotesButtonProps>(
<EuiToolTip content={toolTip || ''} data-test-subj="timeline-notes-tool-tip">
<SmallNotesButton
ariaLabel={ariaLabel}
isDisabled={isDisabled}
toggleShowNotes={toggleShowNotes}
timelineType={timelineType}
/>