From 87f3c49c34eafffb7f4b44438772156b135f932e Mon Sep 17 00:00:00 2001 From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:42:11 +0100 Subject: [PATCH] [Spaces] Rework privileges computation for customize selection (#195253) ## Summary This PR reworks how privileges get computed when a user selects the customize option, and then opts to further customize each available feature, and is particularly necessary because the previous implementation for when bulk actions where applied for customization applied the privilege value on the `base` property instead of on each feature to further easier customization this in turn resulted in quite the buggy experience. See visuals below; ## Visuals ### Before https://github.com/user-attachments/assets/e0bf8c39-5aaf-4489-bfe4-efe4a79650a4 ### After https://github.com/user-attachments/assets/eacbd2db-b9c1-41c2-9c34-8ba21a3f230c - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Elastic Machine --- .../space_assign_role_privilege_form.test.tsx | 80 ++++++++++++++++++- .../space_assign_role_privilege_form.tsx | 44 +++++++++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx index a7cc54820774d..3595cefd1220c 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx @@ -27,7 +27,11 @@ import { import { PrivilegesRolesForm } from './space_assign_role_privilege_form'; import type { Space } from '../../../../../common'; -import { FEATURE_PRIVILEGES_ALL, FEATURE_PRIVILEGES_READ } from '../../../../../common/constants'; +import { + FEATURE_PRIVILEGES_ALL, + FEATURE_PRIVILEGES_CUSTOM, + FEATURE_PRIVILEGES_READ, +} from '../../../../../common/constants'; import { spacesManagerMock } from '../../../../spaces_manager/spaces_manager.mock'; import { createPrivilegeAPIClientMock, @@ -316,6 +320,11 @@ describe('PrivilegesRolesForm', () => { await userEvent.click(screen.getByTestId('custom-privilege-button')); + expect(screen.getByTestId(`${FEATURE_PRIVILEGES_CUSTOM}-privilege-button`)).toHaveAttribute( + 'aria-pressed', + String(true) + ); + expect( screen.getByTestId('space-assign-role-privilege-customization-form') ).toBeInTheDocument(); @@ -330,5 +339,74 @@ describe('PrivilegesRolesForm', () => { String(true) ); }); + + it('allows modifying individual features after selecting a base privilege within the customize table', async () => { + const user = userEvent.setup(); + + getRolesSpy.mockResolvedValue([]); + getAllKibanaPrivilegeSpy.mockResolvedValue(createRawKibanaPrivileges(kibanaFeatures)); + + const featureIds: string[] = kibanaFeatures.map((kibanaFeature) => kibanaFeature.id); + + const roles: Role[] = [ + createRole('test_role_1', [ + { base: [FEATURE_PRIVILEGES_READ], feature: {}, spaces: [space.id] }, + ]), + ]; + + renderPrivilegeRolesForm({ + preSelectedRoles: roles, + }); + + await waitFor(() => null); + + expect(screen.getByTestId(`${FEATURE_PRIVILEGES_READ}-privilege-button`)).toHaveAttribute( + 'aria-pressed', + String(true) + ); + + await user.click(screen.getByTestId('custom-privilege-button')); + + expect(screen.getByTestId(`${FEATURE_PRIVILEGES_CUSTOM}-privilege-button`)).toHaveAttribute( + 'aria-pressed', + String(true) + ); + + expect( + screen.getByTestId('space-assign-role-privilege-customization-form') + ).toBeInTheDocument(); + + // By default all features are set to the none privilege + expect(screen.queryByTestId(`${featureIds[0]}_read`)).not.toHaveAttribute( + 'aria-pressed', + String(true) + ); + + await user.click(screen.getByTestId('changeAllPrivilegesButton')); + + // change all privileges to read + await user.click(screen.getByTestId(`changeAllPrivileges-${FEATURE_PRIVILEGES_READ}`)); + + featureIds.forEach((_, idx) => { + // verify that all features are set to read + expect(screen.queryByTestId(`${featureIds[idx]}_read`)).toHaveAttribute( + 'aria-pressed', + String(true) + ); + }); + + // change a single feature to all + await user.click(screen.getByTestId(`${featureIds[0]}_all`)); + + expect(screen.queryByTestId(`${featureIds[0]}_read`)).not.toHaveAttribute( + 'aria-pressed', + String(true) + ); + + expect(screen.getByTestId(`${featureIds[0]}_all`)).toHaveAttribute( + 'aria-pressed', + String(true) + ); + }); }); }); diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx index 23a7383a01a06..fcb9f8ad2b8cc 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx @@ -32,7 +32,7 @@ import type { KibanaFeature, KibanaFeatureConfig } from '@kbn/features-plugin/co import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { type RawKibanaPrivileges } from '@kbn/security-authorization-core'; -import type { Role } from '@kbn/security-plugin-types-common'; +import type { Role, RoleKibanaPrivilege } from '@kbn/security-plugin-types-common'; import type { BulkUpdateRoleResponse } from '@kbn/security-plugin-types-public/src/roles/roles_api_client'; import { KibanaPrivileges } from '@kbn/security-role-management-model'; import { KibanaPrivilegeTable, PrivilegeFormCalculator } from '@kbn/security-ui-components'; @@ -513,10 +513,48 @@ export const PrivilegesRolesForm: FC = (props) => { // apply selected changes only to the designated customization anchor, this way we delay reconciling the intending privileges // to all of the selected roles till we decide to commit the changes chosen setRoleCustomizationAnchor(({ value, privilegeIndex }) => { - let privilege; + let privilege: RoleKibanaPrivilege; if ((privilege = value!.kibana?.[privilegeIndex!])) { - privilege.base = _privilege; + // empty out base to setup customizing all features + privilege.base = []; + + const securedFeatures = new KibanaPrivileges( + kibanaPrivileges, + features + ).getSecuredFeatures(); + + securedFeatures.forEach((feature) => { + const nextFeaturePrivilege = feature + .getPrimaryFeaturePrivileges({ + includeMinimalFeaturePrivileges: true, + }) + .find((pfp) => { + if (pfp?.disabled || pfp?.requireAllSpaces) { + return false; + } + return Array.isArray(_privilege) && _privilege.includes(pfp.id); + }); + + let newPrivileges: string[] = []; + + if (nextFeaturePrivilege) { + newPrivileges = [nextFeaturePrivilege.id]; + feature.getSubFeaturePrivileges().forEach((psf) => { + if (Array.isArray(_privilege) && _privilege.includes(psf.id)) { + if (!psf.requireAllSpaces) { + newPrivileges.push(psf.id); + } + } + }); + } + + if (newPrivileges.length === 0) { + delete privilege.feature[feature.id]; + } else { + privilege.feature[feature.id] = newPrivileges; + } + }); } return { value, privilegeIndex };