From 0a3ea5d47d20817ea3b119a2c0764a9997218416 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 20 Nov 2023 19:18:33 +0100 Subject: [PATCH] [Security Solution][Detections] Add test coverage for Alert User Assignment (#171307) --- .../common/api/detection_engine/index.ts | 1 + .../right/components/assignees.tsx | 14 +- .../right/components/test_ids.ts | 1 + .../__snapshots__/index.test.tsx.snap | 2 + .../alerts/assignments/assignments.ts | 518 ++++++++++++++++++ .../alerts/assignments/assignments_ess.ts | 92 ++++ .../assignments/assignments_serverless.ts | 112 ++++ .../alerts/assignments/index.ts | 15 + .../default_license/alerts/index.ts | 1 + .../utils/alerts/alert_assignees.ts | 25 + .../detections_response/utils/alerts/index.ts | 1 + .../detection_alerts/alert_assignees.cy.ts | 70 --- .../assignments/assignments.cy.ts | 368 +++++++++++++ .../assignments/assignments_ess.cy.ts | 49 ++ .../assignments/assignments_ess_basic.cy.ts | 53 ++ .../assignments_serverless_complete.cy.ts | 89 +++ .../assignments_serverless_essentials.cy.ts | 89 +++ .../cypress/screens/alerts.ts | 33 +- .../alert_details_right_panel.ts | 3 + .../cypress/tasks/alert_assignees.ts | 55 -- .../cypress/tasks/alert_assignments.ts | 223 ++++++++ .../cypress/tasks/alerts.ts | 30 - .../cypress/tasks/navigation.ts | 13 +- 23 files changed, 1694 insertions(+), 163 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_ess.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_serverless.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/alert_assignees.ts delete mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_assignees.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess_basic.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_complete.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_essentials.cy.ts delete mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/alert_assignees.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/alert_assignments.ts diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/index.ts index eadf1e48e9e31..56c6d4225f745 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +export * from './alert_assignees'; export * from './alert_tags'; export * from './fleet_integrations'; export * from './index_management'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx index b7b99c07920c7..550cbb16e9d7a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx @@ -21,7 +21,11 @@ import type { AssigneesIdsSelection } from '../../../../common/components/assign import { AssigneesPopover } from '../../../../common/components/assignees/assignees_popover'; import { UsersAvatarsPanel } from '../../../../common/components/user_profiles/users_avatars_panel'; 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'; +import { + ASSIGNEES_ADD_BUTTON_TEST_ID, + ASSIGNEES_HEADER_TEST_ID, + ASSIGNEES_TITLE_TEST_ID, +} from './test_ids'; const UpdateAssigneesButton: FC<{ togglePopover: () => void; isDisabled: boolean }> = memo( ({ togglePopover, isDisabled }) => ( @@ -106,7 +110,13 @@ export const Assignees: FC = memo( ); return ( - +

diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts index 7b3800454e5fe..5b176a34014ab 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts @@ -19,6 +19,7 @@ export const RISK_SCORE_VALUE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}RiskScoreValue` export const SHARE_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}ShareButton` as const; export const CHAT_BUTTON_TEST_ID = 'newChatById' as const; +export const ASSIGNEES_HEADER_TEST_ID = `${FLYOUT_HEADER_TEST_ID}AssigneesHeader` as const; export const ASSIGNEES_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}AssigneesTitle` as const; export const ASSIGNEES_ADD_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}AssigneesAddButton` as const; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 1a99f549759a6..a78ee98b8e61e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -80,6 +80,7 @@ Array [ >
{ + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const es = getService('es'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@ess @serverless Alert User Assignment - ESS & Serverless', () => { + describe('validation checks', () => { + it('should give errors when no alert ids are provided', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send(setAlertAssignees({ assigneesToAdd: [], assigneesToRemove: [], ids: [] })) + .expect(400); + + expect(body).to.eql({ + error: 'Bad Request', + message: '[request body]: Invalid value "[]" supplied to "ids"', + statusCode: 400, + }); + }); + + it('should give errors when empty alert ids are provided', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send(setAlertAssignees({ assigneesToAdd: [], assigneesToRemove: [], ids: ['123', ''] })) + .expect(400); + + expect(body).to.eql({ + error: 'Bad Request', + message: '[request body]: Invalid value "" supplied to "ids"', + statusCode: 400, + }); + }); + + it('should give errors when duplicate assignees exist in both add and remove', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['test-1'], + assigneesToRemove: ['test-1'], + ids: ['123'], + }) + ) + .expect(400); + + expect(body).to.eql({ + message: ['Duplicate assignees ["test-1"] were found in the add and remove parameters.'], + status_code: 400, + }); + }); + }); + + describe('tests with auditbeat data', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + }); + + describe('updating assignees', () => { + it('should add new assignees to single alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + const alertId = alertIds[0]; + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1'], + assigneesToRemove: [], + ids: [alertId], + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds([alertId])) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql(['user-1']); + }); + }); + + it('should add new assignees to multiple alerts', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-2', 'user-3'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-2', + 'user-3', + ]); + }); + }); + + it('should update assignees for single alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + const alertId = alertIds[0]; + + // Assign users + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: [alertId], + }) + ) + .expect(200); + + // Update assignees + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-3'], + assigneesToRemove: ['user-2'], + ids: [alertId], + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds([alertId])) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-3', + ]); + }); + }); + + it('should update assignees for multiple alerts', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + // Assign users + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + // Update assignees + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-3'], + assigneesToRemove: ['user-2'], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-3', + ]); + }); + }); + + it('should add assignee once to the alert even if same assignee was passed multiple times', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-1', 'user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + + it('should remove assignee once to the alert even if same assignee was passed multiple times', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: [], + assigneesToRemove: ['user-2', 'user-2'], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql(['user-1']); + }); + }); + + it('should not update assignees if both `add` and `remove` are empty', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: [], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + + it('should not update assignees when adding user which is assigned to alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + + it('should not update assignees when removing user which is not assigned to alert', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1', 'user-2'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + + await supertest + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .send( + setAlertAssignees({ + assigneesToAdd: [], + assigneesToRemove: ['user-3'], + ids: alertIds, + }) + ) + .expect(200); + + const { body }: { body: estypes.SearchResponse } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAlertIds(alertIds)) + .expect(200); + + body.hits.hits.map((alert) => { + expect(alert._source?.['kibana.alert.workflow_assignee_ids']).to.eql([ + 'user-1', + 'user-2', + ]); + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_ess.ts new file mode 100644 index 0000000000000..527d02295f6a0 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_ess.ts @@ -0,0 +1,92 @@ +/* + * 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 { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { ROLES } from '@kbn/security-solution-plugin/common/test'; + +import { + createUserAndRole, + deleteUserAndRole, +} from '../../../../../../common/services/security_solution'; +import { + createAlertsIndex, + createRule, + deleteAllAlerts, + deleteAllRules, + getAlertsByIds, + getRuleForAlertTesting, + setAlertAssignees, + waitForAlertsToBePresent, + waitForRuleSuccess, +} from '../../../utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const es = getService('es'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@ess Alert User Assignment - ESS', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + }); + + describe('authorization / RBAC', () => { + it('should not allow viewer user to assign alerts', async () => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + const userAndRole = ROLES.reader; + await createUserAndRole(getService, userAndRole); + + // Try to set all of the alerts to the state of closed. + // This should not be possible with the given user. + await supertestWithoutAuth + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .auth(userAndRole, 'changeme') // each user has the same password + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(403); + + await deleteUserAndRole(getService, userAndRole); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_serverless.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_serverless.ts new file mode 100644 index 0000000000000..aafa621f71ecd --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/assignments_serverless.ts @@ -0,0 +1,112 @@ +/* + * 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 { DETECTION_ENGINE_ALERT_ASSIGNEES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { ROLES } from '@kbn/security-solution-plugin/common/test'; + +import { + createAlertsIndex, + createRule, + deleteAllAlerts, + deleteAllRules, + getAlertsByIds, + getRuleForAlertTesting, + setAlertAssignees, + waitForAlertsToBePresent, + waitForRuleSuccess, +} from '../../../utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const es = getService('es'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@serverless Alert User Assignment - Serverless', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + }); + + describe('authorization / RBAC', () => { + const successfulAssignWithRole = async (userAndRole: ROLES) => { + const rule = { + ...getRuleForAlertTesting(['auditbeat-*']), + query: 'process.executable: "/usr/bin/sudo"', + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccess({ supertest, log, id }); + await waitForAlertsToBePresent(supertest, log, 10, [id]); + const alerts = await getAlertsByIds(supertest, log, [id]); + const alertIds = alerts.hits.hits.map((alert) => alert._id); + + // Try to set all of the alerts to the state of closed. + // This should not be possible with the given user. + await supertestWithoutAuth + .post(DETECTION_ENGINE_ALERT_ASSIGNEES_URL) + .set('kbn-xsrf', 'true') + .auth(userAndRole, 'changeme') // each user has the same password + .send( + setAlertAssignees({ + assigneesToAdd: ['user-1'], + assigneesToRemove: [], + ids: alertIds, + }) + ) + .expect(200); + }; + + it('should allow `ROLES.t1_analyst` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.t1_analyst); + }); + + it('should allow `ROLES.t2_analyst` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.t2_analyst); + }); + + // TODO: Remove @brokenInServerless once https://github.com/elastic/kibana/pull/170778 has been merged + it('@brokenInServerless should allow `ROLES.t3_analyst` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.t3_analyst); + }); + + it('should allow `ROLES.detections_admin` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.detections_admin); + }); + + it('should allow `ROLES.platform_engineer` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.platform_engineer); + }); + + it('should allow `ROLES.rule_author` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.rule_author); + }); + + it('should allow `ROLES.soc_manager` to assign alerts', async () => { + await successfulAssignWithRole(ROLES.soc_manager); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/index.ts new file mode 100644 index 0000000000000..3d9e5c1dd69ee --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/assignments/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Actions API', function () { + loadTestFile(require.resolve('./assignments')); + loadTestFile(require.resolve('./assignments_ess')); + loadTestFile(require.resolve('./assignments_serverless')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts index 7482e1bac558f..85e2e602a8929 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./migrations')); loadTestFile(require.resolve('./open_close_alerts')); loadTestFile(require.resolve('./set_alert_tags')); + loadTestFile(require.resolve('./assignments')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/alert_assignees.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/alert_assignees.ts new file mode 100644 index 0000000000000..59c70d5d6bd9e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/alert_assignees.ts @@ -0,0 +1,25 @@ +/* + * 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 { AlertIds } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { SetAlertAssigneesRequestBody } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +export const setAlertAssignees = ({ + assigneesToAdd, + assigneesToRemove, + ids, +}: { + assigneesToAdd: string[]; + assigneesToRemove: string[]; + ids: AlertIds; +}): SetAlertAssigneesRequestBody => ({ + assignees: { + add: assigneesToAdd, + remove: assigneesToRemove, + }, + ids, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts index e78bfa1922d36..867f85653ef4f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts @@ -21,4 +21,5 @@ export * from './get_query_alert_ids'; export * from './set_alert_tags'; export * from './get_preview_alerts'; export * from './get_alert_status'; +export * from './alert_assignees'; export * from './migrations'; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_assignees.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_assignees.cy.ts deleted file mode 100644 index 0073b4a5fabcc..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_assignees.cy.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 { getNewRule } from '../../../objects/rule'; -import { - clickAlertAssignee, - findSelectedAlertAssignee, - findUnselectedAlertAssignee, - openAlertAssigningBulkActionMenu, - selectNumberOfAlerts, - updateAlertAssignees, -} from '../../../tasks/alerts'; -import { createRule } from '../../../tasks/api_calls/rules'; -import { deleteAlertsAndRules } from '../../../tasks/common'; -import { login } from '../../../tasks/login'; -import { visitWithTimeRange } from '../../../tasks/navigation'; -import { ALERTS_URL } from '../../../urls/navigation'; -import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; -import { ALERTS_TABLE_ROW_LOADER } from '../../../screens/alerts'; -import { - waitForAssigneesToPopulatePopover, - waitForAssigneeToAppearInTable, - waitForAssigneeToDisappearInTable, -} from '../../../tasks/alert_assignees'; - -describe('Alert assigning', { tags: ['@ess', '@serverless'] }, () => { - beforeEach(() => { - login(); - deleteAlertsAndRules(); - cy.task('esArchiverLoad', { archiveName: 'endpoint' }); - createRule(getNewRule({ rule_id: 'new custom rule' })); - visitWithTimeRange(ALERTS_URL); - waitForAlertsToPopulate(); - }); - - afterEach(() => { - cy.task('esArchiverUnload', 'endpoint'); - }); - - it('Add and remove an assignee using the alert bulk action menu', () => { - const userName = Cypress.env('ELASTICSEARCH_USERNAME'); - - // Add an assignee to one alert - selectNumberOfAlerts(1); - openAlertAssigningBulkActionMenu(); - waitForAssigneesToPopulatePopover(); - clickAlertAssignee(userName); - updateAlertAssignees(); - cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); - waitForAssigneeToAppearInTable(userName); - selectNumberOfAlerts(1); - openAlertAssigningBulkActionMenu(); - waitForAssigneesToPopulatePopover(); - findSelectedAlertAssignee(userName); - - // Remove assignee from that alert - clickAlertAssignee(userName); - updateAlertAssignees(); - cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); - waitForAssigneeToDisappearInTable(userName); - selectNumberOfAlerts(1); - openAlertAssigningBulkActionMenu(); - waitForAssigneesToPopulatePopover(); - findUnselectedAlertAssignee(userName); - }); -}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments.cy.ts new file mode 100644 index 0000000000000..b1bfc373d1385 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments.cy.ts @@ -0,0 +1,368 @@ +/* + * 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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { + closeAlertFlyout, + closeAlerts, + expandFirstAlert, + selectFirstPageAlerts, + selectNumberOfAlerts, + selectPageFilterValue, +} from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { login } from '../../../../tasks/login'; +import { loadPageAs } from '../../../../tasks/navigation'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertDetailsFlyoutShowsAssignees, + alertDetailsFlyoutShowsAssigneesBadge, + alertsTableShowsAssigneesBadgeForAlert, + alertsTableShowsAssigneesForAlert, + updateAssigneesForAlert, + checkEmptyAssigneesStateInAlertDetailsFlyout, + checkEmptyAssigneesStateInAlertsTable, + removeAllAssigneesForAlert, + bulkUpdateAssignees, + alertsTableShowsAssigneesForAllAlerts, + bulkRemoveAllAssignees, + filterByAssignees, + NO_ASSIGNEES, + clearAssigneesFilter, + updateAssigneesViaAddButtonInFlyout, + updateAssigneesViaTakeActionButtonInFlyout, + removeAllAssigneesViaTakeActionButtonInFlyout, +} from '../../../../tasks/alert_assignments'; +import { ALERTS_COUNT } from '../../../../screens/alerts'; + +describe('Alert user assignment - ESS & Serverless', { tags: ['@ess', '@serverless'] }, () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + + // Login into accounts so that they got activated and visible in user profiles list + login(ROLES.t1_analyst); + login(ROLES.t2_analyst); + login(ROLES.t3_analyst); + login(ROLES.soc_manager); + login(ROLES.detections_admin); + login(ROLES.platform_engineer); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + context('Basic rendering', () => { + it('alert with no assignees in alerts table', () => { + checkEmptyAssigneesStateInAlertsTable(); + }); + + it(`alert with no assignees in alert's details flyout`, () => { + expandFirstAlert(); + checkEmptyAssigneesStateInAlertDetailsFlyout(); + }); + + it('alert with some assignees in alerts table', () => { + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(users); + alertsTableShowsAssigneesForAlert(users); + }); + + it(`alert with some assignees in alert's details flyout`, () => { + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(users); + expandFirstAlert(); + alertDetailsFlyoutShowsAssignees(users); + }); + + it('alert with many assignees (collapsed into badge) in alerts table', () => { + const users = [ + ROLES.t1_analyst, + ROLES.t2_analyst, + ROLES.t3_analyst, + ROLES.soc_manager, + ROLES.detections_admin, + ]; + updateAssigneesForAlert(users); + alertsTableShowsAssigneesBadgeForAlert(users); + }); + + it(`alert with many assignees (collapsed into badge) in alert's details flyout`, () => { + const users = [ROLES.detections_admin, ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesForAlert(users); + expandFirstAlert(); + alertDetailsFlyoutShowsAssigneesBadge(users); + }); + }); + + context('Updating assignees (single alert)', () => { + it('adding new assignees via `More actions` in alerts table', () => { + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(users); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert(users); + + // Assignees should appear in the alert's details flyout + expandFirstAlert(); + alertDetailsFlyoutShowsAssignees(users); + }); + + it('adding new assignees via add button in flyout', () => { + expandFirstAlert(); + + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaAddButtonInFlyout(users); + + // Assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(users); + + // Assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(users); + }); + + it('adding new assignees via `Take action` button in flyout', () => { + expandFirstAlert(); + + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(users); + + // Assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(users); + + // Assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(users); + }); + + it('updating assignees via `More actions` in alerts table', () => { + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(initialAssignees); + alertsTableShowsAssigneesForAlert(initialAssignees); + + // Update assignees + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesForAlert(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert(expectedAssignees); + + // Expected assignees should appear in the alert's details flyout + expandFirstAlert(); + alertDetailsFlyoutShowsAssignees(expectedAssignees); + }); + + it('updating assignees via add button in flyout', () => { + expandFirstAlert(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaAddButtonInFlyout(initialAssignees); + alertDetailsFlyoutShowsAssignees(initialAssignees); + + // Update assignees + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesViaAddButtonInFlyout(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(expectedAssignees); + + // Expected assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(expectedAssignees); + }); + + it('updating assignees via `Take action` button in flyout', () => { + expandFirstAlert(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(initialAssignees); + alertDetailsFlyoutShowsAssignees(initialAssignees); + + // Update assignees + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alert's details flyout + alertDetailsFlyoutShowsAssignees(expectedAssignees); + + // Expected assignees should appear in the alerts table + closeAlertFlyout(); + alertsTableShowsAssigneesForAlert(expectedAssignees); + }); + + it('removing all assignees via `More actions` in alerts table', () => { + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesForAlert(initialAssignees); + alertsTableShowsAssigneesForAlert(initialAssignees); + + removeAllAssigneesForAlert(); + + // Alert should not show any assignee in alerts table or in details flyout + checkEmptyAssigneesStateInAlertsTable(); + expandFirstAlert(); + checkEmptyAssigneesStateInAlertDetailsFlyout(); + }); + + it('removing all assignees via `Take action` button in flyout', () => { + expandFirstAlert(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + updateAssigneesViaTakeActionButtonInFlyout(initialAssignees); + alertDetailsFlyoutShowsAssignees(initialAssignees); + + removeAllAssigneesViaTakeActionButtonInFlyout(); + + // Alert should not show any assignee in alerts table or in details flyout + checkEmptyAssigneesStateInAlertDetailsFlyout(); + closeAlertFlyout(); + checkEmptyAssigneesStateInAlertsTable(); + }); + }); + + context('Updating assignees (bulk actions)', () => { + it('adding new assignees should be reflected in UI (alerts table and details flyout)', () => { + selectFirstPageAlerts(); + + // Assign users + const users = [ROLES.detections_admin, ROLES.t1_analyst]; + bulkUpdateAssignees(users); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAllAlerts(users); + }); + + it('updating assignees should be reflected in UI (alerts table and details flyout)', () => { + selectFirstPageAlerts(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + bulkUpdateAssignees(initialAssignees); + alertsTableShowsAssigneesForAllAlerts(initialAssignees); + + // Update assignees + selectFirstPageAlerts(); + const updatedAssignees = [ROLES.t1_analyst, ROLES.t2_analyst]; + bulkUpdateAssignees(updatedAssignees); + + const expectedAssignees = [ROLES.detections_admin, ROLES.t2_analyst]; + + // Expected assignees should appear in the alerts table + alertsTableShowsAssigneesForAllAlerts(expectedAssignees); + }); + + it('removing all assignees should be reflected in UI (alerts table and details flyout)', () => { + selectFirstPageAlerts(); + + // Initially assigned users + const initialAssignees = [ROLES.detections_admin, ROLES.t1_analyst]; + bulkUpdateAssignees(initialAssignees); + alertsTableShowsAssigneesForAllAlerts(initialAssignees); + + // Unassign alert + selectFirstPageAlerts(); + bulkRemoveAllAssignees(); + + // Alerts should not have assignees + checkEmptyAssigneesStateInAlertsTable(); + }); + }); + + context('Alerts filtering', () => { + it('by `No assignees` option', () => { + const totalNumberOfAlerts = 5; + const numberOfSelectedAlerts = 2; + selectNumberOfAlerts(numberOfSelectedAlerts); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([NO_ASSIGNEES]); + + const expectedNumberOfAlerts = totalNumberOfAlerts - numberOfSelectedAlerts; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAlerts); + }); + + it('by one assignee', () => { + const numberOfSelectedAlerts = 2; + selectNumberOfAlerts(numberOfSelectedAlerts); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([ROLES.t1_analyst]); + + cy.get(ALERTS_COUNT).contains(numberOfSelectedAlerts); + }); + + it('by multiple assignees', () => { + const numberOfSelectedAlerts1 = 1; + selectNumberOfAlerts(numberOfSelectedAlerts1); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([NO_ASSIGNEES]); + + const numberOfSelectedAlerts2 = 2; + selectNumberOfAlerts(numberOfSelectedAlerts2); + bulkUpdateAssignees([ROLES.detections_admin]); + + clearAssigneesFilter(); + + cy.get(ALERTS_COUNT).contains(5); + + filterByAssignees([ROLES.t1_analyst, ROLES.detections_admin]); + + const expectedNumberOfAlerts = numberOfSelectedAlerts1 + numberOfSelectedAlerts2; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAlerts); + }); + + it('by assignee and alert status', () => { + const totalNumberOfAlerts = 5; + const numberOfAssignedAlerts = 3; + selectNumberOfAlerts(numberOfAssignedAlerts); + bulkUpdateAssignees([ROLES.t1_analyst]); + + filterByAssignees([ROLES.t1_analyst]); + + const numberOfClosedAlerts = 1; + selectNumberOfAlerts(numberOfClosedAlerts); + closeAlerts(); + + const expectedNumberOfAllerts1 = numberOfAssignedAlerts - numberOfClosedAlerts; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAllerts1); + + clearAssigneesFilter(); + + const expectedNumberOfAllerts2 = totalNumberOfAlerts - numberOfClosedAlerts; + cy.get(ALERTS_COUNT).contains(expectedNumberOfAllerts2); + + filterByAssignees([ROLES.t1_analyst]); + selectPageFilterValue(0, 'closed'); + cy.get(ALERTS_COUNT).contains(numberOfClosedAlerts); + }); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess.cy.ts new file mode 100644 index 0000000000000..6fddde59a88a1 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess.cy.ts @@ -0,0 +1,49 @@ +/* + * 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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { expandFirstAlert } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { loadPageAs } from '../../../../tasks/navigation'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertsTableMoreActionsAreNotAvailable, + cannotAddAssigneesViaDetailsFlyout, +} from '../../../../tasks/alert_assignments'; + +describe('Alert user assignment - ESS', { tags: ['@ess'] }, () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + it('viewer/reader should not be able to update assignees', () => { + // Login as a reader + loadPageAs(ALERTS_URL, ROLES.reader); + waitForAlertsToPopulate(); + + // Check alerts table + alertsTableMoreActionsAreNotAvailable(); + + // Check alert's details flyout + expandFirstAlert(); + cannotAddAssigneesViaDetailsFlyout(); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess_basic.cy.ts new file mode 100644 index 0000000000000..12881280388a3 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_ess_basic.cy.ts @@ -0,0 +1,53 @@ +/* + * 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 { getNewRule } from '../../../../objects/rule'; +import { expandFirstAlert } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { loadPageAs } from '../../../../tasks/navigation'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + asigneesMenuItemsAreNotAvailable, + cannotAddAssigneesViaDetailsFlyout, +} from '../../../../tasks/alert_assignments'; + +describe('Alert user assignment - Basic License', { tags: ['@ess'] }, () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + + cy.request({ + method: 'POST', + url: '/api/license/start_basic?acknowledge=true', + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + }, + }); + }); + + it('user with Basic license should not be able to update assignees', () => { + // Check alerts table + asigneesMenuItemsAreNotAvailable(); + + // Check alert's details flyout + expandFirstAlert(); + cannotAddAssigneesViaDetailsFlyout(); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_complete.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_complete.cy.ts new file mode 100644 index 0000000000000..a4e63ee1e952a --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_complete.cy.ts @@ -0,0 +1,89 @@ +/* + * 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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { refreshAlertPageFilter, selectFirstPageAlerts } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { login } from '../../../../tasks/login'; +import { loadPageAs } from '../../../../tasks/navigation'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertsTableShowsAssigneesForAlert, + updateAssigneesForAlert, + bulkRemoveAllAssignees, +} from '../../../../tasks/alert_assignments'; + +describe( + 'Alert user assignment - Serverless Complete', + { + tags: ['@serverless'], + env: { + ftrConfig: { + productTypes: [ + { product_line: 'security', product_tier: 'complete' }, + { product_line: 'endpoint', product_tier: 'complete' }, + ], + }, + }, + }, + () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + + // Login into accounts so that they got activated and visible in user profiles list + login(ROLES.t1_analyst); + login(ROLES.t2_analyst); + login(ROLES.t3_analyst); + login(ROLES.soc_manager); + login(ROLES.detections_admin); + login(ROLES.platform_engineer); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + context('Authorization / RBAC', () => { + it('users with editing privileges should be able to update assignees', () => { + const editors = [ + ROLES.t1_analyst, + ROLES.t2_analyst, + // TODO: uncomment when https://github.com/elastic/kibana/pull/170778 has been merged + // ROLES.t3_analyst, + ROLES.rule_author, + ROLES.soc_manager, + ROLES.detections_admin, + ROLES.platform_engineer, + ]; + editors.forEach((role) => { + loadPageAs(ALERTS_URL, role); + waitForAlertsToPopulate(); + + // Unassign alert + selectFirstPageAlerts(); + bulkRemoveAllAssignees(); + refreshAlertPageFilter(); + + updateAssigneesForAlert([role]); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert([role]); + }); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_essentials.cy.ts new file mode 100644 index 0000000000000..a5becdba6b097 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/assignments/assignments_serverless_essentials.cy.ts @@ -0,0 +1,89 @@ +/* + * 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 { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { getNewRule } from '../../../../objects/rule'; +import { refreshAlertPageFilter, selectFirstPageAlerts } from '../../../../tasks/alerts'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { login } from '../../../../tasks/login'; +import { loadPageAs } from '../../../../tasks/navigation'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { + alertsTableShowsAssigneesForAlert, + updateAssigneesForAlert, + bulkRemoveAllAssignees, +} from '../../../../tasks/alert_assignments'; + +describe( + 'Alert user assignment - Serverless Essentials', + { + tags: ['@serverless'], + env: { + ftrConfig: { + productTypes: [ + { product_line: 'security', product_tier: 'essentials' }, + { product_line: 'endpoint', product_tier: 'essentials' }, + ], + }, + }, + }, + () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + + // Login into accounts so that they got activated and visible in user profiles list + login(ROLES.t1_analyst); + login(ROLES.t2_analyst); + login(ROLES.t3_analyst); + login(ROLES.soc_manager); + login(ROLES.detections_admin); + login(ROLES.platform_engineer); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + loadPageAs(ALERTS_URL); + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'new custom rule' })); + waitForAlertsToPopulate(); + }); + + context('Authorization / RBAC', () => { + it('users with editing privileges should be able to update assignees', () => { + const editors = [ + ROLES.t1_analyst, + ROLES.t2_analyst, + // TODO: uncomment when https://github.com/elastic/kibana/pull/170778 has been merged + // ROLES.t3_analyst, + ROLES.rule_author, + ROLES.soc_manager, + ROLES.detections_admin, + ROLES.platform_engineer, + ]; + editors.forEach((role) => { + loadPageAs(ALERTS_URL, role); + waitForAlertsToPopulate(); + + // Unassign alert + selectFirstPageAlerts(); + bulkRemoveAllAssignees(); + refreshAlertPageFilter(); + + updateAssigneesForAlert([role]); + + // Assignees should appear in the alerts table + alertsTableShowsAssigneesForAlert([role]); + }); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index 7ca67a67629cd..cfcd52372daeb 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -33,6 +33,8 @@ export const ALERT_SEVERITY = '[data-test-subj="formatted-field-kibana.alert.sev export const ALERT_DATA_GRID = '[data-test-subj="euiDataGridBody"]'; +export const ALERT_DATA_GRID_ROW = `${ALERT_DATA_GRID} .euiDataGridRow`; + export const ALERTS_COUNT = '[data-test-subj="toolbar-alerts-count"]'; export const CLOSE_ALERT_BTN = '[data-test-subj="close-alert-status"]'; @@ -181,14 +183,35 @@ export const ALERT_RENDERER_HOST_NAME = export const HOVER_ACTIONS_CONTAINER = getDataTestSubjectSelector('hover-actions-container'); -export const ALERT_ASSIGNING_CONTEXT_MENU_ITEM = +export const ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM = '.euiSelectableListItem'; +export const ALERT_USERS_PROFILES_CLEAR_SEARCH_BUTTON = '[data-test-subj="clearSearchButton"]'; + +export const ALERT_ASSIGN_CONTEXT_MENU_ITEM = '[data-test-subj="alert-assignees-context-menu-item"]'; -export const ALERT_ASSIGNING_SELECTABLE_MENU_ITEM = - '[data-test-subj="alert-assignees-selectable-menu"]'; +export const ALERT_UNASSIGN_CONTEXT_MENU_ITEM = + '[data-test-subj="remove-alert-assignees-menu-item"]'; + +export const ALERT_ASSIGNEES_SELECT_PANEL = + '[data-test-subj="securitySolutionAssigneesApplyPanel"]'; -export const ALERT_ASSIGNING_UPDATE_BUTTON = +export const ALERT_ASSIGNEES_UPDATE_BUTTON = '[data-test-subj="securitySolutionAssigneesApplyButton"]'; -export const ALERT_ASSIGNING_USER_AVATAR = (assignee: string) => +export const ALERT_USER_AVATAR = (assignee: string) => `[data-test-subj="securitySolutionUsersAvatar-${assignee}"][title='${assignee}']`; + +export const ALERT_AVATARS_PANEL = '[data-test-subj="securitySolutionUsersAvatarsPanel"]'; + +export const ALERT_ASIGNEES_COLUMN = + '[data-test-subj="dataGridRowCell"][data-gridcell-column-id="kibana.alert.workflow_assignee_ids"]'; + +export const ALERT_ASSIGNEES_COUNT_BADGE = + '[data-test-subj="securitySolutionUsersAvatarsCountBadge"]'; + +export const FILTER_BY_ASSIGNEES_BUTTON = '[data-test-subj="filter-popover-button-assignees"]'; + +export const ALERT_DETAILS_ASSIGN_BUTTON = + '[data-test-subj="securitySolutionFlyoutHeaderAssigneesAddButton"]'; + +export const ALERT_DETAILS_TAKE_ACTION_BUTTON = '[data-test-subj="take-action-dropdown-btn"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts index abf9585e368ec..afe87189acd37 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel.ts @@ -17,6 +17,7 @@ import { SEVERITY_VALUE_TEST_ID, STATUS_BUTTON_TEST_ID, FLYOUT_HEADER_TITLE_TEST_ID, + ASSIGNEES_HEADER_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/document_details/right/components/test_ids'; import { COLLAPSE_DETAILS_BUTTON_TEST_ID, @@ -59,6 +60,8 @@ export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE = getDataTestSubjectSelector(RISK_SCORE_VALUE_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE = getDataTestSubjectSelector(SEVERITY_VALUE_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES = + getDataTestSubjectSelector(ASSIGNEES_HEADER_TEST_ID); /* Footer */ diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignees.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignees.ts deleted file mode 100644 index 8e89bc3e2d52c..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignees.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { ALERT_ASSIGNING_UPDATE_BUTTON, ALERT_ASSIGNING_USER_AVATAR } from '../screens/alerts'; - -export const waitForAssigneesToPopulatePopover = () => { - cy.waitUntil( - () => { - cy.log('Waiting for assignees to appear in popover'); - return cy.root().then(($el) => { - const $updateButton = $el.find(ALERT_ASSIGNING_UPDATE_BUTTON); - return !$updateButton.prop('disabled'); - }); - }, - { interval: 500, timeout: 12000 } - ); -}; - -export const waitForAssigneeToAppearInTable = (userName: string) => { - cy.reload(); - cy.waitUntil( - () => { - cy.log('Waiting for assignees to appear in the "Assignees" column'); - return cy.root().then(($el) => { - const assigneesState = $el.find(`.euiAvatar${ALERT_ASSIGNING_USER_AVATAR(userName)}`); - if (assigneesState.length > 0) { - return true; - } - return false; - }); - }, - { interval: 500, timeout: 12000 } - ); -}; - -export const waitForAssigneeToDisappearInTable = (userName: string) => { - cy.reload(); - cy.waitUntil( - () => { - cy.log('Waiting for assignees to disappear in the "Assignees" column'); - return cy.root().then(($el) => { - const assigneesState = $el.find(`.euiAvatar${ALERT_ASSIGNING_USER_AVATAR(userName)}`); - if (assigneesState.length > 0) { - return false; - } - return true; - }); - }, - { interval: 500, timeout: 12000 } - ); -}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignments.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignments.ts new file mode 100644 index 0000000000000..5d32be7827440 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alert_assignments.ts @@ -0,0 +1,223 @@ +/* + * 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 { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; +import { + ALERTS_TABLE_ROW_LOADER, + ALERT_AVATARS_PANEL, + ALERT_ASSIGNEES_SELECT_PANEL, + ALERT_ASSIGN_CONTEXT_MENU_ITEM, + ALERT_ASSIGNEES_UPDATE_BUTTON, + ALERT_USER_AVATAR, + ALERT_DATA_GRID_ROW, + ALERT_DETAILS_ASSIGN_BUTTON, + ALERT_DETAILS_TAKE_ACTION_BUTTON, + ALERT_UNASSIGN_CONTEXT_MENU_ITEM, + ALERT_USERS_PROFILES_CLEAR_SEARCH_BUTTON, + ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM, + ALERT_ASIGNEES_COLUMN, + ALERT_ASSIGNEES_COUNT_BADGE, + FILTER_BY_ASSIGNEES_BUTTON, + TAKE_ACTION_POPOVER_BTN, + TIMELINE_CONTEXT_MENU_BTN, +} from '../screens/alerts'; +import { DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES } from '../screens/expandable_flyout/alert_details_right_panel'; +import { selectFirstPageAlerts } from './alerts'; + +export const NO_ASSIGNEES = 'No assignees'; + +export const waitForAssigneesToPopulatePopover = () => { + cy.waitUntil( + () => { + cy.log('Waiting for assignees to appear in popover'); + return cy.root().then(($el) => { + const $updateButton = $el.find(ALERT_ASSIGNEES_UPDATE_BUTTON); + return !$updateButton.prop('disabled'); + }); + }, + { interval: 500, timeout: 12000 } + ); +}; + +export const openAlertAssigningActionMenu = (alertIndex = 0) => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(alertIndex).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).click(); +}; + +export const openAlertAssigningBulkActionMenu = () => { + cy.get(TAKE_ACTION_POPOVER_BTN).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).click(); +}; + +export const updateAlertAssignees = () => { + cy.get(ALERT_ASSIGNEES_UPDATE_BUTTON).click(); +}; + +export const checkEmptyAssigneesStateInAlertsTable = () => { + cy.get(ALERT_DATA_GRID_ROW) + .its('length') + .then((count) => { + cy.get(ALERT_ASIGNEES_COLUMN).should('have.length', count); + }); + cy.get(ALERT_ASIGNEES_COLUMN).each(($column) => { + cy.wrap($column).within(() => { + cy.get(ALERT_AVATARS_PANEL).children().should('have.length', 0); + }); + }); +}; + +export const checkEmptyAssigneesStateInAlertDetailsFlyout = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES).within(() => { + cy.get(ALERT_AVATARS_PANEL).children().should('have.length', 0); + }); +}; + +export const alertsTableMoreActionsAreNotAvailable = () => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).should('not.exist'); +}; + +export const asigneesMenuItemsAreNotAvailable = (alertIndex = 0) => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(alertIndex).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); +}; + +export const asigneesBulkMenuItemsAreNotAvailable = () => { + selectFirstPageAlerts(); + cy.get(TAKE_ACTION_POPOVER_BTN).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).should('not.exist'); +}; + +export const cannotAddAssigneesViaDetailsFlyout = () => { + cy.get(ALERT_DETAILS_ASSIGN_BUTTON).should('be.disabled'); +}; + +export const alertsTableShowsAssigneesForAlert = (users: SecurityRoleName[], alertIndex = 0) => { + cy.get(ALERT_ASIGNEES_COLUMN) + .eq(alertIndex) + .within(() => { + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('exist')); + }); +}; + +export const alertsTableShowsAssigneesForAllAlerts = (users: SecurityRoleName[]) => { + cy.get(ALERT_ASIGNEES_COLUMN).each(($column) => { + cy.wrap($column).within(() => { + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('exist')); + }); + }); +}; + +export const alertsTableShowsAssigneesBadgeForAlert = ( + users: SecurityRoleName[], + alertIndex = 0 +) => { + cy.get(ALERT_ASIGNEES_COLUMN) + .eq(alertIndex) + .within(() => { + cy.get(ALERT_ASSIGNEES_COUNT_BADGE).contains(users.length); + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('not.exist')); + }); +}; + +export const alertDetailsFlyoutShowsAssignees = (users: SecurityRoleName[]) => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES).within(() => { + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('exist')); + }); +}; + +export const alertDetailsFlyoutShowsAssigneesBadge = (users: SecurityRoleName[]) => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES).within(() => { + cy.get(ALERT_ASSIGNEES_COUNT_BADGE).contains(users.length); + users.forEach((user) => cy.get(`.euiAvatar${ALERT_USER_AVATAR(user)}`).should('not.exist')); + }); +}; + +export const selectAlertAssignee = (assignee: string) => { + cy.get(ALERT_ASSIGNEES_SELECT_PANEL).within(() => { + if (assignee === NO_ASSIGNEES) { + cy.get(ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM).contains(assignee).click(); + return; + } + cy.get('input').type(assignee); + cy.get(ALERT_USERS_PROFILES_SELECTABLE_MENU_ITEM).contains(assignee).click(); + cy.get(ALERT_USERS_PROFILES_CLEAR_SEARCH_BUTTON).click(); + }); +}; + +/** + * This will update assignees for selected alert + * @param users The list of assugnees to update. If assignee is not assigned yet it will be assigned, otherwise it will be unassigned + * @param alertIndex The index of the alert in the alerts table + */ +export const updateAssigneesForAlert = (users: SecurityRoleName[], alertIndex = 0) => { + openAlertAssigningActionMenu(alertIndex); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const updateAssigneesViaAddButtonInFlyout = (users: SecurityRoleName[]) => { + cy.get(ALERT_DETAILS_ASSIGN_BUTTON).click(); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const updateAssigneesViaTakeActionButtonInFlyout = (users: SecurityRoleName[]) => { + cy.get(ALERT_DETAILS_TAKE_ACTION_BUTTON).click(); + cy.get(ALERT_ASSIGN_CONTEXT_MENU_ITEM).click(); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const bulkUpdateAssignees = (users: SecurityRoleName[]) => { + openAlertAssigningBulkActionMenu(); + waitForAssigneesToPopulatePopover(); + users.forEach((user) => selectAlertAssignee(user)); + updateAlertAssignees(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const removeAllAssigneesForAlert = (alertIndex = 0) => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(alertIndex).click(); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).click(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const removeAllAssigneesViaTakeActionButtonInFlyout = () => { + cy.get(ALERT_DETAILS_TAKE_ACTION_BUTTON).click(); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).click(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const bulkRemoveAllAssignees = () => { + cy.get(TAKE_ACTION_POPOVER_BTN).click(); + cy.get(ALERT_UNASSIGN_CONTEXT_MENU_ITEM).click(); + cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); +}; + +export const filterByAssignees = (users: Array) => { + cy.get(FILTER_BY_ASSIGNEES_BUTTON).scrollIntoView(); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); + users.forEach((user) => selectAlertAssignee(user)); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); +}; + +export const clearAssigneesFilter = () => { + cy.get(FILTER_BY_ASSIGNEES_BUTTON).scrollIntoView(); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); + cy.get(ALERT_ASSIGNEES_SELECT_PANEL).within(() => { + cy.contains('Clear filters').click(); + }); + cy.get(FILTER_BY_ASSIGNEES_BUTTON).click(); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index 4df05787756e1..cc1a06d3545de 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -43,9 +43,6 @@ import { ALERTS_HISTOGRAM_LEGEND, LEGEND_ACTIONS, SESSION_VIEWER_BUTTON, - ALERT_ASSIGNING_CONTEXT_MENU_ITEM, - ALERT_ASSIGNING_SELECTABLE_MENU_ITEM, - ALERT_ASSIGNING_UPDATE_BUTTON, ALERT_TAGGING_CONTEXT_MENU_ITEM, ALERT_TAGGING_CONTEXT_MENU, ALERT_TAGGING_UPDATE_BUTTON, @@ -495,30 +492,3 @@ export const switchAlertTableToGridView = () => { cy.get(ALERT_TABLE_SUMMARY_VIEW_SELECTABLE).should('be.visible').trigger('click'); cy.get(ALERT_TABLE_GRID_VIEW_OPTION).should('be.visible').trigger('click'); }; - -export const openAlertAssigningBulkActionMenu = () => { - cy.get(TAKE_ACTION_POPOVER_BTN).click(); - cy.get(ALERT_ASSIGNING_CONTEXT_MENU_ITEM).click(); -}; - -export const clickAlertAssignee = (assignee: string) => { - cy.get(ALERT_ASSIGNING_SELECTABLE_MENU_ITEM).contains(assignee).click(); -}; - -export const updateAlertAssignees = () => { - cy.get(ALERT_ASSIGNING_UPDATE_BUTTON).click(); -}; - -export const findSelectedAlertAssignee = (assignee: string) => { - cy.get(ALERT_ASSIGNING_SELECTABLE_MENU_ITEM) - .find('[aria-checked="true"]') - .first() - .contains(assignee); -}; - -export const findUnselectedAlertAssignee = (assignee: string) => { - cy.get(ALERT_ASSIGNING_SELECTABLE_MENU_ITEM) - .find('[aria-checked="false"]') - .first() - .contains(assignee); -}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts b/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts index b7c6f55386c3f..d05b964fffe0c 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts @@ -9,8 +9,9 @@ import { encode } from '@kbn/rison'; import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '@kbn/security-solution-plugin/common/constants'; import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; +import { PAGE_TITLE } from '../screens/common/page'; import { GET_STARTED_URL, hostDetailsUrl, userDetailsUrl } from '../urls/navigation'; -import { constructUrlWithUser, getUrlWithRoute, User } from './login'; +import { constructUrlWithUser, getUrlWithRoute, login, User } from './login'; export const visit = ( url: string, @@ -112,3 +113,13 @@ const disableNewFeaturesTours = (window: Window) => { window.localStorage.setItem(key, JSON.stringify(tourConfig)); }); }; + +export const waitForPageTitleToBeShown = () => { + cy.get(PAGE_TITLE).should('be.visible'); +}; + +export const loadPageAs = (url: string, role?: SecurityRoleName) => { + login(role); + visitWithTimeRange(url, { role }); + waitForPageTitleToBeShown(); +};