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 Object defaultValue;
protected List<String> options; protected List<String> options;
protected boolean secret; protected boolean secret;
protected boolean required;
private boolean readOnly; private boolean readOnly;
public String getName() { public String getName() {
@ -89,6 +90,14 @@ public class ConfigPropertyRepresentation {
this.secret = secret; this.secret = secret;
} }
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public void setReadOnly(boolean readOnly) { public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly; this.readOnly = readOnly;
} }

View file

@ -77,6 +77,12 @@ public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMap
return configProperty; 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 { protected void checkMandatoryConfigAttribute(String name, String displayName, ComponentModel mapperModel) throws ComponentValidationException {
String attrConfigValue = mapperModel.getConfig().getFirst(name); String attrConfigValue = mapperModel.getConfig().getFirst(name);
if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) { 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>(); protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static { 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", "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.", "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(attrName);
configProperties.add(attrValue); configProperties.add(attrValue);

View file

@ -38,14 +38,20 @@ public class HardcodedLDAPAttributeMapperFactory extends AbstractLDAPStorageMapp
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static { 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", "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. " "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.", + "Only supported token right now is '${RANDOM}' , which will be replaced with some randomly generated String.",
ProviderConfigProperty.STRING_TYPE, null); ProviderConfigProperty.STRING_TYPE,
null,
true);
configProperties.add(attrName); configProperties.add(attrName);
configProperties.add(attrValue); configProperties.add(attrValue);

View file

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

View file

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

View file

@ -56,9 +56,11 @@ public class UserAttributeLDAPStorageMapperFactory extends AbstractLDAPStorageMa
.label("User Model Attribute") .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.") .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) .type(ProviderConfigProperty.STRING_TYPE)
.required(true)
.add() .add()
.property().name(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE).label("LDAP Attribute").helpText("Name of mapped attribute on LDAP object. For example 'cn', 'sn, 'mail', 'street' etc.") .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) .type(ProviderConfigProperty.STRING_TYPE)
.required(true)
.add() .add()
.property().name(UserAttributeLDAPStorageMapper.READ_ONLY).label("Read Only") .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.") .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") .label("LDAP Groups DN")
.helpText("LDAP DN where are groups of this tree saved. For example 'ou=groups,dc=example,dc=org' ") .helpText("LDAP DN where are groups of this tree saved. For example 'ou=groups,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE) .type(ProviderConfigProperty.STRING_TYPE)
.required(true)
.add() .add()
.property().name(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE) .property().name(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE)
.label("Group Name LDAP Attribute") .label("Group Name LDAP Attribute")

View file

@ -94,6 +94,7 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
.label("LDAP Roles DN") .label("LDAP Roles DN")
.helpText("LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ") .helpText("LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE) .type(ProviderConfigProperty.STRING_TYPE)
.required(true)
.add() .add()
.property().name(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE) .property().name(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE)
.label("Role Name LDAP Attribute") .label("Role Name LDAP Attribute")

View file

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

View file

@ -15,7 +15,12 @@ import { HelpItem } from "ui-shared";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { convertToName } from "./DynamicComponents"; import { convertToName } from "./DynamicComponents";
export const GroupComponent = ({ name, label, helpText }: ComponentProps) => { export const GroupComponent = ({
name,
label,
helpText,
required,
}: ComponentProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [groups, setGroups] = useState<GroupRepresentation[]>(); const [groups, setGroups] = useState<GroupRepresentation[]>();
@ -51,6 +56,7 @@ export const GroupComponent = ({ name, label, helpText }: ComponentProps) => {
<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} /> <HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />
} }
fieldId={name!} fieldId={name!}
isRequired={required}
> >
<InputGroup> <InputGroup>
<ChipGroup> <ChipGroup>

View file

@ -18,6 +18,7 @@ export const ListComponent = ({
helpText, helpText,
defaultValue, defaultValue,
options, options,
required,
isDisabled = false, isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -29,6 +30,7 @@ export const ListComponent = ({
label={t(label!)} label={t(label!)}
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />} labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!} fieldId={name!}
isRequired={required}
> >
<Controller <Controller
name={convertToName(name!)} name={convertToName(name!)}

View file

@ -22,7 +22,12 @@ type IdKeyValueType = KeyValueType & {
id: number; id: number;
}; };
export const MapComponent = ({ name, label, helpText }: ComponentProps) => { export const MapComponent = ({
name,
label,
helpText,
required,
}: ComponentProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { getValues, setValue, register } = useFormContext(); const { getValues, setValue, register } = useFormContext();
@ -65,6 +70,7 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
label={t(label!)} label={t(label!)}
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />} labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!} fieldId={name!}
isRequired={required}
> >
<Flex direction={{ default: "column" }}> <Flex direction={{ default: "column" }}>
<Flex> <Flex>

View file

@ -28,6 +28,7 @@ export const MultiValuedListComponent = ({
options, options,
isDisabled = false, isDisabled = false,
stringify, stringify,
required,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { control } = useFormContext(); const { control } = useFormContext();
@ -38,6 +39,7 @@ export const MultiValuedListComponent = ({
label={t(label!)} label={t(label!)}
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />} labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={`${label}`} />}
fieldId={name!} fieldId={name!}
isRequired={required}
> >
<Controller <Controller
name={convertToName(name!)} name={convertToName(name!)}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -70,6 +70,7 @@ public class ProviderConfigProperty {
protected Object defaultValue; protected Object defaultValue;
protected List<String> options; protected List<String> options;
protected boolean secret; protected boolean secret;
protected boolean required;
private boolean readOnly; private boolean readOnly;
public ProviderConfigProperty() { public ProviderConfigProperty() {
@ -97,6 +98,11 @@ public class ProviderConfigProperty {
this.secret = secret; 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 * Name of the config variable stored in the database
* *
@ -190,6 +196,17 @@ public class ProviderConfigProperty {
this.secret = secret; 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) { public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly; this.readOnly = readOnly;
} }

View file

@ -79,6 +79,7 @@ public class ProviderConfigurationBuilder {
private Object defaultValue; private Object defaultValue;
private List<String> options; private List<String> options;
private boolean secret; private boolean secret;
private boolean required;
public ProviderConfigPropertyBuilder name(String name) { public ProviderConfigPropertyBuilder name(String name) {
this.name = name; this.name = name;
@ -168,6 +169,17 @@ public class ProviderConfigurationBuilder {
return this; 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 * Add the current property, and start building the next one
* *
@ -182,6 +194,7 @@ public class ProviderConfigurationBuilder {
property.setDefaultValue(defaultValue); property.setDefaultValue(defaultValue);
property.setOptions(options); property.setOptions(options);
property.setSecret(secret); property.setSecret(secret);
property.setRequired(required);
ProviderConfigurationBuilder.this.properties.add(property); ProviderConfigurationBuilder.this.properties.add(property);
return ProviderConfigurationBuilder.this; return ProviderConfigurationBuilder.this;
} }