diff --git a/x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts b/x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts index 1e027e50f5070..caf7e6d8da62b 100644 --- a/x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts +++ b/x-pack/plugins/lists/server/routes/read_privileges_route.mock.ts @@ -88,7 +88,7 @@ export const getReadPrivilegeMock = ( manage_index_templates: booleanValues, manage_ingest_pipelines: booleanValues, manage_ml: booleanValues, - manage_own_api_key: false, + manage_own_api_key: booleanValues, manage_pipeline: booleanValues, manage_rollup: booleanValues, manage_saml: booleanValues, @@ -105,7 +105,7 @@ export const getReadPrivilegeMock = ( read_ilm: booleanValues, transport_client: booleanValues, }, - has_all_requested: false, + has_all_requested: booleanValues, index: { [listItemsIndex]: { all: booleanValues, @@ -141,7 +141,7 @@ export const getReadPrivilegeMock = ( manage_index_templates: booleanValues, manage_ingest_pipelines: booleanValues, manage_ml: booleanValues, - manage_own_api_key: false, + manage_own_api_key: booleanValues, manage_pipeline: booleanValues, manage_rollup: booleanValues, manage_saml: booleanValues, @@ -158,7 +158,7 @@ export const getReadPrivilegeMock = ( read_ilm: booleanValues, transport_client: booleanValues, }, - has_all_requested: false, + has_all_requested: booleanValues, index: { [listIndex]: { all: booleanValues, diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts index d7a5ce6799230..e5c9832c52fd7 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts @@ -20,7 +20,7 @@ import { login, loginAndWaitForPage, waitForPageWithoutDateRange } from '../../t import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; -import { ATTACH_ALERT_TO_CASE_BUTTON } from '../../screens/alerts'; +import { ATTACH_ALERT_TO_CASE_BUTTON, TIMELINE_CONTEXT_MENU_BTN } from '../../screens/alerts'; const loadDetectionsPage = (role: ROLES) => { waitForPageWithoutDateRange(ALERTS_URL, role); @@ -48,8 +48,8 @@ describe('Alerts timeline', () => { }); it('should not allow user with read only privileges to attach alerts to cases', () => { - expandFirstAlertActions(); - cy.get(ATTACH_ALERT_TO_CASE_BUTTON).should('not.exist'); + // Disabled actions for read only users are hidden, so actions button should not show + cy.get(TIMELINE_CONTEXT_MENU_BTN).should('not.exist'); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx index 228de32fee7d2..6070924523f63 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx @@ -23,7 +23,7 @@ describe('ExceptionEntries', () => { const wrapper = mount( { const wrapper = mount( { const wrapper = mount( { const wrapper = mount( { expect(mockOnDelete).toHaveBeenCalledTimes(1); }); - test('it renders edit button disabled if "disableDelete" is "true"', () => { + test('it does not render edit button if "disableActions" is "true"', () => { const wrapper = mount( ); - const editBtn = wrapper.find('[data-test-subj="exceptionsViewerEditBtn"] button').at(0); + const editBtns = wrapper.find('[data-test-subj="exceptionsViewerEditBtn"] button'); - expect(editBtn.prop('disabled')).toBeTruthy(); + expect(editBtns).toHaveLength(0); }); - test('it renders delete button in loading state if "disableDelete" is "true"', () => { + test('it does not render delete button if "disableActions" is "true"', () => { const wrapper = mount( ); - const deleteBtn = wrapper.find('[data-test-subj="exceptionsViewerDeleteBtn"] button').at(0); + const deleteBtns = wrapper.find('[data-test-subj="exceptionsViewerDeleteBtn"] button').at(0); - expect(deleteBtn.prop('disabled')).toBeTruthy(); - expect(deleteBtn.find('.euiLoadingSpinner')).toBeTruthy(); + expect(deleteBtns).toHaveLength(0); }); test('it renders nested entry', () => { @@ -126,7 +125,7 @@ describe('ExceptionEntries', () => { const wrapper = mount( { const wrapper = mount( void; onEdit: () => void; } const ExceptionEntriesComponent = ({ entries, - disableDelete, + disableActions, onDelete, onEdit, }: ExceptionEntriesComponentProps): JSX.Element => { @@ -181,32 +181,32 @@ const ExceptionEntriesComponent = ({ - - - - - {i18n.EDIT} - - - - - {i18n.REMOVE} - - - - + {!disableActions && ( + + + + + {i18n.EDIT} + + + + + {i18n.REMOVE} + + + + + )} ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx index f8697b2f3db79..898a9e3ab0388 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx @@ -35,6 +35,7 @@ storiesOf('Components/ExceptionItem', module) return ( ); + }) + .add('with actions disabled', () => { + const payload = getExceptionListItemSchemaMock(); + payload.description = ''; + payload.comments = getCommentsArrayMock(); + payload.entries = [ + { + field: 'actingProcess.file.signer', + type: 'match', + operator: 'included', + value: 'Elastic, N.V.', + }, + ]; + + return ( + + ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx index 7c55c0de68c64..983d837267795 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx @@ -32,6 +32,7 @@ describe('ExceptionItem', () => { const wrapper = mount( { const wrapper = mount( { ); }); + it('it does not render edit or delete action buttons when "disableActions" is "true"', () => { + const mockOnEditException = jest.fn(); + const exceptionItem = getExceptionListItemSchemaMock(); + + const wrapper = mount( + + + + ); + + const editBtn = wrapper.find('[data-test-subj="exceptionsViewerEditBtn"] button'); + const deleteBtn = wrapper.find('[data-test-subj="exceptionsViewerDeleteBtn"] button'); + + expect(editBtn).toHaveLength(0); + expect(deleteBtn).toHaveLength(0); + }); + it('it invokes "onEditException" when edit button clicked', () => { const mockOnEditException = jest.fn(); const exceptionItem = getExceptionListItemSchemaMock(); @@ -77,6 +103,7 @@ describe('ExceptionItem', () => { const wrapper = mount( { const wrapper = mount( { ); - const editBtn = wrapper.find('[data-test-subj="exceptionsViewerDeleteBtn"] button').at(0); - editBtn.simulate('click'); + const deleteBtn = wrapper.find('[data-test-subj="exceptionsViewerDeleteBtn"] button').at(0); + deleteBtn.simulate('click'); expect(mockOnDeleteException).toHaveBeenCalledWith({ id: '1', @@ -124,6 +152,7 @@ describe('ExceptionItem', () => { const wrapper = mount( { const wrapper = mount( void; showName?: boolean; showModified?: boolean; + disableActions: boolean; 'data-test-subj'?: string; } const ExceptionItemComponent = ({ + disableActions, loadingItemIds, exceptionItem, commentsAccordionId, @@ -78,7 +80,7 @@ const ExceptionItemComponent = ({ return getFormattedComments(exceptionItem.comments); }, [exceptionItem.comments]); - const disableDelete = useMemo((): boolean => { + const disableItemActions = useMemo((): boolean => { const foundItems = loadingItemIds.filter(({ id }) => id === exceptionItem.id); return foundItems.length > 0; }, [loadingItemIds, exceptionItem.id]); @@ -96,7 +98,7 @@ const ExceptionItemComponent = ({ showName={showName} /> { ).toBeTruthy(); }); + // This occurs if user does not have sufficient privileges + it('it does not display add exception button if no list types available', () => { + const wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="exceptionsHeaderAddExceptionBtn"]').exists()).toBeFalsy(); + }); + it('it displays toggles and add exception popover when more than one list type available', () => { const wrapper = mount( - {supportedListTypes.length < 2 && ( + {supportedListTypes.length === 1 && ( { showEmpty showNoResults={false} isInitLoading={false} + disableActions={false} exceptions={[]} loadingItemIds={[]} commentsAccordionId="comments-accordion-id" @@ -54,6 +55,7 @@ describe('ExceptionsViewerItems', () => { showEmpty={false} showNoResults isInitLoading={false} + disableActions={false} exceptions={[]} loadingItemIds={[]} commentsAccordionId="comments-accordion-id" @@ -78,6 +80,7 @@ describe('ExceptionsViewerItems', () => { showEmpty={false} showNoResults={false} isInitLoading={false} + disableActions={false} exceptions={[getExceptionListItemSchemaMock()]} loadingItemIds={[]} commentsAccordionId="comments-accordion-id" @@ -98,6 +101,7 @@ describe('ExceptionsViewerItems', () => { showEmpty={false} showNoResults={false} isInitLoading={true} + disableActions={false} exceptions={[]} loadingItemIds={[]} commentsAccordionId="comments-accordion-id" @@ -122,6 +126,7 @@ describe('ExceptionsViewerItems', () => { showEmpty={false} showNoResults={false} isInitLoading={false} + disableActions={false} exceptions={[exception1, exception2]} loadingItemIds={[]} commentsAccordionId="comments-accordion-id" @@ -147,6 +152,7 @@ describe('ExceptionsViewerItems', () => { showEmpty={false} showNoResults={false} isInitLoading={false} + disableActions={false} exceptions={[exception1, exception2]} loadingItemIds={[]} commentsAccordionId="comments-accordion-id" @@ -172,6 +178,7 @@ describe('ExceptionsViewerItems', () => { showEmpty={false} showNoResults={false} isInitLoading={false} + disableActions={false} exceptions={[getExceptionListItemSchemaMock()]} loadingItemIds={[]} commentsAccordionId="comments-accordion-id" diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_items.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_items.tsx index 64fb032b0425c..5331b2376fd9f 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_items.tsx @@ -36,6 +36,7 @@ interface ExceptionsViewerItemsProps { showEmpty: boolean; showNoResults: boolean; isInitLoading: boolean; + disableActions: boolean; exceptions: ExceptionListItemSchema[]; loadingItemIds: ExceptionListItemIdentifiers[]; commentsAccordionId: string; @@ -52,6 +53,7 @@ const ExceptionsViewerItemsComponent: React.FC = ({ commentsAccordionId, onDeleteException, onEditExceptionItem, + disableActions, }): JSX.Element => ( {showEmpty || showNoResults || isInitLoading ? ( @@ -93,6 +95,7 @@ const ExceptionsViewerItemsComponent: React.FC = ({ )} ([]); + + const [{ canUserCRUD, hasIndexWrite }] = useUserData(); + + useEffect((): void => { + if (!canUserCRUD || !hasIndexWrite) { + setSupportedListTypes([]); + } else { + setSupportedListTypes(availableListTypes); + } + }, [availableListTypes, canUserCRUD, hasIndexWrite]); const setExceptions = useCallback( ({ @@ -356,7 +368,7 @@ const ExceptionsViewerComponent = ({ [ - - {ACTION_ADD_ENDPOINT_EXCEPTION} - , + () => + disabledAddException + ? [] + : [ + + {ACTION_ADD_ENDPOINT_EXCEPTION} + , - - {ACTION_ADD_EXCEPTION} - , - ], + + {ACTION_ADD_EXCEPTION} + , + ], [ disabledAddEndpointException, disabledAddException, diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx index bf990cb292cd7..e12f01458da4c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx @@ -18,6 +18,9 @@ import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; import { useKibana } from '../../../common/lib/kibana'; +jest.mock('../user_info', () => ({ + useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), +})); jest.mock('../../../common/hooks/endpoint/use_isolate_privileges', () => ({ useIsolationPrivileges: jest.fn().mockReturnValue({ isAllowed: true }), })); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx index 06835e78b03e1..b6e6aa40876cc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx @@ -12,6 +12,10 @@ import { TestProviders, mockTimelineModel, mockTimelineData } from '../../../../ import { Actions } from '.'; import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; + +jest.mock('../../../../../detections/components/user_info', () => ({ + useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), +})); jest.mock('../../../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(false), })); diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts index 939291d1e4a5d..cecb4510be9c3 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/delete_lists.ts @@ -166,8 +166,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); }); - // Tests in development - it.skip('should delete a single list referenced within an exception list item if ignoreReferences=true', async () => { + it('should delete a single list referenced within an exception list item if ignoreReferences=true', async () => { // create a list const { body: valueListBody } = await supertest .post(LIST_URL) @@ -207,8 +206,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(409); }); - // Tests in development - it.skip('should delete a single list referenced within an exception list item and referenced exception list items if deleteReferences=true', async () => { + it('should delete a single list referenced within an exception list item and referenced exception list items if deleteReferences=true', async () => { // create a list const { body: valueListBody } = await supertest .post(LIST_URL) @@ -241,6 +239,13 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); + // sanity check + await supertest + .get(`${LIST_ITEM_URL}/_find?list_id=${LIST_ID}`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + // delete that list by its auto-generated id and delete referenced list items const deleteListBody = await supertest .delete(`${LIST_URL}?id=${valueListBody.id}&ignoreReferences=true`) @@ -253,7 +258,7 @@ export default ({ getService }: FtrProviderContext) => { .get(`${LIST_ITEM_URL}/_find?list_id=${LIST_ID}`) .set('kbn-xsrf', 'true') .send() - .expect(200); + .expect(404); }); }); }); diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts index b90a3f86a290d..e2633eea08e12 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/read_list_privileges.ts @@ -18,8 +18,7 @@ export default ({ getService }: FtrProviderContext) => { const spacesService = getService('spaces'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/88302 - describe.skip('read_list_privileges', () => { + describe('read_list_privileges', () => { const space1Id = 'space_1'; const user1 = {