inherited roles disabled instead of non selectable
This commit is contained in:
parent
1f1fa7681e
commit
e68d4ff5c8
4 changed files with 106 additions and 104 deletions
|
@ -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) => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue