Added saml export dialog (#3648)
This commit is contained in:
parent
7730509796
commit
ef8574c188
6 changed files with 144 additions and 4 deletions
|
@ -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.",
|
||||
|
|
|
@ -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",
|
||||
|
|
91
apps/admin-ui/src/clients/keys/ExportSamlKeyDialog.tsx
Normal file
91
apps/admin-ui/src/clients/keys/ExportSamlKeyDialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
Loading…
Reference in a new issue