better form convert (#1640)

* better form convert

* fixed other forms

* changed to use npm package

* better form convert

* merge fix

* fixed mapper test
This commit is contained in:
Erik Jan de Wit 2021-12-08 16:08:42 +01:00 committed by GitHub
parent 1f568d8888
commit 242c1d8445
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 433 additions and 484 deletions

27
package-lock.json generated
View file

@ -15,6 +15,7 @@
"@patternfly/react-table": "^4.44.4",
"dagre": "^0.8.5",
"file-saver": "^2.0.5",
"flat": "^5.0.2",
"i18next": "^21.5.5",
"lodash": "^4.17.20",
"moment": "^2.29.1",
@ -41,6 +42,7 @@
"@testing-library/react-hooks": "^7.0.2",
"@types/dagre": "^0.7.45",
"@types/file-saver": "^2.0.4",
"@types/flat": "^5.0.2",
"@types/lodash": "^4.14.177",
"@types/node": "^16.11.12",
"@types/react": "^17.0.37",
@ -4873,6 +4875,12 @@
"integrity": "sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg==",
"dev": true
},
"node_modules/@types/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ==",
"dev": true
},
"node_modules/@types/geojson": {
"version": "7946.0.8",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
@ -10616,6 +10624,14 @@
"node": ">=8"
}
},
"node_modules/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"bin": {
"flat": "cli.js"
}
},
"node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@ -25327,6 +25343,12 @@
"integrity": "sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg==",
"dev": true
},
"@types/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ==",
"dev": true
},
"@types/geojson": {
"version": "7946.0.8",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
@ -29871,6 +29893,11 @@
"path-exists": "^4.0.0"
}
},
"flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="
},
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",

View file

@ -31,6 +31,7 @@
"@patternfly/react-table": "^4.44.4",
"dagre": "^0.8.5",
"file-saver": "^2.0.5",
"flat": "^5.0.2",
"i18next": "^21.5.5",
"lodash": "^4.17.20",
"moment": "^2.29.1",
@ -57,6 +58,7 @@
"@testing-library/react-hooks": "^7.0.2",
"@types/dagre": "^0.7.45",
"@types/file-saver": "^2.0.4",
"@types/flat": "^5.0.2",
"@types/lodash": "^4.14.177",
"@types/node": "^16.11.12",
"@types/react": "^17.0.37",

View file

