From 9e93e35aa794aece5cf2f322caeb16a10813149e Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 5 Dec 2024 05:01:58 +1100 Subject: [PATCH] [8.x] [ResponseOps][Cases] Fix edit cases settings privilege (#202053) (#202971) # Backport This will backport the following commits from `main` to `8.x`: - [[ResponseOps][Cases] Fix edit cases settings privilege (#202053)](https://github.com/elastic/kibana/pull/202053) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com> --- .../cases/common/utils/capabilities.test.tsx | 1 - .../cases/common/utils/capabilities.ts | 1 - .../components/configure_cases/index.test.tsx | 6 +- .../components/configure_cases/index.tsx | 24 +++---- .../public/components/custom_fields/index.tsx | 8 +-- .../public/components/templates/index.tsx | 51 +++++++-------- .../apps/cases/common/roles.ts | 26 +++++++- .../apps/cases/common/users.ts | 28 +++++--- .../apps/cases/group1/index.ts | 2 +- .../group1/{deletion.ts => sub_privileges.ts} | 65 ++++++++++++++++++- 10 files changed, 146 insertions(+), 66 deletions(-) rename x-pack/test/functional_with_es_ssl/apps/cases/group1/{deletion.ts => sub_privileges.ts} (69%) diff --git a/x-pack/plugins/cases/common/utils/capabilities.test.tsx b/x-pack/plugins/cases/common/utils/capabilities.test.tsx index 11f74af8e02d8..6194cfd9aef02 100644 --- a/x-pack/plugins/cases/common/utils/capabilities.test.tsx +++ b/x-pack/plugins/cases/common/utils/capabilities.test.tsx @@ -17,7 +17,6 @@ describe('createUICapabilities', () => { "update_cases", "push_cases", "cases_connectors", - "cases_settings", ], "createComment": Array [ "create_comment", diff --git a/x-pack/plugins/cases/common/utils/capabilities.ts b/x-pack/plugins/cases/common/utils/capabilities.ts index 6897dc6bae774..79f67b7b5445e 100644 --- a/x-pack/plugins/cases/common/utils/capabilities.ts +++ b/x-pack/plugins/cases/common/utils/capabilities.ts @@ -36,7 +36,6 @@ export const createUICapabilities = (): CasesUiCapabilities => ({ UPDATE_CASES_CAPABILITY, PUSH_CASES_CAPABILITY, CASES_CONNECTORS_CAPABILITY, - CASES_SETTINGS_CAPABILITY, ] as const, read: [READ_CASES_CAPABILITY, CASES_CONNECTORS_CAPABILITY] as const, delete: [DELETE_CASES_CAPABILITY] as const, diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index 7a29c959d2525..c309509d563a3 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -12,7 +12,7 @@ import { waitFor, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ConfigureCases } from '.'; -import { noUpdateCasesPermissions, TestProviders, createAppMockRenderer } from '../../common/mock'; +import { noCasesSettingsPermission, TestProviders, createAppMockRenderer } from '../../common/mock'; import { customFieldsConfigurationMock, templatesConfigurationMock } from '../../containers/mock'; import type { AppMockRenderer } from '../../common/mock'; import { Connectors } from './connectors'; @@ -200,10 +200,10 @@ describe('ConfigureCases', () => { expect(wrapper.find('[data-test-subj="edit-connector-flyout"]').exists()).toBe(false); }); - test('it disables correctly when the user cannot update', () => { + test('it disables correctly when the user does not have settings privilege', () => { const newWrapper = mount(, { wrappingComponent: TestProviders as ComponentType>, - wrappingComponentProps: { permissions: noUpdateCasesPermissions() }, + wrappingComponentProps: { permissions: noCasesSettingsPermission() }, }); expect(newWrapper.find('button[data-test-subj="dropdown-connectors"]').prop('disabled')).toBe( diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index 641482ceca4fe..071a4c5cfac4e 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -480,12 +480,7 @@ export const ConfigureCases: React.FC = React.memo(() => { flyOutVisibility?.type === 'customField' && flyOutVisibility?.visible ? ( isLoading={loadingCaseConfigure || isPersistingConfiguration} - disabled={ - !permissions.create || - !permissions.update || - loadingCaseConfigure || - isPersistingConfiguration - } + disabled={!permissions.settings || loadingCaseConfigure || isPersistingConfiguration} onCloseFlyout={onCloseCustomFieldFlyout} onSaveField={onCustomFieldSave} renderHeader={() => ( @@ -502,12 +497,7 @@ export const ConfigureCases: React.FC = React.memo(() => { flyOutVisibility?.type === 'template' && flyOutVisibility?.visible ? ( isLoading={loadingCaseConfigure || isPersistingConfiguration} - disabled={ - !permissions.create || - !permissions.update || - loadingCaseConfigure || - isPersistingConfiguration - } + disabled={!permissions.settings || loadingCaseConfigure || isPersistingConfiguration} onCloseFlyout={onCloseTemplateFlyout} onSaveField={onTemplateSave} renderHeader={() => ( @@ -565,7 +555,9 @@ export const ConfigureCases: React.FC = React.memo(() => {
@@ -574,13 +566,15 @@ export const ConfigureCases: React.FC = React.memo(() => { diff --git a/x-pack/plugins/cases/public/components/custom_fields/index.tsx b/x-pack/plugins/cases/public/components/custom_fields/index.tsx index d749a7aba9bea..f93c72eb6a18e 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/index.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/index.tsx @@ -17,7 +17,6 @@ import { } from '@elastic/eui'; import * as i18n from './translations'; -import { useCasesContext } from '../cases_context/use_cases_context'; import type { CustomFieldsConfiguration } from '../../../common/types/domain'; import { MAX_CUSTOM_FIELDS_PER_CASE } from '../../../common/constants'; import { CustomFieldsList } from './custom_fields_list'; @@ -38,8 +37,6 @@ const CustomFieldsComponent: React.FC = ({ handleEditCustomField, customFields, }) => { - const { permissions } = useCasesContext(); - const canAddCustomFields = permissions.create && permissions.update; const [error, setError] = useState(false); const onAddCustomField = useCallback(() => { @@ -64,7 +61,7 @@ const CustomFieldsComponent: React.FC = ({ setError(false); } - return canAddCustomFields ? ( + return ( {i18n.TITLE}} @@ -113,10 +110,11 @@ const CustomFieldsComponent: React.FC = ({ )} + - ) : null; + ); }; CustomFieldsComponent.displayName = 'CustomFields'; diff --git a/x-pack/plugins/cases/public/components/templates/index.tsx b/x-pack/plugins/cases/public/components/templates/index.tsx index 479101d2889ad..6c15f1e9ef464 100644 --- a/x-pack/plugins/cases/public/components/templates/index.tsx +++ b/x-pack/plugins/cases/public/components/templates/index.tsx @@ -17,7 +17,6 @@ import { } from '@elastic/eui'; import { MAX_TEMPLATES_LENGTH } from '../../../common/constants'; import type { CasesConfigurationUITemplate } from '../../../common/ui'; -import { useCasesContext } from '../cases_context/use_cases_context'; import { ExperimentalBadge } from '../experimental_badge/experimental_badge'; import * as i18n from './translations'; import { TemplatesList } from './templates_list'; @@ -39,8 +38,6 @@ const TemplatesComponent: React.FC = ({ onEditTemplate, onDeleteTemplate, }) => { - const { permissions } = useCasesContext(); - const canAddTemplates = permissions.create && permissions.update; const [error, setError] = useState(false); const handleAddTemplate = useCallback(() => { @@ -103,31 +100,29 @@ const TemplatesComponent: React.FC = ({ ) : null} - {canAddTemplates ? ( - - - {templates.length < MAX_TEMPLATES_LENGTH ? ( - - {i18n.ADD_TEMPLATE} - - ) : ( - - - {i18n.MAX_TEMPLATE_LIMIT(MAX_TEMPLATES_LENGTH)} - - - )} - - - - ) : null} + + + {templates.length < MAX_TEMPLATES_LENGTH ? ( + + {i18n.ADD_TEMPLATE} + + ) : ( + + + {i18n.MAX_TEMPLATE_LIMIT(MAX_TEMPLATES_LENGTH)} + + + )} + + + ); diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/common/roles.ts b/x-pack/test/functional_with_es_ssl/apps/cases/common/roles.ts index 0e8cb455ad299..f10fc0394569f 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/common/roles.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/common/roles.ts @@ -59,6 +59,30 @@ export const casesNoDelete: Role = { }, }; +export const casesReadAndEditSettings: Role = { + name: 'cases_read_and_edit_settings', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + generalCasesV2: ['minimal_read', 'cases_settings'], + actions: ['all'], + actionsSimulators: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; + export const casesAll: Role = { name: 'cases_all_role', privileges: { @@ -83,4 +107,4 @@ export const casesAll: Role = { }, }; -export const roles = [casesReadDelete, casesNoDelete, casesAll]; +export const roles = [casesReadDelete, casesNoDelete, casesAll, casesReadAndEditSettings]; diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/common/users.ts b/x-pack/test/functional_with_es_ssl/apps/cases/common/users.ts index 8d213e5b78075..8964f414662fc 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/common/users.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/common/users.ts @@ -6,7 +6,7 @@ */ import { User } from '../../../../cases_api_integration/common/lib/authentication/types'; -import { casesAll, casesNoDelete, casesReadDelete } from './roles'; +import { casesAll, casesNoDelete, casesReadDelete, casesReadAndEditSettings } from './roles'; /** * Users for Cases in the Stack @@ -18,12 +18,6 @@ export const casesReadDeleteUser: User = { roles: [casesReadDelete.name], }; -export const casesNoDeleteUser: User = { - username: 'cases_no_delete_user', - password: 'password', - roles: [casesNoDelete.name], -}; - export const casesAllUser: User = { username: 'cases_all_user', password: 'password', @@ -36,4 +30,22 @@ export const casesAllUser2: User = { roles: [casesAll.name], }; -export const users = [casesReadDeleteUser, casesNoDeleteUser, casesAllUser, casesAllUser2]; +export const casesReadAndEditSettingsUser: User = { + username: 'cases_read_and_edit_settings_user', + password: 'password', + roles: [casesReadAndEditSettings.name], +}; + +export const casesNoDeleteUser: User = { + username: 'cases_no_delete_user', + password: 'password', + roles: [casesNoDelete.name], +}; + +export const users = [ + casesReadDeleteUser, + casesNoDeleteUser, + casesAllUser, + casesAllUser2, + casesReadAndEditSettingsUser, +]; diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/index.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/index.ts index 330bd820aea85..c7a1405c562b4 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/index.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/index.ts @@ -11,6 +11,6 @@ export default ({ loadTestFile }: FtrProviderContext) => { describe('Cases - group 1', function () { loadTestFile(require.resolve('./create_case_form')); loadTestFile(require.resolve('./view_case')); - loadTestFile(require.resolve('./deletion')); + loadTestFile(require.resolve('./sub_privileges')); }); }; diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/deletion.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/sub_privileges.ts similarity index 69% rename from x-pack/test/functional_with_es_ssl/apps/cases/group1/deletion.ts rename to x-pack/test/functional_with_es_ssl/apps/cases/group1/sub_privileges.ts index f23d0d01867cf..aecdb1623ff3d 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/deletion.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/sub_privileges.ts @@ -5,8 +5,16 @@ * 2.0. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { users, roles, casesReadDeleteUser, casesAllUser, casesNoDeleteUser } from '../common'; +import { + users, + roles, + casesReadDeleteUser, + casesAllUser, + casesNoDeleteUser, + casesReadAndEditSettingsUser, +} from '../common'; import { createUsersAndRoles, deleteUsersAndRoles, @@ -16,8 +24,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const PageObjects = getPageObjects(['security', 'header']); const testSubjects = getService('testSubjects'); const cases = getService('cases'); + const toasts = getService('toasts'); - describe('cases deletion sub privilege', () => { + describe('cases sub privilege', () => { before(async () => { await createUsersAndRoles(getService, users, roles); await PageObjects.security.forceLogout(); @@ -29,7 +38,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await PageObjects.security.forceLogout(); }); - describe('create two cases', () => { + describe('cases_delete', () => { beforeEach(async () => { await cases.api.createNthRandomCases(2); }); @@ -119,6 +128,56 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); } }); + + describe('cases_settings', () => { + afterEach(async () => { + await cases.api.deleteAllCases(); + }); + + for (const user of [casesReadAndEditSettingsUser, casesAllUser]) { + describe(`logging in with user ${user.username}`, () => { + before(async () => { + await PageObjects.security.login(user.username, user.password); + }); + + after(async () => { + await PageObjects.security.forceLogout(); + }); + + it(`User ${user.username} can navigate to settings`, async () => { + await cases.navigation.navigateToConfigurationPage(); + }); + + it(`User ${user.username} can update settings`, async () => { + await cases.common.selectRadioGroupValue( + 'closure-options-radio-group', + 'close-by-pushing' + ); + const toast = await toasts.getElementByIndex(1); + expect(await toast.getVisibleText()).to.be('Settings successfully updated'); + await toasts.dismissAll(); + }); + }); + } + + // below users do not have access to settings + for (const user of [casesNoDeleteUser, casesReadDeleteUser]) { + describe(`cannot access settings page with user ${user.username}`, () => { + before(async () => { + await PageObjects.security.login(user.username, user.password); + }); + + after(async () => { + await PageObjects.security.forceLogout(); + }); + + it(`User ${user.username} cannot navigate to settings`, async () => { + await cases.navigation.navigateToApp(); + await testSubjects.missingOrFail('configure-case-button'); + }); + }); + } + }); }); };