inherited roles disabled instead of non selectable

This commit is contained in:
Erik Jan de Wit 2021-06-15 08:14:38 +02:00
parent 1f1fa7681e
commit e68d4ff5c8
4 changed files with 106 additions and 104 deletions

View file

@ -19,7 +19,11 @@ import { convertFormValuesToObject } from "../../util";
import { MapperList } from "../details/MapperList"; import { MapperList } from "../details/MapperList";
import { ScopeForm } from "../details/ScopeForm"; import { ScopeForm } from "../details/ScopeForm";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; 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 type { RoleMappingPayload } from "keycloak-admin/lib/defs/roleRepresentation";
import { import {
AllClientScopes, AllClientScopes,
@ -61,41 +65,35 @@ export const ClientScopeForm = () => {
); );
const loader = async () => { const loader = async () => {
const assignedRoles = hide const assignedRoles = await adminClient.clientScopes.listRealmScopeMappings(
? await adminClient.clientScopes.listRealmScopeMappings({ id }) { id }
: await adminClient.clientScopes.listCompositeRealmScopeMappings({ id }); );
const effectiveRoles = await adminClient.clientScopes.listCompositeRealmScopeMappings(
{ id }
);
const clients = await adminClient.clients.find(); const clients = await adminClient.clients.find();
const clientRoles = ( const clientRoles = (
await Promise.all( await Promise.all(
clients.map(async (client) => { clients.map(async (client) => {
const clientScope = hide const clientAssignedRoles = await adminClient.clientScopes.listClientScopeMappings(
? await adminClient.clientScopes.listClientScopeMappings({ {
id, id,
client: client.id!, client: client.id!,
}) }
: await adminClient.clientScopes.listCompositeClientScopeMappings({ );
id, const clientEffectiveRoles = await adminClient.clientScopes.listCompositeClientScopeMappings(
client: client.id!, {
}); id,
return clientScope.map((scope) => { client: client.id!,
return { }
client, );
role: scope, return mapRoles(clientAssignedRoles, clientEffectiveRoles, hide);
};
});
}) })
) )
).flat(); ).flat();
return [ return [...mapRoles(assignedRoles, effectiveRoles, hide), ...clientRoles];
...assignedRoles.map((role) => {
return {
role,
};
}),
...clientRoles,
];
}; };
const save = async (clientScopes: ClientScopeDefaultOptionalType) => { const save = async (clientScopes: ClientScopeDefaultOptionalType) => {

View file

@ -358,7 +358,7 @@ export const ClientDetails = () => {
eventKey="serviceAccount" eventKey="serviceAccount"
title={<TabTitleText>{t("serviceAccount")}</TabTitleText>} title={<TabTitleText>{t("serviceAccount")}</TabTitleText>}
> >
<ServiceAccount clientId={clientId} /> <ServiceAccount client={client} />
</Tab> </Tab>
)} )}
<Tab <Tab

View file

