import type ClientPolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyRepresentation"; import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation"; import { ActionGroup, AlertVariant, Button, ButtonVariant, DataList, DataListCell, DataListItem, DataListItemCells, DataListItemRow, Divider, Flex, FlexItem, FormGroup, Label, PageSection, Text, TextVariants, } from "@patternfly/react-core"; import { DropdownItem } from "@patternfly/react-core/deprecated"; import { PlusCircleIcon, TrashIcon } from "@patternfly/react-icons"; import { useState } from "react"; import { Controller, FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { Link, useNavigate } from "react-router-dom"; import { HelpItem, KeycloakTextArea, TextControl } from "ui-shared"; import { adminClient } from "../admin-client"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { FormAccess } from "../components/form/FormAccess"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useFetch } from "../utils/useFetch"; import { useParams } from "../utils/useParams"; import { AddClientProfileModal } from "./AddClientProfileModal"; import { toNewClientPolicyCondition } from "./routes/AddCondition"; import { toClientPolicies } from "./routes/ClientPolicies"; import { toClientProfile } from "./routes/ClientProfile"; import { EditClientPolicyParams, toEditClientPolicy, } from "./routes/EditClientPolicy"; import { toEditClientPolicyCondition } from "./routes/EditCondition"; import "./realm-settings-section.css"; type FormFields = Required; const defaultValues: FormFields = { name: "", description: "", conditions: [], enabled: true, profiles: [], }; type PolicyDetailAttributes = { idx: number; name: string; }; export default function NewClientPolicy() { const { t } = useTranslation(); const { realm } = useRealm(); const { addAlert, addError } = useAlerts(); const [isGlobalPolicy, setIsGlobalPolicy] = useState(false); const [policies, setPolicies] = useState(); const [globalPolicies, setGlobalPolicies] = useState(); const [allPolicies, setAllPolicies] = useState(); const [clientProfiles, setClientProfiles] = useState< ClientProfileRepresentation[] >([]); const [currentPolicy, setCurrentPolicy] = useState(); const [ showAddConditionsAndProfilesForm, setShowAddConditionsAndProfilesForm, ] = useState(false); const [conditionToDelete, setConditionToDelete] = useState(); const [profilesModalOpen, setProfilesModalOpen] = useState(false); const [profileToDelete, setProfileToDelete] = useState(); const { policyName } = useParams(); const navigate = useNavigate(); const form = useForm({ mode: "onChange", defaultValues, }); const { handleSubmit } = form; const formValues = form.getValues(); useFetch( async () => { const [policies, profiles] = await Promise.all([ adminClient.clientPolicies.listPolicies({ includeGlobalPolicies: true, }), adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true, }), ]); return { policies, profiles }; }, ({ policies, profiles }) => { let currentPolicy = policies.policies?.find( (item) => item.name === policyName, ); if (currentPolicy === undefined) { currentPolicy = policies.globalPolicies?.find( (item) => item.name === policyName, ); setIsGlobalPolicy(currentPolicy !== undefined); } const allClientProfiles = [ ...(profiles.globalProfiles ?? []), ...(profiles.profiles ?? []), ]; const allClientPolicies = [ ...(policies.globalPolicies ?? []), ...(policies.policies ?? []), ]; setPolicies(policies.policies ?? []); setGlobalPolicies(policies.globalPolicies ?? []); setAllPolicies(allClientPolicies); if (currentPolicy) { setupForm(currentPolicy); setClientProfiles(allClientProfiles); setCurrentPolicy(currentPolicy); setShowAddConditionsAndProfilesForm(true); } }, [], ); const setupForm = (policy: ClientPolicyRepresentation) => { form.reset(policy); }; const policy = (allPolicies || []).filter( (policy) => policy.name === policyName, ); const policyConditions = policy[0]?.conditions || []; const policyProfiles = policy[0]?.profiles || []; const serverInfo = useServerInfo(); const conditionTypes = serverInfo.componentTypes?.[ "org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider" ]; const save = async () => { const createdForm = form.getValues(); const createdPolicy = { ...createdForm, }; const getAllPolicies = () => { const policyNameExists = policies?.some( (policy) => policy.name === createdPolicy.name, ); if (policyNameExists) { return policies?.map((policy) => policy.name === createdPolicy.name ? createdPolicy : policy, ); } else if (createdForm.name !== policyName) { return policies ?.filter((item) => item.name !== policyName) .concat(createdForm); } return policies?.concat(createdForm); }; try { await adminClient.clientPolicies.updatePolicy({ policies: getAllPolicies(), }); addAlert( policyName ? t("updateClientPolicySuccess") : t("createClientPolicySuccess"), AlertVariant.success, ); navigate(toEditClientPolicy({ realm, policyName: createdForm.name! })); setShowAddConditionsAndProfilesForm(true); } catch (error) { addError("createClientPolicyError", error); } }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: t("deleteClientPolicyConfirmTitle"), messageKey: t("deleteClientPolicyConfirm", { policyName: policyName, }), continueButtonLabel: t("delete"), continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { const updatedPolicies = policies?.filter( (policy) => policy.name !== policyName, ); try { await adminClient.clientPolicies.updatePolicy({ policies: updatedPolicies, }); addAlert(t("deleteClientPolicySuccess"), AlertVariant.success); navigate( toClientPolicies({ realm, tab: "policies", }), ); } catch (error) { addError(t("deleteClientPolicyError"), error); } }, }); const [toggleDeleteConditionDialog, DeleteConditionConfirm] = useConfirmDialog({ titleKey: t("deleteClientPolicyConditionConfirmTitle"), messageKey: t("deleteClientPolicyConditionConfirm", { condition: conditionToDelete?.name, }), continueButtonLabel: t("delete"), continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { if (conditionToDelete?.name) { currentPolicy?.conditions?.splice(conditionToDelete.idx!, 1); try { await adminClient.clientPolicies.updatePolicy({ policies: policies, }); addAlert(t("deleteConditionSuccess"), AlertVariant.success); navigate( toEditClientPolicy({ realm, policyName: formValues.name! }), ); } catch (error) { addError(t("deleteConditionError"), error); } } else { const updatedPolicies = policies?.filter( (policy) => policy.name !== policyName, ); try { await adminClient.clientPolicies.updatePolicy({ policies: updatedPolicies, }); addAlert(t("deleteClientSuccess"), AlertVariant.success); navigate( toClientPolicies({ realm, tab: "policies", }), ); } catch (error) { addError(t("deleteClientError"), error); } } }, }); const [toggleDeleteProfileDialog, DeleteProfileConfirm] = useConfirmDialog({ titleKey: t("deleteClientPolicyProfileConfirmTitle"), messageKey: t("deleteClientPolicyProfileConfirm", { profileName: profileToDelete?.name, policyName, }), continueButtonLabel: t("delete"), continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { if (profileToDelete?.name) { currentPolicy?.profiles?.splice(profileToDelete.idx!, 1); try { await adminClient.clientPolicies.updatePolicy({ policies: policies, }); addAlert(t("deleteClientPolicyProfileSuccess"), AlertVariant.success); form.setValue("profiles", currentPolicy?.profiles || []); navigate(toEditClientPolicy({ realm, policyName: formValues.name! })); } catch (error) { addError(t("deleteClientPolicyProfileError"), error); } } else { const updatedPolicies = policies?.filter( (policy) => policy.name !== policyName, ); try { await adminClient.clientPolicies.updatePolicy({ policies: updatedPolicies, }); addAlert(t("deleteClientSuccess"), AlertVariant.success); navigate( toClientPolicies({ realm, tab: "policies", }), ); } catch (error) { addError(t("deleteClientError"), error); } } }, }); const reset = () => { if (currentPolicy?.name !== undefined) { form.setValue("name", currentPolicy.name); } if (currentPolicy?.description !== undefined) { form.setValue("description", currentPolicy.description); } }; const toggleModal = () => { setProfilesModalOpen(!profilesModalOpen); }; const addProfiles = async (profiles: string[]) => { const createdPolicy = { ...currentPolicy, profiles: policyProfiles.concat(profiles), conditions: currentPolicy?.conditions, }; const index = policies?.findIndex( (policy) => createdPolicy.name === policy.name, ); if (index === undefined || index === -1) { return; } const newPolicies = [ ...(policies || []).slice(0, index), createdPolicy, ...(policies || []).slice(index + 1), ]; try { await adminClient.clientPolicies.updatePolicy({ policies: newPolicies, }); setPolicies(newPolicies); const allClientPolicies = [...(globalPolicies || []), ...newPolicies]; setAllPolicies(allClientPolicies); setCurrentPolicy(createdPolicy); form.setValue("profiles", createdPolicy.profiles); navigate(toEditClientPolicy({ realm, policyName: formValues.name! })); addAlert(t("addClientProfileSuccess"), AlertVariant.success); } catch (error) { addError("addClientProfileError", error); } }; const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({ titleKey: "disablePolicyConfirmTitle", messageKey: "disablePolicyConfirm", continueButtonLabel: "disable", onConfirm: () => { form.setValue("enabled", !form.getValues().enabled); save(); }, }); if (!policies) { return ; } return ( <> { addProfiles(profiles.map((item) => item.name!)); }} allProfiles={policyProfiles} open={profilesModalOpen} toggleDialog={toggleModal} /> ( <> {t("global")} ) : ( "" ), }, ]} divider dropdownItems={ (showAddConditionsAndProfilesForm || policyName) && !isGlobalPolicy ? [ { toggleDeleteDialog(); }} data-testid="deleteClientPolicyDropdown" > {t("deleteClientPolicy")} , ] : undefined } isReadOnly={isGlobalPolicy} isEnabled={field.value} onToggle={(value) => { if (!value) { toggleDisableDialog(); } else { field.onChange(value); save(); } }} /> )} /> policies.some((policy) => policy.name === value) ? t("createClientProfileNameHelperText").toString() : true, }} /> {(showAddConditionsAndProfilesForm || form.formState.isSubmitted) && ( <> {t("conditions")} {!isGlobalPolicy && ( )} {policyConditions.length > 0 ? ( {policyConditions.map((condition, idx) => ( {Object.keys(condition.configuration!) .length !== 0 ? ( {condition.condition} ) : ( condition.condition )} {conditionTypes?.map( (type) => type.id === condition.condition && ( <> {!isGlobalPolicy && ( )} ), )} , ]} /> ))} ) : ( <> {t("emptyConditions")} )} )} {(showAddConditionsAndProfilesForm || form.formState.isSubmitted) && ( <> {t("clientProfiles")} {!isGlobalPolicy && ( )} {policyProfiles.length > 0 ? ( {policyProfiles.map((profile, idx) => ( {profile && ( {profile} )} {policyProfiles .filter((type) => type === profile) .map((type) => ( <> type === profile.name, )?.description } fieldLabelId={profile} /> {!isGlobalPolicy && ( )} ))} , ]} /> ))} ) : ( <> {t("emptyProfiles")} )} )} ); }