Skip to content

Commit

Permalink
[Security Solution][Detections] Disable alert assignees updates for V…
Browse files Browse the repository at this point in the history
…IEWER role (#8019)
  • Loading branch information
e40pud committed Nov 10, 2023
1 parent fa16176 commit 80b99ee
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import { useGetCurrentUser } from '../../user_profiles/use_get_current_user';
import { useBulkGetUserProfiles } from '../../user_profiles/use_bulk_get_user_profiles';
import { useSuggestUsers } from '../../user_profiles/use_suggest_users';
import { ASSIGNEES_APPLY_BUTTON_TEST_ID } from '../../assignees/test_ids';
import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges';

jest.mock('./use_set_alert_assignees');
jest.mock('../../user_profiles/use_get_current_user');
jest.mock('../../user_profiles/use_bulk_get_user_profiles');
jest.mock('../../user_profiles/use_suggest_users');
jest.mock('../../../../detections/containers/detection_engine/alerts/use_alerts_privileges');

const mockUserProfiles = [
{ uid: 'user-id-1', enabled: true, user: { username: 'fakeUser1' }, data: {} },
Expand Down Expand Up @@ -66,6 +68,7 @@ describe('useBulkAlertAssigneesItems', () => {
isLoading: false,
data: mockUserProfiles,
});
(useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: true });
});

afterEach(() => {
Expand Down Expand Up @@ -117,4 +120,14 @@ describe('useBulkAlertAssigneesItems', () => {
});
expect(mockSetAlertAssignees).toHaveBeenCalled();
});

