Mark required config properties for LDAP Mappers

Closes #23685
This commit is contained in:
Martin Bartoš 2023-10-04 10:38:22 +02:00 committed by Alexander Schwartz
parent 990a54dce5
commit 21a23ace1d
25 changed files with 113 additions and 16 deletions

View file

@ -31,6 +31,7 @@ public class ConfigPropertyRepresentation {
protected Object defaultValue;
protected List<String> 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;
}

View file

@ -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<String> 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()) {

View file

@ -36,13 +36,19 @@ public class HardcodedAttributeMapperFactory extends AbstractLDAPStorageMapperFa
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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);

View file

@ -38,14 +38,20 @@ public class HardcodedLDAPAttributeMapperFactory extends AbstractLDAPStorageMapp
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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);

View file

@ -38,9 +38,12 @@ public class HardcodedLDAPGroupStorageMapperFactory extends AbstractLDAPStorageM
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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);
}

View file

@ -38,9 +38,12 @@ public class HardcodedLDAPRoleStorageMapperFactory extends AbstractLDAPStorageMa
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
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);
}

View file

@ -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.")

View file

@ -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")

View file

@ -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")

View file

@ -17,7 +17,6 @@ import type { ComponentProps } from "../dynamic/components";
type ClientSelectProps = ComponentProps & {
namespace: string;
required?: boolean;
};
export const ClientSelect = ({

View file

@ -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<GroupRepresentation[]>();
@ -51,6 +56,7 @@ export const GroupComponent = ({ name, label, helpText }: ComponentProps) => {
<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />
}
fieldId={name!}
isRequired={required}
>
<InputGroup>
<ChipGroup>

View file

@ -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={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!}
isRequired={required}
>
<Controller
name={convertToName(name!)}

View file

@ -22,7 +22,12 @@ type IdKeyValueType = KeyValueType & {
id: number;
};
export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
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={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!}
isRequired={required}
>
<Flex direction={{ default: "column" }}>
<Flex>

View file

@ -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={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!}
isRequired={required}
>
<Controller
name={convertToName(name!)}

View file

@ -12,6 +12,7 @@ export const MultiValuedStringComponent = ({
defaultValue,
helpText,
stringify,
required,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation();
@ -22,6 +23,7 @@ export const MultiValuedStringComponent = ({
label={t(label!)}
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!}
isRequired={required}
>
<MultiLineInput
aria-label={t(label!)}

View file

@ -12,6 +12,7 @@ export const PasswordComponent = ({
label,
helpText,
defaultValue,
required,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation();
@ -22,6 +23,7 @@ export const PasswordComponent = ({
label={t(label!)}
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!}
isRequired={required}
>
<PasswordInput
id={name!}

View file

@ -28,6 +28,7 @@ export const RoleComponent = ({
label,
helpText,
defaultValue,
required,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation();
@ -47,6 +48,7 @@ export const RoleComponent = ({
validated={errors[fieldName] ? "error" : "default"}
helperTextInvalid={t("required")}
fieldId={name!}
isRequired={required}
>
<Controller
name={fieldName}

View file

@ -12,6 +12,7 @@ export const ScriptComponent = ({
label,
helpText,
defaultValue,
required,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation();
@ -27,6 +28,7 @@ export const ScriptComponent = ({
/>
}
fieldId={name!}
isRequired={required}
>
<Controller
name={convertToName(name!)}

View file

@ -13,6 +13,7 @@ export const StringComponent = ({
helpText,
defaultValue,
isDisabled = false,
required,
}: ComponentProps) => {
const { t } = useTranslation();
const { register } = useFormContext();
@ -22,6 +23,7 @@ export const StringComponent = ({
label={t(label!)}
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!}
isRequired={required}
>
<KeycloakTextInput
id={name!}

View file

@ -12,6 +12,7 @@ export const TextComponent = ({
label,
helpText,
defaultValue,
required,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation();
@ -22,6 +23,7 @@ export const TextComponent = ({
label={t(label!)}
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!}
required={required}
>
<KeycloakTextArea
id={name!}

View file

@ -16,4 +16,5 @@ export interface ConfigPropertyRepresentation {
defaultValue?: any;
options?: string[];
secret?: boolean;
required?: boolean;
}

View file

@ -9,4 +9,5 @@ export interface ConfigPropertyRepresentation {
defaultValue?: object;
options?: string[];
secret?: boolean;
required?: boolean;
}

View file

@ -951,6 +951,7 @@ public class ModelToRepresentation {
propRep.setOptions(prop.getOptions());
propRep.setHelpText(prop.getHelpText());
propRep.setSecret(prop.isSecret());
propRep.setRequired(prop.isRequired());
return propRep;
}

View file

@ -70,6 +70,7 @@ public class ProviderConfigProperty {
protected Object defaultValue;
protected List<String> 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;
}

View file

@ -79,6 +79,7 @@ public class ProviderConfigurationBuilder {
private Object defaultValue;
private List<String> 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;
}