@ -115,12 +115,7 @@ export default function MappingDetails() {
setConfig(config);
setMapping(mapping);
if (data) {
Object.entries(data).map(([key, value]) => {
if (key === "config") {
convertToFormValues(value, "config", setValue);
}
setValue(key, value);
});
convertToFormValues(data, setValue);
}
},
[]
@ -153,10 +148,9 @@ export default function MappingDetails() {
});
const save = async (formMapping: ProtocolMapperRepresentation) => {
const configAttributes = convertFormValuesToObject(formMapping.config);
const key = isUpdating ? "Updated" : "Created";
try {
const mapping = { ...formMapping, ...config, config: configAttributes };
const mapping = { ...config, ...convertFormValuesToObject(formMapping) };
if (isUpdating) {
isOnClientScope
? await adminClient.clientScopes.updateProtocolMapper(

View file

@ -40,7 +40,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
const { t: tc } = useTranslation("clients");
const { register, control, handleSubmit, errors, setValue } =
useForm<ClientScopeRepresentation>({
defaultValues: { attributes: { "display-on-consent-screen": "true" } },
defaultValues: { attributes: { "display.on.consent.screen": "true" } },
});
const { realm } = useRealm();
@ -51,16 +51,11 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
const displayOnConsentScreen = useWatch({
control,
name: "attributes.display-on-consent-screen",
name: "attributes.display.on.consent.screen",
});
useEffect(() => {
Object.entries(clientScope).map((entry) => {
if (entry[0] === "attributes") {
convertToFormValues(entry[1], "attributes", setValue);
}
setValue(entry[0], entry[1]);
});
convertToFormValues(clientScope, setValue);
}, [clientScope]);
return (
@ -213,7 +208,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldId="kc-display.on.consent.screen"
>
<Controller
name="attributes.display-on-consent-screen"
name="attributes.display.on.consent.screen"
control={control}
defaultValue="true"
render={({ onChange, value }) => (
@ -243,7 +238,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
ref={register}
type="text"
id="kc-consent-screen-text"
name="attributes.consent-screen-text"
name="attributes.consent.screen.text"
/>
</FormGroup>
)}
@ -260,7 +255,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldId="includeInTokenScope"
>
<Controller
name="attributes.include-in-token-scope"
name="attributes.include.in.token.scope"
control={control}
defaultValue="true"
render={({ onChange, value }) => (
@ -286,7 +281,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldId="kc-gui-order"
>
<Controller
name="attributes.gui-order"
name="attributes.gui.order"
defaultValue={1}
control={control}
render={({ onChange, value }) => {

View file

@ -107,9 +107,9 @@ export default function ClientScopeForm() {
const save = async (clientScopes: ClientScopeDefaultOptionalType) => {
try {
clientScopes.name = clientScopes.name?.trim();
clientScopes.attributes = convertFormValuesToObject(
clientScopes.attributes!
);
clientScopes = convertFormValuesToObject(
clientScopes
) as ClientScopeDefaultOptionalType;
if (id) {
await adminClient.clientScopes.update({ id }, clientScopes);

View file

@ -104,9 +104,7 @@ export const AdvancedTab = ({
const resetFields = (names: string[]) => {
const values: { [name: string]: string } = {};
for (const name of names) {
values[`attributes.${name}`] = attributes
? attributes[name.replace(/-/g, ".")] || ""
: "";
values[`attributes.${name}`] = attributes?.[name];
}
reset(values);
};
@ -385,7 +383,9 @@ export const AdvancedTab = ({
control={control}
save={() => save()}
reset={() =>
convertToFormValues(attributes, "attributes", setValue)
convertToFormValues(attributes, (key, value) =>
setValue(`attributes.${key}`, value)
)
}
/>
</>
@ -399,7 +399,9 @@ export const AdvancedTab = ({
control={control}
save={() => save()}
reset={() =>
convertToFormValues(attributes, "attributes", setValue)
convertToFormValues(attributes, (key, value) =>
setValue(`attributes.${key}`, value)
)
}
/>
</>
@ -414,7 +416,7 @@ export const AdvancedTab = ({
control={control}
save={() => save()}
reset={() =>
resetFields(["exclude-session-state-from-auth-response"])
resetFields(["exclude.session.state.from.auth.response"])
}
/>
</>
@ -429,10 +431,10 @@ export const AdvancedTab = ({
save={() => save()}
reset={() => {
resetFields([
"saml-assertion-lifespan",
"access-token-lifespan",
"tls-client-certificate-bound-access-tokens",
"pkce-code-challenge-method",
"saml.assertion.lifespan",
"access.token.lifespan",
"tls.client.certificate.bound.access.tokens",
"pkce.code.challenge.method",
]);
}}
/>

View file

@ -13,7 +13,7 @@ import {
} from "@patternfly/react-core";
import { InfoCircleIcon } from "@patternfly/react-icons";
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import _ from "lodash";
import _, { cloneDeep } from "lodash";
import React, { useMemo, useState } from "react";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
@ -25,11 +25,7 @@ import {
} from "../components/confirm-dialog/ConfirmDialog";
import { DownloadDialog } from "../components/download-dialog/DownloadDialog";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import {
convertToMultiline,
MultiLine,
toValue,
} from "../components/multi-line-input/MultiLineInput";
import type { MultiLine } from "../components/multi-line-input/multi-line-convert";
import {
ViewHeader,
ViewHeaderBadge,
@ -219,18 +215,7 @@ export default function ClientDetails() {
});
const setupForm = (client: ClientRepresentation) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { redirectUris, webOrigins, ...formValues } = client;
form.reset(formValues);
Object.entries(client).map((entry) => {
if (entry[0] === "redirectUris" || entry[0] === "webOrigins") {
form.setValue(entry[0], convertToMultiline(entry[1]));
} else if (entry[0] === "attributes") {
convertToFormValues(entry[1], "attributes", form.setValue);
} else {
form.setValue(entry[0], entry[1]);
}
});
convertToFormValues(client, form.setValue, ["redirectUris", "webOrigins"]);
};
useFetch(
@ -239,7 +224,7 @@ export default function ClientDetails() {
if (!fetchedClient) {
throw new Error(t("common:notFound"));
}
setClient(fetchedClient);
setClient(cloneDeep(fetchedClient));
setupForm(fetchedClient);
},
[clientId]
@ -260,19 +245,15 @@ export default function ClientDetails() {
toggleChangeAuthenticatorOpen();
return;
}
const redirectUris = toValue(form.getValues()["redirectUris"]);
const webOrigins = toValue(form.getValues()["webOrigins"]);
const attributes = convertFormValuesToObject(
form.getValues()["attributes"]
);
const submittedClient = convertFormValuesToObject(form.getValues(), [
"redirectUris",
"webOrigins",
]);
try {
const newClient: ClientRepresentation = {
...client,
...form.getValues(),
redirectUris,
webOrigins,
attributes,
...submittedClient,
};
newClient.clientId = newClient.clientId?.trim();
@ -282,7 +263,7 @@ export default function ClientDetails() {
setClient(newClient);
addAlert(t(messageKey), AlertVariant.success);
} catch (error) {
addError("client:clientSaveError", error);
addError("clients:clientSaveError", error);
}
}
};

View file

@ -43,7 +43,7 @@ export const ClientSettings = ({
const loginThemes = useServerInfo().themes!["login"];
const consentRequired = watch("consentRequired");
const displayOnConsentScreen: string = watch(
"attributes.display-on-consent-screen"
"attributes.display.on.consent.screen"
);
const protocol = watch("protocol");
@ -250,7 +250,7 @@ export const ClientSettings = ({
hasNoPaddingTop
>
<Controller
name="attributes.display-on-consent-screen"
name="attributes.display.on.consent.screen"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
@ -278,7 +278,7 @@ export const ClientSettings = ({
>
<TextArea
id="kc-consent-screen-text"
name="attributes.consent-screen-text"
name="attributes.consent.screen.text"
ref={register}
isDisabled={!(consentRequired && displayOnConsentScreen === "true")}
/>

View file

@ -248,7 +248,7 @@ export const CapabilityConfig = ({
hasNoPaddingTop
>
<Controller
name="attributes.saml-encrypt"
name="attributes.saml.encrypt"
control={control}
defaultValue="false"
render={({ onChange, value }) => (
@ -276,7 +276,7 @@ export const CapabilityConfig = ({
hasNoPaddingTop
>
<Controller
name="attributes.saml-client-signature"
name="attributes.saml.client.signature"
control={control}
defaultValue="false"
render={({ onChange, value }) => (

View file

@ -44,15 +44,10 @@ export default function NewClientForm() {
const methods = useForm<ClientRepresentation>({ defaultValues: client });
const save = async () => {
const attributes = client.attributes
? convertFormValuesToObject(client.attributes)
: undefined;
try {
const newClient = await adminClient.clients.create({
...client,
...convertFormValuesToObject(client),
clientId: client.clientId?.trim(),
attributes,
});
addAlert(t("createSuccess"), AlertVariant.success);
history.push(
@ -65,15 +60,21 @@ export default function NewClientForm() {
const forward = async (onNext?: () => void) => {
if (await methods.trigger()) {
setClient({ ...client, ...methods.getValues() });
setClient({
...client,
...convertFormValuesToObject(methods.getValues()),
});
setShowCapabilityConfig(true);
onNext?.();
}
};
const back = () => {
setClient({ ...client, ...methods.getValues() });
methods.reset({ ...client, ...methods.getValues() });
setClient({ ...client, ...convertFormValuesToObject(methods.getValues()) });
methods.reset({
...client,
...convertFormValuesToObject(methods.getValues()),
});
};
const onGoToStep = (newStep: { id?: string | number }) => {

View file

@ -100,23 +100,23 @@ export const SamlConfig = () => {
/>
</FormGroup>
<Toggle
name="attributes.saml_force_name_id_format"
name="attributes.saml.force.name.id.format"
label="forceNameIdFormat"
/>
<Toggle
name="attributes.saml-force-post-binding"
name="attributes.saml.force.post.binding"
label="forcePostBinding"
/>
<Toggle
name="attributes.saml-artifact-binding"
name="attributes.saml.artifact.binding"
label="forceArtifactBinding"
/>
<Toggle
name="attributes.saml-onetimeuse-condition"
name="attributes.saml.onetimeuse.condition"
label="includeOneTimeUseCondition"
/>
<Toggle
name="attributes.saml-server-signature-keyinfo-ext"
name="attributes.saml.server.signature.keyinfo.ext"
label="optimizeLookup"
/>
</FormAccess>

View file

@ -48,8 +48,8 @@ export const SamlSignature = () => {
const { control, watch } = useFormContext<ClientForm>();
const signDocs = watch("attributes.saml-server-signature");
const signAssertion = watch("attributes.saml-assertion-signature");
const signDocs = watch("attributes.saml.server.signature");
const signAssertion = watch("attributes.saml.assertion.signature");
return (
<FormAccess
@ -57,9 +57,9 @@ export const SamlSignature = () => {
role="manage-clients"
className="keycloak__capability-config__form"
>
<Toggle name="attributes.saml-server-signature" label="signDocuments" />
<Toggle name="attributes.saml.server.signature" label="signDocuments" />
<Toggle
name="attributes.saml-assertion-signature"
name="attributes.saml.assertion.signature"
label="signAssertions"
/>
{(signDocs === "true" || signAssertion === "true") && (
@ -78,7 +78,7 @@ export const SamlSignature = () => {
}
>
<Controller
name="attributes.saml-signature-algorithm"
name="attributes.saml.signature.algorithm"
defaultValue={SIGNATURE_ALGORITHMS[0]}
Key
control={control}
@ -120,7 +120,7 @@ export const SamlSignature = () => {
}
>
<Controller
name="attributes.saml-server-signature-keyinfo-xmlSigKeyInfoKeyNameTransformer"
name="attributes.saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer"
defaultValue={KEYNAME_TRANSFORMER[0]}
control={control}
render={({ onChange, value }) => (

View file

@ -46,7 +46,7 @@ export const AdvancedSettings = ({
}
>
<Controller
name="attributes.saml-assertion-lifespan"
name="attributes.saml.assertion.lifespan"
defaultValue=""
control={control}
render={({ onChange, value }) => (
@ -63,7 +63,7 @@ export const AdvancedSettings = ({
<>
<TokenLifespan
id="accessTokenLifespan"
name="attributes.access-token-lifespan"
name="attributes.access.token.lifespan"
defaultValue=""
units={["minutes", "days", "hours"]}
control={control}
@ -111,7 +111,7 @@ export const AdvancedSettings = ({
}
>
<Controller
name="attributes.pkce-code-challenge-method"
name="attributes.pkce.code.challenge.method"
defaultValue=""
control={control}
render={({ onChange, value }) => (

View file

@ -100,10 +100,6 @@ export const FineGrainOpenIdConnect = ({
</SelectOption>
));
const selectOptionToString = (value: string, options: JSX.Element[]) => {
const selectOption = options.find((s) => s.props.value === value);
return selectOption?.props.children || selectOption?.props.value;
};
return (
<FormAccess role="manage-clients" isHorizontal>
<FormGroup
@ -118,7 +114,7 @@ export const FineGrainOpenIdConnect = ({
}
>
<Controller
name="attributes.access-token-signed-response-alg"
name="attributes.access.token.signed.response.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (
@ -131,7 +127,7 @@ export const FineGrainOpenIdConnect = ({
onChange(value);
setAccessTokenOpen(false);
}}
selections={[selectOptionToString(value, keyOptions)]}
selections={value}
>
{keyOptions}
</Select>
@ -150,7 +146,7 @@ export const FineGrainOpenIdConnect = ({
}
>
<Controller
name="attributes.id-token-signed-response-alg"
name="attributes.id.token.signed.response.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (
@ -163,7 +159,7 @@ export const FineGrainOpenIdConnect = ({
onChange(value);
setIdTokenOpen(false);
}}
selections={[selectOptionToString(value, keyOptions)]}
selections={value}
>
{keyOptions}
</Select>
@ -182,7 +178,7 @@ export const FineGrainOpenIdConnect = ({
}
>
<Controller
name="attributes.id-token-encrypted-response-alg"
name="attributes.id.token.encrypted.response.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (
@ -195,7 +191,7 @@ export const FineGrainOpenIdConnect = ({
onChange(value);
setIdTokenKeyManagementOpen(false);
}}
selections={[selectOptionToString(value, cekManagementOptions)]}
selections={value}
>
{cekManagementOptions}
</Select>
@ -214,7 +210,7 @@ export const FineGrainOpenIdConnect = ({
}
>
<Controller
name="attributes.id-token-encrypted-response-enc"
name="attributes.id.token.encrypted.response.enc"
defaultValue=""
control={control}
render={({ onChange, value }) => (
@ -227,7 +223,7 @@ export const FineGrainOpenIdConnect = ({
onChange(value);
setIdTokenContentOpen(false);
}}
selections={[selectOptionToString(value, contentOptions)]}
selections={value}
>
{contentOptions}
</Select>
@ -246,7 +242,7 @@ export const FineGrainOpenIdConnect = ({
}
>
<Controller
name="attributes.user-info-response-signature-alg"
name="attributes.user.info.response.signature.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (
@ -259,7 +255,7 @@ export const FineGrainOpenIdConnect = ({
onChange(value);
setUserInfoSignedResponseOpen(false);
}}
selections={[selectOptionToString(value, signatureOptions)]}
selections={value}
>
{signatureOptions}
</Select>
@ -291,7 +287,7 @@ export const FineGrainOpenIdConnect = ({
onChange(value);
setRequestObjectSignatureOpen(false);
}}
selections={[selectOptionToString(value, requestObjectOptions)]}
selections={value}
>
{requestObjectOptions}
</Select>
@ -310,7 +306,7 @@ export const FineGrainOpenIdConnect = ({
}
>
<Controller
name="attributes.request-object-required"
name="attributes.request.object.required"
defaultValue=""
control={control}
render={({ onChange, value }) => (
@ -323,9 +319,7 @@ export const FineGrainOpenIdConnect = ({
onChange(value);
setRequestObjectRequiredOpen(false);
}}
selections={[
selectOptionToString(value, requestObjectRequiredOptions),
]}
selections={value}
>
{requestObjectRequiredOptions}
</Select>

View file

@ -27,19 +27,12 @@ import { ViewHeader } from "../../components/view-header/ViewHeader";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
import { useAlerts } from "../../components/alert/Alerts";
import { FormAccess } from "../../components/form-access/FormAccess";
import {
convertToMultiline,
MultiLine,
MultiLineInput,
toValue,
} from "../../components/multi-line-input/MultiLineInput";
import type { MultiLine } from "../../components/multi-line-input/multi-line-convert";
import type { KeyValueType } from "../../components/attribute-form/attribute-convert";
import { convertFormValuesToObject, convertToFormValues } from "../../util";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
import { toClient } from "../routes/Client";
import { ScopePicker } from "./ScopePicker";
import {
arrayToAttributes,
attributesToArray,
KeyValueType,
} from "../../components/attribute-form/attribute-convert";
import { AttributeInput } from "../../components/attribute-input/AttributeInput";
import "./resource-details.css";
@ -75,15 +68,7 @@ export default function ResourceDetails() {
const history = useHistory();
const setupForm = (resource: ResourceRepresentation = {}) => {
Object.entries(resource).forEach(([key, value]) => {
if (key === "uris") {
setValue("uris", convertToMultiline(value));
} else if (key === "attributes") {
setValue("attributes", attributesToArray(value));
} else {
setValue(key, value);
}
});
convertToFormValues(resource, setValue, ["uris"]);
};
useFetch(
@ -113,12 +98,7 @@ export default function ResourceDetails() {
);
const save = async (submitted: SubmittedResource) => {
const { attributes, uris, ...rest } = submitted;
const resource = {
...rest,
attributes: arrayToAttributes(attributes),
uris: toValue(uris),
};
const resource = convertFormValuesToObject(submitted, ["uris"]);
try {
if (resourceId) {

View file

@ -33,7 +33,7 @@ export const SignedJWT = () => {
}
>
<Controller
name="attributes.token-endpoint-auth-signing-alg"
name="attributes.token.endpoint.auth.signing.alg"
defaultValue=""
control={control}
render={({ onChange, value }) => (

View file

@ -20,7 +20,7 @@ export const X509 = () => {
}
helperTextInvalid={t("common:required")}
validated={
errors.attributes?.["x509-subjectdn"]
errors.attributes?.["x509.subjectdn"]
? ValidatedOptions.error
: ValidatedOptions.default
}
@ -30,9 +30,9 @@ export const X509 = () => {
ref={register({ required: true })}
type="text"
id="kc-subject"
name="attributes.x509-subjectdn"
name="attributes.x509.subjectdn"
validated={
errors.attributes?.["x509-subjectdn"]
errors.attributes?.["x509.subjectdn"]
? ValidatedOptions.error
: ValidatedOptions.default
}

View file

@ -34,7 +34,7 @@ export default function ImportForm() {
const { addAlert, addError } = useAlerts();
const handleFileChange = (obj: object) => {
const handleFileChange = (obj?: object) => {
const defaultClient = {
protocol: "",
clientId: "",
@ -42,13 +42,7 @@ export default function ImportForm() {
description: "",
};
Object.entries(obj || defaultClient).forEach((entries) => {
if (entries[0] === "attributes") {
convertToFormValues(entries[1], "attributes", form.setValue);
} else {
setValue(entries[0], entries[1]);
}
});
convertToFormValues(obj || defaultClient, setValue);
setImported(obj || defaultClient);
};
@ -56,8 +50,7 @@ export default function ImportForm() {
try {
const newClient = await adminClient.clients.create({
...imported,
...client,
attributes: convertFormValuesToObject(client.attributes || {}),
...convertFormValuesToObject(client),
});
addAlert(t("clientImportSuccess"), AlertVariant.success);
history.push(

View file

@ -54,7 +54,7 @@ export const Keys = ({ clientId, save }: KeysProps) => {
const useJwksUrl = useWatch({
control,
name: "attributes.use-jwks-url",
name: "attributes.use.jwks.url",
defaultValue: "false",
});
@ -137,7 +137,7 @@ export const Keys = ({ clientId, save }: KeysProps) => {
}
>
<Controller
name="attributes.use-jwks-url"
name="attributes.use.jwks.url"
defaultValue="false"
control={control}
render={({ onChange, value }) => (
@ -173,7 +173,7 @@ export const Keys = ({ clientId, save }: KeysProps) => {
<TextInput
type="text"
id="jwksUrl"
name="attributes.jwks-url"
name="attributes.jwks.url"
ref={register}
/>
</FormGroup>

View file

@ -38,12 +38,12 @@ export type KeyTypes = typeof KEYS[number];
const KEYS_MAPPING: { [key in KeyTypes]: { [index: string]: string } } = {
"saml.signing": {
name: "attributes.saml-client-signature",
name: "attributes.saml.client.signature",
title: "signingKeysConfig",
key: "clientSignature",
},
"saml.encryption": {
name: "attributes.saml-encrypt",
name: "attributes.saml.encrypt",
title: "encryptionKeysConfig",
key: "encryptAssertions",
},

View file

@ -127,7 +127,7 @@ export default {
createError: "Could not create client: '{{error}}'",
clientImportError: "Could not import client: {{error}}",
clientSaveSuccess: "Client successfully updated",
clientSaveError: "Client could not be updated:",
clientSaveError: "Client could not be updated: {{error}}",
clientImportSuccess: "Client imported successfully",
clientDelete: "Delete {{clientId}} ?",
clientDeletedSuccess: "The client has been deleted",

View file

@ -5,7 +5,6 @@ import { FormGroup, Switch } from "@patternfly/react-core";
import type { ComponentProps } from "./components";
import { HelpItem } from "../help-enabler/HelpItem";
import { convertToHyphens } from "../../util";
export const BooleanComponent = ({
name,
@ -26,7 +25,7 @@ export const BooleanComponent = ({
}
>
<Controller
name={`config.${convertToHyphens(name!)}`}
name={`config.${name}`}
data-testid={name}
defaultValue={defaultValue}
control={control}

View file

@ -12,7 +12,6 @@ import type { ClientQuery } from "@keycloak/keycloak-admin-client/lib/resources/
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { convertToHyphens } from "../../util";
export const ClientSelectComponent = ({
name,
@ -58,7 +57,7 @@ export const ClientSelectComponent = ({
fieldId={name!}
>
<Controller
name={`config.${convertToHyphens(name!)}`}
name={`config.${name}`}
defaultValue={defaultValue || ""}
control={control}
render={({ onChange, value }) => (

View file

@ -10,7 +10,6 @@ import {
import type { ComponentProps } from "./components";
import { HelpItem } from "../help-enabler/HelpItem";
import { convertToHyphens } from "../../util";
export const ListComponent = ({
name,
@ -32,7 +31,7 @@ export const ListComponent = ({
fieldId={name!}
>
<Controller
name={`config.${convertToHyphens(name!)}`}
name={`config.${name}`}
data-testid={name}
defaultValue={defaultValue || ""}
control={control}

View file

@ -10,7 +10,6 @@ import {
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { convertToHyphens } from "../../util";
export const MultiValuedListComponent = ({
name,
@ -32,7 +31,7 @@ export const MultiValuedListComponent = ({
fieldId={name!}
>
<Controller
name={`config.${convertToHyphens(name!)}`}
name={`config.${name}`}
control={control}
defaultValue={defaultValue ? [defaultValue] : []}
render={({ onChange, value }) => (

View file

@ -11,10 +11,9 @@ import {
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import type { ComponentProps } from "./components";
import type { MultiLine } from "../multi-line-input/MultiLineInput";
import type { MultiLine } from "../multi-line-input/multi-line-convert";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { HelpItem } from "../help-enabler/HelpItem";
import { convertToHyphens } from "../../util";
import { useWhoAmI } from "../../context/whoami/WhoAmI";
export const MultivaluedRoleComponent = ({
@ -24,7 +23,7 @@ export const MultivaluedRoleComponent = ({
}: ComponentProps) => {
const { t } = useTranslation("dynamic");
const { whoAmI } = useWhoAmI();
const fieldName = `config.${convertToHyphens(name!)}`;
const fieldName = `config.${name}`;
const adminClient = useAdminClient();
const { control } = useFormContext();

View file

@ -5,7 +5,6 @@ import { FormGroup } from "@patternfly/react-core";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
import { convertToHyphens } from "../../util";
export const MultiValuedStringComponent = ({
name,
@ -23,7 +22,7 @@ export const MultiValuedStringComponent = ({
fieldId={name!}
>
<MultiLineInput
name={`config.${convertToHyphens(name!)}`}
name={`config.${name}`}
aria-label={name}
addButtonLabel={t("addMultivaluedLabel", {
fieldLabel: t(label!).toLowerCase(),

View file

@ -18,7 +18,6 @@ import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { convertToHyphens } from "../../util";
export const RoleComponent = ({ name, label, helpText }: ComponentProps) => {
const { t } = useTranslation("dynamic");
@ -34,7 +33,7 @@ export const RoleComponent = ({ name, label, helpText }: ComponentProps) => {
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleRepresentation>();
const fieldName = `config.${convertToHyphens(name!)}`;
const fieldName = `config.${name}`;
useFetch(
async () => {

View file

@ -6,7 +6,6 @@ import { CodeEditor, Language } from "@patternfly/react-code-editor";
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { convertToHyphens } from "../../util";
export const ScriptComponent = ({
name,
@ -30,7 +29,7 @@ export const ScriptComponent = ({
fieldId={name!}
>
<Controller
name={`config.${convertToHyphens(name!)}`}
name={`config.${name}`}
defaultValue={defaultValue}
control={control}
render={({ onChange, value }) => (

View file

@ -5,7 +5,6 @@ import { FormGroup, TextInput } from "@patternfly/react-core";
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { convertToHyphens } from "../../util";
export const StringComponent = ({
name,
@ -29,7 +28,7 @@ export const StringComponent = ({
data-testid={name}
ref={register()}
type="text"
name={`config.${convertToHyphens(name!)}`}
name={`config.${name}`}
defaultValue={defaultValue?.toString()}
/>
</FormGroup>

View file

@ -10,20 +10,6 @@ import {
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next";
export type MultiLine = {
value: string;
};
export function convertToMultiline(fields: string[]): MultiLine[] {
return (fields.length > 0 ? fields : [""]).map((field) => {
return { value: field };
});
}
export function toValue(formValue: MultiLine[]): string[] {
return formValue.map((field) => field.value);
}
export type MultiLineInputProps = Omit<TextInputProps, "form"> & {
name: string;
addButtonLabel?: string;

View file

@ -0,0 +1,13 @@
export type MultiLine = {
value: string;
};
export function convertToMultiline(fields: string[]): MultiLine[] {
return (fields.length > 0 ? fields : [""]).map((field) => {
return { value: field };
});
}
export function toValue(formValue: MultiLine[]): string[] {
return formValue.map((field) => field.value);
}

View file

@ -91,18 +91,17 @@ export default function AddMapper() {
const save = async (idpMapper: IdentityProviderMapperRepresentation) => {
const attributes = JSON.stringify(idpMapper.config?.attributes ?? []);
const config = convertFormValuesToObject({
...idpMapper.config,
attributes,
});
const mapper = convertFormValuesToObject(idpMapper);
if (id) {
const updatedMapper = {
...idpMapper,
...mapper,
config: {
attributes,
},
identityProviderAlias: alias!,
id: id,
name: currentMapper?.name!,
config,
};
try {
await adminClient.identityProviders.updateMapper(
@ -120,9 +119,11 @@ export default function AddMapper() {
try {
const createdMapper = await adminClient.identityProviders.createMapper({
identityProviderMapper: {
...idpMapper,
...mapper,
identityProviderAlias: alias,
config,
config: {
attributes,
},
},
alias: alias!,
});
@ -161,28 +162,8 @@ export default function AddMapper() {
const setupForm = (mapper: IdentityProviderMapperRepresentation) => {
form.reset();
Object.entries(mapper).map(([key, value]) => {
if (key === "config") {
if (mapper.config?.["are.attribute.values.regex"]) {
form.setValue(
"config.are-attribute-values-regex",
mapper.config["are.attribute.values.regex"]
);
}
if (mapper.config?.attributes) {
form.setValue("config.attributes", JSON.parse(value.attributes));
}
if (mapper.config?.role) {
form.setValue("config.role", value.role[0]);
}
convertToFormValues(value, "config", form.setValue);
}
form.setValue(key, value);
});
convertToFormValues(mapper, form.setValue);
form.setValue("config.attributes", JSON.parse(mapper.config.attributes));
};
const targetOptions = ["local", "brokerId", "brokerUsername"];
@ -229,7 +210,6 @@ export default function AddMapper() {
() => formValues.identityProviderMapper?.includes("user-attribute-mapper"),
[formValues.identityProviderMapper]
);
const toggleModal = () => {
setRolesModalOpen(!rolesModalOpen);
};
@ -357,8 +337,8 @@ export default function AddMapper() {
<Controller
name={
isOIDCAdvancedClaimToRole
? "config.are-claim-values-regex"
: "config.are-attribute-values-regex"
? "config.are.claim.values.regex"
: "config.are.attribute.values.regex"
}
control={control}
defaultValue="false"
@ -548,14 +528,14 @@ export default function AddMapper() {
<TextInput
ref={register()}
type="text"
defaultValue={currentMapper?.config["attribute-value"]}
defaultValue={currentMapper?.config["attribute.value"]}
data-testid={
isHardcodedUserSessionAttribute
? "user-session-attribute-value"
: "user-attribute-value"
}
id="kc-user-session-attribute-value"
name="config.attribute-value"
name="config.attribute.value"
validated={
errors.name
? ValidatedOptions.error
@ -594,10 +574,10 @@ export default function AddMapper() {
<TextInput
ref={register()}
type="text"
defaultValue={currentMapper?.config["attribute-name"]}
defaultValue={currentMapper?.config["attribute.name"]}
id="kc-attribute-name"
data-testid="attribute-name"
name="config.attribute-name"
name="config.attribute.name"
validated={
errors.name
? ValidatedOptions.error
@ -629,11 +609,11 @@ export default function AddMapper() {
ref={register()}
type="text"
defaultValue={
currentMapper?.config["attribute-friendly-name"]
currentMapper?.config["attribute.friendly.name"]
}
data-testid="attribute-friendly-name"
id="kc-attribute-friendly-name"
name="config.attribute-friendly-name"
name="config.attribute.friendly.name"
validated={
errors.name
? ValidatedOptions.error
@ -715,11 +695,11 @@ export default function AddMapper() {
type="text"
defaultValue={
isOIDCclaimToRole
? currentMapper?.config["claim-value"]
: currentMapper?.config["attribute-value"]
? currentMapper?.config["claim.value"]
: currentMapper?.config["attribute.value"]
}
data-testid={
isOIDCclaimToRole ? "claim-value" : "user-attribute-name"
isOIDCclaimToRole ? "claim.value" : "user-attribute-name"
}
id={
isOIDCclaimToRole
@ -727,7 +707,7 @@ export default function AddMapper() {
: "kc-user-attribute-name"
}
name={
isOIDCclaimToRole ? "config.claim" : "config.user-attribute"
isOIDCclaimToRole ? "config.claim" : "config.user.attribute"
}
validated={
errors.name

View file

@ -77,12 +77,7 @@ export default function ExecutorForm() {
);
if (profileExecutor) {
Object.entries(profileExecutor).map(([key, value]) => {
if (key === "configuration") {
convertToFormValues(value, "config", setValue);
}
setValue(key, value);
});
convertToFormValues(profileExecutor, setValue);
}
},
[]

View file

@ -33,7 +33,7 @@ import type { EditClientPolicyConditionParams } from "./routes/EditCondition";
import {
convertToMultiline,
toValue,
} from "../components/multi-line-input/MultiLineInput";
} from "../components/multi-line-input/multi-line-convert";
import {
COMPONENTS,
isValidComponentType,

View file

@ -49,7 +49,6 @@ import { toRealmSettings } from "./routes/RealmSettings";
import { LocalizationTab } from "./LocalizationTab";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { UserRegistration } from "./UserRegistration";
import { DEFAULT_LOCALE } from "../i18n";
import { toDashboard } from "../dashboard/routes/Dashboard";
import environment from "../environment";
import { UserProfileTab } from "./UserProfileTab";
@ -188,15 +187,7 @@ export const RealmSettingsTabs = ({
};
const setupForm = (r: RealmRepresentation = realm) => {
Object.entries(r).map(([key, value]) => {
if (key === "attributes") {
convertToFormValues(value, "attributes", setValue);
} else if (key === "supportedLocales" && value?.length === 0) {
setValue(key, [DEFAULT_LOCALE]);
} else {
setValue(key, value);
}
});
convertToFormValues(r, setValue);
resetForm(getValues());
};
@ -206,18 +197,13 @@ export const RealmSettingsTabs = ({
const save = async (realm: RealmRepresentation) => {
try {
const attributes = Object.fromEntries(
Object.entries(
convertFormValuesToObject(realm.attributes, true)
).filter(([, v]) => v !== "")
);
realm = convertFormValuesToObject(realm);
await adminClient.realms.update(
{ realm: realmName },
{
...realm,
id: realmName,
attributes,
}
);
setupForm(realm);

View file

@ -88,20 +88,7 @@ export const AESGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => {
form.reset();
Object.entries(component).map(([key, value]) => {
if (
key === "config" &&
component.config?.secretSize &&
component.config.active
) {
form.setValue("config.secretSize", value.secretSize[0]);
form.setValue("config.active", value.active[0]);
convertToFormValues(value, "config", form.setValue);
}
form.setValue(key, value);
});
convertToFormValues(component, form.setValue);
};
useFetch(

View file

@ -88,20 +88,7 @@ export const ECDSAGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => {
form.reset();
Object.entries(component).map(([key, value]) => {
if (
key === "config" &&
component.config?.ecdsaEllipticCurveKey &&
component.config.active
) {
form.setValue("config.secretSize", value.ecdsaEllipticCurveKey[0]);
form.setValue("config.active", value.active[0]);
convertToFormValues(value, "config", form.setValue);
}
form.setValue(key, value);
});
convertToFormValues(component, form.setValue);
};
useFetch(

View file

@ -90,23 +90,7 @@ export const HMACGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => {
form.reset();
Object.entries(component).map(([key, value]) => {
if (
key === "config" &&
component.config?.secretSize &&
component.config.active &&
component.config.algorithm
) {
form.setValue("config.secretSize", value.secretSize[0]);
form.setValue("config.active", value.active[0]);
form.setValue("config.algorithm", value.active[0]);
convertToFormValues(value, "config", form.setValue);
}
form.setValue(key, value);
});
convertToFormValues(component, form.setValue);
};
useFetch(

View file

@ -88,33 +88,7 @@ export const JavaKeystoreForm = ({
const setupForm = (component: ComponentRepresentation) => {
form.reset();
Object.entries(component).map(([key, value]) => {
if (
key === "config" &&
component.config?.secretSize &&
component.config.active &&
component.config.algorithm &&
component.config.keystore &&
component.config.keystorePassword &&
component.config.keyAlias &&
component.config.keyPassword
) {
form.setValue("config.secretSize", value.secretSize[0]);
form.setValue("config.active", value.active[0]);
form.setValue("config.algorithm", value.algorithm[0]);
form.setValue("config.keystore", value.keystore[0]);
form.setValue("config.keyAlias", value.keyAlias[0]);
form.setValue("config.keyPassword", value.keyPassword[0]);
convertToFormValues(value, "config", form.setValue);
}
form.setValue(key, value);
});
convertToFormValues(component, form.setValue);
};
useFetch(

View file

@ -90,23 +90,7 @@ export const RSAGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => {
form.reset();
Object.entries(component).map(([key, value]) => {
if (
key === "config" &&
component.config?.secretSize &&
component.config.active &&
component.config.algorithm
) {
form.setValue("config.secretSize", value.secretSize[0]);
form.setValue("config.active", value.active[0]);
form.setValue("config.algorithm", value.active[0]);
convertToFormValues(value, "config", form.setValue);
}
form.setValue(key, value);
});
convertToFormValues(component, form.setValue);
};
useFetch(

View file

@ -95,29 +95,7 @@ export const RSAForm = ({
const setupForm = (component: ComponentRepresentation) => {
form.reset();
Object.entries(component).map(([key, value]) => {
if (
key === "config" &&
component.config?.secretSize &&
component.config.active &&
component.config.algorithm &&
component.config.privateKey &&
component.config.certificate
) {
form.setValue("config.secretSize", value.secretSize[0]);
form.setValue("config.active", value.active[0]);
form.setValue("config.algorithm", value.algorithm[0]);
form.setValue("config.privateKey", value.privateKey[0]);
form.setValue("config.certificate", value.certificate[0]);
convertToFormValues(value, "config", form.setValue);
}
form.setValue(key, value);
});
convertToFormValues(component, form.setValue);
};
useFetch(

View file

@ -12,7 +12,6 @@ import {
import { KerberosSettingsRequired } from "./kerberos/KerberosSettingsRequired";
import { SettingsCache } from "./shared/SettingsCache";
import { useRealm } from "../context/realm-context/RealmContext";
import { convertToFormValues } from "../util";
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import { Controller, useForm } from "react-hook-form";
@ -107,16 +106,7 @@ export default function UserFederationKerberosSettings() {
);
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
form.setValue(
"config.allowPasswordAuthentication",
component.config?.allowPasswordAuthentication
);
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", form.setValue);
}
form.setValue(entry[0], entry[1]);
});
form.reset({ ...component });
};
const save = async (component: ComponentRepresentation) => {

View file

@ -21,7 +21,6 @@ import { LdapSettingsConnection } from "./ldap/LdapSettingsConnection";
import { LdapSettingsSearching } from "./ldap/LdapSettingsSearching";
import { useRealm } from "../context/realm-context/RealmContext";
import { convertToFormValues } from "../util";
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import { Controller, useForm } from "react-hook-form";
@ -220,22 +219,16 @@ export default function UserFederationLdapSettings() {
);
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
form.setValue(
"config.periodicChangedUsersSync",
entry[1].changedSyncPeriod[0] !== "-1"
);
form.reset({ ...component });
form.setValue(
"config.periodicChangedUsersSync",
component.config?.["changedSyncPeriod"][0] !== "-1"
);
form.setValue(
"config.periodicFullSync",
entry[1].fullSyncPeriod[0] !== "-1"
);
convertToFormValues(entry[1], "config", form.setValue);
}
form.setValue(entry[0], entry[1]);
});
form.setValue(
"config.periodicFullSync",
component.config?.["fullSyncPeriod"][0] !== "-1"
);
};
const removeImportedUsers = async () => {

View file

@ -71,21 +71,11 @@ export default function LdapMapperDetails() {
);
const setupForm = (mapper: ComponentRepresentation) => {
Object.entries(mapper).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", form.setValue);
} else {
form.setValue(entry[0], entry[1]);
}
});
convertToFormValues(mapper, form.setValue);
};
const save = async (mapper: ComponentRepresentation) => {
let config = {};
if (mapper.config !== undefined) {
config = convertFormValuesToObject(mapper.config);
}
const map = { ...mapper, config };
const map = convertFormValuesToObject(mapper);
try {
if (mapperId) {

View file

@ -34,7 +34,7 @@ export const LdapMapperFullNameAttribute = ({
defaultValue="cn"
id="kc-full-name-attribute"
data-testid="mapper-fullNameAttribute-fld"
name="config.ldap-full-name-attribute[0]"
name="config.ldap.full.name.attribute[0]"
ref={form.register}
/>
</FormGroup>
@ -51,7 +51,7 @@ export const LdapMapperFullNameAttribute = ({
hasNoPaddingTop
>
<Controller
name="config.read-only"
name="config.read.only"
defaultValue={["true"]}
control={form.control}
render={({ onChange, value }) => (
@ -79,7 +79,7 @@ export const LdapMapperFullNameAttribute = ({
hasNoPaddingTop
>
<Controller
name="config.write-only"
name="config.write.only"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (

View file

@ -33,7 +33,7 @@ export const LdapMapperHardcodedAttribute = ({
type="text"
id="kc-user-model-attribute"
data-testid="mapper-userModelAttributeName-fld"
name="config.user-model-attribute[0]"
name="config.user.model.attribute[0]"
ref={form.register}
/>
</FormGroup>
@ -54,7 +54,7 @@ export const LdapMapperHardcodedAttribute = ({
type="text"
id="kc-attribute-value"
data-testid="mapper-attributeValue-fld"
name="config.attribute-value[0]"
name="config.attribute.value[0]"
ref={form.register}
/>
</FormGroup>

View file

@ -33,7 +33,7 @@ export const LdapMapperHardcodedLdapAttribute = ({
type="text"
id="kc-ldap-attribute-name"
data-testid="mapper-ldapAttributeName-fld"
name="config.ldap-attribute-name[0]"
name="config.ldap.attribute.name[0]"
ref={form.register}
/>
</FormGroup>
@ -54,7 +54,7 @@ export const LdapMapperHardcodedLdapAttribute = ({
type="text"
id="kc-ldap-attribute-value"
data-testid="mapper-ldapAttributeValue-fld"
name="config.ldap-attribute-value[0]"
name="config.ldap.attribute.value[0]"
ref={form.register}
/>
</FormGroup>

View file

@ -12,7 +12,7 @@ export const LdapMapperMsadUserAccount = ({
form,
}: LdapMapperMsadUserAccountProps) => {
const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t;
const { t: helpText } = useTranslation("user-federation-help");
return (
<FormGroup
@ -28,7 +28,7 @@ export const LdapMapperMsadUserAccount = ({
hasNoPaddingTop
>
<Controller
name="config.ldap-password-policy-hints-enabled"
name="config.ldap.password.policy.hints.enabled"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (

View file

@ -67,11 +67,11 @@ export const LdapMapperRoleGroup = ({
if (mapperId === "new" && vendor === "ad") {
form.setValue(
isRole
? "config.role-object-classes[0]"
: "config.group-object-classes[0]",
? "config.role.object.classes[0]"
: "config.group.object.classes[0]",
"group"
);
form.setValue("config.membership-user-ldap-attribute[0]", "cn");
form.setValue("config.membership.user.ldap.attribute[0]", "cn");
}
} else if (id) {
throw new Error(t("common:notFound"));
@ -103,7 +103,7 @@ export const LdapMapperRoleGroup = ({
type="text"
id="kc-ldap-dn"
data-testid="ldap-dn"
name={isRole ? "config.roles-dn[0]" : "config.groups-dn[0]"}
name={isRole ? "config.roles.dn[0]" : "config.groups.dn[0]"}
ref={form.register({ required: true })}
validated={
isRole
@ -144,8 +144,8 @@ export const LdapMapperRoleGroup = ({
defaultValue="cn"
name={
isRole
? "config.role-name-ldap-attribute[0]"
: "config.group-name-ldap-attribute[0]"
? "config.role.name.ldap.attribute[0]"
: "config.group.name.ldap.attribute[0]"
}
ref={form.register}
/>
@ -174,8 +174,8 @@ export const LdapMapperRoleGroup = ({
defaultValue="groupOfNames"
name={
isRole
? "config.role-object-classes[0]"
: "config.group-object-classes[0]"
? "config.role.object.classes[0]"
: "config.group.object.classes[0]"
}
ref={form.register}
/>
@ -195,7 +195,7 @@ export const LdapMapperRoleGroup = ({
hasNoPaddingTop
>
<Controller
name="config.preserve-group-inheritance"
name="config.preserve.group.inheritance"
defaultValue={["true"]}
control={form.control}
render={({ onChange, value }) => (
@ -223,7 +223,7 @@ export const LdapMapperRoleGroup = ({
hasNoPaddingTop
>
<Controller
name="config.ignore-missing-groups"
name="config.ignore.missing.groups"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
@ -258,7 +258,7 @@ export const LdapMapperRoleGroup = ({
defaultValue="member"
id="kc-membership-ldap-attribute"
data-testid="membership-ldap-attribute"
name="config.membership-ldap-attribute[0]"
name="config.membership.ldap.attribute[0]"
ref={form.register}
/>
</FormGroup>
@ -274,7 +274,7 @@ export const LdapMapperRoleGroup = ({
fieldId="kc-membership-attribute-type"
>
<Controller
name="config.membership-attribute-type[0]"
name="config.membership.attribute.type[0]"
defaultValue="DN"
control={form.control}
render={({ onChange, value }) => (
@ -319,7 +319,7 @@ export const LdapMapperRoleGroup = ({
id="kc-membership-user-ldap-attribute"
data-testid="membership-user-ldap-attribute"
defaultValue="uid"
name="config.membership-user-ldap-attribute[0]"
name="config.membership.user.ldap.attribute[0]"
ref={form.register}
/>
</FormGroup>
@ -340,8 +340,8 @@ export const LdapMapperRoleGroup = ({
data-testid="ldap-filter"
name={
isRole
? "config.roles-ldap-filter[0]"
: "config.groups-ldap-filter[0]"
? "config.roles.ldap.filter[0]"
: "config.groups.ldap.filter[0]"
}
ref={form.register}
/>
@ -399,7 +399,7 @@ export const LdapMapperRoleGroup = ({
fieldId="kc-user-retrieve-strategy"
>
<Controller
name="config.user-roles-retrieve-strategy[0]"
name="config.user.roles.retrieve.strategy[0]"
defaultValue="LOAD_ROLES_BY_MEMBER_ATTRIBUTE"
control={form.control}
render={({ onChange, value }) => (
@ -449,7 +449,7 @@ export const LdapMapperRoleGroup = ({
fieldId="kc-user-retrieve-strategy"
>
<Controller
name="config.user-roles-retrieve-strategy[0]"
name="config.user.roles.retrieve.strategy[0]"
defaultValue="LOAD_GROUPS_BY_MEMBER_ATTRIBUTE"
control={form.control}
render={({ onChange, value }) => (
@ -505,7 +505,7 @@ export const LdapMapperRoleGroup = ({
id="kc-member-of-attribute"
defaultValue="memberOf"
data-testid="member-of-attribute"
name="config.memberof-ldap-attribute[0]"
name="config.memberof.ldap.attribute[0]"
ref={form.register}
/>
</FormGroup>
@ -524,7 +524,7 @@ export const LdapMapperRoleGroup = ({
hasNoPaddingTop
>
<Controller
name="config.use-realm-roles-mapping"
name="config.use.realm.roles.mapping"
defaultValue={["true"]}
control={form.control}
render={({ onChange, value }) => (
@ -551,7 +551,7 @@ export const LdapMapperRoleGroup = ({
fieldId="kc-client-id"
>
<Controller
name="config.client-id[0]"
name="config.client.id[0]"
defaultValue=""
control={form.control}
render={({ onChange, value }) => (
@ -596,7 +596,7 @@ export const LdapMapperRoleGroup = ({
type="text"
id="kc-mapped-attributes"
data-testid="mapped-attributes"
name="config.mapped-group-attributes[0]"
name="config.mapped.group.attributes[0]"
ref={form.register}
/>
</FormGroup>
@ -613,7 +613,7 @@ export const LdapMapperRoleGroup = ({
hasNoPaddingTop
>
<Controller
name="config.drop-non-existing-groups-during-sync"
name="config.drop.non.existing.groups.during.sync"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
@ -646,7 +646,7 @@ export const LdapMapperRoleGroup = ({
id="kc-path"
data-testid="path"
defaultValue="/"
name="config.groups-path[0]"
name="config.groups.path[0]"
ref={form.register({ required: true })}
validated={
form.errors.config?.["groups-path"]

View file

@ -35,7 +35,7 @@ export const LdapMapperUserAttribute = ({
type="text"
id="kc-user-model-attribute"
data-testid="mapper-userModelAttribute-fld"
name="config.user-model-attribute[0]"
name="config.user.model.attribute[0]"
ref={form.register}
/>
</FormGroup>
@ -56,7 +56,7 @@ export const LdapMapperUserAttribute = ({
type="text"
id="kc-ldap-attribute"
data-testid="mapper-ldapAttribute-fld"
name="config.ldap-attribute[0]"
name="config.ldap.attribute[0]"
ref={form.register}
/>
</FormGroup>
@ -73,7 +73,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop
>
<Controller
name="config.read-only"
name="config.read.only"
defaultValue={
mapperType === "user-attribute-ldap-mapper" ? ["true"] : ["false"]
}
@ -103,7 +103,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop
>
<Controller
name="config.always-read-value-from-ldap"
name="config.always.read.value.from.ldap"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
@ -131,7 +131,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop
>
<Controller
name="config.is-mandatory-in-ldap"
name="config.is.mandatory.in.ldap"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
@ -161,7 +161,7 @@ export const LdapMapperUserAttribute = ({
type="text"
id="kc-attribute-default-value"
data-testid="mapper-attributeDefaultValue-fld"
name="config.attribute-default-value[0]"
name="config.attribute.default.value[0]"
ref={form.register}
/>
</FormGroup>
@ -178,7 +178,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop
>
<Controller
name="config.is-binary-attribute"
name="config.is.binary.attribute"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
@ -207,12 +207,12 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop
>
<Controller
name="config.is-der-formatted"
name="config.is.der.formatted"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-der-formatted"}
id="kc-der-formatted"
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}

140
src/util.test.ts Normal file
View file

@ -0,0 +1,140 @@
import { convertFormValuesToObject, convertToFormValues } from "./util";
jest.mock("react");
describe("Tests the form convert util functions", () => {
it("convert to form values", () => {
const given = {
name: "client",
other: { one: "1", two: "2" },
attributes: { one: ["1"] },
};
const values: { [index: string]: any } = {};
const spy = (name: string, value: any) => (values[name] = value);
//when
convertToFormValues(given, spy);
//then
expect(values).toEqual({
name: "client",
other: { one: "1", two: "2" },
attributes: [
{ key: "one", value: "1" },
{ key: "", value: "" },
],
});
});
it("convert save values", () => {
const given = {
name: "client",
attributes: [{ key: "one", value: "1" }],
config: { one: { two: "3" } },
};
//when
const values = convertFormValuesToObject(given);
//then
expect(values).toEqual({
name: "client",
attributes: { one: ["1"] },
config: { "one.two": "3" },
});
});
it("convert attributes flatten", () => {
const given = {
name: "test",
description: "",
type: "default",
attributes: {
display: { on: { consent: { screen: "true" } } },
include: { in: { token: { scope: "true" } } },
gui: { order: "1" },
consent: { screen: { text: "" } },
},
};
//when
const values = convertFormValuesToObject(given);
//then
expect(values).toEqual({
name: "test",
description: "",
type: "default",
attributes: {
"display.on.consent.screen": "true",
"include.in.token.scope": "true",
"gui.order": "1",
"consent.screen.text": "",
},
});
});
it("convert flatten attributes to object", () => {
const given = {
attributes: {
"display.on.consent.screen": "true",
"include.in.token.scope": "true",
"gui.order": "1",
"consent.screen.text": "",
},
};
const values: { [index: string]: any } = {};
const spy = (name: string, value: any) => (values[name] = value);
//when
convertToFormValues(given, spy);
//then
expect(values).toEqual({
attributes: {
display: { on: { consent: { screen: "true" } } },
include: { in: { token: { scope: "true" } } },
gui: { order: "1" },
consent: { screen: { text: "" } },
},
});
});
it("convert arrays to form values", () => {
const given = {
name: "test",
description: "",
redirectUris: ["http://bla.nl", "http://test.nl/bla", "http://test.nl"],
};
const values: { [index: string]: any } = {};
const spy = (name: string, value: any) => (values[name] = value);
//when
convertToFormValues(given, spy, ["redirectUris"]);
//then
expect(values).toEqual({
name: "test",
description: "",
redirectUris: [
{ value: "http://bla.nl" },
{ value: "http://test.nl/bla" },
{ value: "http://test.nl" },
],
});
});
it("convert form values to object", () => {
const given = {
redirectUris: [{ value: "http://bla.nl" }, { value: "http://test.nl" }],
};
//when
const values = convertFormValuesToObject(given, ["redirectUris"]);
//then
expect(values).toEqual({
redirectUris: ["http://bla.nl", "http://test.nl"],
});
});
});

View file

@ -1,10 +1,22 @@
import type { IFormatter, IFormatterValueType } from "@patternfly/react-table";
import { cloneDeep } from "lodash";
import { useTranslation } from "react-i18next";
import FileSaver from "file-saver";
import _ from "lodash";
import type { IFormatter, IFormatterValueType } from "@patternfly/react-table";
import { unflatten, flatten } from "flat";
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import type { ProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
import type KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import { useTranslation } from "react-i18next";
import {
arrayToAttributes,
attributesToArray,
KeyValueType,
} from "./components/attribute-form/attribute-convert";
import {
convertToMultiline,
toValue,
} from "./components/multi-line-input/multi-line-convert";
export const sortProviders = (providers: {
[index: string]: ProviderRepresentation;
@ -34,7 +46,7 @@ const sortProvider = (
};
export const exportClient = (client: ClientRepresentation): void => {
const clientCopy = _.cloneDeep(client);
const clientCopy = cloneDeep(client);
delete clientCopy.id;
if (clientCopy.protocolMappers) {
@ -54,49 +66,59 @@ export const exportClient = (client: ClientRepresentation): void => {
export const toUpperCase = <T extends string>(name: T) =>
(name.charAt(0).toUpperCase() + name.slice(1)) as Capitalize<T>;
export const convertToHyphens = (s: string) => {
return s.replaceAll(".", "-");
const isAttributesObject = (value: any) => {
return (
Object.values(value).filter(
(value) => Array.isArray(value) && value.length === 1
).length !== 0
);
};
const isAttributeArray = (value: any) => {
if (!Array.isArray(value)) {
return false;
}
return value.filter((e) => e.key && e.value).length !== 0;
};
const isEmpty = (obj: any) => Object.keys(obj).length === 0;
export const convertToFormValues = (
obj: any,
prefix: string,
setValue: (name: string, value: any) => void
setValue: (name: string, value: any) => void,
multiline?: string[]
) => {
return Object.keys(obj).map((key) => {
const newKey = convertToHyphens(key);
setValue(prefix + "." + newKey, obj[key]);
Object.entries(obj).map(([key, value]) => {
if (key === "attributes" && isAttributesObject(value)) {
setValue(key, attributesToArray(value as Record<string, string[]>));
} else if (key === "config" || key === "attributes") {
setValue(key, !isEmpty(value) ? unflatten(value) : undefined);
} else if (multiline?.includes(key)) {
setValue(key, convertToMultiline(value as string[]));
} else {
setValue(key, value);
}
});
};
export const flatten = (
obj: Record<string, any> | undefined,
path = ""
): {} => {
if (!(obj instanceof Object)) return { [path.replace(/\.$/g, "")]: obj };
return Object.keys(obj).reduce((output, key) => {
return obj instanceof Array
? {
...output,
...flatten(obj[key as unknown as number], path + "[" + key + "]."),
}
: { ...output, ...flatten(obj[key], path + key + ".") };
}, {});
};
export const convertFormValuesToObject = (
obj: any,
firstInstanceOnly?: boolean
) => {
const keyValues = Object.keys(obj).map((key) => {
const newKey = firstInstanceOnly
? key.replace(/-/, ".")
: key.replace(/-/g, ".");
return { [newKey]: obj[key] };
export function convertFormValuesToObject<T>(
obj: T,
multiline: string[] | undefined = []
): Omit<T, typeof multiline[number] | "attributes" | "config"> {
const result: any = {};
Object.entries(obj).map(([key, value]) => {
if (isAttributeArray(value)) {
result[key] = arrayToAttributes(value as KeyValueType[]);
} else if (multiline.includes(key)) {
result[key] = toValue(value);
} else if (key === "config" || key === "attributes") {
result[key] = flatten(value as Record<string, any>, { safe: true });
} else {
result[key] = value;
}
});
return Object.assign({}, ...keyValues);
};
return result;
}
export const emptyFormatter =
(): IFormatter => (data?: IFormatterValueType) => {