import type CertificateRepresentation from "@keycloak/keycloak-admin-client/lib/defs/certificateRepresentation"; import { ActionGroup, AlertVariant, Button, Card, CardBody, Form, FormGroup, PageSection, Switch, Text, TextContent, } from "@patternfly/react-core"; import { saveAs } from "file-saver"; import { Fragment, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { HelpItem } 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 { FormPanel } from "../../components/scroll-form/FormPanel"; import { convertAttributeNameToForm } from "../../util"; import { useFetch } from "../../utils/useFetch"; import useToggle from "../../utils/useToggle"; import { FormFields } from "../ClientDetails"; import { Certificate } from "./Certificate"; import { ExportSamlKeyDialog } from "./ExportSamlKeyDialog"; import { SamlImportKeyDialog } from "./SamlImportKeyDialog"; import { SamlKeysDialog } from "./SamlKeysDialog"; type SamlKeysProps = { clientId: string; save: () => void; }; const KEYS = ["saml.signing", "saml.encryption"] as const; export type KeyTypes = (typeof KEYS)[number]; const KEYS_MAPPING: { [key in KeyTypes]: { [index: string]: string } } = { "saml.signing": { name: convertAttributeNameToForm("attributes.saml.client.signature"), title: "signingKeysConfig", key: "clientSignature", }, "saml.encryption": { name: convertAttributeNameToForm("attributes.saml.encrypt"), title: "encryptionKeysConfig", key: "encryptAssertions", }, }; type KeySectionProps = { clientId: string; keyInfo?: CertificateRepresentation; attr: KeyTypes; onChanged: (key: KeyTypes) => void; onGenerate: (key: KeyTypes, regenerate: boolean) => void; onImport: (key: KeyTypes) => void; }; const KeySection = ({ clientId, keyInfo, attr, onChanged, onGenerate, onImport, }: KeySectionProps) => { const { t } = useTranslation(); const { control, watch } = useFormContext(); const title = KEYS_MAPPING[attr].title; const key = KEYS_MAPPING[attr].key; const name = KEYS_MAPPING[attr].name; const [showImportDialog, toggleImportDialog] = useToggle(); const section = watch(name as keyof FormFields); return ( <> {showImportDialog && ( )} {t(`${title}Explain`)} } label={t(key)} fieldId={key} hasNoPaddingTop > ( { const v = value.toString(); if (v === "true") { onChanged(attr); field.onChange(v); } else { onGenerate(attr, false); } }} aria-label={t(key)} /> )} /> {keyInfo?.certificate && section === "true" && (
)} ); }; export const SamlKeys = ({ clientId, save }: SamlKeysProps) => { const { t } = useTranslation(); const [isChanged, setIsChanged] = useState(); const [keyInfo, setKeyInfo] = useState(); const [selectedType, setSelectedType] = useState(); const [openImport, setImportOpen] = useState(); const [refresh, setRefresh] = useState(0); const { setValue } = useFormContext(); const { addAlert, addError } = useAlerts(); useFetch( () => Promise.all( KEYS.map((attr) => adminClient.clients.getKeyInfo({ id: clientId, attr }), ), ), (info) => setKeyInfo(info), [refresh], ); const generate = async (attr: KeyTypes) => { const index = KEYS.indexOf(attr); try { const info = [...(keyInfo || [])]; info[index] = await adminClient.clients.generateKey({ id: clientId, attr, }); setKeyInfo(info); saveAs( new Blob([info[index].privateKey!], { type: "application/octet-stream", }), "private.key", ); addAlert(t("generateSuccess"), AlertVariant.success); } catch (error) { addError("generateError", error); } }; const key = selectedType ? KEYS_MAPPING[selectedType].key : ""; const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({ titleKey: t("disableSigning", { key: t(key), }), messageKey: t("disableSigningExplain", { key: t(key), }), continueButtonLabel: "common:yes", cancelButtonLabel: "common:no", onConfirm: () => { setValue(KEYS_MAPPING[selectedType!].name, "false"); save(); }, }); const [toggleReGenerateDialog, ReGenerateConfirm] = useConfirmDialog({ titleKey: "reGenerateSigning", messageKey: "reGenerateSigningExplain", continueButtonLabel: "common:yes", cancelButtonLabel: "common:no", onConfirm: () => { generate(selectedType!); }, }); return ( {isChanged && ( { setIsChanged(undefined); save(); setRefresh(refresh + 1); }} onCancel={() => { setValue(KEYS_MAPPING[selectedType!].name, "false"); setIsChanged(undefined); }} /> )} {KEYS.map((attr, index) => ( {openImport === attr && ( setImportOpen(undefined)} /> )} { setSelectedType(type); if (!isNew) { toggleDisableDialog(); } else { toggleReGenerateDialog(); } }} onImport={() => setImportOpen(attr)} /> ))} ); };