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 f9f3e5ec68..702931f3e2 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 @@ -39,7 +39,7 @@ public class HardcodedAttributeMapperFactory extends AbstractLDAPStorageMapperFa 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, + ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE, null, true); 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 e13ccd32ef..533d574484 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 @@ -55,7 +55,7 @@ public class UserAttributeLDAPStorageMapperFactory extends AbstractLDAPStorageMa .property().name(UserAttributeLDAPStorageMapper.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.") - .type(ProviderConfigProperty.STRING_TYPE) + .type(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_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.") diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/client_scope_details/tabs/mappers/MapperDetailsPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/client_scope_details/tabs/mappers/MapperDetailsPage.ts index 32ff7567bb..951f4d7cb7 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/client_scope_details/tabs/mappers/MapperDetailsPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/client_scope_details/tabs/mappers/MapperDetailsPage.ts @@ -9,7 +9,7 @@ export enum ClaimJsonType { } export default class MapperDetailsPage extends CommonPage { - #userAttributeInput = '[id="user.attribute"]'; + #userAttributeInput = '[data-testid="config.user🍺attribute"]'; #tokenClaimNameInput = '[id="claim.name"]'; #claimJsonType = '[id="jsonType.label"]'; diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts index c9fe89607b..d781912b66 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts @@ -8,13 +8,13 @@ export default class AddMapperPage { #addMapperButton = "#add-mapper-button"; #mapperNameInput = "#kc-name"; - #attribute = "user.attribute"; + #attribute = "config.user🍺attribute"; #attributeName = "attribute.name"; #attributeFriendlyName = "attribute.friendly.name"; #claimInput = "claim"; #socialProfileJSONfieldPath = "jsonField"; - #userAttribute = "attribute"; - #userAttributeName = "userAttribute"; + #userAttribute = "config.attribute"; + #userAttributeName = "config.userAttribute"; #userAttributeValue = "attribute.value"; #userSessionAttribute = "attribute"; #userSessionAttributeValue = "attribute.value"; diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts index 14ab64b052..741fc47203 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts @@ -63,9 +63,9 @@ export default class ProviderPage { #cachePolicyList = "#kc-cache-policy + ul"; // Mapper input values - #userModelAttInput = "user.model.attribute"; + #userModelAttInput = "config.user🍺model🍺attribute"; #ldapAttInput = "ldap.attribute"; - #userModelAttNameInput = "user.model.attribute"; + #userModelAttNameInput = "config.user🍺model🍺attribute"; #attValueInput = "attribute.value"; #ldapFullNameAttInput = "ldap.full.name.attribute"; #ldapAttNameInput = "ldap.attribute.name"; @@ -317,7 +317,7 @@ export default class ProviderPage { } createNewMapper(mapperType: string) { - const userModelAttValue = "firstName"; + const userModelAttValue = "middleName"; const ldapAttValue = "cn"; const ldapDnValue = "ou=groups"; diff --git a/js/apps/admin-ui/src/components/dynamic/UserProfileAttributeListComponent.tsx b/js/apps/admin-ui/src/components/dynamic/UserProfileAttributeListComponent.tsx new file mode 100644 index 0000000000..3fb77f01d9 --- /dev/null +++ b/js/apps/admin-ui/src/components/dynamic/UserProfileAttributeListComponent.tsx @@ -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(); + 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 ( + } + fieldId={convertedName!} + validated={errors[convertedName!] ? "error" : "default"} + helperTextInvalid={t("required")} + > + + + ); +}; diff --git a/js/apps/admin-ui/src/components/dynamic/components.ts b/js/apps/admin-ui/src/components/dynamic/components.ts index 1f93cd64c5..f860088f4c 100644 --- a/js/apps/admin-ui/src/components/dynamic/components.ts +++ b/js/apps/admin-ui/src/components/dynamic/components.ts @@ -1,4 +1,5 @@ import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation"; +import { FunctionComponent } from "react"; import { BooleanComponent } from "./BooleanComponent"; import { ClientSelectComponent } from "./ClientSelectComponent"; @@ -13,6 +14,7 @@ import { RoleComponent } from "./RoleComponent"; import { ScriptComponent } from "./ScriptComponent"; import { StringComponent } from "./StringComponent"; import { TextComponent } from "./TextComponent"; +import { UserProfileAttributeListComponent } from "./UserProfileAttributeListComponent"; export type ComponentProps = Omit & { isDisabled?: boolean; @@ -31,6 +33,7 @@ const ComponentTypes = [ "Group", "MultivaluedList", "ClientList", + "UserProfileAttributeList", "MultivaluedString", "File", "Password", @@ -39,7 +42,7 @@ const ComponentTypes = [ export type Components = (typeof ComponentTypes)[number]; export const COMPONENTS: { - [index in Components]: (props: ComponentProps) => JSX.Element; + [index in Components]: FunctionComponent; } = { String: StringComponent, Text: TextComponent, @@ -50,6 +53,7 @@ export const COMPONENTS: { Map: MapComponent, Group: GroupComponent, ClientList: ClientSelectComponent, + UserProfileAttributeList: UserProfileAttributeListComponent, MultivaluedList: MultiValuedListComponent, MultivaluedString: MultiValuedStringComponent, File: FileComponent, diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java index 37a11db38e..4e7b88ad15 100755 --- a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java +++ b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java @@ -51,6 +51,11 @@ public class ProviderConfigProperty { public static final String MULTIVALUED_LIST_TYPE="MultivaluedList"; 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"; /** diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java index 615cb0d322..313059967d 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java @@ -83,7 +83,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr property.setName(CONF_USER_ATTRIBUTE); property.setLabel("User Attribute Name"); 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); } diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java index 57f65de160..af4e00ad81 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/UserAttributeMapper.java @@ -68,7 +68,7 @@ public class UserAttributeMapper extends AbstractClaimMapper { property.setName(USER_ATTRIBUTE); 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.setType(ProviderConfigProperty.STRING_TYPE); + property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE); configProperties.add(property); } diff --git a/services/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java b/services/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java index bc827ea559..c25167eea0 100755 --- a/services/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java +++ b/services/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java @@ -46,7 +46,7 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper { property.setName(ATTRIBUTE); property.setLabel("User Attribute"); 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); property = new ProviderConfigProperty(); property.setName(ATTRIBUTE_VALUE); diff --git a/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java index 3b2d000427..f3e2251373 100755 --- a/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/broker/saml/mappers/UserAttributeMapper.java @@ -97,7 +97,7 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper implemen property.setName(USER_ATTRIBUTE); 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.setType(ProviderConfigProperty.STRING_TYPE); + property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE); configProperties.add(property); } diff --git a/services/src/main/java/org/keycloak/broker/saml/mappers/XPathAttributeMapper.java b/services/src/main/java/org/keycloak/broker/saml/mappers/XPathAttributeMapper.java index 0a52cb14db..42d5be8c93 100644 --- a/services/src/main/java/org/keycloak/broker/saml/mappers/XPathAttributeMapper.java +++ b/services/src/main/java/org/keycloak/broker/saml/mappers/XPathAttributeMapper.java @@ -99,7 +99,7 @@ public class XPathAttributeMapper extends AbstractIdentityProviderMapper impleme property.setName(USER_ATTRIBUTE); 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.setType(ProviderConfigProperty.STRING_TYPE); + property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE); configProperties.add(property); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java index c99f4da142..77f77fe43e 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java @@ -47,7 +47,7 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O property.setName(ProtocolMapperUtils.USER_ATTRIBUTE); property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL); property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); - property.setType(ProviderConfigProperty.STRING_TYPE); + property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE); configProperties.add(property); OIDCAttributeMapperHelper.addAttributeConfig(configProperties, UserAttributeMapper.class); diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java index 0eb92e2a4b..9c324f3c1d 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java +++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java @@ -46,6 +46,7 @@ public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper imp property.setName(ProtocolMapperUtils.USER_ATTRIBUTE); property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL); property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT); + property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE); configProperties.add(property); AttributeStatementHelper.setConfigProperties(configProperties); diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java index e46e09f8d6..8523f0ac0e 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java +++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java @@ -44,6 +44,7 @@ public class UserPropertyAttributeStatementMapper extends AbstractSAMLProtocolMa property.setName(ProtocolMapperUtils.USER_ATTRIBUTE); property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL); property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT); + property.setType(ProviderConfigProperty.USER_PROFILE_ATTRIBUTE_LIST_TYPE); configProperties.add(property); AttributeStatementHelper.setConfigProperties(configProperties);