it('should return 0 items for the VIEWER role', () => {
(useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: false });

const { result } = renderHook(() => useBulkAlertAssigneesItems(defaultProps), {
wrapper: TestProviders,
});
expect(result.current.alertAssigneesItems.length).toEqual(0);
expect(result.current.alertAssigneesPanels.length).toEqual(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { EuiFlexGroup, EuiIconTip, EuiFlexItem } from '@elastic/eui';
import type { RenderContentPanelProps } from '@kbn/triggers-actions-ui-plugin/public/types';
import React, { useCallback, useMemo } from 'react';
import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges';
import { ASSIGNEES_PANEL_WIDTH } from '../../assignees/constants';
import { BulkAlertAssigneesPanel } from './alert_bulk_assignees';
import * as i18n from './translations';
Expand All @@ -26,6 +27,7 @@ export interface UseBulkAlertAssigneesPanel {
}

export const useBulkAlertAssigneesItems = ({ refetch }: UseBulkAlertAssigneesItemsProps) => {
const { hasIndexWrite } = useAlertsPrivileges();
const setAlertAssignees = useSetAlertAssignees();
const handleOnAlertAssigneesSubmit = useCallback(
async (assignees, ids, onSuccess, setIsLoading) => {
Expand All @@ -36,16 +38,22 @@ export const useBulkAlertAssigneesItems = ({ refetch }: UseBulkAlertAssigneesIte
[setAlertAssignees]
);

const alertAssigneesItems = [
{
key: 'manage-alert-assignees',
'data-test-subj': 'alert-assignees-context-menu-item',
name: i18n.ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE,
panel: 2,
label: i18n.ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE,
disableOnQuery: true,
},
];
const alertAssigneesItems = useMemo(
() =>
hasIndexWrite
? [
{
key: 'manage-alert-assignees',
'data-test-subj': 'alert-assignees-context-menu-item',
name: i18n.ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE,
panel: 2,
label: i18n.ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE,
disableOnQuery: true,
},
]
: [],
[hasIndexWrite]
);

const TitleContent = useMemo(
() => (
Expand Down Expand Up @@ -84,16 +92,19 @@ export const useBulkAlertAssigneesItems = ({ refetch }: UseBulkAlertAssigneesIte
);

const alertAssigneesPanels: UseBulkAlertAssigneesPanel[] = useMemo(
() => [
{
id: 2,
title: TitleContent,
'data-test-subj': 'alert-assignees-context-menu-panel',
renderContent,
width: ASSIGNEES_PANEL_WIDTH,
},
],
[TitleContent, renderContent]
() =>
hasIndexWrite
? [
{
id: 2,
title: TitleContent,
'data-test-subj': 'alert-assignees-context-menu-panel',
renderContent,
width: ASSIGNEES_PANEL_WIDTH,
},
]
: [],
[TitleContent, hasIndexWrite, renderContent]
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import { useBulkGetUserProfiles } from '../../../common/components/user_profiles
import { useSuggestUsers } from '../../../common/components/user_profiles/use_suggest_users';
import type { BulkActionsConfig } from '@kbn/triggers-actions-ui-plugin/public/types';
import type { TimelineItem } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alerts_table/bulk_actions/components/toolbar';
import { useAlertsPrivileges } from '../../containers/detection_engine/alerts/use_alerts_privileges';

jest.mock('../../../common/components/toolbar/bulk_actions/use_set_alert_assignees');
jest.mock('../../../common/components/user_profiles/use_get_current_user');
jest.mock('../../../common/components/user_profiles/use_bulk_get_user_profiles');
jest.mock('../../../common/components/user_profiles/use_suggest_users');
jest.mock('../../containers/detection_engine/alerts/use_alerts_privileges');

const mockUserProfiles = [
{ uid: 'user-id-1', enabled: true, user: { username: 'fakeUser1' }, data: {} },
Expand All @@ -46,6 +48,7 @@ describe('useAssigneesActionItems', () => {
isLoading: false,
data: mockUserProfiles,
});
(useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: true });
});

afterEach(() => {
Expand Down Expand Up @@ -110,4 +113,15 @@ describe('useAssigneesActionItems', () => {
setAlertLoadingMock
);
});

it('should return 0 items for the VIEWER role', () => {
(useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: false });

const { result } = renderHook(() => useAssigneesActionItems(defaultProps), {
wrapper: TestProviders,
});

expect(result.current.alertAssigneesItems.length).toEqual(0);
expect(result.current.alertAssigneesPanels.length).toEqual(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,70 @@
*/

import { union } from 'lodash';
import { useCallback, useMemo } from 'react';

import type { BulkActionsConfig } from '@kbn/triggers-actions-ui-plugin/public/types';
import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils';

import { useSetAlertAssignees } from '../../../common/components/toolbar/bulk_actions/use_set_alert_assignees';
import { useBulkAlertAssigneesItems } from '../../../common/components/toolbar/bulk_actions/use_bulk_alert_assignees_items';
import * as i18n from '../translations';
import { useAlertsPrivileges } from '../../containers/detection_engine/alerts/use_alerts_privileges';

export interface UseAssigneesActionItemsProps {
refetch?: () => void;
}

export const useAssigneesActionItems = ({ refetch }: UseAssigneesActionItemsProps) => {
const { hasIndexWrite } = useAlertsPrivileges();
const setAlertAssignees = useSetAlertAssignees();

const { alertAssigneesItems: basicAssigneesItems, alertAssigneesPanels } =
useBulkAlertAssigneesItems({
refetch,
});

const onActionClick: BulkActionsConfig['onClick'] = async (
items,
isSelectAllChecked,
setAlertLoading,
clearSelection,
refresh
) => {
const ids: string[] | undefined = items.map((item) => item._id);
const assignedUserIds = union(
...items.map(
(item) => item.data.find((data) => data.field === ALERT_WORKFLOW_ASSIGNEE_IDS)?.value ?? []
)
);
if (!assignedUserIds.length) {
return;
}
const assignees = {
assignees_to_add: [],
assignees_to_remove: assignedUserIds,
};
if (setAlertAssignees) {
await setAlertAssignees(assignees, ids, refresh, setAlertLoading);
}
};
const onActionClick = useCallback<Required<BulkActionsConfig>['onClick']>(
async (items, isSelectAllChecked, setAlertLoading, clearSelection, refresh) => {
const ids: string[] | undefined = items.map((item) => item._id);
const assignedUserIds = union(
...items.map(
(item) =>
item.data.find((data) => data.field === ALERT_WORKFLOW_ASSIGNEE_IDS)?.value ?? []
)
);
if (!assignedUserIds.length) {
return;
}
const assignees = {
assignees_to_add: [],
assignees_to_remove: assignedUserIds,
};
if (setAlertAssignees) {
await setAlertAssignees(assignees, ids, refresh, setAlertLoading);
}
},
[setAlertAssignees]
);

const alertAssigneesItems = [
...basicAssigneesItems,
...[
{
label: i18n.BULK_REMOVE_ASSIGNEES_CONTEXT_MENU_TITLE,
key: 'bulk-alert-assignees-remove-all-action',
'data-test-subj': 'bulk-alert-assignees-remove-all-action',
disableOnQuery: false,
onClick: onActionClick,
},
],
];
const alertAssigneesItems = useMemo(
() =>
hasIndexWrite
? [
...basicAssigneesItems,
...[
{
label: i18n.BULK_REMOVE_ASSIGNEES_CONTEXT_MENU_TITLE,
key: 'bulk-alert-assignees-remove-all-action',
'data-test-subj': 'bulk-alert-assignees-remove-all-action',
disableOnQuery: false,
onClick: onActionClick,
},
],
]
: [],
[basicAssigneesItems, hasIndexWrite, onActionClick]
);

return {
alertAssigneesItems,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import {
USERS_AVATARS_PANEL_TEST_ID,
USER_AVATAR_ITEM_TEST_ID,
} from '../../../../common/components/user_profiles/test_ids';
import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges';

jest.mock('../../../../common/components/user_profiles/use_get_current_user');
jest.mock('../../../../common/components/user_profiles/use_bulk_get_user_profiles');
jest.mock('../../../../common/components/user_profiles/use_suggest_users');
jest.mock('../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees');
jest.mock('../../../../detections/containers/detection_engine/alerts/use_alerts_privileges');

const mockUserProfiles = [
{ uid: 'user-id-1', enabled: true, user: { username: 'user1', full_name: 'User 1' }, data: {} },
Expand Down Expand Up @@ -69,6 +71,7 @@ describe('<Assignees />', () => {
isLoading: false,
data: mockUserProfiles,
});
(useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: true });

setAlertAssigneesMock = jest.fn().mockReturnValue(Promise.resolve());
(useSetAlertAssignees as jest.Mock).mockReturnValue(setAlertAssigneesMock);
Expand All @@ -80,6 +83,7 @@ describe('<Assignees />', () => {
expect(getByTestId(ASSIGNEES_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(USERS_AVATARS_PANEL_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).not.toBeDisabled();
});

it('should render assignees avatars', () => {
Expand Down Expand Up @@ -133,4 +137,14 @@ describe('<Assignees />', () => {
expect.anything()
);
});

it('should render add assignees button as disabled if user has readonly priviliges', () => {
(useAlertsPrivileges as jest.Mock).mockReturnValue({ hasIndexWrite: false });

const assignees = ['user-id-1', 'user-id-2'];
const { getByTestId } = renderAssignees('test-event', assignees);

expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeDisabled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiToolTip } from '
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';

import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges';
import { useBulkGetUserProfiles } from '../../../../common/components/user_profiles/use_bulk_get_user_profiles';
import { removeNoAssigneesSelection } from '../../../../common/components/assignees/utils';
import type { AssigneesIdsSelection } from '../../../../common/components/assignees/types';
Expand All @@ -21,24 +22,27 @@ import { UsersAvatarsPanel } from '../../../../common/components/user_profiles/u
import { useSetAlertAssignees } from '../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees';
import { ASSIGNEES_ADD_BUTTON_TEST_ID, ASSIGNEES_TITLE_TEST_ID } from './test_ids';

const UpdateAssigneesButton: FC<{ togglePopover: () => void }> = memo(({ togglePopover }) => (
<EuiToolTip
position="left"
content={i18n.translate(
'xpack.securitySolution.flyout.right.visualizations.assignees.popoverTooltip',
{
defaultMessage: 'Assignees',
}
)}
>
<EuiButtonIcon
aria-label="Update assignees"
data-test-subj={ASSIGNEES_ADD_BUTTON_TEST_ID}
iconType={'plusInCircle'}
onClick={togglePopover}
/>
</EuiToolTip>
));
const UpdateAssigneesButton: FC<{ togglePopover: () => void; isDisabled: boolean }> = memo(
({ togglePopover, isDisabled }) => (
<EuiToolTip
position="bottom"
content={i18n.translate(
'xpack.securitySolution.flyout.right.visualizations.assignees.popoverTooltip',
{
defaultMessage: 'Assignees',
}
)}
>
<EuiButtonIcon
aria-label="Update assignees"
data-test-subj={ASSIGNEES_ADD_BUTTON_TEST_ID}
iconType={'plusInCircle'}
onClick={togglePopover}
isDisabled={isDisabled}
/>
</EuiToolTip>
)
);
UpdateAssigneesButton.displayName = 'UpdateAssigneesButton';

export interface AssigneesProps {
Expand All @@ -63,6 +67,7 @@ export interface AssigneesProps {
*/
export const Assignees: FC<AssigneesProps> = memo(
({ eventId, assignedUserIds, onAssigneesUpdated }) => {
const { hasIndexWrite } = useAlertsPrivileges();
const setAlertAssignees = useSetAlertAssignees();

const uids = useMemo(() => new Set(assignedUserIds), [assignedUserIds]);
Expand Down Expand Up @@ -117,7 +122,9 @@ export const Assignees: FC<AssigneesProps> = memo(
<EuiFlexItem grow={false}>
<AssigneesPopover
assignedUserIds={assignedUserIds}
button={<UpdateAssigneesButton togglePopover={togglePopover} />}
button={
<UpdateAssigneesButton togglePopover={togglePopover} isDisabled={!hasIndexWrite} />
}
isPopoverOpen={isPopoverOpen}
closePopover={togglePopover}
onAssigneesApply={onAssigneesApply}
Expand Down

0 comments on commit 80b99ee

Please sign in to comment.