diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts index 4fd330cae2af8..f918fa983c701 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts @@ -320,5 +320,81 @@ describe('GET all roles by space id', () => { ], }, }); + + getRolesTest(`filters roles with reserved only privileges`, { + apiResponse: () => ({ + first_role: { + description: 'first role description', + cluster: [], + indices: [], + applications: [], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + second_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['space_all', 'space_read'], + resources: ['space:marketing', 'space:sales'], + }, + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + third_role: { + cluster: [], + indices: [], + applications: [], + run_as: [], + transient_metadata: { + enabled: true, + }, + }, + }), + spaceId: 'marketing', + asserts: { + statusCode: 200, + result: [ + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + remote_cluster: undefined, + remote_indices: undefined, + run_as: [], + }, + kibana: [ + { + base: ['all', 'read'], + feature: {}, + spaces: ['marketing', 'sales'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'second_role', + transient_metadata: { + enabled: true, + }, + }, + ], + }, + }); }); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts index 3ac74cade0202..9cfdf3ba301ac 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.ts @@ -7,6 +7,7 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '../..'; +import type { Role } from '../../../../common'; import { ALL_SPACES_ID } from '../../../../common/constants'; import { compareRolesByName, transformElasticsearchRoleToRole } from '../../../authorization'; import { wrapIntoCustomErrorResponse } from '../../../errors'; @@ -43,25 +44,42 @@ export function defineGetAllRolesBySpaceRoutes({ // Transform elasticsearch roles into Kibana roles and return in a list sorted by the role name. return response.ok({ body: Object.entries(elasticsearchRoles) - .map(([roleName, elasticsearchRole]) => - transformElasticsearchRoleToRole( + .reduce((acc, [roleName, elasticsearchRole]) => { + if (hideReservedRoles && elasticsearchRole.metadata?._reserved) { + return acc; + } + + const role = transformElasticsearchRoleToRole( features, // @ts-expect-error @elastic/elasticsearch SecurityIndicesPrivileges.names expected to be string[] elasticsearchRole, roleName, authz.applicationName, logger - ) - ) - .filter( - (role) => - !(hideReservedRoles && role.metadata?._reserved) && - role.kibana.some( - (privilege) => - privilege.spaces.includes(request.params.spaceId) || - privilege.spaces.includes(ALL_SPACES_ID) - ) - ) + ); + + const includeRoleForSpace = role.kibana.some((privilege) => { + const privilegeInSpace = + privilege.spaces.includes(request.params.spaceId) || + privilege.spaces.includes(ALL_SPACES_ID); + + if (privilegeInSpace && privilege.base.length) { + return true; + } + + const hasFeaturePrivilege = Object.values(privilege.feature).some( + (featureList) => featureList.length + ); + + return privilegeInSpace && hasFeaturePrivilege; + }); + + if (includeRoleForSpace) { + acc.push(role); + } + + return acc; + }, []) .sort(compareRolesByName), }); } catch (error) {