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 };