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", "@patternfly/react-table": "^4.44.4",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"flat": "^5.0.2",
"i18next": "^21.5.5", "i18next": "^21.5.5",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"moment": "^2.29.1", "moment": "^2.29.1",
@ -41,6 +42,7 @@
"@testing-library/react-hooks": "^7.0.2", "@testing-library/react-hooks": "^7.0.2",
"@types/dagre": "^0.7.45", "@types/dagre": "^0.7.45",
"@types/file-saver": "^2.0.4", "@types/file-saver": "^2.0.4",
"@types/flat": "^5.0.2",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",
"@types/node": "^16.11.12", "@types/node": "^16.11.12",
"@types/react": "^17.0.37", "@types/react": "^17.0.37",
@ -4873,6 +4875,12 @@
"integrity": "sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg==", "integrity": "sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg==",
"dev": true "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": { "node_modules/@types/geojson": {
"version": "7946.0.8", "version": "7946.0.8",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
@ -10616,6 +10624,14 @@
"node": ">=8" "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": { "node_modules/flat-cache": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@ -25327,6 +25343,12 @@
"integrity": "sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg==", "integrity": "sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg==",
"dev": true "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": { "@types/geojson": {
"version": "7946.0.8", "version": "7946.0.8",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
@ -29871,6 +29893,11 @@
"path-exists": "^4.0.0" "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": { "flat-cache": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "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", "@patternfly/react-table": "^4.44.4",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"flat": "^5.0.2",
"i18next": "^21.5.5", "i18next": "^21.5.5",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"moment": "^2.29.1", "moment": "^2.29.1",
@ -57,6 +58,7 @@
"@testing-library/react-hooks": "^7.0.2", "@testing-library/react-hooks": "^7.0.2",
"@types/dagre": "^0.7.45", "@types/dagre": "^0.7.45",
"@types/file-saver": "^2.0.4", "@types/file-saver": "^2.0.4",
"@types/flat": "^5.0.2",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",
"@types/node": "^16.11.12", "@types/node": "^16.11.12",
"@types/react": "^17.0.37", "@types/react": "^17.0.37",

View file

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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { InfoCircleIcon } from "@patternfly/react-icons"; import { InfoCircleIcon } from "@patternfly/react-icons";
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; 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 React, { useMemo, useState } from "react";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form"; import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -25,11 +25,7 @@ import {
} from "../components/confirm-dialog/ConfirmDialog"; } from "../components/confirm-dialog/ConfirmDialog";
import { DownloadDialog } from "../components/download-dialog/DownloadDialog"; import { DownloadDialog } from "../components/download-dialog/DownloadDialog";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { import type { MultiLine } from "../components/multi-line-input/multi-line-convert";
convertToMultiline,
MultiLine,
toValue,
} from "../components/multi-line-input/MultiLineInput";
import { import {
ViewHeader, ViewHeader,
ViewHeaderBadge, ViewHeaderBadge,
@ -219,18 +215,7 @@ export default function ClientDetails() {
}); });
const setupForm = (client: ClientRepresentation) => { const setupForm = (client: ClientRepresentation) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars convertToFormValues(client, form.setValue, ["redirectUris", "webOrigins"]);
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]);
}
});
}; };
useFetch( useFetch(
@ -239,7 +224,7 @@ export default function ClientDetails() {
if (!fetchedClient) { if (!fetchedClient) {
throw new Error(t("common:notFound")); throw new Error(t("common:notFound"));
} }
setClient(fetchedClient); setClient(cloneDeep(fetchedClient));
setupForm(fetchedClient); setupForm(fetchedClient);
}, },
[clientId] [clientId]
@ -260,19 +245,15 @@ export default function ClientDetails() {
toggleChangeAuthenticatorOpen(); toggleChangeAuthenticatorOpen();
return; return;
} }
const redirectUris = toValue(form.getValues()["redirectUris"]); const submittedClient = convertFormValuesToObject(form.getValues(), [
const webOrigins = toValue(form.getValues()["webOrigins"]); "redirectUris",
const attributes = convertFormValuesToObject( "webOrigins",
form.getValues()["attributes"] ]);
);
try { try {
const newClient: ClientRepresentation = { const newClient: ClientRepresentation = {
...client, ...client,
...form.getValues(), ...submittedClient,
redirectUris,
webOrigins,
attributes,
}; };
newClient.clientId = newClient.clientId?.trim(); newClient.clientId = newClient.clientId?.trim();
@ -282,7 +263,7 @@ export default function ClientDetails() {
setClient(newClient); setClient(newClient);
addAlert(t(messageKey), AlertVariant.success); addAlert(t(messageKey), AlertVariant.success);
} catch (error) { } 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 loginThemes = useServerInfo().themes!["login"];
const consentRequired = watch("consentRequired"); const consentRequired = watch("consentRequired");
const displayOnConsentScreen: string = watch( const displayOnConsentScreen: string = watch(
"attributes.display-on-consent-screen" "attributes.display.on.consent.screen"
); );
const protocol = watch("protocol"); const protocol = watch("protocol");
@ -250,7 +250,7 @@ export const ClientSettings = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="attributes.display-on-consent-screen" name="attributes.display.on.consent.screen"
defaultValue={false} defaultValue={false}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -278,7 +278,7 @@ export const ClientSettings = ({
> >
<TextArea <TextArea
id="kc-consent-screen-text" id="kc-consent-screen-text"
name="attributes.consent-screen-text" name="attributes.consent.screen.text"
ref={register} ref={register}
isDisabled={!(consentRequired && displayOnConsentScreen === "true")} isDisabled={!(consentRequired && displayOnConsentScreen === "true")}
/> />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -127,7 +127,7 @@ export default {
createError: "Could not create client: '{{error}}'", createError: "Could not create client: '{{error}}'",
clientImportError: "Could not import client: {{error}}", clientImportError: "Could not import client: {{error}}",
clientSaveSuccess: "Client successfully updated", clientSaveSuccess: "Client successfully updated",
clientSaveError: "Client could not be updated:", clientSaveError: "Client could not be updated: {{error}}",
clientImportSuccess: "Client imported successfully", clientImportSuccess: "Client imported successfully",
clientDelete: "Delete {{clientId}} ?", clientDelete: "Delete {{clientId}} ?",
clientDeletedSuccess: "The client has been deleted", 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 type { ComponentProps } from "./components";
import { HelpItem } from "../help-enabler/HelpItem"; import { HelpItem } from "../help-enabler/HelpItem";
import { convertToHyphens } from "../../util";
export const BooleanComponent = ({ export const BooleanComponent = ({
name, name,
@ -26,7 +25,7 @@ export const BooleanComponent = ({
} }
> >
<Controller <Controller
name={`config.${convertToHyphens(name!)}`} name={`config.${name}`}
data-testid={name} data-testid={name}
defaultValue={defaultValue} defaultValue={defaultValue}
control={control} 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 { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { HelpItem } from "../help-enabler/HelpItem"; import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { convertToHyphens } from "../../util";
export const ClientSelectComponent = ({ export const ClientSelectComponent = ({
name, name,
@ -58,7 +57,7 @@ export const ClientSelectComponent = ({
fieldId={name!} fieldId={name!}
> >
<Controller <Controller
name={`config.${convertToHyphens(name!)}`} name={`config.${name}`}
defaultValue={defaultValue || ""} defaultValue={defaultValue || ""}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

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

View file

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

View file

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

View file

@ -5,7 +5,6 @@ import { FormGroup } from "@patternfly/react-core";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput"; import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
import { convertToHyphens } from "../../util";
export const MultiValuedStringComponent = ({ export const MultiValuedStringComponent = ({
name, name,
@ -23,7 +22,7 @@ export const MultiValuedStringComponent = ({
fieldId={name!} fieldId={name!}
> >
<MultiLineInput <MultiLineInput
name={`config.${convertToHyphens(name!)}`} name={`config.${name}`}
aria-label={name} aria-label={name}
addButtonLabel={t("addMultivaluedLabel", { addButtonLabel={t("addMultivaluedLabel", {
fieldLabel: t(label!).toLowerCase(), 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 { useRealm } from "../../context/realm-context/RealmContext";
import { HelpItem } from "../help-enabler/HelpItem"; import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { convertToHyphens } from "../../util";
export const RoleComponent = ({ name, label, helpText }: ComponentProps) => { export const RoleComponent = ({ name, label, helpText }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
@ -34,7 +33,7 @@ export const RoleComponent = ({ name, label, helpText }: ComponentProps) => {
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]); const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleRepresentation>(); const [selectedRole, setSelectedRole] = useState<RoleRepresentation>();
const fieldName = `config.${convertToHyphens(name!)}`; const fieldName = `config.${name}`;
useFetch( useFetch(
async () => { async () => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -88,20 +88,7 @@ export const AESGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => { const setupForm = (component: ComponentRepresentation) => {
form.reset(); form.reset();
Object.entries(component).map(([key, value]) => { convertToFormValues(component, form.setValue);
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);
});
}; };
useFetch( useFetch(

View file

@ -88,20 +88,7 @@ export const ECDSAGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => { const setupForm = (component: ComponentRepresentation) => {
form.reset(); form.reset();
Object.entries(component).map(([key, value]) => { convertToFormValues(component, form.setValue);
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);
});
}; };
useFetch( useFetch(

View file

@ -90,23 +90,7 @@ export const HMACGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => { const setupForm = (component: ComponentRepresentation) => {
form.reset(); form.reset();
Object.entries(component).map(([key, value]) => { convertToFormValues(component, form.setValue);
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);
});
}; };
useFetch( useFetch(

View file

@ -88,33 +88,7 @@ export const JavaKeystoreForm = ({
const setupForm = (component: ComponentRepresentation) => { const setupForm = (component: ComponentRepresentation) => {
form.reset(); form.reset();
Object.entries(component).map(([key, value]) => { convertToFormValues(component, form.setValue);
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);
});
}; };
useFetch( useFetch(

View file

@ -90,23 +90,7 @@ export const RSAGeneratedForm = ({
const setupForm = (component: ComponentRepresentation) => { const setupForm = (component: ComponentRepresentation) => {
form.reset(); form.reset();
Object.entries(component).map(([key, value]) => { convertToFormValues(component, form.setValue);
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);
});
}; };
useFetch( useFetch(

View file

@ -95,29 +95,7 @@ export const RSAForm = ({
const setupForm = (component: ComponentRepresentation) => { const setupForm = (component: ComponentRepresentation) => {
form.reset(); form.reset();
Object.entries(component).map(([key, value]) => { convertToFormValues(component, form.setValue);
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);
});
}; };
useFetch( useFetch(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -35,7 +35,7 @@ export const LdapMapperUserAttribute = ({
type="text" type="text"
id="kc-user-model-attribute" id="kc-user-model-attribute"
data-testid="mapper-userModelAttribute-fld" data-testid="mapper-userModelAttribute-fld"
name="config.user-model-attribute[0]" name="config.user.model.attribute[0]"
ref={form.register} ref={form.register}
/> />
</FormGroup> </FormGroup>
@ -56,7 +56,7 @@ export const LdapMapperUserAttribute = ({
type="text" type="text"
id="kc-ldap-attribute" id="kc-ldap-attribute"
data-testid="mapper-ldapAttribute-fld" data-testid="mapper-ldapAttribute-fld"
name="config.ldap-attribute[0]" name="config.ldap.attribute[0]"
ref={form.register} ref={form.register}
/> />
</FormGroup> </FormGroup>
@ -73,7 +73,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="config.read-only" name="config.read.only"
defaultValue={ defaultValue={
mapperType === "user-attribute-ldap-mapper" ? ["true"] : ["false"] mapperType === "user-attribute-ldap-mapper" ? ["true"] : ["false"]
} }
@ -103,7 +103,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="config.always-read-value-from-ldap" name="config.always.read.value.from.ldap"
defaultValue={["false"]} defaultValue={["false"]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -131,7 +131,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="config.is-mandatory-in-ldap" name="config.is.mandatory.in.ldap"
defaultValue={["false"]} defaultValue={["false"]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -161,7 +161,7 @@ export const LdapMapperUserAttribute = ({
type="text" type="text"
id="kc-attribute-default-value" id="kc-attribute-default-value"
data-testid="mapper-attributeDefaultValue-fld" data-testid="mapper-attributeDefaultValue-fld"
name="config.attribute-default-value[0]" name="config.attribute.default.value[0]"
ref={form.register} ref={form.register}
/> />
</FormGroup> </FormGroup>
@ -178,7 +178,7 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="config.is-binary-attribute" name="config.is.binary.attribute"
defaultValue={["false"]} defaultValue={["false"]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -207,12 +207,12 @@ export const LdapMapperUserAttribute = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="config.is-der-formatted" name="config.is.der.formatted"
defaultValue={["false"]} defaultValue={["false"]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
id={"kc-der-formatted"} id="kc-der-formatted"
isDisabled={false} isDisabled={false}
onChange={(value) => onChange([`${value}`])} onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"} 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 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 ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import type { ProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation"; import type { ProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
import type KeycloakAdminClient from "@keycloak/keycloak-admin-client"; 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: { export const sortProviders = (providers: {
[index: string]: ProviderRepresentation; [index: string]: ProviderRepresentation;
@ -34,7 +46,7 @@ const sortProvider = (
}; };
export const exportClient = (client: ClientRepresentation): void => { export const exportClient = (client: ClientRepresentation): void => {
const clientCopy = _.cloneDeep(client); const clientCopy = cloneDeep(client);
delete clientCopy.id; delete clientCopy.id;
if (clientCopy.protocolMappers) { if (clientCopy.protocolMappers) {
@ -54,49 +66,59 @@ export const exportClient = (client: ClientRepresentation): void => {
export const toUpperCase = <T extends string>(name: T) => export const toUpperCase = <T extends string>(name: T) =>
(name.charAt(0).toUpperCase() + name.slice(1)) as Capitalize<T>; (name.charAt(0).toUpperCase() + name.slice(1)) as Capitalize<T>;
export const convertToHyphens = (s: string) => { const isAttributesObject = (value: any) => {
return s.replaceAll(".", "-"); 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 = ( export const convertToFormValues = (
obj: any, obj: any,
prefix: string, setValue: (name: string, value: any) => void,
setValue: (name: string, value: any) => void multiline?: string[]
) => { ) => {
return Object.keys(obj).map((key) => { Object.entries(obj).map(([key, value]) => {
const newKey = convertToHyphens(key); if (key === "attributes" && isAttributesObject(value)) {
setValue(prefix + "." + newKey, obj[key]); 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 = ( export function convertFormValuesToObject<T>(
obj: Record<string, any> | undefined, obj: T,
path = "" multiline: string[] | undefined = []
): {} => { ): Omit<T, typeof multiline[number] | "attributes" | "config"> {
if (!(obj instanceof Object)) return { [path.replace(/\.$/g, "")]: obj }; const result: any = {};
Object.entries(obj).map(([key, value]) => {
return Object.keys(obj).reduce((output, key) => { if (isAttributeArray(value)) {
return obj instanceof Array result[key] = arrayToAttributes(value as KeyValueType[]);
? { } else if (multiline.includes(key)) {
...output, result[key] = toValue(value);
...flatten(obj[key as unknown as number], path + "[" + key + "]."), } else if (key === "config" || key === "attributes") {
} result[key] = flatten(value as Record<string, any>, { safe: true });
: { ...output, ...flatten(obj[key], path + key + ".") }; } else {
}, {}); result[key] = value;
}; }
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] };
}); });
return Object.assign({}, ...keyValues); return result;
}; }
export const emptyFormatter = export const emptyFormatter =
(): IFormatter => (data?: IFormatterValueType) => { (): IFormatter => (data?: IFormatterValueType) => {