diff --git a/x-pack/platform/plugins/shared/streams_app/test/scout/ui/fixtures/page_objects/streams_app.ts b/x-pack/platform/plugins/shared/streams_app/test/scout/ui/fixtures/page_objects/streams_app.ts index d6ef13af161a8..d05c79d19209e 100644 --- a/x-pack/platform/plugins/shared/streams_app/test/scout/ui/fixtures/page_objects/streams_app.ts +++ b/x-pack/platform/plugins/shared/streams_app/test/scout/ui/fixtures/page_objects/streams_app.ts @@ -493,6 +493,18 @@ export class StreamsApp { return targetCondition.getByRole('button', { name: 'Create nested step' }); } + async getConditionContextMenuButton(pos: number) { + const conditions = await this.getConditionsListItems(); + const targetCondition = conditions[pos]; + + const allButtons = await targetCondition + .getByTestId('streamsAppStreamDetailEnrichmentStepContextMenuButton') + .all(); + + // Return the condition's context menu button, not nested conditions / actions. + return allButtons[0]; + } + // Gets the first level of nested steps under a condition at position 'pos' async getConditionNestedStepsList(pos: number) { const conditions = await this.getConditionsListItems(); diff --git a/x-pack/platform/plugins/shared/streams_app/test/scout/ui/tests/data_management/data_processing/permissions.spec.ts b/x-pack/platform/plugins/shared/streams_app/test/scout/ui/tests/data_management/data_processing/permissions.spec.ts new file mode 100644 index 0000000000000..0fd59f040f209 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams_app/test/scout/ui/tests/data_management/data_processing/permissions.spec.ts @@ -0,0 +1,353 @@ +/* + * 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 { expect } from '@kbn/scout'; +import { test } from '../../../fixtures'; +import { generateLogsData } from '../../../fixtures/generators'; + +test.describe( + 'Streams data processing permissions - viewer role (no simulate, no manage)', + { tag: ['@ess', '@svlOblt'] }, + () => { + test.beforeAll(async ({ logsSynthtraceEsClient }) => { + await generateLogsData(logsSynthtraceEsClient)({ index: 'logs-generic-default' }); + }); + + test.beforeEach(async ({ apiServices, browserAuth, pageObjects }) => { + // Setup as admin first to create processors + await browserAuth.loginAsAdmin(); + await apiServices.streams.updateStreamProcessors('logs-generic-default', { + steps: [ + { + action: 'grok', + from: 'message', + patterns: ['%{WORD:attributes.method}'], + }, + ], + }); + + // Now login as viewer for the actual test + await browserAuth.loginAsViewer(); + await pageObjects.streams.gotoProcessingTab('logs-generic-default'); + }); + + test.afterAll(async ({ logsSynthtraceEsClient }) => { + await logsSynthtraceEsClient.clean(); + }); + + test('should NOT allow viewer to add processors (requires simulate privilege)', async ({ + page, + }) => { + // Verify the "Create step" button is not visible (requires simulate) + const createStepButton = page.getByTestId('streamsAppStreamDetailEnrichmentCreateStepButton'); + await expect(createStepButton).toBeHidden(); + }); + + test('should NOT allow viewer to edit existing processors (requires simulate privilege)', async ({ + pageObjects, + }) => { + // Verify processors are visible + expect(await pageObjects.streams.getProcessorsListItems()).toHaveLength(1); + + // Verify edit button is disabled (requires simulate) + const editButton = await pageObjects.streams.getProcessorEditButton(0); + await expect(editButton).toBeDisabled(); + }); + + test('should NOT allow viewer to duplicate processors (requires manage privilege)', async ({ + pageObjects, + }) => { + // Verify processors are visible + expect(await pageObjects.streams.getProcessorsListItems()).toHaveLength(1); + + // Verify duplicate button is disabled (requires manage) + const duplicateButton = await pageObjects.streams.getProcessorDuplicateButton(0); + await expect(duplicateButton).toBeDisabled(); + }); + + test('should NOT allow viewer to delete processors (requires manage privilege)', async ({ + page, + pageObjects, + }) => { + // Verify processors are visible + expect(await pageObjects.streams.getProcessorsListItems()).toHaveLength(1); + + // Try to access the context menu + const processors = await pageObjects.streams.getProcessorsListItems(); + const targetProcessor = processors[0]; + await targetProcessor.getByRole('button', { name: 'Step context menu' }).click(); + + // Verify delete option is disabled (requires manage) + const deleteButton = page.getByTestId('stepContextMenuDeleteItem'); + await expect(deleteButton).toBeDisabled(); + }); + + test('should NOT allow viewer to add conditions (requires simulate privilege)', async ({ + page, + }) => { + // Verify the create step button is not visible (requires simulate) + const createStepButton = page.getByTestId('streamsAppStreamDetailEnrichmentCreateStepButton'); + await expect(createStepButton).toBeHidden(); + }); + + test('should NOT show save changes button for viewer (requires manage privilege)', async ({ + page, + }) => { + // Save changes button should not be visible (requires manage) + const saveButton = page.getByRole('button', { name: 'Save changes' }); + await expect(saveButton).toBeHidden(); + }); + } +); + +test.describe( + 'Streams data processing permissions - editor role (no simulate, no manage)', + { tag: ['@ess', '@svlOblt'] }, + () => { + test.beforeAll(async ({ logsSynthtraceEsClient }) => { + await generateLogsData(logsSynthtraceEsClient)({ index: 'logs-generic-default' }); + }); + + test.beforeEach(async ({ apiServices, browserAuth, pageObjects }) => { + // Setup as admin first to create processors + await browserAuth.loginAsAdmin(); + await apiServices.streams.updateStreamProcessors('logs-generic-default', { + steps: [ + { + action: 'grok', + from: 'message', + patterns: ['%{WORD:attributes.method}'], + }, + { + action: 'set', + to: 'test_field', + value: 'test_value', + }, + ], + }); + + // Now login as editor for the actual test + await browserAuth.loginAs('editor'); + await pageObjects.streams.gotoProcessingTab('logs-generic-default'); + }); + + test.afterAll(async ({ logsSynthtraceEsClient }) => { + await logsSynthtraceEsClient.clean(); + }); + + test('should NOT allow editor to add processors (requires simulate privilege)', async ({ + page, + }) => { + // Verify the "Create step" button is not visible (requires simulate) + const createStepButton = page.getByTestId('streamsAppStreamDetailEnrichmentCreateStepButton'); + await expect(createStepButton).toBeHidden(); + }); + + test('should NOT allow editor to edit existing processors (requires simulate privilege)', async ({ + pageObjects, + }) => { + // Verify processors are visible + expect(await pageObjects.streams.getProcessorsListItems()).toHaveLength(2); + + // Verify edit button is disabled (requires simulate) + const editButton = await pageObjects.streams.getProcessorEditButton(0); + await expect(editButton).toBeDisabled(); + }); + + test('should NOT allow editor to duplicate processors (requires manage privilege)', async ({ + pageObjects, + }) => { + // Verify processors are visible + expect(await pageObjects.streams.getProcessorsListItems()).toHaveLength(2); + + // Verify duplicate button is disabled (requires manage) + const duplicateButton = await pageObjects.streams.getProcessorDuplicateButton(0); + await expect(duplicateButton).toBeDisabled(); + }); + + test('should NOT allow editor to delete processors (requires manage privilege)', async ({ + page, + pageObjects, + }) => { + // Verify processors are visible + expect(await pageObjects.streams.getProcessorsListItems()).toHaveLength(2); + + // Try to access the context menu + const processors = await pageObjects.streams.getProcessorsListItems(); + const targetProcessor = processors[0]; + await targetProcessor.getByRole('button', { name: 'Step context menu' }).click(); + + // Verify delete option is disabled (requires manage) + const deleteButton = page.getByTestId('stepContextMenuDeleteItem'); + await expect(deleteButton).toBeDisabled(); + }); + + test('should NOT show save changes button for editor (requires manage privilege)', async ({ + page, + }) => { + // Save changes button should not be visible (requires manage) + const saveButton = page.getByRole('button', { name: 'Save changes' }); + await expect(saveButton).toBeHidden(); + }); + } +); + +test.describe( + 'Streams data processing permissions - editor with conditions (no simulate, no manage)', + { tag: ['@ess', '@svlOblt'] }, + () => { + test.beforeAll(async ({ logsSynthtraceEsClient }) => { + await generateLogsData(logsSynthtraceEsClient)({ index: 'logs-generic-default' }); + }); + + test.beforeEach(async ({ apiServices, browserAuth, pageObjects }) => { + // Setup as admin first to create conditions + await browserAuth.loginAsAdmin(); + await apiServices.streams.updateStreamProcessors('logs-generic-default', { + steps: [ + { + where: { + field: 'test_field', + contains: 'logs', + steps: [ + { + action: 'grok', + from: 'message', + patterns: ['%{WORD:attributes.method}'], + }, + ], + }, + }, + ], + }); + + // Now login as editor for the actual test + await browserAuth.loginAs('editor'); + await pageObjects.streams.gotoProcessingTab('logs-generic-default'); + }); + + test.afterAll(async ({ logsSynthtraceEsClient }) => { + await logsSynthtraceEsClient.clean(); + }); + + test('should NOT allow editor to edit existing conditions (requires simulate privilege)', async ({ + pageObjects, + }) => { + // Verify condition is visible + expect(await pageObjects.streams.getConditionsListItems()).toHaveLength(1); + + // Verify edit button is disabled (requires simulate) + const editButton = await pageObjects.streams.getConditionEditButton(0); + await expect(editButton).toBeDisabled(); + }); + + test('should NOT allow editor to delete conditions (requires manage privilege)', async ({ + page, + pageObjects, + }) => { + // Verify condition is visible + expect(await pageObjects.streams.getConditionsListItems()).toHaveLength(1); + + // Click the condition's own context menu button (not nested processor buttons) + const contextMenuButton = await pageObjects.streams.getConditionContextMenuButton(0); + await contextMenuButton.click(); + + // Verify delete option is disabled (requires manage) + const deleteButton = page.getByTestId('stepContextMenuDeleteItem'); + await expect(deleteButton).toBeDisabled(); + }); + + test('should NOT allow editor to add nested steps to conditions (requires simulate privilege)', async ({ + pageObjects, + }) => { + // Verify condition is visible + expect(await pageObjects.streams.getConditionsListItems()).toHaveLength(1); + + // Verify the "Create nested step" button is disabled (requires simulate) + const addNestedStepButton = await pageObjects.streams.getConditionAddStepMenuButton(0); + await expect(addNestedStepButton).toBeDisabled(); + }); + } +); + +test.describe( + 'Streams data processing permissions - viewer with conditions (no simulate, no manage)', + { tag: ['@ess', '@svlOblt'] }, + () => { + test.beforeAll(async ({ logsSynthtraceEsClient }) => { + await generateLogsData(logsSynthtraceEsClient)({ index: 'logs-generic-default' }); + }); + + test.beforeEach(async ({ apiServices, browserAuth, pageObjects }) => { + // Setup as admin first to create conditions + await browserAuth.loginAsAdmin(); + await apiServices.streams.updateStreamProcessors('logs-generic-default', { + steps: [ + { + where: { + field: 'test_field', + contains: 'logs', + steps: [ + { + action: 'grok', + from: 'message', + patterns: ['%{WORD:attributes.method}'], + }, + ], + }, + }, + ], + }); + + // Now login as viewer for the actual test + await browserAuth.loginAsViewer(); + await pageObjects.streams.gotoProcessingTab('logs-generic-default'); + }); + + test.afterAll(async ({ logsSynthtraceEsClient }) => { + await logsSynthtraceEsClient.clean(); + }); + + test('should NOT allow viewer to edit existing conditions (requires simulate privilege)', async ({ + pageObjects, + }) => { + // Verify condition is visible + expect(await pageObjects.streams.getConditionsListItems()).toHaveLength(1); + + // Verify edit button is disabled (requires simulate) + const editButton = await pageObjects.streams.getConditionEditButton(0); + await expect(editButton).toBeDisabled(); + }); + + test('should NOT allow viewer to delete conditions (requires manage privilege)', async ({ + page, + pageObjects, + }) => { + // Verify condition is visible + expect(await pageObjects.streams.getConditionsListItems()).toHaveLength(1); + + // Click the condition's own context menu button (not nested processor buttons) + const contextMenuButton = await pageObjects.streams.getConditionContextMenuButton(0); + await contextMenuButton.click(); + + // Verify delete option is disabled (requires manage) + const deleteButton = page.getByTestId('stepContextMenuDeleteItem'); + await expect(deleteButton).toBeDisabled(); + }); + + test('should NOT allow viewer to add nested steps to conditions (requires simulate privilege)', async ({ + pageObjects, + }) => { + // Verify condition is visible + expect(await pageObjects.streams.getConditionsListItems()).toHaveLength(1); + + // Verify the "Create nested step" button is disabled (requires simulate) + const addNestedStepButton = await pageObjects.streams.getConditionAddStepMenuButton(0); + await expect(addNestedStepButton).toBeDisabled(); + }); + } +);