Added saml export dialog (#3648)

This commit is contained in:
Erik Jan de Wit 2022-10-26 15:31:33 +02:00 committed by GitHub
parent 7730509796
commit ef8574c188
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 4 deletions

View file

@ -128,6 +128,7 @@
"archiveFormat": "Java keystore or PKCS12 archive format.",
"keyAlias": "Archive alias for your private key and certificate.",
"keyPassword": "Password to access the private key in the archive",
"realmCertificateAlias": "Realm certificate is stored in archive too. This is the alias to it.",
"storePassword": "Password to access the archive itself",
"consentRequired": "If enabled, users have to consent to client access.",
"displayOnClient": "Applicable only if 'Consent Required' is on for this client. If this switch is off, the consent screen will contain just the consents corresponding to configured client scopes. If on, there will be also one item on the consent screen about this client itself.",

View file

@ -396,6 +396,10 @@
"generate": "Generate",
"import": "Import"
},
"realmCertificateAlias": "Realm certificate alias",
"exportSamlKeyTitle": "Export SAML Keys",
"samlKeysExportSuccess": "Successfully exported keys",
"samlKeysExportError": "Could not export keys due to: {{error}}",
"confirm": "Confirm",
"browse": "Browse",
"importKey": "Import key",

View file

@ -0,0 +1,91 @@
import { useTranslation } from "react-i18next";
import { FormProvider, useForm } from "react-hook-form";
import { Button, Modal, Form } from "@patternfly/react-core";
import FileSaver from "file-saver";
import KeyStoreConfig from "@keycloak/keycloak-admin-client/lib/defs/keystoreConfig";
import { KeyForm } from "./GenerateKeyDialog";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useAdminClient } from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts";
type ExportSamlKeyDialogProps = {
clientId: string;
close: () => void;
};
export const ExportSamlKeyDialog = ({
clientId,
close,
}: ExportSamlKeyDialogProps) => {
const { t } = useTranslation("clients");
const { realm } = useRealm();
const { adminClient } = useAdminClient();
const { addAlert, addError } = useAlerts();
const form = useForm<KeyStoreConfig>({
defaultValues: { realmAlias: realm },
});
const download = async (config: KeyStoreConfig) => {
try {
const keyStore = await adminClient.clients.downloadKey(
{
id: clientId,
attr: "saml.signing",
},
config
);
FileSaver.saveAs(
new Blob([keyStore], { type: "application/octet-stream" }),
`keystore.${config.format == "PKCS12" ? "p12" : "jks"}`
);
addAlert(t("samlKeysExportSuccess"));
close();
} catch (error) {
addError("clients:samlKeysExportError", error);
}
};
return (
<Modal
variant="medium"
title={t("exportSamlKeyTitle")}
isOpen
onClose={close}
actions={[
<Button
id="modal-confirm"
data-testid="confirm"
key="confirm"
type="submit"
form="export-saml-key-form"
>
{t("common:export")}
</Button>,
<Button
id="modal-cancel"
data-testid="cancel"
key="cancel"
variant="link"
onClick={() => {
close();
}}
>
{t("common:cancel")}
</Button>,
]}
>
<Form
id="export-saml-key-form"
className="pf-u-pt-lg"
onSubmit={form.handleSubmit(download)}
>
<FormProvider {...form}>
<KeyForm isSaml />
</FormProvider>
</Form>
</Modal>
);
};

View file

@ -33,9 +33,10 @@ type GenerateKeyDialogProps = {
type KeyFormProps = {
useFile?: boolean;
isSaml?: boolean;
};
export const KeyForm = ({ useFile = false }: KeyFormProps) => {
export const KeyForm = ({ isSaml = false, useFile = false }: KeyFormProps) => {
const { t } = useTranslation("clients");
const [filename, setFilename] = useState<string>();
@ -113,7 +114,7 @@ export const KeyForm = ({ useFile = false }: KeyFormProps) => {
/>
</FormGroup>
)}
<StoreSettings hidePassword={useFile} />
<StoreSettings hidePassword={useFile} isSaml={isSaml} />
</Form>
);
};

View file

