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:
Marek Posolda 2024-01-25 17:01:02 +01:00 committed by GitHub
parent 7797f778d1
commit 651d99db25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 88 additions and 16 deletions

View file

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

View file

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

View file

@ -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"]';

View file

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

View file

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

View file

@ -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>
);
};

View file

@ -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,

View file

@ -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";
/** /**

View file

@ -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);
} }

View file

@ -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);
} }

View file

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

View file

@ -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);
} }

View file

@ -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);
} }

View file

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

View file

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

View file

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