import type KeycloakAdminClient from "@keycloak/keycloak-admin-client"; import type MappingsRepresentation from "@keycloak/keycloak-admin-client/lib/defs/mappingsRepresentation"; import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; import type { ClientScopes } from "@keycloak/keycloak-admin-client/lib/resources/clientScopes"; import type { Clients } from "@keycloak/keycloak-admin-client/lib/resources/clients"; import type { Groups } from "@keycloak/keycloak-admin-client/lib/resources/groups"; import type { Roles } from "@keycloak/keycloak-admin-client/lib/resources/roles"; import type { Users } from "@keycloak/keycloak-admin-client/lib/resources/users"; import { adminClient } from "../../admin-client"; import { Row } from "./RoleMapping"; export type ResourcesKey = keyof KeycloakAdminClient; type DeleteFunctions = | keyof Pick | keyof Pick | keyof Pick; type ListEffectiveFunction = | keyof Pick | keyof Pick< ClientScopes, | "listScopeMappings" | "listAvailableRealmScopeMappings" | "listCompositeClientScopeMappings" > | keyof Pick | keyof Pick< Users, "listCompositeClientRoleMappings" | "listCompositeRealmRoleMappings" >; type ListAvailableFunction = | keyof Pick< Groups, "listAvailableClientRoleMappings" | "listAvailableRealmRoleMappings" > | keyof Pick< ClientScopes, "listAvailableClientScopeMappings" | "listAvailableRealmScopeMappings" > | keyof Pick | keyof Pick; type FunctionMapping = { delete: DeleteFunctions[]; listAvailable: ListAvailableFunction[]; listEffective: ListEffectiveFunction[]; }; type ResourceMapping = Partial>; const groupFunctions: FunctionMapping = { delete: ["delClientRoleMappings", "delRealmRoleMappings"], listEffective: [ "listRoleMappings", "listCompositeRealmRoleMappings", "listCompositeClientRoleMappings", ], listAvailable: [ "listAvailableClientRoleMappings", "listAvailableRealmRoleMappings", ], }; const clientFunctions: FunctionMapping = { delete: ["delClientScopeMappings", "delRealmScopeMappings"], listEffective: [ "listScopeMappings", "listAvailableRealmScopeMappings", "listCompositeClientScopeMappings", ], listAvailable: [ "listAvailableClientScopeMappings", "listAvailableRealmScopeMappings", ], }; const mapping: ResourceMapping = { groups: groupFunctions, users: groupFunctions, clientScopes: clientFunctions, clients: clientFunctions, roles: { delete: ["delCompositeRoles", "delCompositeRoles"], listEffective: [ "getCompositeRoles", "getCompositeRoles", "getCompositeRolesForClient", ], listAvailable: ["listRoles", "find"], }, }; type queryType = | DeleteFunctions | ListAvailableFunction | ListEffectiveFunction; const castAdminClient = (resource: ResourcesKey) => adminClient[resource] as unknown as { [index in queryType]: (...params: any) => Promise; }; const applyQuery = ( type: ResourcesKey, query: queryType, ...params: object[] ): Promise => castAdminClient(type)[query](...params); export const deleteMapping = (type: ResourcesKey, id: string, rows: Row[]) => rows.map((row) => { const role = { id: row.role.id!, name: row.role.name! }; const query = mapping[type]?.delete[row.client ? 0 : 1]!; return applyQuery( type, query, { id, clientUniqueId: row.client?.id, client: row.client?.id, roles: [role], }, [role] ); }); export const getMapping = async ( type: ResourcesKey, id: string ): Promise => { const query = mapping[type]!.listEffective[0]; const result = applyQuery(type, query, { id }); if (type !== "roles") { return result as MappingsRepresentation; } const roles = await result; const clientRoles = await Promise.all( roles .filter((r) => r.clientRole) .map(async (role) => { const client = await adminClient.clients.findOne({ id: role.containerId!, }); role.containerId = client?.clientId; return { ...client, mappings: [role] }; }) ); return { clientMappings: clientRoles, realmMappings: roles.filter((r) => !r.clientRole), }; }; export const getEffectiveRoles = async ( type: ResourcesKey, id: string ): Promise => { const query = mapping[type]!.listEffective[1]; if (type !== "roles") { return (await applyQuery(type, query, { id })).map((role) => ({ role, })); } const roles = await applyQuery(type, query, { id }); const parentRoles = await Promise.all( roles .filter((r) => r.composite) .map((r) => applyQuery(type, query, { id: r.id })) ); return [...roles, ...parentRoles.flat()].map((role) => ({ role })); }; export const getAvailableRoles = async ( type: ResourcesKey, params: Record ): Promise => { const query = mapping[type]!.listAvailable[1]; return (await applyQuery(type, query, params)).map((role) => ({ role, })); };