@ -28,6 +28,8 @@ import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"
import { useAlerts } from "../../components/alert/Alerts";
import { SamlImportKeyDialog } from "./SamlImportKeyDialog";
import { convertAttributeNameToForm } from "../../util";
import useToggle from "../../utils/useToggle";
import { ExportSamlKeyDialog } from "./ExportSamlKeyDialog";
type SamlKeysProps = {
clientId: string;
@ -51,6 +53,7 @@ const KEYS_MAPPING: { [key in KeyTypes]: { [index: string]: string } } = {
};
type KeySectionProps = {
clientId: string;
keyInfo?: CertificateRepresentation;
attr: KeyTypes;
onChanged: (key: KeyTypes) => void;
@ -59,6 +62,7 @@ type KeySectionProps = {
};
const KeySection = ({
clientId,
keyInfo,
attr,
onChanged,
@ -71,9 +75,14 @@ const KeySection = ({
const key = KEYS_MAPPING[attr].key;
const name = KEYS_MAPPING[attr].name;
const [showImportDialog, toggleImportDialog] = useToggle();
const section = watch(name);
return (
<>
{showImportDialog && (
<ExportSamlKeyDialog clientId={clientId} close={toggleImportDialog} />
)}
<FormPanel title={t(title)} className="kc-form-panel__panel">
<TextContent className="pf-u-pb-lg">
<Text>{t(`${title}Explain`)}</Text>
@ -132,7 +141,9 @@ const KeySection = ({
<Button variant="secondary" onClick={() => onImport(attr)}>
{t("importKey")}
</Button>
<Button variant="tertiary">{t("common:export")}</Button>
<Button variant="tertiary" onClick={toggleImportDialog}>
{t("common:export")}
</Button>
</ActionGroup>
</Form>
</CardBody>
@ -244,6 +255,7 @@ export const SamlKeys = ({ clientId, save }: SamlKeysProps) => {
/>
)}
<KeySection
clientId={clientId}
keyInfo={keyInfo?.[index]}
attr={attr}
onChanged={setIsChanged}

View file

@ -9,11 +9,13 @@ import { KeycloakTextInput } from "../../components/keycloak-text-input/Keycloak
export const StoreSettings = ({
hidePassword = false,
isSaml = false,
}: {
hidePassword?: boolean;
isSaml?: boolean;
}) => {
const { t } = useTranslation("clients");
const { register } = useFormContext<KeyStoreConfig>();
const { register, errors } = useFormContext<KeyStoreConfig>();
return (
<>
@ -27,6 +29,8 @@ export const StoreSettings = ({
fieldLabelId="clients:keyAlias"
/>
}
helperTextInvalid={t("common:required")}
validated={errors.keyAlias ? "error" : "default"}
>
<KeycloakTextInput
data-testid="keyAlias"
@ -34,6 +38,7 @@ export const StoreSettings = ({
id="keyAlias"
name="keyAlias"
ref={register({ required: true })}
validated={errors.keyAlias ? "error" : "default"}
/>
</FormGroup>
{!hidePassword && (
@ -47,12 +52,35 @@ export const StoreSettings = ({
fieldLabelId="clients:keyPassword"
/>
}
helperTextInvalid={t("common:required")}
validated={errors.keyPassword ? "error" : "default"}
>
<PasswordInput
data-testid="keyPassword"
id="keyPassword"
name="keyPassword"
ref={register({ required: true })}
validated={errors.keyPassword ? "error" : "default"}
/>
</FormGroup>
)}
{isSaml && (
<FormGroup
label={t("realmCertificateAlias")}
fieldId="realmCertificateAlias"
labelIcon={
<HelpItem
helpText="clients-help:realmCertificateAlias"
fieldLabelId="clients:realmCertificateAlias"
/>
}
>
<KeycloakTextInput
data-testid="realmCertificateAlias"
type="text"
id="realmCertificateAlias"
name="realmAlias"
ref={register()}
/>
</FormGroup>
)}
@ -66,12 +94,15 @@ export const StoreSettings = ({
fieldLabelId="clients:storePassword"
/>
}
helperTextInvalid={t("common:required")}
validated={errors.storePassword ? "error" : "default"}
>
<PasswordInput
data-testid="storePassword"
id="storePassword"
name="storePassword"
ref={register({ required: true })}
validated={errors.storePassword ? "error" : "default"}
/>
</FormGroup>
</>