@ -1,99 +1,72 @@
import React, { useContext, useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AlertVariant } from "@patternfly/react-core"; import { AlertVariant } from "@patternfly/react-core";
import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
import type { RoleMappingPayload } from "keycloak-admin/lib/defs/roleRepresentation"; import type { RoleMappingPayload } from "keycloak-admin/lib/defs/roleRepresentation";
import { useAdminClient } from "../../context/auth/AdminClient"; import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import { RealmContext } from "../../context/realm-context/RealmContext"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { import {
CompositeRole, mapRoles,
RoleMapping, RoleMapping,
Row, Row,
} from "../../components/role-mapping/RoleMapping"; } from "../../components/role-mapping/RoleMapping";
type ServiceAccountProps = { type ServiceAccountProps = {
clientId: string; client: ClientRepresentation;
}; };
export const ServiceAccount = ({ clientId }: ServiceAccountProps) => { export const ServiceAccount = ({ client }: ServiceAccountProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { realm } = useContext(RealmContext);
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const [hide, setHide] = useState(false); const [hide, setHide] = useState(false);
const [serviceAccountId, setServiceAccountId] = useState(""); const [serviceAccount, setServiceAccount] = useState<UserRepresentation>();
const [name, setName] = useState("");
useFetch(
() =>
adminClient.clients.getServiceAccountUser({
id: client.id!,
}),
(serviceAccount) => setServiceAccount(serviceAccount),
[]
);
const loader = async () => { const loader = async () => {
const serviceAccount = await adminClient.clients.getServiceAccountUser({ 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( 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(); const clients = await adminClient.clients.find();
setName(clients.find((c) => c.id === clientId)?.clientId!);
const clientRoles = ( const clientRoles = (
await Promise.all( await Promise.all(
clients.map(async (client) => { clients.map(async (client) => {
return { const clientAssignedRoles = await adminClient.users.listClientRoleMappings(
client, {
roles: await adminClient.users.listClientRoleMappings({ id,
id: serviceAccount.id!,
clientUniqueId: client.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) => { return [...mapRoles(assignedRoles, effectiveRoles, hide), ...clientRoles];
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;
});
}; };
const assignRoles = async (rows: Row[]) => { const assignRoles = async (rows: Row[]) => {
@ -103,7 +76,7 @@ export const ServiceAccount = ({ clientId }: ServiceAccountProps) => {
.map((row) => row.role as RoleMappingPayload) .map((row) => row.role as RoleMappingPayload)
.flat(); .flat();
adminClient.users.addRealmRoleMappings({ adminClient.users.addRealmRoleMappings({
id: serviceAccountId, id: serviceAccount?.id!,
roles: realmRoles, roles: realmRoles,
}); });
await Promise.all( await Promise.all(
@ -111,7 +84,7 @@ export const ServiceAccount = ({ clientId }: ServiceAccountProps) => {
.filter((row) => row.client !== undefined) .filter((row) => row.client !== undefined)
.map((row) => .map((row) =>
adminClient.users.addClientRoleMappings({ adminClient.users.addClientRoleMappings({
id: serviceAccountId, id: serviceAccount?.id!,
clientUniqueId: row.client!.id!, clientUniqueId: row.client!.id!,
roles: [row.role as RoleMappingPayload], roles: [row.role as RoleMappingPayload],
}) })
@ -128,13 +101,17 @@ export const ServiceAccount = ({ clientId }: ServiceAccountProps) => {
} }
}; };
return ( return (
<RoleMapping <>
name={name} {serviceAccount && (
id={serviceAccountId} <RoleMapping
type={"service-account"} name={client.clientId!}
loader={loader} id={serviceAccount.id!}
save={assignRoles} type="service-account"
onHideRolesToggle={() => setHide(!hide)} loader={loader}
/> save={assignRoles}
onHideRolesToggle={() => setHide(!hide)}
/>
)}
</>
); );
}; };

View file

@ -22,11 +22,35 @@ import { useAlerts } from "../alert/Alerts";
export type CompositeRole = RoleRepresentation & { export type CompositeRole = RoleRepresentation & {
parent: RoleRepresentation; parent: RoleRepresentation;
isInherited?: boolean;
}; };
export type Row = { export type Row = {
client?: ClientRepresentation; 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) => ( export const ServiceRole = ({ role, client }: Row) => (
@ -152,10 +176,13 @@ export const RoleMapping = ({
data-testid="assigned-roles" data-testid="assigned-roles"
key={key} key={key}
loader={loader} loader={loader}
canSelectAll={hide} canSelectAll
onSelect={hide ? (rows) => setSelected(rows) : undefined} onSelect={(rows) => setSelected(rows)}
searchPlaceholderKey="clients:searchByName" searchPlaceholderKey="clients:searchByName"
ariaLabelKey="clients:clientScopeList" ariaLabelKey="clients:clientScopeList"
isRowDisabled={(value) =>
(value.role as CompositeRole).isInherited || false
}
toolbarItem={ toolbarItem={
<> <>
<ToolbarItem> <ToolbarItem>