From 09d7194f9bf7bc0107a4d191b77e4b00000ecd2c Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 9 Aug 2022 10:32:16 +0200 Subject: [PATCH] Work around object collisions in forms (#3035) --- .../components/ExecutionConfigModal.tsx | 22 ++----- src/client-scopes/details/ScopeForm.tsx | 12 ++-- src/clients/ClientDetails.tsx | 35 ++++------- src/clients/add/AccessSettings.tsx | 5 +- src/clients/add/CapabilityConfig.tsx | 22 +++++-- src/clients/add/LoginSettingsPanel.tsx | 7 ++- src/clients/add/LogoutPanel.tsx | 17 ++++-- src/clients/add/SamlConfig.tsx | 19 ++++-- src/clients/add/SamlSignature.tsx | 18 ++++-- src/clients/advanced/AdvancedSettings.tsx | 25 ++++++-- .../advanced/FineGrainOpenIdConnect.tsx | 58 +++++++++++++------ .../OpenIdConnectCompatibilityModes.tsx | 11 +++- src/clients/credentials/SignedJWT.tsx | 6 +- src/clients/credentials/X509.tsx | 7 ++- src/clients/keys/Keys.tsx | 5 +- src/components/dynamic/BooleanComponent.tsx | 3 +- .../dynamic/ClientSelectComponent.tsx | 3 +- src/components/dynamic/DynamicComponents.tsx | 4 ++ src/components/dynamic/FileComponent.tsx | 3 +- src/components/dynamic/GroupComponent.tsx | 3 +- src/components/dynamic/ListComponent.tsx | 3 +- src/components/dynamic/MapComponent.tsx | 3 +- .../dynamic/MultivaluedListComponent.tsx | 3 +- .../dynamic/MultivaluedStringComponent.tsx | 3 +- src/components/dynamic/RoleComponent.tsx | 3 +- src/components/dynamic/ScriptComponent.tsx | 3 +- src/components/dynamic/StringComponent.tsx | 3 +- src/identity-providers/add/AddMapper.tsx | 8 ++- src/realm-settings/GeneralTab.tsx | 16 +++-- src/util.test.ts | 54 +++++++++++------ src/util.ts | 24 +++++++- 31 files changed, 267 insertions(+), 141 deletions(-) diff --git a/src/authentication/components/ExecutionConfigModal.tsx b/src/authentication/components/ExecutionConfigModal.tsx index 4034cf700a..e84193ab33 100644 --- a/src/authentication/components/ExecutionConfigModal.tsx +++ b/src/authentication/components/ExecutionConfigModal.tsx @@ -17,13 +17,13 @@ import { CogIcon, TrashIcon } from "@patternfly/react-icons"; import type AuthenticatorConfigRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation"; import type AuthenticatorConfigInfoRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation"; -import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation"; import type { ExpandableExecution } from "../execution-model"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import { useAlerts } from "../../components/alert/Alerts"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { DynamicComponents } from "../../components/dynamic/DynamicComponents"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; +import { convertToFormValues } from "../../util"; type ExecutionConfigModalForm = { alias: string; @@ -54,22 +54,8 @@ export const ExecutionConfigModal = ({ formState: { errors }, } = form; - const setupForm = ( - configDescription: AuthenticatorConfigInfoRepresentation, - config?: AuthenticatorConfigRepresentation - ) => { - configDescription.properties!.map( - (property: ConfigPropertyRepresentation) => { - setValue( - `config.${property.name}`, - config?.config?.[property.name!] || property.defaultValue || "" - ); - } - ); - if (config) { - setValue("alias", config.alias); - setValue("id", config.id); - } + const setupForm = (config?: AuthenticatorConfigRepresentation) => { + convertToFormValues(config, setValue); }; useFetch( @@ -94,7 +80,7 @@ export const ExecutionConfigModal = ({ ); useEffect(() => { - if (configDescription) setupForm(configDescription, config); + if (configDescription) setupForm(config); }, [show]); const save = async (changedConfig: ExecutionConfigModalForm) => { diff --git a/src/client-scopes/details/ScopeForm.tsx b/src/client-scopes/details/ScopeForm.tsx index 585abe616c..56455b6d6e 100644 --- a/src/client-scopes/details/ScopeForm.tsx +++ b/src/client-scopes/details/ScopeForm.tsx @@ -21,7 +21,7 @@ import { } from "../../components/client-scope/ClientScopeTypes"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { useLoginProviders } from "../../context/server-info/ServerInfoProvider"; -import { convertToFormValues } from "../../util"; +import { convertAttributeNameToForm, convertToFormValues } from "../../util"; import { useRealm } from "../../context/realm-context/RealmContext"; import { getProtocolName } from "../../clients/utils"; import { toClientScopes } from "../routes/ClientScopes"; @@ -211,7 +211,9 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { fieldId="kc-display.on.consent.screen" > ( @@ -240,7 +242,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { ref={register} type="text" id="kc-consent-screen-text" - name="attributes.consent.screen.text" + name={convertAttributeNameToForm("attributes.consent.screen.text")} /> )} @@ -256,7 +258,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { fieldId="includeInTokenScope" > ( @@ -281,7 +283,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { fieldId="kc-gui-order" > ( diff --git a/src/clients/ClientDetails.tsx b/src/clients/ClientDetails.tsx index bfa2c14535..9d4adfa906 100644 --- a/src/clients/ClientDetails.tsx +++ b/src/clients/ClientDetails.tsx @@ -36,6 +36,7 @@ import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; import { RolesList } from "../realm-roles/RolesList"; import { + convertAttributeNameToForm, convertFormValuesToObject, convertToFormValues, exportClient, @@ -237,12 +238,12 @@ export default function ClientDetails() { form.reset({ ...client }); convertToFormValues(client, form.setValue); form.setValue( - "attributes.request.uris", + convertAttributeNameToForm("attributes.request.uris"), stringToMultiline(client.attributes?.["request.uris"]) ); if (client.attributes?.["acr.loa.map"]) { form.setValue( - "attributes.acr.loa.map", + convertAttributeNameToForm("attributes.acr.loa.map"), Object.entries(JSON.parse(client.attributes["acr.loa.map"])).flatMap( ([key, value]) => ({ key, value }) ) @@ -250,21 +251,16 @@ export default function ClientDetails() { } if (client.attributes?.["default.acr.values"]) { form.setValue( - "attributes.default.acr.values", + convertAttributeNameToForm("attributes.default.acr.values"), stringToMultiline(client.attributes["default.acr.values"]) ); } if (client.attributes?.["post.logout.redirect.uris"]) { form.setValue( - "attributes.post.logout.redirect.uris", + convertAttributeNameToForm("attributes.post.logout.redirect.uris"), stringToMultiline(client.attributes["post.logout.redirect.uris"]) ); } - Object.entries(client.attributes || {}) - .filter(([key]) => key.startsWith("saml.server.signature")) - .map(([key, value]) => - form.setValue("attributes." + key.replaceAll(".", "$"), value) - ); }; useFetch( @@ -295,36 +291,29 @@ export default function ClientDetails() { return; } - const values = form.getValues(); + const values = convertFormValuesToObject(form.getValues()); - if (values.attributes?.request.uris) { + if (values.attributes?.["request.uris"]) { values.attributes["request.uris"] = toStringValue( - values.attributes.request.uris + values.attributes["request.uris"] ); } - if (values.attributes?.default?.acr?.values) { + if (values.attributes?.["default.acr.values"]) { values.attributes["default.acr.values"] = toStringValue( - values.attributes.default.acr.values + values.attributes["default.acr.values"] ); } - if (values.attributes?.post.logout.redirect.uris) { + if (values.attributes?.["post.logout.redirect.uris"]) { values.attributes["post.logout.redirect.uris"] = toStringValue( - values.attributes.post.logout.redirect.uris + values.attributes["post.logout.redirect.uris"] ); } const submittedClient = convertFormValuesToObject(values); - Object.entries(values.attributes || {}) - .filter(([key]) => key.includes("$")) - .map( - ([key, value]) => - (submittedClient.attributes![key.replaceAll("$", ".")] = value) - ); - if (submittedClient.attributes?.["acr.loa.map"]) { submittedClient.attributes["acr.loa.map"] = JSON.stringify( Object.fromEntries( diff --git a/src/clients/add/AccessSettings.tsx b/src/clients/add/AccessSettings.tsx index aa9406ae5e..0ed5031c15 100644 --- a/src/clients/add/AccessSettings.tsx +++ b/src/clients/add/AccessSettings.tsx @@ -12,6 +12,7 @@ import { SaveReset } from "../advanced/SaveReset"; import environment from "../../environment"; import { useRealm } from "../../context/realm-context/RealmContext"; import { useAccess } from "../../context/access/Access"; +import { convertAttributeNameToForm } from "../../util"; export const AccessSettings = ({ client, @@ -99,7 +100,9 @@ export const AccessSettings = ({ } > diff --git a/src/clients/add/CapabilityConfig.tsx b/src/clients/add/CapabilityConfig.tsx index f9606a9552..d5a8582933 100644 --- a/src/clients/add/CapabilityConfig.tsx +++ b/src/clients/add/CapabilityConfig.tsx @@ -12,6 +12,7 @@ import { import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import { FormAccess } from "../../components/form-access/FormAccess"; import { HelpItem } from "../../components/help-enabler/HelpItem"; +import { convertAttributeNameToForm } from "../../util"; import "./capability-config.css"; @@ -68,7 +69,12 @@ export const CapabilityConfig = ({ if (!value) { setValue("authorizationServicesEnabled", false); setValue("serviceAccountsEnabled", false); - setValue("attributes.oidc.ciba.grant.enabled", false); + setValue( + convertAttributeNameToForm( + "attributes.oidc.ciba.grant.enabled" + ), + false + ); } }} /> @@ -216,7 +222,9 @@ export const CapabilityConfig = ({ ( @@ -239,7 +247,9 @@ export const CapabilityConfig = ({ ( @@ -279,7 +289,7 @@ export const CapabilityConfig = ({ hasNoPaddingTop > ( @@ -306,7 +316,9 @@ export const CapabilityConfig = ({ hasNoPaddingTop > ( diff --git a/src/clients/add/LoginSettingsPanel.tsx b/src/clients/add/LoginSettingsPanel.tsx index 43e0f3b914..7ef508b2bd 100644 --- a/src/clients/add/LoginSettingsPanel.tsx +++ b/src/clients/add/LoginSettingsPanel.tsx @@ -14,6 +14,7 @@ import { FormAccess } from "../../components/form-access/FormAccess"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea"; +import { convertAttributeNameToForm } from "../../util"; export const LoginSettingsPanel = ({ access }: { access?: boolean }) => { const { t } = useTranslation("clients"); @@ -109,7 +110,9 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => { hasNoPaddingTop > ( @@ -136,7 +139,7 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => { > diff --git a/src/clients/add/LogoutPanel.tsx b/src/clients/add/LogoutPanel.tsx index 4967e586e5..944a54a2c8 100644 --- a/src/clients/add/LogoutPanel.tsx +++ b/src/clients/add/LogoutPanel.tsx @@ -9,6 +9,7 @@ import { HelpItem } from "../../components/help-enabler/HelpItem"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; import { useAccess } from "../../context/access/Access"; import { SaveReset } from "../advanced/SaveReset"; +import { convertAttributeNameToForm } from "../../util"; export const LogoutPanel = ({ save, @@ -84,7 +85,9 @@ export const LogoutPanel = ({ ((uri.startsWith("https://") || uri.startsWith("http://")) && @@ -123,7 +126,9 @@ export const LogoutPanel = ({ ((uri.startsWith("https://") || uri.startsWith("http://")) && @@ -150,7 +155,9 @@ export const LogoutPanel = ({ hasNoPaddingTop > ( @@ -176,7 +183,9 @@ export const LogoutPanel = ({ hasNoPaddingTop > ( diff --git a/src/clients/add/SamlConfig.tsx b/src/clients/add/SamlConfig.tsx index b9a4a9a2a1..3e2629f91e 100644 --- a/src/clients/add/SamlConfig.tsx +++ b/src/clients/add/SamlConfig.tsx @@ -12,6 +12,7 @@ import { import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import { FormAccess } from "../../components/form-access/FormAccess"; import { HelpItem } from "../../components/help-enabler/HelpItem"; +import { convertAttributeNameToForm } from "../../util"; export const Toggle = ({ name, label }: { name: string; label: string }) => { const { t } = useTranslation("clients"); @@ -98,27 +99,33 @@ export const SamlConfig = () => { /> diff --git a/src/clients/add/SamlSignature.tsx b/src/clients/add/SamlSignature.tsx index a1eaebda09..fbc483b216 100644 --- a/src/clients/add/SamlSignature.tsx +++ b/src/clients/add/SamlSignature.tsx @@ -9,6 +9,7 @@ import { } from "@patternfly/react-core"; import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; +import { convertAttributeNameToForm } from "../../util"; import { FormAccess } from "../../components/form-access/FormAccess"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { Toggle } from "./SamlConfig"; @@ -48,7 +49,7 @@ export const SamlSignature = () => { const { control, watch } = useFormContext(); - const signDocs = watch("attributes.saml$server$signature"); + const signDocs = watch("attributes.saml.server.signature"); const signAssertion = watch("attributes.saml.assertion.signature"); return ( @@ -57,9 +58,12 @@ export const SamlSignature = () => { role="manage-clients" className="keycloak__capability-config__form" > - + {(signDocs === "true" || signAssertion === "true") && ( @@ -75,7 +79,9 @@ export const SamlSignature = () => { } > { } > ( diff --git a/src/clients/advanced/AdvancedSettings.tsx b/src/clients/advanced/AdvancedSettings.tsx index f35697f2e2..085eae9b29 100644 --- a/src/clients/advanced/AdvancedSettings.tsx +++ b/src/clients/advanced/AdvancedSettings.tsx @@ -17,6 +17,7 @@ import { TimeSelector } from "../../components/time-selector/TimeSelector"; import { TokenLifespan } from "./TokenLifespan"; import { KeyValueInput } from "../../components/key-value-form/KeyValueInput"; import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput"; +import { convertAttributeNameToForm } from "../../util"; type AdvancedSettingsProps = { control: Control>; @@ -53,7 +54,9 @@ export const AdvancedSettings = ({ } > ( @@ -70,7 +73,9 @@ export const AdvancedSettings = ({ <> ( @@ -149,7 +156,9 @@ export const AdvancedSettings = ({ } > ( @@ -173,7 +182,9 @@ export const AdvancedSettings = ({ /> } > - + } > - + )} diff --git a/src/clients/advanced/FineGrainOpenIdConnect.tsx b/src/clients/advanced/FineGrainOpenIdConnect.tsx index 341f0d5585..0fe0b80673 100644 --- a/src/clients/advanced/FineGrainOpenIdConnect.tsx +++ b/src/clients/advanced/FineGrainOpenIdConnect.tsx @@ -13,7 +13,7 @@ import { import { FormAccess } from "../../components/form-access/FormAccess"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; -import { sortProviders } from "../../util"; +import { convertAttributeNameToForm, sortProviders } from "../../util"; import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; @@ -161,7 +161,7 @@ export const FineGrainOpenIdConnect = ({ @@ -179,7 +179,7 @@ export const FineGrainOpenIdConnect = ({ @@ -197,7 +197,7 @@ export const FineGrainOpenIdConnect = ({ @@ -213,7 +213,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -244,7 +246,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -275,7 +279,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -306,7 +312,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -337,7 +345,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -368,7 +378,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -399,7 +411,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -430,7 +444,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -461,7 +477,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -492,7 +510,7 @@ export const FineGrainOpenIdConnect = ({ } > @@ -508,7 +526,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -539,7 +559,9 @@ export const FineGrainOpenIdConnect = ({ } > ( @@ -570,7 +592,9 @@ export const FineGrainOpenIdConnect = ({ } > ( diff --git a/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx b/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx index 52f32a2894..8663158839 100644 --- a/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx +++ b/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx @@ -4,6 +4,7 @@ import { ActionGroup, Button, FormGroup, Switch } from "@patternfly/react-core"; import { FormAccess } from "../../components/form-access/FormAccess"; import { HelpItem } from "../../components/help-enabler/HelpItem"; +import { convertAttributeNameToForm } from "../../util"; type OpenIdConnectCompatibilityModesProps = { control: Control>; @@ -37,7 +38,9 @@ export const OpenIdConnectCompatibilityModes = ({ } > ( @@ -63,7 +66,7 @@ export const OpenIdConnectCompatibilityModes = ({ } > ( @@ -115,7 +118,9 @@ export const OpenIdConnectCompatibilityModes = ({ } > ( diff --git a/src/clients/credentials/SignedJWT.tsx b/src/clients/credentials/SignedJWT.tsx index aa6d2140fd..2b4eb49555 100644 --- a/src/clients/credentials/SignedJWT.tsx +++ b/src/clients/credentials/SignedJWT.tsx @@ -10,7 +10,7 @@ import { import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { HelpItem } from "../../components/help-enabler/HelpItem"; -import { sortProviders } from "../../util"; +import { convertAttributeNameToForm, sortProviders } from "../../util"; export const SignedJWT = () => { const { control } = useFormContext(); @@ -32,7 +32,9 @@ export const SignedJWT = () => { } > ( diff --git a/src/clients/credentials/X509.tsx b/src/clients/credentials/X509.tsx index 2b14b6987d..8ab4de3289 100644 --- a/src/clients/credentials/X509.tsx +++ b/src/clients/credentials/X509.tsx @@ -3,6 +3,7 @@ import { Controller, useFormContext } from "react-hook-form"; import { FormGroup, Switch, ValidatedOptions } from "@patternfly/react-core"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; +import { convertAttributeNameToForm } from "../../util"; export const X509 = () => { const { t } = useTranslation("clients"); @@ -25,7 +26,9 @@ export const X509 = () => { hasNoPaddingTop > ( @@ -60,7 +63,7 @@ export const X509 = () => { ref={register({ required: true })} type="text" id="kc-subject" - name="attributes.x509.subjectdn" + name={convertAttributeNameToForm("attributes.x509.subjectdn")} validated={ errors.attributes?.["x509.subjectdn"] ? ValidatedOptions.error diff --git a/src/clients/keys/Keys.tsx b/src/clients/keys/Keys.tsx index 26c391700d..12f3b66fee 100644 --- a/src/clients/keys/Keys.tsx +++ b/src/clients/keys/Keys.tsx @@ -27,6 +27,7 @@ import { GenerateKeyDialog } from "./GenerateKeyDialog"; import { useFetch, useAdminClient } from "../../context/auth/AdminClient"; import { useAlerts } from "../../components/alert/Alerts"; import useToggle from "../../utils/useToggle"; +import { convertAttributeNameToForm } from "../../util"; import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog"; import { Certificate } from "./Certificate"; @@ -143,7 +144,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => { } > ( @@ -178,7 +179,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => { diff --git a/src/components/dynamic/BooleanComponent.tsx b/src/components/dynamic/BooleanComponent.tsx index 84fd1ea276..68f7e1a692 100644 --- a/src/components/dynamic/BooleanComponent.tsx +++ b/src/components/dynamic/BooleanComponent.tsx @@ -4,6 +4,7 @@ import { FormGroup, Switch } from "@patternfly/react-core"; import type { ComponentProps } from "./components"; import { HelpItem } from "../help-enabler/HelpItem"; +import { convertToName } from "./DynamicComponents"; export const BooleanComponent = ({ name, @@ -25,7 +26,7 @@ export const BooleanComponent = ({ } > { return ( ); diff --git a/src/components/dynamic/DynamicComponents.tsx b/src/components/dynamic/DynamicComponents.tsx index a24239bb51..1b08e8b4ae 100644 --- a/src/components/dynamic/DynamicComponents.tsx +++ b/src/components/dynamic/DynamicComponents.tsx @@ -1,6 +1,7 @@ import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation"; import { COMPONENTS, isValidComponentType } from "./components"; +import { convertAttributeNameToForm } from "../../util"; type DynamicComponentProps = { properties: ConfigPropertyRepresentation[]; @@ -24,3 +25,6 @@ export const DynamicComponents = ({ })} ); + +export const convertToName = (name: string) => + convertAttributeNameToForm(`config.${name}`); diff --git a/src/components/dynamic/FileComponent.tsx b/src/components/dynamic/FileComponent.tsx index 9fa9af3123..842590eef6 100644 --- a/src/components/dynamic/FileComponent.tsx +++ b/src/components/dynamic/FileComponent.tsx @@ -5,6 +5,7 @@ import { FileUpload, FormGroup } from "@patternfly/react-core"; import { HelpItem } from "../help-enabler/HelpItem"; import type { ComponentProps } from "./components"; +import { convertToName } from "./DynamicComponents"; export const FileComponent = ({ name, @@ -26,7 +27,7 @@ export const FileComponent = ({ fieldId={name!} > ( diff --git a/src/components/dynamic/GroupComponent.tsx b/src/components/dynamic/GroupComponent.tsx index 6f79a00070..21d45a8503 100644 --- a/src/components/dynamic/GroupComponent.tsx +++ b/src/components/dynamic/GroupComponent.tsx @@ -12,6 +12,7 @@ import { import type { ComponentProps } from "./components"; import { HelpItem } from "../help-enabler/HelpItem"; import { GroupPickerDialog } from "../group/GroupPickerDialog"; +import { convertToName } from "./DynamicComponents"; export const GroupComponent = ({ name, label, helpText }: ComponentProps) => { const { t } = useTranslation("dynamic"); @@ -20,7 +21,7 @@ export const GroupComponent = ({ name, label, helpText }: ComponentProps) => { return ( { const { t } = useTranslation("dynamic"); @@ -16,7 +17,7 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => { } fieldId={name!} > - + ); }; diff --git a/src/components/dynamic/MultivaluedListComponent.tsx b/src/components/dynamic/MultivaluedListComponent.tsx index 93474f7fd7..b46c022a0a 100644 --- a/src/components/dynamic/MultivaluedListComponent.tsx +++ b/src/components/dynamic/MultivaluedListComponent.tsx @@ -10,6 +10,7 @@ import { import { HelpItem } from "../help-enabler/HelpItem"; import type { ComponentProps } from "./components"; +import { convertToName } from "./DynamicComponents"; export const MultiValuedListComponent = ({ name, @@ -32,7 +33,7 @@ export const MultiValuedListComponent = ({ fieldId={name!} > ( diff --git a/src/components/dynamic/MultivaluedStringComponent.tsx b/src/components/dynamic/MultivaluedStringComponent.tsx index ff2cc228cc..a0eb9ee26b 100644 --- a/src/components/dynamic/MultivaluedStringComponent.tsx +++ b/src/components/dynamic/MultivaluedStringComponent.tsx @@ -4,6 +4,7 @@ import { FormGroup } from "@patternfly/react-core"; import type { ComponentProps } from "./components"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { MultiLineInput } from "../multi-line-input/MultiLineInput"; +import { convertToName } from "./DynamicComponents"; export const MultiValuedStringComponent = ({ name, @@ -13,7 +14,7 @@ export const MultiValuedStringComponent = ({ isDisabled = false, }: ComponentProps) => { const { t } = useTranslation("dynamic"); - const fieldName = `config.${name}`; + const fieldName = convertToName(name!); return ( ({ name: "realmRoles", @@ -47,7 +48,7 @@ export const RoleComponent = ({ const [clientRoles, setClientRoles] = useState([]); const [selectedRole, setSelectedRole] = useState(); - const fieldName = `config.${name}`; + const fieldName = convertToName(name!); useFetch( async () => { diff --git a/src/components/dynamic/ScriptComponent.tsx b/src/components/dynamic/ScriptComponent.tsx index 23bff01014..78a2cc8d58 100644 --- a/src/components/dynamic/ScriptComponent.tsx +++ b/src/components/dynamic/ScriptComponent.tsx @@ -5,6 +5,7 @@ import { CodeEditor, Language } from "@patternfly/react-code-editor"; import { HelpItem } from "../help-enabler/HelpItem"; import type { ComponentProps } from "./components"; +import { convertToName } from "./DynamicComponents"; export const ScriptComponent = ({ name, @@ -28,7 +29,7 @@ export const ScriptComponent = ({ fieldId={name!} > ( diff --git a/src/components/dynamic/StringComponent.tsx b/src/components/dynamic/StringComponent.tsx index 7c01af80be..d4a28d41db 100644 --- a/src/components/dynamic/StringComponent.tsx +++ b/src/components/dynamic/StringComponent.tsx @@ -5,6 +5,7 @@ import { FormGroup } from "@patternfly/react-core"; import { HelpItem } from "../help-enabler/HelpItem"; import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput"; import type { ComponentProps } from "./components"; +import { convertToName } from "./DynamicComponents"; export const StringComponent = ({ name, @@ -30,7 +31,7 @@ export const StringComponent = ({ isDisabled={isDisabled} ref={register()} type="text" - name={`config.${name}`} + name={convertToName(name!)} defaultValue={defaultValue?.toString()} /> diff --git a/src/identity-providers/add/AddMapper.tsx b/src/identity-providers/add/AddMapper.tsx index 6a13f26e9b..8c03ec87c4 100644 --- a/src/identity-providers/add/AddMapper.tsx +++ b/src/identity-providers/add/AddMapper.tsx @@ -166,8 +166,10 @@ export default function AddMapper() { const setupForm = (mapper: IdentityProviderMapperRepresentation) => { convertToFormValues(mapper, form.setValue); - form.setValue("config.attributes", JSON.parse(mapper.config.attributes)); - form.setValue("config.claims", JSON.parse(mapper.config.claims)); + form.setValue( + "config.attributes", + JSON.parse(mapper.config.attributes || "{}") + ); }; if (!mapperTypes || !currentMapper) { @@ -241,7 +243,7 @@ export default function AddMapper() { /> - {" "} + )} diff --git a/src/realm-settings/GeneralTab.tsx b/src/realm-settings/GeneralTab.tsx index 9f6aea4f43..3f5baaa7cb 100644 --- a/src/realm-settings/GeneralTab.tsx +++ b/src/realm-settings/GeneralTab.tsx @@ -16,7 +16,11 @@ import { } from "@patternfly/react-core"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; -import { addTrailingSlash, convertToFormValues } from "../util"; +import { + addTrailingSlash, + convertAttributeNameToForm, + convertToFormValues, +} from "../util"; import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled"; import { useAdminClient } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; @@ -58,7 +62,7 @@ export const RealmSettingsGeneralTab = ({ JSON.parse(realm.attributes["acr.loa.map"]) ).flatMap(([key, value]) => ({ key, value })); result.concat({ key: "", value: "" }); - setValue("attributes.acr.loa.map", result); + setValue(convertAttributeNameToForm("attributes.acr.loa.map"), result); } }; @@ -113,7 +117,7 @@ export const RealmSettingsGeneralTab = ({ @@ -168,7 +172,9 @@ export const RealmSettingsGeneralTab = ({ } > - + ( diff --git a/src/util.test.ts b/src/util.test.ts index f6503123cd..32dee9725a 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -1,8 +1,14 @@ import { describe, expect, it, vi } from "vitest"; -import { convertFormValuesToObject, convertToFormValues } from "./util"; +import { + convertAttributeNameToForm, + convertFormValuesToObject, + convertToFormValues, +} from "./util"; vi.mock("react"); +const TOKEN = "🍺"; + describe("Tests the form convert util functions", () => { it("convert to form values", () => { const given = { @@ -31,7 +37,7 @@ describe("Tests the form convert util functions", () => { const given = { name: "client", attributes: [{ key: "one", value: "1" }], - config: { one: { two: "3" } }, + config: { [`one${TOKEN}two`]: "3" }, }; //when @@ -51,10 +57,10 @@ describe("Tests the form convert util functions", () => { description: "", type: "default", attributes: { - display: { on: { consent: { screen: "true" } } }, - include: { in: { token: { scope: "true" } } }, - gui: { order: "1" }, - consent: { screen: { text: "" } }, + [`display${TOKEN}on${TOKEN}consent${TOKEN}screen`]: "true", + [`include${TOKEN}in${TOKEN}token${TOKEN}scope`]: "true", + [`gui${TOKEN}order`]: "1", + [`consent${TOKEN}screen${TOKEN}text`]: "", }, }; @@ -92,12 +98,10 @@ describe("Tests the form convert util functions", () => { //then expect(values).toEqual({ - attributes: { - display: { on: { consent: { screen: "true" } } }, - include: { in: { token: { scope: "true" } } }, - gui: { order: "1" }, - consent: { screen: { text: "" } }, - }, + [`attributes.display${TOKEN}on${TOKEN}consent${TOKEN}screen`]: "true", + [`attributes.include${TOKEN}in${TOKEN}token${TOKEN}scope`]: "true", + [`attributes.gui${TOKEN}order`]: "1", + [`attributes.consent${TOKEN}screen${TOKEN}text`]: "", }); }); @@ -115,17 +119,31 @@ describe("Tests the form convert util functions", () => { it("convert single element arrays to string", () => { const given = { - config: { group: ["one"], another: { nested: ["value"] } }, + config: { + group: ["one"], + "another.nested": ["value"], + }, }; - const setValue = vi.fn(); + const values: { [index: string]: any } = {}; + const spy = (name: string, value: any) => (values[name] = value); //when - convertToFormValues(given, setValue); + convertToFormValues(given, spy); //then - expect(setValue).toHaveBeenCalledWith("config", { - group: "one", - another: { nested: "value" }, + expect(values).toEqual({ + "config.group": "one", + [`config.another${TOKEN}nested`]: "value", }); }); + + it("should convert attribute name to form", () => { + const given = "attributes.some.strange.attribute"; + + //when + const form = convertAttributeNameToForm(given); + + //then + expect(form).toEqual(`attributes.some${TOKEN}strange${TOKEN}attribute`); + }); }); diff --git a/src/util.ts b/src/util.ts index 8e99fe3f05..bcc9939f59 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,7 +1,7 @@ import { cloneDeep } from "lodash-es"; import FileSaver from "file-saver"; import type { IFormatter, IFormatterValueType } from "@patternfly/react-table"; -import { unflatten, flatten } from "flat"; +import { 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"; @@ -85,6 +85,16 @@ const isAttributeArray = (value: any) => { const isEmpty = (obj: any) => Object.keys(obj).length === 0; +export const convertAttributeNameToForm = (name: string) => { + const index = name.indexOf("."); + return `${name.substring(0, index)}.${convertAttribute( + name.substring(index + 1) + )}`; +}; + +const convertAttribute = (name: string) => name.replace(/\./g, "🍺"); +const convertFormNameToAttribute = (name: string) => name.replace(/🍺/g, "."); + export const convertToFormValues = ( obj: any, setValue: (name: string, value: any) => void @@ -98,7 +108,10 @@ export const convertToFormValues = ( const convertedValues = Object.entries(flattened).map(([key, value]) => Array.isArray(value) ? [key, value[0]] : [key, value] ); - setValue(key, unflatten(Object.fromEntries(convertedValues))); + + convertedValues.forEach(([k, v]) => + setValue(`${key}.${convertAttribute(k)}`, v) + ); } else { setValue(key, undefined); } @@ -114,7 +127,12 @@ export function convertFormValuesToObject(obj: T): G { if (isAttributeArray(value)) { result[key] = keyValueToArray(value as KeyValueType[]); } else if (key === "config" || key === "attributes") { - result[key] = flatten(value as Record, { safe: true }); + result[key] = Object.fromEntries( + Object.entries(value).map(([k, v]) => [ + convertFormNameToAttribute(k), + v, + ]) + ); } else { result[key] = value; }