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,
"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);

View file

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

View file

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

View file

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

View file

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

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 { 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<ConfigPropertyRepresentation, "type"> & {
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<ComponentProps>;
} = {
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,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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