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,
|
||||
"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);
|
||||
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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"]';
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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 { 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,
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue