Work around object collisions in forms (#3035)

This commit is contained in:
Erik Jan de Wit 2022-08-09 10:32:16 +02:00 committed by GitHub
parent cfe9706baf
commit 09d7194f9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 267 additions and 141 deletions

View file

@ -17,13 +17,13 @@ import { CogIcon, TrashIcon } from "@patternfly/react-icons";
import type AuthenticatorConfigRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation"; import type AuthenticatorConfigRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation";
import type AuthenticatorConfigInfoRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation"; 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 type { ExpandableExecution } from "../execution-model";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { DynamicComponents } from "../../components/dynamic/DynamicComponents"; import { DynamicComponents } from "../../components/dynamic/DynamicComponents";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { convertToFormValues } from "../../util";
type ExecutionConfigModalForm = { type ExecutionConfigModalForm = {
alias: string; alias: string;
@ -54,22 +54,8 @@ export const ExecutionConfigModal = ({
formState: { errors }, formState: { errors },
} = form; } = form;
const setupForm = ( const setupForm = (config?: AuthenticatorConfigRepresentation) => {
configDescription: AuthenticatorConfigInfoRepresentation, convertToFormValues(config, setValue);
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);
}
}; };
useFetch( useFetch(
@ -94,7 +80,7 @@ export const ExecutionConfigModal = ({
); );
useEffect(() => { useEffect(() => {
if (configDescription) setupForm(configDescription, config); if (configDescription) setupForm(config);
}, [show]); }, [show]);
const save = async (changedConfig: ExecutionConfigModalForm) => { const save = async (changedConfig: ExecutionConfigModalForm) => {

View file

@ -21,7 +21,7 @@ import {
} from "../../components/client-scope/ClientScopeTypes"; } from "../../components/client-scope/ClientScopeTypes";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider"; import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
import { convertToFormValues } from "../../util"; import { convertAttributeNameToForm, convertToFormValues } from "../../util";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import { getProtocolName } from "../../clients/utils"; import { getProtocolName } from "../../clients/utils";
import { toClientScopes } from "../routes/ClientScopes"; import { toClientScopes } from "../routes/ClientScopes";
@ -211,7 +211,9 @@ 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={convertAttributeNameToForm(
"attributes.display.on.consent.screen"
)}
control={control} control={control}
defaultValue={displayOnConsentScreen} defaultValue={displayOnConsentScreen}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -240,7 +242,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={convertAttributeNameToForm("attributes.consent.screen.text")}
/> />
</FormGroup> </FormGroup>
)} )}
@ -256,7 +258,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldId="includeInTokenScope" fieldId="includeInTokenScope"
> >
<Controller <Controller
name="attributes.include.in.token.scope" name={convertAttributeNameToForm("attributes.include.in.token.scope")}
control={control} control={control}
defaultValue="true" defaultValue="true"
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -281,7 +283,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
fieldId="kc-gui-order" fieldId="kc-gui-order"
> >
<Controller <Controller
name="attributes.gui.order" name={convertAttributeNameToForm("attributes.gui.order")}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -36,6 +36,7 @@ import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { RolesList } from "../realm-roles/RolesList"; import { RolesList } from "../realm-roles/RolesList";
import { import {
convertAttributeNameToForm,
convertFormValuesToObject, convertFormValuesToObject,
convertToFormValues, convertToFormValues,
exportClient, exportClient,
@ -237,12 +238,12 @@ export default function ClientDetails() {
form.reset({ ...client }); form.reset({ ...client });
convertToFormValues(client, form.setValue); convertToFormValues(client, form.setValue);
form.setValue( form.setValue(
"attributes.request.uris", convertAttributeNameToForm("attributes.request.uris"),
stringToMultiline(client.attributes?.["request.uris"]) stringToMultiline(client.attributes?.["request.uris"])
); );
if (client.attributes?.["acr.loa.map"]) { if (client.attributes?.["acr.loa.map"]) {
form.setValue( form.setValue(
"attributes.acr.loa.map", convertAttributeNameToForm("attributes.acr.loa.map"),
Object.entries(JSON.parse(client.attributes["acr.loa.map"])).flatMap( Object.entries(JSON.parse(client.attributes["acr.loa.map"])).flatMap(
([key, value]) => ({ key, value }) ([key, value]) => ({ key, value })
) )
@ -250,21 +251,16 @@ export default function ClientDetails() {
} }
if (client.attributes?.["default.acr.values"]) { if (client.attributes?.["default.acr.values"]) {
form.setValue( form.setValue(
"attributes.default.acr.values", convertAttributeNameToForm("attributes.default.acr.values"),
stringToMultiline(client.attributes["default.acr.values"]) stringToMultiline(client.attributes["default.acr.values"])
); );
} }
if (client.attributes?.["post.logout.redirect.uris"]) { if (client.attributes?.["post.logout.redirect.uris"]) {
form.setValue( form.setValue(
"attributes.post.logout.redirect.uris", convertAttributeNameToForm("attributes.post.logout.redirect.uris"),
stringToMultiline(client.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( useFetch(
@ -295,36 +291,29 @@ export default function ClientDetails() {
return; 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"] = 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"] = 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"] = toStringValue(
values.attributes.post.logout.redirect.uris values.attributes["post.logout.redirect.uris"]
); );
} }
const submittedClient = const submittedClient =
convertFormValuesToObject<ClientRepresentation>(values); convertFormValuesToObject<ClientRepresentation>(values);
Object.entries(values.attributes || {})
.filter(([key]) => key.includes("$"))
.map(
([key, value]) =>
(submittedClient.attributes![key.replaceAll("$", ".")] = value)
);
if (submittedClient.attributes?.["acr.loa.map"]) { if (submittedClient.attributes?.["acr.loa.map"]) {
submittedClient.attributes["acr.loa.map"] = JSON.stringify( submittedClient.attributes["acr.loa.map"] = JSON.stringify(
Object.fromEntries( Object.fromEntries(

View file

@ -12,6 +12,7 @@ import { SaveReset } from "../advanced/SaveReset";
import environment from "../../environment"; import environment from "../../environment";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import { useAccess } from "../../context/access/Access"; import { useAccess } from "../../context/access/Access";
import { convertAttributeNameToForm } from "../../util";
export const AccessSettings = ({ export const AccessSettings = ({
client, client,
@ -99,7 +100,9 @@ export const AccessSettings = ({
} }
> >
<MultiLineInput <MultiLineInput
name="attributes.post.logout.redirect.uris" name={convertAttributeNameToForm(
"attributes.post.logout.redirect.uris"
)}
aria-label={t("validPostLogoutRedirectUri")} aria-label={t("validPostLogoutRedirectUri")}
addButtonLabel="clients:addPostLogoutRedirectUri" addButtonLabel="clients:addPostLogoutRedirectUri"
/> />

View file

@ -12,6 +12,7 @@ import {
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { convertAttributeNameToForm } from "../../util";
import "./capability-config.css"; import "./capability-config.css";
@ -68,7 +69,12 @@ export const CapabilityConfig = ({
if (!value) { if (!value) {
setValue("authorizationServicesEnabled", false); setValue("authorizationServicesEnabled", false);
setValue("serviceAccountsEnabled", 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 = ({
</GridItem> </GridItem>
<GridItem lg={8} sm={6}> <GridItem lg={8} sm={6}>
<Controller <Controller
name="attributes.oauth2.device.authorization.grant.enabled" name={convertAttributeNameToForm(
"attributes.oauth2.device.authorization.grant.enabled"
)}
defaultValue={false} defaultValue={false}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -239,7 +247,9 @@ export const CapabilityConfig = ({
</GridItem> </GridItem>
<GridItem lg={8} sm={6}> <GridItem lg={8} sm={6}>
<Controller <Controller
name="attributes.oidc.ciba.grant.enabled" name={convertAttributeNameToForm(
"attributes.oidc.ciba.grant.enabled"
)}
defaultValue={false} defaultValue={false}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -279,7 +289,7 @@ export const CapabilityConfig = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="attributes.saml.encrypt" name={convertAttributeNameToForm("attributes.saml.encrypt")}
control={control} control={control}
defaultValue={false} defaultValue={false}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -306,7 +316,9 @@ export const CapabilityConfig = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="attributes.saml.client.signature" name={convertAttributeNameToForm(
"attributes.saml.client.signature"
)}
control={control} control={control}
defaultValue={false} defaultValue={false}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -14,6 +14,7 @@ import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea"; import { KeycloakTextArea } from "../../components/keycloak-text-area/KeycloakTextArea";
import { convertAttributeNameToForm } from "../../util";
export const LoginSettingsPanel = ({ access }: { access?: boolean }) => { export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
@ -109,7 +110,9 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="attributes.display.on.consent.screen" name={convertAttributeNameToForm(
"attributes.display.on.consent.screen"
)}
defaultValue={false} defaultValue={false}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -136,7 +139,7 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => {
> >
<KeycloakTextArea <KeycloakTextArea
id="kc-consent-screen-text" id="kc-consent-screen-text"
name="attributes.consent.screen.text" name={convertAttributeNameToForm("attributes.consent.screen.text")}
ref={register} ref={register}
isDisabled={!(consentRequired && displayOnConsentScreen === "true")} isDisabled={!(consentRequired && displayOnConsentScreen === "true")}
/> />

View file

@ -9,6 +9,7 @@ import { HelpItem } from "../../components/help-enabler/HelpItem";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { useAccess } from "../../context/access/Access"; import { useAccess } from "../../context/access/Access";
import { SaveReset } from "../advanced/SaveReset"; import { SaveReset } from "../advanced/SaveReset";
import { convertAttributeNameToForm } from "../../util";
export const LogoutPanel = ({ export const LogoutPanel = ({
save, save,
@ -84,7 +85,9 @@ export const LogoutPanel = ({
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"
id="frontchannelLogoutUrl" id="frontchannelLogoutUrl"
name="attributes.frontchannel.logout.url" name={convertAttributeNameToForm(
"attributes.frontchannel.logout.url"
)}
ref={register({ ref={register({
validate: (uri) => validate: (uri) =>
((uri.startsWith("https://") || uri.startsWith("http://")) && ((uri.startsWith("https://") || uri.startsWith("http://")) &&
@ -123,7 +126,9 @@ export const LogoutPanel = ({
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"
id="backchannelLogoutUrl" id="backchannelLogoutUrl"
name="attributes.backchannel.logout.url" name={convertAttributeNameToForm(
"attributes.backchannel.logout.url"
)}
ref={register({ ref={register({
validate: (uri) => validate: (uri) =>
((uri.startsWith("https://") || uri.startsWith("http://")) && ((uri.startsWith("https://") || uri.startsWith("http://")) &&
@ -150,7 +155,9 @@ export const LogoutPanel = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="attributes.backchannel.logout.session.required" name={convertAttributeNameToForm(
"attributes.backchannel.logout.session.required"
)}
defaultValue="true" defaultValue="true"
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -176,7 +183,9 @@ export const LogoutPanel = ({
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="attributes.backchannel.logout.revoke.offline.tokens" name={convertAttributeNameToForm(
"attributes.backchannel.logout.revoke.offline.tokens"
)}
defaultValue="false" defaultValue="false"
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -12,6 +12,7 @@ import {
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { convertAttributeNameToForm } from "../../util";
export const Toggle = ({ name, label }: { name: string; label: string }) => { export const Toggle = ({ name, label }: { name: string; label: string }) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
@ -98,27 +99,33 @@ export const SamlConfig = () => {
/> />
</FormGroup> </FormGroup>
<Toggle <Toggle
name="attributes.saml.force.name.id.format" name={convertAttributeNameToForm(
"attributes.saml.force.name.id.format"
)}
label="forceNameIdFormat" label="forceNameIdFormat"
/> />
<Toggle <Toggle
name="attributes.saml.force.post.binding" name={convertAttributeNameToForm("attributes.saml.force.post.binding")}
label="forcePostBinding" label="forcePostBinding"
/> />
<Toggle <Toggle
name="attributes.saml.artifact.binding" name={convertAttributeNameToForm("attributes.saml.artifact.binding")}
label="forceArtifactBinding" label="forceArtifactBinding"
/> />
<Toggle <Toggle
name="attributes.saml.authnstatement" name={convertAttributeNameToForm("attributes.saml.authnstatement")}
label="includeAuthnStatement" label="includeAuthnStatement"
/> />
<Toggle <Toggle
name="attributes.saml.onetimeuse.condition" name={convertAttributeNameToForm(
"attributes.saml.onetimeuse.condition"
)}
label="includeOneTimeUseCondition" label="includeOneTimeUseCondition"
/> />
<Toggle <Toggle
name="attributes.saml.server.signature.keyinfo.ext" name={convertAttributeNameToForm(
"attributes.saml.server.signature.keyinfo.ext"
)}
label="optimizeLookup" label="optimizeLookup"
/> />
</FormAccess> </FormAccess>

View file

@ -9,6 +9,7 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import { convertAttributeNameToForm } from "../../util";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { Toggle } from "./SamlConfig"; import { Toggle } from "./SamlConfig";
@ -48,7 +49,7 @@ export const SamlSignature = () => {
const { control, watch } = useFormContext<ClientRepresentation>(); const { control, watch } = useFormContext<ClientRepresentation>();
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 (
@ -57,9 +58,12 @@ 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 <Toggle
name="attributes.saml.assertion.signature" name={convertAttributeNameToForm("attributes.saml.server.signature")}
label="signDocuments"
/>
<Toggle
name={convertAttributeNameToForm("attributes.saml.assertion.signature")}
label="signAssertions" label="signAssertions"
/> />
{(signDocs === "true" || signAssertion === "true") && ( {(signDocs === "true" || signAssertion === "true") && (
@ -75,7 +79,9 @@ export const SamlSignature = () => {
} }
> >
<Controller <Controller
name="attributes.saml.signature.algorithm" name={convertAttributeNameToForm(
"attributes.saml.signature.algorithm"
)}
defaultValue={SIGNATURE_ALGORITHMS[0]} defaultValue={SIGNATURE_ALGORITHMS[0]}
Key Key
control={control} control={control}
@ -114,7 +120,9 @@ export const SamlSignature = () => {
} }
> >
<Controller <Controller
name="attributes.saml$server$signature$keyinfo$xmlSigKeyInfoKeyNameTransformer" name={convertAttributeNameToForm(
"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

@ -17,6 +17,7 @@ import { TimeSelector } from "../../components/time-selector/TimeSelector";
import { TokenLifespan } from "./TokenLifespan"; import { TokenLifespan } from "./TokenLifespan";
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput"; import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput"; import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
import { convertAttributeNameToForm } from "../../util";
type AdvancedSettingsProps = { type AdvancedSettingsProps = {
control: Control<Record<string, any>>; control: Control<Record<string, any>>;
@ -53,7 +54,9 @@ export const AdvancedSettings = ({
} }
> >
<Controller <Controller
name="attributes.saml.assertion.lifespan" name={convertAttributeNameToForm(
"attributes.saml.assertion.lifespan"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -70,7 +73,9 @@ export const AdvancedSettings = ({
<> <>
<TokenLifespan <TokenLifespan
id="accessTokenLifespan" id="accessTokenLifespan"
name="attributes.access.token.lifespan" name={convertAttributeNameToForm(
"attributes.access.token.lifespan"
)}
defaultValue="" defaultValue=""
units={["minute", "day", "hour"]} units={["minute", "day", "hour"]}
control={control} control={control}
@ -114,7 +119,9 @@ export const AdvancedSettings = ({
} }
> >
<Controller <Controller
name="attributes.pkce.code.challenge.method" name={convertAttributeNameToForm(
"attributes.pkce.code.challenge.method"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -149,7 +156,9 @@ export const AdvancedSettings = ({
} }
> >
<Controller <Controller
name="attributes.require.pushed.authorization.requests" name={convertAttributeNameToForm(
"attributes.require.pushed.authorization.requests"
)}
defaultValue="false" defaultValue="false"
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -173,7 +182,9 @@ export const AdvancedSettings = ({
/> />
} }
> >
<KeyValueInput name="attributes.acr.loa.map" /> <KeyValueInput
name={convertAttributeNameToForm("attributes.acr.loa.map")}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("defaultACRValues")} label={t("defaultACRValues")}
@ -185,7 +196,9 @@ export const AdvancedSettings = ({
/> />
} }
> >
<MultiLineInput name="attributes.default.acr.values" /> <MultiLineInput
name={convertAttributeNameToForm("attributes.default.acr.values")}
/>
</FormGroup> </FormGroup>
</> </>
)} )}

View file

@ -13,7 +13,7 @@ import {
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; 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 { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
@ -161,7 +161,7 @@ export const FineGrainOpenIdConnect = ({
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"
id="logoUrl" id="logoUrl"
name="attributes.logoUri" name={convertAttributeNameToForm("attributes.logoUri")}
data-testid="logoUrl" data-testid="logoUrl"
ref={register} ref={register}
/> />
@ -179,7 +179,7 @@ export const FineGrainOpenIdConnect = ({
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"
id="policyUrl" id="policyUrl"
name="attributes.policyUri" name={convertAttributeNameToForm("attributes.policyUri")}
data-testid="policyUrl" data-testid="policyUrl"
ref={register} ref={register}
/> />
@ -197,7 +197,7 @@ export const FineGrainOpenIdConnect = ({
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"
id="termsOfServiceUrl" id="termsOfServiceUrl"
name="attributes.tosUri" name={convertAttributeNameToForm("attributes.tosUri")}
data-testid="termsOfServiceUrl" data-testid="termsOfServiceUrl"
ref={register} ref={register}
/> />
@ -213,7 +213,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.access.token.signed.response.alg" name={convertAttributeNameToForm(
"attributes.access.token.signed.response.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -244,7 +246,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.id.token.signed.response.alg" name={convertAttributeNameToForm(
"attributes.id.token.signed.response.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -275,7 +279,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.id.token.encrypted.response.alg" name={convertAttributeNameToForm(
"attributes.id.token.encrypted.response.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -306,7 +312,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.id.token.encrypted.response.enc" name={convertAttributeNameToForm(
"attributes.id.token.encrypted.response.enc"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -337,7 +345,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.user.info.response.signature.alg" name={convertAttributeNameToForm(
"attributes.user.info.response.signature.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -368,7 +378,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.request.object.signature.alg" name={convertAttributeNameToForm(
"attributes.request.object.signature.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -399,7 +411,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.request.object.encryption.alg" name={convertAttributeNameToForm(
"attributes.request.object.encryption.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -430,7 +444,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.request.object.encryption.enc" name={convertAttributeNameToForm(
"attributes.request.object.encryption.enc"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -461,7 +477,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.request.object.required" name={convertAttributeNameToForm(
"attributes.request.object.required"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -492,7 +510,7 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<MultiLineInput <MultiLineInput
name="attributes.request.uris" name={convertAttributeNameToForm("attributes.request.uris")}
aria-label={t("validRequestURIs")} aria-label={t("validRequestURIs")}
addButtonLabel="clients:addRequestUri" addButtonLabel="clients:addRequestUri"
/> />
@ -508,7 +526,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.authorization.signed.response.alg" name={convertAttributeNameToForm(
"attributes.authorization.signed.response.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -539,7 +559,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.authorization.encrypted.response.alg" name={convertAttributeNameToForm(
"attributes.authorization.encrypted.response.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -570,7 +592,9 @@ export const FineGrainOpenIdConnect = ({
} }
> >
<Controller <Controller
name="attributes.authorization.encrypted.response.enc" name={convertAttributeNameToForm(
"attributes.authorization.encrypted.response.enc"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -4,6 +4,7 @@ import { ActionGroup, Button, FormGroup, Switch } from "@patternfly/react-core";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { convertAttributeNameToForm } from "../../util";
type OpenIdConnectCompatibilityModesProps = { type OpenIdConnectCompatibilityModesProps = {
control: Control<Record<string, any>>; control: Control<Record<string, any>>;
@ -37,7 +38,9 @@ export const OpenIdConnectCompatibilityModes = ({
} }
> >
<Controller <Controller
name="attributes.exclude.session.state.from.auth.response" name={convertAttributeNameToForm(
"attributes.exclude.session.state.from.auth.response"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -63,7 +66,7 @@ export const OpenIdConnectCompatibilityModes = ({
} }
> >
<Controller <Controller
name="attributes.use.refresh.tokens" name={convertAttributeNameToForm("attributes.use.refresh.tokens")}
defaultValue="true" defaultValue="true"
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -115,7 +118,9 @@ export const OpenIdConnectCompatibilityModes = ({
} }
> >
<Controller <Controller
name="attributes.token.response.type.bearer.lower-case" name={convertAttributeNameToForm(
"attributes.token.response.type.bearer.lower-case"
)}
defaultValue="false" defaultValue="false"
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -10,7 +10,7 @@ import {
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { sortProviders } from "../../util"; import { convertAttributeNameToForm, sortProviders } from "../../util";
export const SignedJWT = () => { export const SignedJWT = () => {
const { control } = useFormContext(); const { control } = useFormContext();
@ -32,7 +32,9 @@ export const SignedJWT = () => {
} }
> >
<Controller <Controller
name="attributes.token.endpoint.auth.signing.alg" name={convertAttributeNameToForm(
"attributes.token.endpoint.auth.signing.alg"
)}
defaultValue="" defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -3,6 +3,7 @@ import { Controller, useFormContext } from "react-hook-form";
import { FormGroup, Switch, ValidatedOptions } from "@patternfly/react-core"; import { FormGroup, Switch, ValidatedOptions } from "@patternfly/react-core";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput";
import { convertAttributeNameToForm } from "../../util";
export const X509 = () => { export const X509 = () => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
@ -25,7 +26,9 @@ export const X509 = () => {
hasNoPaddingTop hasNoPaddingTop
> >
<Controller <Controller
name="attributes.x509.allow.regex.pattern.comparison" name={convertAttributeNameToForm(
"attributes.x509.allow.regex.pattern.comparison"
)}
defaultValue="false" defaultValue="false"
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -60,7 +63,7 @@ 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={convertAttributeNameToForm("attributes.x509.subjectdn")}
validated={ validated={
errors.attributes?.["x509.subjectdn"] errors.attributes?.["x509.subjectdn"]
? ValidatedOptions.error ? ValidatedOptions.error

View file

@ -27,6 +27,7 @@ import { GenerateKeyDialog } from "./GenerateKeyDialog";
import { useFetch, useAdminClient } from "../../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import useToggle from "../../utils/useToggle"; import useToggle from "../../utils/useToggle";
import { convertAttributeNameToForm } from "../../util";
import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog"; import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog";
import { Certificate } from "./Certificate"; import { Certificate } from "./Certificate";
@ -143,7 +144,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => {
} }
> >
<Controller <Controller
name="attributes.use.jwks.url" name={convertAttributeNameToForm("attributes.use.jwks.url")}
defaultValue="false" defaultValue="false"
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
@ -178,7 +179,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => {
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"
id="jwksUrl" id="jwksUrl"
name="attributes.jwks.url" name={convertAttributeNameToForm("attributes.jwks.url")}
ref={register} ref={register}
/> />
</FormGroup> </FormGroup>

View file

@ -4,6 +4,7 @@ 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 { convertToName } from "./DynamicComponents";
export const BooleanComponent = ({ export const BooleanComponent = ({
name, name,
@ -25,7 +26,7 @@ export const BooleanComponent = ({
} }
> >
<Controller <Controller
name={`config.${name}`} name={convertToName(name!)}
data-testid={name} data-testid={name}
defaultValue={defaultValue || false} defaultValue={defaultValue || false}
control={control} control={control}

View file

@ -1,11 +1,12 @@
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { ClientSelect } from "../client/ClientSelect"; import { ClientSelect } from "../client/ClientSelect";
import { convertToName } from "./DynamicComponents";
export const ClientSelectComponent = (props: ComponentProps) => { export const ClientSelectComponent = (props: ComponentProps) => {
return ( return (
<ClientSelect <ClientSelect
{...props} {...props}
name={`config.${props.name}`} name={convertToName(props.name!)}
namespace="dynamic" namespace="dynamic"
/> />
); );

View file

@ -1,6 +1,7 @@
import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation"; import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation";
import { COMPONENTS, isValidComponentType } from "./components"; import { COMPONENTS, isValidComponentType } from "./components";
import { convertAttributeNameToForm } from "../../util";
type DynamicComponentProps = { type DynamicComponentProps = {
properties: ConfigPropertyRepresentation[]; properties: ConfigPropertyRepresentation[];
@ -24,3 +25,6 @@ export const DynamicComponents = ({
})} })}
</> </>
); );
export const convertToName = (name: string) =>
convertAttributeNameToForm(`config.${name}`);

View file

@ -5,6 +5,7 @@ import { FileUpload, FormGroup } 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 { convertToName } from "./DynamicComponents";
export const FileComponent = ({ export const FileComponent = ({
name, name,
@ -26,7 +27,7 @@ export const FileComponent = ({
fieldId={name!} fieldId={name!}
> >
<Controller <Controller
name={`config.${name}`} name={convertToName(name!)}
control={control} control={control}
defaultValue={defaultValue || ""} defaultValue={defaultValue || ""}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -12,6 +12,7 @@ import {
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { HelpItem } from "../help-enabler/HelpItem"; import { HelpItem } from "../help-enabler/HelpItem";
import { GroupPickerDialog } from "../group/GroupPickerDialog"; import { GroupPickerDialog } from "../group/GroupPickerDialog";
import { convertToName } from "./DynamicComponents";
export const GroupComponent = ({ name, label, helpText }: ComponentProps) => { export const GroupComponent = ({ name, label, helpText }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
@ -20,7 +21,7 @@ export const GroupComponent = ({ name, label, helpText }: ComponentProps) => {
return ( return (
<Controller <Controller
name={`config.${name}`} name={convertToName(name!)}
defaultValue="" defaultValue=""
typeAheadAriaLabel={t("selectGroup")} typeAheadAriaLabel={t("selectGroup")}
control={control} control={control}

View file

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

View file

@ -4,6 +4,7 @@ import { FormGroup } 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 { KeyValueInput } from "../key-value-form/KeyValueInput"; import { KeyValueInput } from "../key-value-form/KeyValueInput";
import { convertToName } from "./DynamicComponents";
export const MapComponent = ({ name, label, helpText }: ComponentProps) => { export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
@ -16,7 +17,7 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
} }
fieldId={name!} fieldId={name!}
> >
<KeyValueInput name={`config.${name}`} /> <KeyValueInput name={convertToName(name!)} />
</FormGroup> </FormGroup>
); );
}; };

View file

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

View file

@ -4,6 +4,7 @@ import { FormGroup } from "@patternfly/react-core";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { MultiLineInput } from "../multi-line-input/MultiLineInput"; import { MultiLineInput } from "../multi-line-input/MultiLineInput";
import { convertToName } from "./DynamicComponents";
export const MultiValuedStringComponent = ({ export const MultiValuedStringComponent = ({
name, name,
@ -13,7 +14,7 @@ export const MultiValuedStringComponent = ({
isDisabled = false, isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const fieldName = `config.${name}`; const fieldName = convertToName(name!);
return ( return (
<FormGroup <FormGroup

View file

@ -18,6 +18,7 @@ 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 { convertToName } from "./DynamicComponents";
const RealmClient = (realm: string): ClientRepresentation => ({ const RealmClient = (realm: string): ClientRepresentation => ({
name: "realmRoles", name: "realmRoles",
@ -47,7 +48,7 @@ export const RoleComponent = ({
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]); const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleRepresentation>(); const [selectedRole, setSelectedRole] = useState<RoleRepresentation>();
const fieldName = `config.${name}`; const fieldName = convertToName(name!);
useFetch( useFetch(
async () => { async () => {

View file

@ -5,6 +5,7 @@ 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 { convertToName } from "./DynamicComponents";
export const ScriptComponent = ({ export const ScriptComponent = ({
name, name,
@ -28,7 +29,7 @@ export const ScriptComponent = ({
fieldId={name!} fieldId={name!}
> >
<Controller <Controller
name={`config.${name}`} name={convertToName(name!)}
defaultValue={defaultValue} defaultValue={defaultValue}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -5,6 +5,7 @@ import { FormGroup } from "@patternfly/react-core";
import { HelpItem } from "../help-enabler/HelpItem"; import { HelpItem } from "../help-enabler/HelpItem";
import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput"; import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { convertToName } from "./DynamicComponents";
export const StringComponent = ({ export const StringComponent = ({
name, name,
@ -30,7 +31,7 @@ export const StringComponent = ({
isDisabled={isDisabled} isDisabled={isDisabled}
ref={register()} ref={register()}
type="text" type="text"
name={`config.${name}`} name={convertToName(name!)}
defaultValue={defaultValue?.toString()} defaultValue={defaultValue?.toString()}
/> />
</FormGroup> </FormGroup>

View file

@ -166,8 +166,10 @@ export default function AddMapper() {
const setupForm = (mapper: IdentityProviderMapperRepresentation) => { const setupForm = (mapper: IdentityProviderMapperRepresentation) => {
convertToFormValues(mapper, form.setValue); convertToFormValues(mapper, form.setValue);
form.setValue("config.attributes", JSON.parse(mapper.config.attributes)); form.setValue(
form.setValue("config.claims", JSON.parse(mapper.config.claims)); "config.attributes",
JSON.parse(mapper.config.attributes || "{}")
);
}; };
if (!mapperTypes || !currentMapper) { if (!mapperTypes || !currentMapper) {
@ -241,7 +243,7 @@ export default function AddMapper() {
/> />
<FormProvider {...form}> <FormProvider {...form}>
<DynamicComponents properties={currentMapper.properties!} /> <DynamicComponents properties={currentMapper.properties!} />
</FormProvider>{" "} </FormProvider>
</> </>
)} )}

View file

@ -16,7 +16,11 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; 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 useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
@ -58,7 +62,7 @@ export const RealmSettingsGeneralTab = ({
JSON.parse(realm.attributes["acr.loa.map"]) JSON.parse(realm.attributes["acr.loa.map"])
).flatMap(([key, value]) => ({ key, value })); ).flatMap(([key, value]) => ({ key, value }));
result.concat({ 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 = ({
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"
id="kc-frontend-url" id="kc-frontend-url"
name="attributes.frontendUrl" name={convertAttributeNameToForm("attributes.frontendUrl")}
ref={register} ref={register}
/> />
</FormGroup> </FormGroup>
@ -168,7 +172,9 @@ export const RealmSettingsGeneralTab = ({
} }
> >
<FormProvider {...form}> <FormProvider {...form}>
<KeyValueInput name="attributes.acr.loa.map" /> <KeyValueInput
name={convertAttributeNameToForm("attributes.acr.loa.map")}
/>
</FormProvider> </FormProvider>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
@ -211,7 +217,7 @@ export const RealmSettingsGeneralTab = ({
fieldId="kc-user-profile-enabled" fieldId="kc-user-profile-enabled"
> >
<Controller <Controller
name="attributes.userProfileEnabled" name={convertAttributeNameToForm("attributes.userProfileEnabled")}
control={control} control={control}
defaultValue={false} defaultValue={false}
render={({ onChange, value }) => ( render={({ onChange, value }) => (

View file

@ -1,8 +1,14 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { convertFormValuesToObject, convertToFormValues } from "./util"; import {
convertAttributeNameToForm,
convertFormValuesToObject,
convertToFormValues,
} from "./util";
vi.mock("react"); vi.mock("react");
const TOKEN = "🍺";
describe("Tests the form convert util functions", () => { describe("Tests the form convert util functions", () => {
it("convert to form values", () => { it("convert to form values", () => {
const given = { const given = {
@ -31,7 +37,7 @@ describe("Tests the form convert util functions", () => {
const given = { const given = {
name: "client", name: "client",
attributes: [{ key: "one", value: "1" }], attributes: [{ key: "one", value: "1" }],
config: { one: { two: "3" } }, config: { [`one${TOKEN}two`]: "3" },
}; };
//when //when
@ -51,10 +57,10 @@ describe("Tests the form convert util functions", () => {
description: "", description: "",
type: "default", type: "default",
attributes: { attributes: {
display: { on: { consent: { screen: "true" } } }, [`display${TOKEN}on${TOKEN}consent${TOKEN}screen`]: "true",
include: { in: { token: { scope: "true" } } }, [`include${TOKEN}in${TOKEN}token${TOKEN}scope`]: "true",
gui: { order: "1" }, [`gui${TOKEN}order`]: "1",
consent: { screen: { text: "" } }, [`consent${TOKEN}screen${TOKEN}text`]: "",
}, },
}; };
@ -92,12 +98,10 @@ describe("Tests the form convert util functions", () => {
//then //then
expect(values).toEqual({ expect(values).toEqual({
attributes: { [`attributes.display${TOKEN}on${TOKEN}consent${TOKEN}screen`]: "true",
display: { on: { consent: { screen: "true" } } }, [`attributes.include${TOKEN}in${TOKEN}token${TOKEN}scope`]: "true",
include: { in: { token: { scope: "true" } } }, [`attributes.gui${TOKEN}order`]: "1",
gui: { order: "1" }, [`attributes.consent${TOKEN}screen${TOKEN}text`]: "",
consent: { screen: { text: "" } },
},
}); });
}); });
@ -115,17 +119,31 @@ describe("Tests the form convert util functions", () => {
it("convert single element arrays to string", () => { it("convert single element arrays to string", () => {
const given = { 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 //when
convertToFormValues(given, setValue); convertToFormValues(given, spy);
//then //then
expect(setValue).toHaveBeenCalledWith("config", { expect(values).toEqual({
group: "one", "config.group": "one",
another: { nested: "value" }, [`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`);
});
}); });

View file

@ -1,7 +1,7 @@
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import type { IFormatter, IFormatterValueType } from "@patternfly/react-table"; 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 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";
@ -85,6 +85,16 @@ const isAttributeArray = (value: any) => {
const isEmpty = (obj: any) => Object.keys(obj).length === 0; 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 = ( export const convertToFormValues = (
obj: any, obj: any,
setValue: (name: string, value: any) => void setValue: (name: string, value: any) => void
@ -98,7 +108,10 @@ export const convertToFormValues = (
const convertedValues = Object.entries(flattened).map(([key, value]) => const convertedValues = Object.entries(flattened).map(([key, value]) =>
Array.isArray(value) ? [key, value[0]] : [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 { } else {
setValue(key, undefined); setValue(key, undefined);
} }
@ -114,7 +127,12 @@ export function convertFormValuesToObject<T, G = T>(obj: T): G {
if (isAttributeArray(value)) { if (isAttributeArray(value)) {
result[key] = keyValueToArray(value as KeyValueType[]); result[key] = keyValueToArray(value as KeyValueType[]);
} else if (key === "config" || key === "attributes") { } else if (key === "config" || key === "attributes") {
result[key] = flatten(value as Record<string, any>, { safe: true }); result[key] = Object.fromEntries(
Object.entries(value).map(([k, v]) => [
convertFormNameToAttribute(k),
v,
])
);
} else { } else {
result[key] = value; result[key] = value;
} }