import { useState } from "react"; import { Link } from "react-router-dom"; import { useNavigate } from "react-router-dom-v5-compat"; import { useTranslation } from "react-i18next"; import { AlertVariant, ButtonVariant, DescriptionList, Dropdown, DropdownItem, DropdownToggle, PageSection, ToolbarItem, } from "@patternfly/react-core"; import { ExpandableRowContent, TableComposable, Tbody, Td, Th, Thead, Tr, } from "@patternfly/react-table"; import type PolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyRepresentation"; import type PolicyProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyProviderRepresentation"; import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { PaginatingTableToolbar } from "../../components/table-toolbar/PaginatingTableToolbar"; import { useAlerts } from "../../components/alert/Alerts"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import useToggle from "../../utils/useToggle"; import { useRealm } from "../../context/realm-context/RealmContext"; import { SearchDropdown, SearchForm } from "./SearchDropdown"; import { MoreLabel } from "./MoreLabel"; import { DetailDescriptionLink } from "./DetailDescription"; import { EmptyPermissionsState } from "./EmptyPermissionsState"; import { toNewPermission } from "../routes/NewPermission"; import { toPermissionDetails } from "../routes/PermissionDetails"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { toPolicyDetails } from "../routes/PolicyDetails"; import "./permissions.css"; type PermissionsProps = { clientId: string; }; type ExpandablePolicyRepresentation = PolicyRepresentation & { associatedPolicies?: PolicyRepresentation[]; isExpanded: boolean; }; export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => { const { t } = useTranslation("clients"); const navigate = useNavigate(); const { adminClient } = useAdminClient(); const { addAlert, addError } = useAlerts(); const { realm } = useRealm(); const [permissions, setPermissions] = useState(); const [selectedPermission, setSelectedPermission] = useState(); const [policyProviders, setPolicyProviders] = useState(); const [disabledCreate, setDisabledCreate] = useState<{ resources: boolean; scopes: boolean; }>(); const [createOpen, toggleCreate] = useToggle(); const [search, setSearch] = useState({}); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); const [max, setMax] = useState(10); const [first, setFirst] = useState(0); const AssociatedPoliciesRenderer = ({ row, }: { row: ExpandablePolicyRepresentation; }) => { return ( <> {row.associatedPolicies?.[0]?.name}{" "} ); }; useFetch( async () => { const permissions = await adminClient.clients.findPermissions({ first, max: max + 1, id: clientId, ...search, }); return await Promise.all( permissions.map(async (permission) => { const associatedPolicies = await adminClient.clients.getAssociatedPolicies({ id: clientId, permissionId: permission.id!, }); return { ...permission, associatedPolicies, isExpanded: false, }; }) ); }, setPermissions, [key, search, first, max] ); useFetch( async () => { const params = { first: 0, max: 1, }; const [policies, resources, scopes] = await Promise.all([ adminClient.clients.listPolicyProviders({ id: clientId, }), adminClient.clients.listResources({ ...params, id: clientId }), adminClient.clients.listAllScopes({ ...params, id: clientId }), ]); return { policies: policies.filter( (p) => p.type === "resource" || p.type === "scope" ), resources: resources.length !== 1, scopes: scopes.length !== 1, }; }, ({ policies, resources, scopes }) => { setPolicyProviders(policies); setDisabledCreate({ resources, scopes }); }, [] ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "clients:deletePermission", messageKey: t("deletePermissionConfirm", { permission: selectedPermission?.name, }), continueButtonVariant: ButtonVariant.danger, continueButtonLabel: "clients:confirm", onConfirm: async () => { try { await adminClient.clients.delPermission({ id: clientId, type: selectedPermission?.type!, permissionId: selectedPermission?.id!, }); addAlert(t("permissionDeletedSuccess"), AlertVariant.success); refresh(); } catch (error) { addError("clients:permissionDeletedError", error); } }, }); if (!permissions) { return ; } const noData = permissions.length === 0; const searching = Object.keys(search).length !== 0; return ( {(!noData || searching) && ( { setFirst(first); setMax(max); }} toolbarItem={ <> {t("createPermission")} } isOpen={createOpen} dropdownItems={[ navigate( toNewPermission({ realm, id: clientId, permissionType: "resource", }) ) } > {t("createResourceBasedPermission")} , navigate( toNewPermission({ realm, id: clientId, permissionType: "scope", }) ) } > {t("createScopeBasedPermission")} , ]} /> } > {!noData && ( {t("common:name")} {t("common:type")} {t("associatedPolicy")} {t("common:description")} {permissions.map((permission, rowIndex) => ( { const rows = permissions.map((p, index) => index === rowIndex ? { ...p, isExpanded: !p.isExpanded } : p ); setPermissions(rows); }, }} /> {permission.name} { policyProviders?.find((p) => p.type === permission.type) ?.name } {permission.description} { setSelectedPermission(permission); toggleDeleteDialog(); }, }, ], }} > {permission.isExpanded && ( p.name!} link={(p) => toPolicyDetails({ id: clientId, realm, policyId: p.id!, policyType: p.type!, }) } /> )} ))} )} )} {noData && !searching && ( )} {noData && searching && ( )} ); };