Allow selecting attributes from user profile when managing token mappers (#26415)
* Allow selecting attributes from user profile when managing token mappers closes #24250 Signed-off-by: mposolda <mposolda@gmail.com> Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
7797f778d1
commit
651d99db25
16 changed files with 88 additions and 16 deletions
|
@ -39,7 +39,7 @@ public class HardcodedAttributeMapperFactory extends AbstractLDAPStorageMapperFa
|
||||||
ProviderConfigProperty attrName = createConfigProperty(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE,
|
ProviderConfigProperty attrName = createConfigProperty(HardcodedAttributeMapper.USER_MODEL_ATTRIBUTE,
|
||||||
"User Model Attribute Name",
|
"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,
|
ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE,
|
||||||
null,
|
null,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class UserAttributeLDAPStorageMapperFactory extends AbstractLDAPStorageMa
|
||||||
.property().name(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE)
|
.property().name(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE)
|
||||||
.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.USER_PROFILE_ATTRIBUTE_LIST_TYPE)
|
||||||
.required(true)
|
.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.")
|
||||||
|
|
|
@ -9,7 +9,7 @@ export enum ClaimJsonType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MapperDetailsPage extends CommonPage {
|
export default class MapperDetailsPage extends CommonPage {
|
||||||
#userAttributeInput = '[id="user.attribute"]';
|
#userAttributeInput = '[data-testid="config.user🍺attribute"]';
|
||||||
#tokenClaimNameInput = '[id="claim.name"]';
|
#tokenClaimNameInput = '[id="claim.name"]';
|
||||||
#claimJsonType = '[id="jsonType.label"]';
|
#claimJsonType = '[id="jsonType.label"]';
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@ export default class AddMapperPage {
|
||||||
#addMapperButton = "#add-mapper-button";
|
#addMapperButton = "#add-mapper-button";
|
||||||
|
|
||||||
#mapperNameInput = "#kc-name";
|
#mapperNameInput = "#kc-name";
|
||||||
#attribute = "user.attribute";
|
#attribute = "config.user🍺attribute";
|
||||||
#attributeName = "attribute.name";
|
#attributeName = "attribute.name";
|
||||||
#attributeFriendlyName = "attribute.friendly.name";
|
#attributeFriendlyName = "attribute.friendly.name";
|
||||||
#claimInput = "claim";
|
#claimInput = "claim";
|
||||||
#socialProfileJSONfieldPath = "jsonField";
|
#socialProfileJSONfieldPath = "jsonField";
|
||||||
#userAttribute = "attribute";
|
#userAttribute = "config.attribute";
|
||||||
#userAttributeName = "userAttribute";
|
#userAttributeName = "config.userAttribute";
|
||||||
#userAttributeValue = "attribute.value";
|
#userAttributeValue = "attribute.value";
|
||||||
#userSessionAttribute = "attribute";
|
#userSessionAttribute = "attribute";
|
||||||
#userSessionAttributeValue = "attribute.value";
|
#userSessionAttributeValue = "attribute.value";
|
||||||
|
|
|
@ -63,9 +63,9 @@ export default class ProviderPage {
|
||||||
#cachePolicyList = "#kc-cache-policy + ul";
|
#cachePolicyList = "#kc-cache-policy + ul";
|
||||||
|
|
||||||
// Mapper input values
|
// Mapper input values
|
||||||
#userModelAttInput = "user.model.attribute";
|
#userModelAttInput = "config.user🍺model🍺attribute";
|
||||||
#ldapAttInput = "ldap.attribute";
|
#ldapAttInput = "ldap.attribute";
|
||||||
#userModelAttNameInput = "user.model.attribute";
|
#userModelAttNameInput = "config.user🍺model🍺attribute";
|
||||||
#attValueInput = "attribute.value";
|
#attValueInput = "attribute.value";
|
||||||
#ldapFullNameAttInput = "ldap.full.name.attribute";
|
#ldapFullNameAttInput = "ldap.full.name.attribute";
|
||||||
#ldapAttNameInput = "ldap.attribute.name";
|
#ldapAttNameInput = "ldap.attribute.name";
|
||||||
|
@ -317,7 +317,7 @@ export default class ProviderPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewMapper(mapperType: string) {
|
createNewMapper(mapperType: string) {
|
||||||
const userModelAttValue = "firstName";
|
const userModelAttValue = "middleName";
|
||||||
const ldapAttValue = "cn";
|
const ldapAttValue = "cn";
|
||||||
const ldapDnValue = "ou=groups";
|
const ldapDnValue = "ou=groups";
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||||
|
import { FormGroup } from "@patternfly/react-core";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { HelpItem } from "ui-shared";
|
||||||
|
|
||||||
|
import { adminClient } from "../../admin-client";
|
||||||
|
import { useFetch } from "../../utils/useFetch";
|
||||||
|
import { KeySelect } from "../key-value-form/KeySelect";
|
||||||
|
import { convertToName } from "./DynamicComponents";
|
||||||
|
import type { ComponentProps } from "./components";
|
||||||
|
|
||||||
|
export const UserProfileAttributeListComponent = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
helpText,
|
||||||
|
required = false,
|
||||||
|
}: ComponentProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
formState: { errors },
|
||||||
|
} = useFormContext();
|
||||||
|
|
||||||
|
const [config, setConfig] = useState<UserProfileConfig>();
|
||||||
|
const convertedName = convertToName(name!);
|
||||||
|
|
||||||
|
useFetch(
|
||||||
|
() => adminClient.users.getProfile(),
|
||||||
|
(cfg) => setConfig(cfg),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const convert = (config?: UserProfileConfig) => {
|
||||||
|
if (!config?.attributes) return [];
|
||||||
|
|
||||||
|
return config.attributes.map((option) => ({
|
||||||
|
key: option.name!,
|
||||||
|
label: option.name!,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!config) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={t(label!)}
|
||||||
|
isRequired={required}
|
||||||
|
labelIcon={<HelpItem helpText={t(helpText!)} fieldLabelId={label!} />}
|
||||||
|
fieldId={convertedName!}
|
||||||
|
validated={errors[convertedName!] ? "error" : "default"}
|
||||||
|
helperTextInvalid={t("required")}
|
||||||
|
>
|
||||||
|
<KeySelect
|
||||||
|
name={convertedName}
|
||||||
|
rules={required ? { required: true } : {}}
|
||||||
|
selectItems={convert(config)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation";
|
import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation";
|
||||||
|
import { FunctionComponent } from "react";
|
||||||
|
|
||||||
import { BooleanComponent } from "./BooleanComponent";
|
import { BooleanComponent } from "./BooleanComponent";
|
||||||
import { ClientSelectComponent } from "./ClientSelectComponent";
|
import { ClientSelectComponent } from "./ClientSelectComponent";
|
||||||
|
@ -13,6 +14,7 @@ import { RoleComponent } from "./RoleComponent";
|
||||||
import { ScriptComponent } from "./ScriptComponent";
|
import { ScriptComponent } from "./ScriptComponent";
|
||||||
import { StringComponent } from "./StringComponent";
|
import { StringComponent } from "./StringComponent";
|
||||||
import { TextComponent } from "./TextComponent";
|
import { TextComponent } from "./TextComponent";
|
||||||
|
import { UserProfileAttributeListComponent } from "./UserProfileAttributeListComponent";
|
||||||
|
|
||||||
export type ComponentProps = Omit<ConfigPropertyRepresentation, "type"> & {
|
export type ComponentProps = Omit<ConfigPropertyRepresentation, "type"> & {
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
@ -31,6 +33,7 @@ const ComponentTypes = [
|
||||||
"Group",
|
"Group",
|
||||||
"MultivaluedList",
|
"MultivaluedList",
|
||||||
"ClientList",
|
"ClientList",
|
||||||
|
"UserProfileAttributeList",
|
||||||
"MultivaluedString",
|
"MultivaluedString",
|
||||||
"File",
|
"File",
|
||||||
"Password",
|
"Password",
|
||||||
|
@ -39,7 +42,7 @@ const ComponentTypes = [
|
||||||
export type Components = (typeof ComponentTypes)[number];
|
export type Components = (typeof ComponentTypes)[number];
|
||||||
|
|
||||||
export const COMPONENTS: {
|
export const COMPONENTS: {
|
||||||
[index in Components]: (props: ComponentProps) => JSX.Element;
|
[index in Components]: FunctionComponent<ComponentProps>;
|
||||||
} = {
|
} = {
|
||||||
String: StringComponent,
|
String: StringComponent,
|
||||||
Text: TextComponent,
|
Text: TextComponent,
|
||||||
|
@ -50,6 +53,7 @@ export const COMPONENTS: {
|
||||||
Map: MapComponent,
|
Map: MapComponent,
|
||||||
Group: GroupComponent,
|
Group: GroupComponent,
|
||||||
ClientList: ClientSelectComponent,
|
ClientList: ClientSelectComponent,
|
||||||
|
UserProfileAttributeList: UserProfileAttributeListComponent,
|
||||||
MultivaluedList: MultiValuedListComponent,
|
MultivaluedList: MultiValuedListComponent,
|
||||||
MultivaluedString: MultiValuedStringComponent,
|
MultivaluedString: MultiValuedStringComponent,
|
||||||
File: FileComponent,
|
File: FileComponent,
|
||||||
|
|
|
@ -51,6 +51,11 @@ public class ProviderConfigProperty {
|
||||||
public static final String MULTIVALUED_LIST_TYPE="MultivaluedList";
|
public static final String MULTIVALUED_LIST_TYPE="MultivaluedList";
|
||||||
|
|
||||||
public static final String CLIENT_LIST_TYPE="ClientList";
|
public static final String CLIENT_LIST_TYPE="ClientList";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possibility to select from user attributes defined in the user-profile, but also still have an option to configure custom value
|
||||||
|
*/
|
||||||
|
public static final String USER_PROFILE_ATTRIBUTE_LIST_TYPE="UserProfileAttributeList";
|
||||||
public static final String PASSWORD="Password";
|
public static final String PASSWORD="Password";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -83,7 +83,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||||
property.setName(CONF_USER_ATTRIBUTE);
|
property.setName(CONF_USER_ATTRIBUTE);
|
||||||
property.setLabel("User Attribute Name");
|
property.setLabel("User Attribute Name");
|
||||||
property.setHelpText("User attribute name to store information into.");
|
property.setHelpText("User attribute name to store information into.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class UserAttributeMapper extends AbstractClaimMapper {
|
||||||
property.setName(USER_ATTRIBUTE);
|
property.setName(USER_ATTRIBUTE);
|
||||||
property.setLabel("User Attribute Name");
|
property.setLabel("User Attribute Name");
|
||||||
property.setHelpText("User attribute name to store claim. Use email, lastName, and firstName to map to those predefined user properties.");
|
property.setHelpText("User attribute name to store claim. Use email, lastName, and firstName to map to those predefined user properties.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
property.setName(ATTRIBUTE);
|
property.setName(ATTRIBUTE);
|
||||||
property.setLabel("User Attribute");
|
property.setLabel("User Attribute");
|
||||||
property.setHelpText("Name of user attribute you want to hardcode");
|
property.setHelpText("Name of user attribute you want to hardcode");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(ATTRIBUTE_VALUE);
|
property.setName(ATTRIBUTE_VALUE);
|
||||||
|
|
|
@ -97,7 +97,7 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper implemen
|
||||||
property.setName(USER_ATTRIBUTE);
|
property.setName(USER_ATTRIBUTE);
|
||||||
property.setLabel("User Attribute Name");
|
property.setLabel("User Attribute Name");
|
||||||
property.setHelpText("User attribute name to store saml attribute. Use email, lastName, and firstName to map to those predefined user properties.");
|
property.setHelpText("User attribute name to store saml attribute. Use email, lastName, and firstName to map to those predefined user properties.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class XPathAttributeMapper extends AbstractIdentityProviderMapper impleme
|
||||||
property.setName(USER_ATTRIBUTE);
|
property.setName(USER_ATTRIBUTE);
|
||||||
property.setLabel("User Attribute Name");
|
property.setLabel("User Attribute Name");
|
||||||
property.setHelpText("User attribute name to store XPath value. Use " + UserModel.EMAIL + ", " + UserModel.FIRST_NAME + ", and " + UserModel.LAST_NAME + " for e-mail, first and last name, respectively.");
|
property.setHelpText("User attribute name to store XPath value. Use " + UserModel.EMAIL + ", " + UserModel.FIRST_NAME + ", and " + UserModel.LAST_NAME + " for e-mail, first and last name, respectively.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
|
||||||
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
|
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
|
||||||
property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
|
property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
|
||||||
property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
|
property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserAttributeMapper.class);
|
OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserAttributeMapper.class);
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper imp
|
||||||
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
|
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
|
||||||
property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
|
property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
|
||||||
property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
|
property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
|
||||||
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
AttributeStatementHelper.setConfigProperties(configProperties);
|
AttributeStatementHelper.setConfigProperties(configProperties);
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ public class UserPropertyAttributeStatementMapper extends AbstractSAMLProtocolMa
|
||||||
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
|
property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
|
||||||
property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL);
|
property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL);
|
||||||
property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT);
|
property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT);
|
||||||
|
property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
AttributeStatementHelper.setConfigProperties(configProperties);
|
AttributeStatementHelper.setConfigProperties(configProperties);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue