diff --git a/src/client-scopes/form/ClientScopeForm.tsx b/src/client-scopes/form/ClientScopeForm.tsx index 960c6e318c..888de30dae 100644 --- a/src/client-scopes/form/ClientScopeForm.tsx +++ b/src/client-scopes/form/ClientScopeForm.tsx @@ -19,7 +19,11 @@ import { convertFormValuesToObject } from "../../util"; import { MapperList } from "../details/MapperList"; import { ScopeForm } from "../details/ScopeForm"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; -import { RoleMapping, Row } from "../../components/role-mapping/RoleMapping"; +import { + mapRoles, + RoleMapping, + Row, +} from "../../components/role-mapping/RoleMapping"; import type { RoleMappingPayload } from "keycloak-admin/lib/defs/roleRepresentation"; import { AllClientScopes, @@ -61,41 +65,35 @@ export const ClientScopeForm = () => { ); const loader = async () => { - const assignedRoles = hide - ? await adminClient.clientScopes.listRealmScopeMappings({ id }) - : await adminClient.clientScopes.listCompositeRealmScopeMappings({ id }); + const assignedRoles = await adminClient.clientScopes.listRealmScopeMappings( + { id } + ); + const effectiveRoles = await adminClient.clientScopes.listCompositeRealmScopeMappings( + { id } + ); const clients = await adminClient.clients.find(); const clientRoles = ( await Promise.all( clients.map(async (client) => { - const clientScope = hide - ? await adminClient.clientScopes.listClientScopeMappings({ - id, - client: client.id!, - }) - : await adminClient.clientScopes.listCompositeClientScopeMappings({ - id, - client: client.id!, - }); - return clientScope.map((scope) => { - return { - client, - role: scope, - }; - }); + const clientAssignedRoles = await adminClient.clientScopes.listClientScopeMappings( + { + id, + client: client.id!, + } + ); + const clientEffectiveRoles = await adminClient.clientScopes.listCompositeClientScopeMappings( + { + id, + client: client.id!, + } + ); + return mapRoles(clientAssignedRoles, clientEffectiveRoles, hide); }) ) ).flat(); - return [ - ...assignedRoles.map((role) => { - return { - role, - }; - }), - ...clientRoles, - ]; + return [...mapRoles(assignedRoles, effectiveRoles, hide), ...clientRoles]; }; const save = async (clientScopes: ClientScopeDefaultOptionalType) => { diff --git a/src/clients/ClientDetails.tsx b/src/clients/ClientDetails.tsx index 82eed5bed4..b42326d188 100644 --- a/src/clients/ClientDetails.tsx +++ b/src/clients/ClientDetails.tsx @@ -358,7 +358,7 @@ export const ClientDetails = () => { eventKey="serviceAccount" title={{t("serviceAccount")}} > - + )} { +export const ServiceAccount = ({ client }: ServiceAccountProps) => { const { t } = useTranslation("clients"); const adminClient = useAdminClient(); - const { realm } = useContext(RealmContext); const { addAlert } = useAlerts(); const [hide, setHide] = useState(false); - const [serviceAccountId, setServiceAccountId] = useState(""); - const [name, setName] = useState(""); + const [serviceAccount, setServiceAccount] = useState(); + + useFetch( + () => + adminClient.clients.getServiceAccountUser({ + id: client.id!, + }), + (serviceAccount) => setServiceAccount(serviceAccount), + [] + ); const loader = async () => { const serviceAccount = await adminClient.clients.getServiceAccountUser({ - id: clientId, + id: client.id!, }); - setServiceAccountId(serviceAccount.id!); + const id = serviceAccount.id!; + + const assignedRoles = await adminClient.users.listRealmRoleMappings({ id }); const effectiveRoles = await adminClient.users.listCompositeRealmRoleMappings( - { id: serviceAccount.id! } + { id } ); - const assignedRoles = await adminClient.users.listRealmRoleMappings({ - id: serviceAccount.id!, - }); const clients = await adminClient.clients.find(); - setName(clients.find((c) => c.id === clientId)?.clientId!); const clientRoles = ( await Promise.all( clients.map(async (client) => { - return { - client, - roles: await adminClient.users.listClientRoleMappings({ - id: serviceAccount.id!, + const clientAssignedRoles = await adminClient.users.listClientRoleMappings( + { + id, clientUniqueId: client.id!, - }), - }; + } + ); + const clientEffectiveRoles = await adminClient.users.listCompositeClientRoleMappings( + { + id, + clientUniqueId: client.id!, + } + ); + return mapRoles(clientAssignedRoles, clientEffectiveRoles, hide); }) ) - ).filter((rows) => rows.roles.length > 0); + ).flat(); - const findClient = (role: RoleRepresentation) => { - const row = clientRoles.filter((row) => - row.roles.find((r) => r.id === role.id) - )[0]; - return row ? row.client : undefined; - }; - - const clientRolesFlat = clientRoles.map((row) => row.roles).flat(); - - const addInherentData = await (async () => - Promise.all( - effectiveRoles.map(async (role) => { - const compositeRoles = await adminClient.roles.getCompositeRolesForRealm( - { realm, id: role.id! } - ); - return compositeRoles.length > 0 - ? compositeRoles.map((r) => { - return { ...r, parent: role }; - }) - : { ...role, parent: undefined }; - }) - ))(); - const uniqueRolesWithParent = addInherentData - .flat() - .filter( - (role, index, array) => - array.findIndex((r) => r.id === role.id) === index - ); - return ([ - ...(hide ? assignedRoles : uniqueRolesWithParent), - ...clientRolesFlat, - ] as CompositeRole[]) - .sort((r1, r2) => r1.name!.localeCompare(r2.name!)) - .map((role) => { - return { - client: findClient(role), - role, - } as Row; - }); + return [...mapRoles(assignedRoles, effectiveRoles, hide), ...clientRoles]; }; const assignRoles = async (rows: Row[]) => { @@ -103,7 +76,7 @@ export const ServiceAccount = ({ clientId }: ServiceAccountProps) => { .map((row) => row.role as RoleMappingPayload) .flat(); adminClient.users.addRealmRoleMappings({ - id: serviceAccountId, + id: serviceAccount?.id!, roles: realmRoles, }); await Promise.all( @@ -111,7 +84,7 @@ export const ServiceAccount = ({ clientId }: ServiceAccountProps) => { .filter((row) => row.client !== undefined) .map((row) => adminClient.users.addClientRoleMappings({ - id: serviceAccountId, + id: serviceAccount?.id!, clientUniqueId: row.client!.id!, roles: [row.role as RoleMappingPayload], }) @@ -128,13 +101,17 @@ export const ServiceAccount = ({ clientId }: ServiceAccountProps) => { } }; return ( - setHide(!hide)} - /> + <> + {serviceAccount && ( + setHide(!hide)} + /> + )} + ); }; diff --git a/src/components/role-mapping/RoleMapping.tsx b/src/components/role-mapping/RoleMapping.tsx index 435a32da01..17e8c5a5b0 100644 --- a/src/components/role-mapping/RoleMapping.tsx +++ b/src/components/role-mapping/RoleMapping.tsx @@ -22,11 +22,35 @@ import { useAlerts } from "../alert/Alerts"; export type CompositeRole = RoleRepresentation & { parent: RoleRepresentation; + isInherited?: boolean; }; export type Row = { client?: ClientRepresentation; - role: CompositeRole | RoleRepresentation; + role: RoleRepresentation | CompositeRole; +}; + +export const mapRoles = ( + assignedRoles: RoleRepresentation[], + effectiveRoles: RoleRepresentation[], + hide: boolean +) => { + return [ + ...(hide + ? assignedRoles.map((role) => ({ + role: { + ...role, + isInherited: false, + }, + })) + : effectiveRoles.map((role) => ({ + role: { + ...role, + isInherited: + assignedRoles.find((r) => r.id === role.id) === undefined, + }, + }))), + ]; }; export const ServiceRole = ({ role, client }: Row) => ( @@ -152,10 +176,13 @@ export const RoleMapping = ({ data-testid="assigned-roles" key={key} loader={loader} - canSelectAll={hide} - onSelect={hide ? (rows) => setSelected(rows) : undefined} + canSelectAll + onSelect={(rows) => setSelected(rows)} searchPlaceholderKey="clients:searchByName" ariaLabelKey="clients:clientScopeList" + isRowDisabled={(value) => + (value.role as CompositeRole).isInherited || false + } toolbarItem={ <>