import React, { useState } from "react"; import { useHistory, useParams, useRouteMatch } from "react-router-dom"; import { AlertVariant, ButtonVariant, DropdownItem, PageSection, Tab, TabTitleText, } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; import { useForm } from "react-hook-form"; import { omit } from "lodash-es"; import { useAlerts } from "../components/alert/Alerts"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; import { AttributesForm, AttributeForm, } from "../components/key-value-form/AttributeForm"; import { arrayToKeyValue, keyValueToArray, } from "../components/key-value-form/key-value-convert"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { RealmRoleForm } from "./RealmRoleForm"; import { useRealm } from "../context/realm-context/RealmContext"; import { AssociatedRolesModal } from "./AssociatedRolesModal"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { AssociatedRolesTab } from "./AssociatedRolesTab"; import { UsersInRoleTab } from "./UsersInRoleTab"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import { toRealmRole } from "./routes/RealmRole"; import { ClientRoleParams, ClientRoleRoute, toClientRole, } from "./routes/ClientRole"; import { PermissionsTab } from "../components/permission-tab/PermissionTab"; export default function RealmRoleTabs() { const { t } = useTranslation("roles"); const form = useForm({ mode: "onChange", }); const { setValue, getValues, trigger, reset } = form; const history = useHistory(); const adminClient = useAdminClient(); const [role, setRole] = useState(); const { id, clientId } = useParams<{ id: string; clientId: string }>(); const { url } = useRouteMatch(); const { realm: realmName } = useRealm(); const [key, setKey] = useState(""); const refresh = () => { setKey(`${new Date().getTime()}`); }; const { addAlert, addError } = useAlerts(); const [open, setOpen] = useState(false); const convert = (role: RoleRepresentation) => { const { attributes, ...rest } = role; return { attributes: arrayToKeyValue(attributes), ...rest, }; }; const [realm, setRealm] = useState(); useFetch( async () => { const realm = await adminClient.realms.findOne({ realm: realmName }); if (!id) { return { realm }; } const role = await adminClient.roles.findOneById({ id }); return { realm, role }; }, ({ realm, role }) => { if (!realm || (!role && id)) { throw new Error(t("common:notFound")); } setRealm(realm); if (role) { const convertedRole = convert(role); setRole(convertedRole); Object.entries(convertedRole).map((entry) => { setValue(entry[0], entry[1]); }); } }, [key, url] ); const save = async () => { try { const values = getValues(); if ( values.attributes && values.attributes[values.attributes.length - 1]?.key === "" ) { setValue( "attributes", values.attributes.slice(0, values.attributes.length - 1) ); } if (!(await trigger())) { return; } const { attributes, ...rest } = values; let roleRepresentation: RoleRepresentation = rest; roleRepresentation.name = roleRepresentation.name?.trim(); if (id) { if (attributes) { roleRepresentation.attributes = keyValueToArray(attributes); } roleRepresentation = { ...omit(role!, "attributes"), ...roleRepresentation, }; if (!clientId) { await adminClient.roles.updateById({ id }, roleRepresentation); } else { await adminClient.clients.updateRole( { id: clientId, roleName: values.name! }, roleRepresentation ); } setRole(convert(roleRepresentation)); } else { let createdRole; if (!clientId) { await adminClient.roles.create(roleRepresentation); createdRole = await adminClient.roles.findOneByName({ name: values.name!, }); } else { await adminClient.clients.createRole({ id: clientId, name: values.name, }); if (values.description) { await adminClient.clients.updateRole( { id: clientId, roleName: values.name! }, roleRepresentation ); } createdRole = await adminClient.clients.findRole({ id: clientId, roleName: values.name!, }); } if (!createdRole) { throw new Error(t("common:notFound")); } setRole(convert(createdRole)); history.push( url.substr(0, url.lastIndexOf("/") + 1) + createdRole.id + "/details" ); } addAlert(t(id ? "roleSaveSuccess" : "roleCreated"), AlertVariant.success); } catch (error) { addError(`roles:${id ? "roleSave" : "roleCreate"}Error`, error); } }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "roles:roleDeleteConfirm", messageKey: t("roles:roleDeleteConfirmDialog", { name: role?.name || t("createRole"), }), continueButtonLabel: "common:delete", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { if (!clientId) { await adminClient.roles.delById({ id }); } else { await adminClient.clients.delRole({ id: clientId, roleName: role!.name as string, }); } addAlert(t("roleDeletedSuccess"), AlertVariant.success); history.push(url.substr(0, url.indexOf("/roles") + "/roles".length)); } catch (error) { addError("roles:roleDeleteError", error); } }, }); const dropdownItems = url.includes("associated-roles") ? [ toggleDeleteAllAssociatedRolesDialog()} > {t("roles:removeAllAssociatedRoles")} , { toggleDeleteDialog(); }} > {t("deleteRole")} , ] : [ toggleModal()} > {t("addAssociatedRolesText")} , toggleDeleteDialog()} > {t("deleteRole")} , ]; const [ toggleDeleteAllAssociatedRolesDialog, DeleteAllAssociatedRolesConfirm, ] = useConfirmDialog({ titleKey: t("roles:removeAllAssociatedRoles") + "?", messageKey: t("roles:removeAllAssociatedRolesConfirmDialog", { name: role?.name || t("createRole"), }), continueButtonLabel: "common:delete", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { const additionalRoles = await adminClient.roles.getCompositeRoles({ id: role!.id!, }); await adminClient.roles.delCompositeRoles({ id }, additionalRoles); addAlert( t("compositeRoleOff"), AlertVariant.success, t("compositesRemovedAlertDescription") ); const loc = url.replace(/\/AssociatedRoles/g, "/details"); history.push(loc); refresh(); } catch (error) { addError("roles:roleDeleteError", error); } }, }); const toggleModal = () => { setOpen(!open); }; const clientRoleRouteMatch = useRouteMatch( ClientRoleRoute.path ); const toAssociatedRoles = () => { const to = clientRoleRouteMatch ? toClientRole({ ...clientRoleRouteMatch.params, tab: "associated-roles", }) : toRealmRole({ realm: realm?.realm!, id, tab: "associated-roles", }); history.push(to); }; const addComposites = async (composites: RoleRepresentation[]) => { try { await adminClient.roles.createComposite( { roleId: role?.id!, realm: realm!.realm }, composites ); refresh(); toAssociatedRoles(); addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success); } catch (error) { addError("roles:addAssociatedRolesError", error); } }; const isDefaultRole = (name: string) => realm?.defaultRole!.name === name; if (!realm) { return ; } if (!role) { return ( reset(role)} form={form} save={save} editMode={false} /> ); } return ( <> {open && ( )} {id && ( {t("common:details")}} > reset(role)} form={form} save={save} editMode={true} /> {role.composite && ( {t("associatedRolesText")}} > )} {!isDefaultRole(role.name!) && ( {t("common:attributes")}} > reset(role)} /> )} {!isDefaultRole(role.name!) && ( {t("usersInRole")}} > )} {t("common:permissions")}} > )} ); }