From 21a23ace1d0aaada4825f4627b9bc3835d190ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Wed, 4 Oct 2023 10:38:22 +0200 Subject: [PATCH] Mark required config properties for LDAP Mappers Closes #23685 --- .../idm/ConfigPropertyRepresentation.java | 9 +++++++++ .../AbstractLDAPStorageMapperFactory.java | 6 ++++++ .../HardcodedAttributeMapperFactory.java | 14 ++++++++++---- .../HardcodedLDAPAttributeMapperFactory.java | 16 +++++++++++----- .../HardcodedLDAPGroupStorageMapperFactory.java | 7 +++++-- .../HardcodedLDAPRoleStorageMapperFactory.java | 7 +++++-- .../UserAttributeLDAPStorageMapperFactory.java | 2 ++ .../group/GroupLDAPStorageMapperFactory.java | 1 + .../role/RoleLDAPStorageMapperFactory.java | 1 + .../src/components/client/ClientSelect.tsx | 1 - .../src/components/dynamic/GroupComponent.tsx | 8 +++++++- .../src/components/dynamic/ListComponent.tsx | 2 ++ .../src/components/dynamic/MapComponent.tsx | 8 +++++++- .../dynamic/MultivaluedListComponent.tsx | 2 ++ .../dynamic/MultivaluedStringComponent.tsx | 2 ++ .../components/dynamic/PasswordComponent.tsx | 2 ++ .../src/components/dynamic/RoleComponent.tsx | 2 ++ .../src/components/dynamic/ScriptComponent.tsx | 2 ++ .../src/components/dynamic/StringComponent.tsx | 2 ++ .../src/components/dynamic/TextComponent.tsx | 2 ++ .../authenticatorConfigInfoRepresentation.ts | 1 + .../src/defs/configPropertyRepresentation.ts | 1 + .../models/utils/ModelToRepresentation.java | 1 + .../provider/ProviderConfigProperty.java | 17 +++++++++++++++++ .../provider/ProviderConfigurationBuilder.java | 13 +++++++++++++ 25 files changed, 113 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java index 946de45a54..f3161c3ee2 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ConfigPropertyRepresentation.java @@ -31,6 +31,7 @@ public class ConfigPropertyRepresentation { protected Object defaultValue; protected List options; protected boolean secret; + protected boolean required; private boolean readOnly; public String getName() { @@ -89,6 +90,14 @@ public class ConfigPropertyRepresentation { this.secret = secret; } + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java index b3b9e891e2..6132db574f 100755 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/AbstractLDAPStorageMapperFactory.java @@ -77,6 +77,12 @@ public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMap return configProperty; } + public static ProviderConfigProperty createConfigProperty(String name, String label, String helpText, String type, List options, boolean required) { + ProviderConfigProperty property = createConfigProperty(name, label, helpText, type, options); + property.setRequired(required); + return property; + } + protected void checkMandatoryConfigAttribute(String name, String displayName, ComponentModel mapperModel) throws ComponentValidationException { String attrConfigValue = mapperModel.getConfig().getFirst(name); if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) { diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedAttributeMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedAttributeMapperFactory.java index f67bf48727..f9f3e5ec68 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedAttributeMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedAttributeMapperFactory.java @@ -36,13 +36,19 @@ public class HardcodedAttributeMapperFactory extends AbstractLDAPStorageMapperFa protected static final List configProperties = new ArrayList(); static { - ProviderConfigProperty attrName = createConfigProperty(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute Name", + ProviderConfigProperty attrName = createConfigProperty(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE, + "User Model Attribute Name", "Name of the model attribute, which will be added when importing user from ldap", - ProviderConfigProperty.STRING_TYPE, null); + ProviderConfigProperty.STRING_TYPE, + null, + true); - ProviderConfigProperty attrValue = createConfigProperty(HardcodedAttributeMapper.ATTRIBUTE_VALUE, "Attribute Value", + ProviderConfigProperty attrValue = createConfigProperty(HardcodedAttributeMapper.ATTRIBUTE_VALUE, + "Attribute Value", "Value of the model attribute, which will be added when importing user from ldap.", - ProviderConfigProperty.STRING_TYPE, null); + ProviderConfigProperty.STRING_TYPE, + null, + true); configProperties.add(attrName); configProperties.add(attrValue); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapperFactory.java index d8e8720750..a6b86bbd60 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPAttributeMapperFactory.java @@ -38,14 +38,20 @@ public class HardcodedLDAPAttributeMapperFactory extends AbstractLDAPStorageMapp protected static final List configProperties = new ArrayList(); static { - ProviderConfigProperty attrName = createConfigProperty(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_NAME, "LDAP Attribute Name", + ProviderConfigProperty attrName = createConfigProperty(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_NAME, + "LDAP Attribute Name", "Name of the LDAP attribute, which will be added to the new user during registration", - ProviderConfigProperty.STRING_TYPE, null); + ProviderConfigProperty.STRING_TYPE, + null, + true); - ProviderConfigProperty attrValue = createConfigProperty(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_VALUE, "LDAP Attribute Value", + ProviderConfigProperty attrValue = createConfigProperty(HardcodedLDAPAttributeMapper.LDAP_ATTRIBUTE_VALUE, + "LDAP Attribute Value", "Value of the LDAP attribute, which will be added to the new user during registration. You can either hardcode any value like 'foo' but you can also use some special tokens. " - + "Only supported token right now is '${RANDOM}' , which will be replaced with some randomly generated String.", - ProviderConfigProperty.STRING_TYPE, null); + + "Only supported token right now is '${RANDOM}' , which will be replaced with some randomly generated String.", + ProviderConfigProperty.STRING_TYPE, + null, + true); configProperties.add(attrName); configProperties.add(attrValue); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapperFactory.java index 0a96ef21a9..33f39686e2 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPGroupStorageMapperFactory.java @@ -38,9 +38,12 @@ public class HardcodedLDAPGroupStorageMapperFactory extends AbstractLDAPStorageM protected static final List configProperties = new ArrayList(); static { - ProviderConfigProperty groupAttr = createConfigProperty(HardcodedLDAPGroupStorageMapper.GROUP, "Group", + ProviderConfigProperty groupAttr = createConfigProperty(HardcodedLDAPGroupStorageMapper.GROUP, + "Group", "Group to add the user in. Fill the full path of the group including path. For example '/root-group/child-group'", - ProviderConfigProperty.STRING_TYPE, null); + ProviderConfigProperty.STRING_TYPE, + null, + true); configProperties.add(groupAttr); } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapperFactory.java index 2f0259a4f9..66cef5543b 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/HardcodedLDAPRoleStorageMapperFactory.java @@ -38,9 +38,12 @@ public class HardcodedLDAPRoleStorageMapperFactory extends AbstractLDAPStorageMa protected static final List configProperties = new ArrayList(); static { - ProviderConfigProperty roleAttr = createConfigProperty(HardcodedLDAPRoleStorageMapper.ROLE, "Role", + ProviderConfigProperty roleAttr = createConfigProperty(HardcodedLDAPRoleStorageMapper.ROLE, + "Role", "Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference a client role the syntax is clientname.clientrole, i.e. myclient.myrole", - ProviderConfigProperty.ROLE_TYPE, null); + ProviderConfigProperty.ROLE_TYPE, + null, + true); configProperties.add(roleAttr); } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapperFactory.java index dad8910b44..e13ccd32ef 100755 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapperFactory.java @@ -56,9 +56,11 @@ public class UserAttributeLDAPStorageMapperFactory extends AbstractLDAPStorageMa .label("User Model Attribute") .helpText("Name of the UserModel property or attribute you want to map the LDAP attribute into. For example 'firstName', 'lastName, 'email', 'street' etc.") .type(ProviderConfigProperty.STRING_TYPE) + .required(true) .add() .property().name(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE).label("LDAP Attribute").helpText("Name of mapped attribute on LDAP object. For example 'cn', 'sn, 'mail', 'street' etc.") .type(ProviderConfigProperty.STRING_TYPE) + .required(true) .add() .property().name(UserAttributeLDAPStorageMapper.READ_ONLY).label("Read Only") .helpText("Read-only attribute is imported from LDAP to UserModel, but it's not saved back to LDAP when user is updated in Keycloak.") diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java index 9cddd3732e..b4bab3ddb4 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java @@ -96,6 +96,7 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact .label("LDAP Groups DN") .helpText("LDAP DN where are groups of this tree saved. For example 'ou=groups,dc=example,dc=org' ") .type(ProviderConfigProperty.STRING_TYPE) + .required(true) .add() .property().name(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE) .label("Group Name LDAP Attribute") diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java index 75142d7e08..e6c4acba06 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapperFactory.java @@ -94,6 +94,7 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto .label("LDAP Roles DN") .helpText("LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ") .type(ProviderConfigProperty.STRING_TYPE) + .required(true) .add() .property().name(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE) .label("Role Name LDAP Attribute") diff --git a/js/apps/admin-ui/src/components/client/ClientSelect.tsx b/js/apps/admin-ui/src/components/client/ClientSelect.tsx index f28b80943c..bbcf6dbe90 100644 --- a/js/apps/admin-ui/src/components/client/ClientSelect.tsx +++ b/js/apps/admin-ui/src/components/client/ClientSelect.tsx @@ -17,7 +17,6 @@ import type { ComponentProps } from "../dynamic/components"; type ClientSelectProps = ComponentProps & { namespace: string; - required?: boolean; }; export const ClientSelect = ({ diff --git a/js/apps/admin-ui/src/components/dynamic/GroupComponent.tsx b/js/apps/admin-ui/src/components/dynamic/GroupComponent.tsx index 81e0fcf7e3..bdc0760534 100644 --- a/js/apps/admin-ui/src/components/dynamic/GroupComponent.tsx +++ b/js/apps/admin-ui/src/components/dynamic/GroupComponent.tsx @@ -15,7 +15,12 @@ import { HelpItem } from "ui-shared"; import type { ComponentProps } from "./components"; import { convertToName } from "./DynamicComponents"; -export const GroupComponent = ({ name, label, helpText }: ComponentProps) => { +export const GroupComponent = ({ + name, + label, + helpText, + required, +}: ComponentProps) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [groups, setGroups] = useState(); @@ -51,6 +56,7 @@ export const GroupComponent = ({ name, label, helpText }: ComponentProps) => { } fieldId={name!} + isRequired={required} > diff --git a/js/apps/admin-ui/src/components/dynamic/ListComponent.tsx b/js/apps/admin-ui/src/components/dynamic/ListComponent.tsx index 5f874b1399..6e76c454f7 100644 --- a/js/apps/admin-ui/src/components/dynamic/ListComponent.tsx +++ b/js/apps/admin-ui/src/components/dynamic/ListComponent.tsx @@ -18,6 +18,7 @@ export const ListComponent = ({ helpText, defaultValue, options, + required, isDisabled = false, }: ComponentProps) => { const { t } = useTranslation(); @@ -29,6 +30,7 @@ export const ListComponent = ({ label={t(label!)} labelIcon={} fieldId={name!} + isRequired={required} > { +export const MapComponent = ({ + name, + label, + helpText, + required, +}: ComponentProps) => { const { t } = useTranslation(); const { getValues, setValue, register } = useFormContext(); @@ -65,6 +70,7 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => { label={t(label!)} labelIcon={} fieldId={name!} + isRequired={required} > diff --git a/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx b/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx index 5f2481e9db..f2e42f1c89 100644 --- a/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx +++ b/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx @@ -28,6 +28,7 @@ export const MultiValuedListComponent = ({ options, isDisabled = false, stringify, + required, }: ComponentProps) => { const { t } = useTranslation(); const { control } = useFormContext(); @@ -38,6 +39,7 @@ export const MultiValuedListComponent = ({ label={t(label!)} labelIcon={} fieldId={name!} + isRequired={required} > { const { t } = useTranslation(); @@ -22,6 +23,7 @@ export const MultiValuedStringComponent = ({ label={t(label!)} labelIcon={} fieldId={name!} + isRequired={required} > { const { t } = useTranslation(); @@ -22,6 +23,7 @@ export const PasswordComponent = ({ label={t(label!)} labelIcon={} fieldId={name!} + isRequired={required} > { const { t } = useTranslation(); @@ -47,6 +48,7 @@ export const RoleComponent = ({ validated={errors[fieldName] ? "error" : "default"} helperTextInvalid={t("required")} fieldId={name!} + isRequired={required} > { const { t } = useTranslation(); @@ -27,6 +28,7 @@ export const ScriptComponent = ({ /> } fieldId={name!} + isRequired={required} > { const { t } = useTranslation(); const { register } = useFormContext(); @@ -22,6 +23,7 @@ export const StringComponent = ({ label={t(label!)} labelIcon={} fieldId={name!} + isRequired={required} > { const { t } = useTranslation(); @@ -22,6 +23,7 @@ export const TextComponent = ({ label={t(label!)} labelIcon={} fieldId={name!} + required={required} > options; protected boolean secret; + protected boolean required; private boolean readOnly; public ProviderConfigProperty() { @@ -97,6 +98,11 @@ public class ProviderConfigProperty { this.secret = secret; } + public ProviderConfigProperty(String name, String label, String helpText, String type, Object defaultValue, boolean secret, boolean required) { + this(name, label, helpText, type, defaultValue, secret); + this.required = required; + } + /** * Name of the config variable stored in the database * @@ -190,6 +196,17 @@ public class ProviderConfigProperty { this.secret = secret; } + /** + * If true, the configuration property must be specified + */ + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java index 554eca10b0..8bbd4666b7 100644 --- a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java +++ b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigurationBuilder.java @@ -79,6 +79,7 @@ public class ProviderConfigurationBuilder { private Object defaultValue; private List options; private boolean secret; + private boolean required; public ProviderConfigPropertyBuilder name(String name) { this.name = name; @@ -168,6 +169,17 @@ public class ProviderConfigurationBuilder { return this; } + /** + * If turned on, this property will be marked as required in the admin console + * + * @param required + * @return + */ + public ProviderConfigPropertyBuilder required(boolean required) { + this.required = required; + return this; + } + /** * Add the current property, and start building the next one * @@ -182,6 +194,7 @@ public class ProviderConfigurationBuilder { property.setDefaultValue(defaultValue); property.setOptions(options); property.setSecret(secret); + property.setRequired(required); ProviderConfigurationBuilder.this.properties.add(property); return ProviderConfigurationBuilder.this; }