import ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation"; import type ProtocolMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation"; import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; import type { ProtocolMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation"; import { Alert, AlertVariant, ButtonVariant, PageSection, Tab, TabTitleText, } from "@patternfly/react-core"; import { DropdownItem } from "@patternfly/react-core/deprecated"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { useHelp } from "@keycloak/keycloak-ui-shared"; import { adminClient } from "../admin-client"; import { useAlerts } from "../components/alert/Alerts"; import { AllClientScopes, ClientScope, ClientScopeDefaultOptionalType, changeScope, } from "../components/client-scope/ClientScopeTypes"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { RoleMapping, Row } from "../components/role-mapping/RoleMapping"; import { RoutableTabs, useRoutableTab, } from "../components/routable-tabs/RoutableTabs"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useRealm } from "../context/realm-context/RealmContext"; import { convertFormValuesToObject } from "../util"; import { useFetch } from "../utils/useFetch"; import { useParams } from "../utils/useParams"; import { MapperList } from "./details/MapperList"; import { ScopeForm } from "./details/ScopeForm"; import { ClientScopeParams, ClientScopeTab, toClientScope, } from "./routes/ClientScope"; import { toMapper } from "./routes/Mapper"; import { toClientScopes } from "./routes/ClientScopes"; export default function EditClientScope() { const { t } = useTranslation(); const navigate = useNavigate(); const { realm } = useRealm(); const { id } = useParams(); const { addAlert, addError } = useAlerts(); const { enabled } = useHelp(); const [clientScope, setClientScope] = useState(); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); useFetch( async () => { const clientScope = await adminClient.clientScopes.findOne({ id }); if (!clientScope) { throw new Error(t("notFound")); } return { ...clientScope, type: await determineScopeType(clientScope), }; }, (clientScope) => { setClientScope(clientScope); }, [key, id], ); async function determineScopeType(clientScope: ClientScopeRepresentation) { const defaultScopes = await adminClient.clientScopes.listDefaultClientScopes(); const hasDefaultScope = defaultScopes.find( (defaultScope) => defaultScope.name === clientScope.name, ); if (hasDefaultScope) { return ClientScope.default; } const optionalScopes = await adminClient.clientScopes.listDefaultOptionalClientScopes(); const hasOptionalScope = optionalScopes.find( (optionalScope) => optionalScope.name === clientScope.name, ); return hasOptionalScope ? ClientScope.optional : AllClientScopes.none; } const useTab = (tab: ClientScopeTab) => useRoutableTab( toClientScope({ realm, id, tab, }), ); const settingsTab = useTab("settings"); const mappersTab = useTab("mappers"); const scopeTab = useTab("scope"); const onSubmit = async (formData: ClientScopeDefaultOptionalType) => { const clientScope = convertFormValuesToObject({ ...formData, name: formData.name?.trim().replace(/ /g, "_"), }); try { await adminClient.clientScopes.update({ id }, clientScope); await changeScope({ ...clientScope, id }, clientScope.type); addAlert(t("updateSuccessClientScope"), AlertVariant.success); } catch (error) { addError("updateErrorClientScope", error); } }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: t("deleteClientScope", { count: 1, name: clientScope?.name, }), messageKey: "deleteConfirmClientScopes", continueButtonLabel: "delete", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { await adminClient.clientScopes.del({ id }); addAlert(t("deletedSuccessClientScope"), AlertVariant.success); navigate(toClientScopes({ realm })); } catch (error) { addError("deleteErrorClientScope", error); } }, }); const assignRoles = async (rows: Row[]) => { try { const realmRoles = rows .filter((row) => row.client === undefined) .map((row) => row.role as RoleMappingPayload) .flat(); await adminClient.clientScopes.addRealmScopeMappings( { id, }, realmRoles, ); await Promise.all( rows .filter((row) => row.client !== undefined) .map((row) => adminClient.clientScopes.addClientScopeMappings( { id, client: row.client!.id!, }, [row.role as RoleMappingPayload], ), ), ); addAlert(t("roleMappingUpdatedSuccess"), AlertVariant.success); } catch (error) { addError("roleMappingUpdatedError", error); } }; const addMappers = async ( mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[], ): Promise => { if (!Array.isArray(mappers)) { const mapper = mappers as ProtocolMapperTypeRepresentation; navigate( toMapper({ realm, id: clientScope!.id!, mapperId: mapper.id!, }), ); } else { try { await adminClient.clientScopes.addMultipleProtocolMappers( { id: clientScope!.id! }, mappers as ProtocolMapperRepresentation[], ); refresh(); addAlert(t("mappingCreatedSuccess"), AlertVariant.success); } catch (error) { addError("mappingCreatedError", error); } } }; const onDelete = async (mapper: ProtocolMapperRepresentation) => { try { await adminClient.clientScopes.delProtocolMapper({ id: clientScope!.id!, mapperId: mapper.id!, }); addAlert(t("mappingDeletedSuccess"), AlertVariant.success); refresh(); } catch (error) { addError("mappingDeletedError", error); } return true; }; if (!clientScope) { return ; } return ( <> {t("delete")} , ]} badges={[{ text: clientScope.protocol }]} divider={false} /> {t("settings")}} {...settingsTab} > {t("mappers")}} {...mappersTab} > toMapper({ realm, id: clientScope.id!, mapperId: id! }) } /> {t("scope")}} {...scopeTab} > {enabled && ( )} ); }