Skip to content

Commit

Permalink
[Security Solution][Investigations] - Disable adding notes when read …
Browse files Browse the repository at this point in the history
…only (#133905) (#134964)

Co-authored-by: Kibana Machine <[email protected]>
(cherry picked from commit b41bc66)

Co-authored-by: Michael Olorunnisola <[email protected]>
  • Loading branch information
kibanamachine and michaelolo24 authored Jun 23, 2022
1 parent 9ee96d7 commit 6824d89
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -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'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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';

Expand Down
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +32,7 @@ const PinEventActionComponent: React.FC<PinEventActionProps> = ({
eventIsPinned,
timelineType,
}) => {
const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges();
const tooltipContent = useMemo(
() =>
getPinTooltip({
Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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} />
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -239,7 +241,7 @@ const NotesTabContentComponent: React.FC<NotesTabContentProps> = ({ timelineId }
showTimelineDescription
/>
<EuiSpacer size="s" />
{!isImmutable && (
{!isImmutable && kibanaSecuritySolutionsPrivileges.crud === true && (
<AddNote
associateNote={associateNote}
newNote={newNote}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface Props {
ariaLabel?: string;
allowUnpinning: boolean;
isAlert: boolean;
isDisabled?: boolean;
timelineType?: TimelineTypeLiteral;
onClick?: () => void;
pinned: boolean;
Expand All @@ -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,
Expand All @@ -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"
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ NewTimeline.displayName = 'NewTimeline';

interface NotesButtonProps {
ariaLabel?: string;
isDisabled?: boolean;
showNotes: boolean;
toggleShowNotes: () => void;
toolTip?: string;
Expand All @@ -99,21 +100,23 @@ interface NotesButtonProps {

interface SmallNotesButtonProps {
ariaLabel?: string;
isDisabled?: boolean;
toggleShowNotes: () => void;
timelineType: TimelineTypeLiteral;
}

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 (
<EuiButtonIcon
aria-label={ariaLabel}
className={NOTES_BUTTON_CLASS_NAME}
data-test-subj="timeline-notes-button-small"
disabled={isDisabled}
iconType="editorComment"
onClick={toggleShowNotes}
size="s"
Expand All @@ -125,17 +128,19 @@ 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}
/>
) : (
<EuiToolTip content={toolTip || ''} data-test-subj="timeline-notes-tool-tip">
<SmallNotesButton
ariaLabel={ariaLabel}
isDisabled={isDisabled}
toggleShowNotes={toggleShowNotes}
timelineType={timelineType}
/>
Expand Down

0 comments on commit 6824d89

Please sign in to comment.