Identity providers(mappers): update form fields for all Social mapper types (#1304)
Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
c71d21c748
commit
8917744c04
6 changed files with 508 additions and 456 deletions
|
@ -272,6 +272,20 @@ describe("Identity provider test", () => {
|
|||
masthead.checkNotificationMessage(createMapperSuccessMsg);
|
||||
});
|
||||
|
||||
it("should add Social mapper of type Attribute Importer", () => {
|
||||
sidebarPage.goToIdentityProviders();
|
||||
|
||||
listingPage.goToItemDetails("facebook");
|
||||
|
||||
addMapperPage.goToMappersTab();
|
||||
|
||||
addMapperPage.addMapper();
|
||||
|
||||
addMapperPage.fillSocialMapper("facebook attribute importer");
|
||||
|
||||
masthead.checkNotificationMessage(createMapperSuccessMsg);
|
||||
});
|
||||
|
||||
it("should edit Username Template Importer mapper", () => {
|
||||
sidebarPage.goToIdentityProviders();
|
||||
|
||||
|
@ -293,7 +307,7 @@ describe("Identity provider test", () => {
|
|||
|
||||
addMapperPage.goToMappersTab();
|
||||
|
||||
listingPage.goToItemDetails("facebook mapper");
|
||||
listingPage.goToItemDetails("facebook attribute importer");
|
||||
|
||||
addMapperPage.editSocialMapper();
|
||||
});
|
||||
|
|
|
@ -9,7 +9,10 @@ export default class AddMapperPage {
|
|||
private mapperRoleInput = "mapper-role-input";
|
||||
private attributeName = "attribute-name";
|
||||
private attributeFriendlyName = "attribute-friendly-name";
|
||||
private attributeValue = "attribute-value";
|
||||
private claimInput = "claim";
|
||||
private claimValueInput = "claim-value-input";
|
||||
private socialProfileJSONfieldPath = "social-profile-JSON-field-path";
|
||||
private userAttribute = "user-attribute";
|
||||
private userAttributeName = "user-attribute-name";
|
||||
private userAttributeValue = "user-attribute-value";
|
||||
|
@ -73,14 +76,17 @@ export default class AddMapperPage {
|
|||
.contains("Attribute Importer")
|
||||
.click();
|
||||
|
||||
cy.findByTestId(this.userSessionAttribute).clear();
|
||||
cy.findByTestId(this.userSessionAttribute).type("user session attribute");
|
||||
cy.findByTestId(this.userSessionAttributeValue).clear();
|
||||
|
||||
cy.findByTestId(this.userSessionAttributeValue).type(
|
||||
"user session attribute value"
|
||||
cy.findByTestId(this.socialProfileJSONfieldPath).clear();
|
||||
cy.findByTestId(this.socialProfileJSONfieldPath).type(
|
||||
"social profile JSON field path"
|
||||
);
|
||||
|
||||
cy.findByTestId(this.userAttributeName).clear();
|
||||
|
||||
cy.findByTestId(this.userAttributeName).type("user attribute name");
|
||||
|
||||
this.saveNewMapper();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -332,16 +338,16 @@ export default class AddMapperPage {
|
|||
|
||||
cy.findByTestId("inherit").click();
|
||||
|
||||
cy.findByTestId(this.userSessionAttribute).clear();
|
||||
cy.findByTestId(this.userSessionAttribute).type(
|
||||
"user session attribute_edited"
|
||||
);
|
||||
cy.findByTestId(this.userSessionAttributeValue).clear();
|
||||
cy.findByTestId(this.socialProfileJSONfieldPath).clear();
|
||||
|
||||
cy.findByTestId(this.userSessionAttributeValue).type(
|
||||
"user session attribute value_edited"
|
||||
cy.findByTestId(this.socialProfileJSONfieldPath).type(
|
||||
"social profile JSON field path edited"
|
||||
);
|
||||
|
||||
cy.findByTestId(this.userAttributeName).clear();
|
||||
|
||||
cy.findByTestId(this.userAttributeName).type("user attribute name edited");
|
||||
|
||||
this.saveNewMapper();
|
||||
|
||||
return this;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
|
@ -35,6 +35,8 @@ import { convertToFormValues } from "../../util";
|
|||
import { toIdentityProvider } from "../routes/IdentityProvider";
|
||||
import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation";
|
||||
import { AddMapperForm } from "./AddMapperForm";
|
||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||
import { groupBy } from "lodash";
|
||||
|
||||
export type IdPMapperRepresentationWithAttributes =
|
||||
IdentityProviderMapperRepresentation & {
|
||||
|
@ -54,11 +56,26 @@ export const AddMapper = () => {
|
|||
const { providerId, alias } = useParams<IdentityProviderAddMapperParams>();
|
||||
const { id } = useParams<IdentityProviderEditMapperParams>();
|
||||
|
||||
const isSAMLorOIDC = providerId === "saml" || providerId === "oidc";
|
||||
const serverInfo = useServerInfo();
|
||||
const identityProviders = useMemo(
|
||||
() => groupBy(serverInfo.identityProviders, "groupName"),
|
||||
[serverInfo]
|
||||
);
|
||||
|
||||
const isSocialIdP = useMemo(
|
||||
() =>
|
||||
identityProviders["Social"]
|
||||
.map((item) => item.id)
|
||||
.includes(providerId.toLowerCase()),
|
||||
[identityProviders, providerId]
|
||||
);
|
||||
|
||||
const [mapperTypes, setMapperTypes] =
|
||||
useState<Record<string, IdentityProviderMapperRepresentation>>();
|
||||
const [mapperType, setMapperType] = useState("advancedAttributeToRole");
|
||||
const [mapperType, setMapperType] = useState(
|
||||
isSocialIdP ? "attributeImporter" : "hardcodedRole"
|
||||
);
|
||||
|
||||
const [currentMapper, setCurrentMapper] =
|
||||
useState<IdentityProviderMapperRepresentation>();
|
||||
const [roles, setRoles] = useState<RoleRepresentation[]>([]);
|
||||
|
@ -207,6 +224,11 @@ export const AddMapper = () => {
|
|||
const isOIDCUsernameTemplateImporter =
|
||||
formValues.identityProviderMapper === "oidc-username-idp-mapper";
|
||||
|
||||
const isSocialAttributeImporter = useMemo(
|
||||
() => formValues.identityProviderMapper?.includes("user-attribute-mapper"),
|
||||
[formValues.identityProviderMapper]
|
||||
);
|
||||
|
||||
const toggleModal = () => {
|
||||
setRolesModalOpen(!rolesModalOpen);
|
||||
};
|
||||
|
@ -218,10 +240,12 @@ export const AddMapper = () => {
|
|||
titleKey={
|
||||
id
|
||||
? t("editIdPMapper", {
|
||||
providerId: providerId.toUpperCase(),
|
||||
providerId:
|
||||
providerId[0].toUpperCase() + providerId.substring(1),
|
||||
})
|
||||
: t("addIdPMapper", {
|
||||
providerId: providerId.toUpperCase(),
|
||||
providerId:
|
||||
providerId[0].toUpperCase() + providerId.substring(1),
|
||||
})
|
||||
}
|
||||
divider
|
||||
|
@ -267,13 +291,12 @@ export const AddMapper = () => {
|
|||
<AddMapperForm
|
||||
form={form}
|
||||
id={id}
|
||||
providerId={providerId}
|
||||
mapperTypes={mapperTypes}
|
||||
updateMapperType={setMapperType}
|
||||
formValues={formValues}
|
||||
mapperType={mapperType}
|
||||
isSocialIdP={isSocialIdP}
|
||||
/>
|
||||
{isSAMLorOIDC ? (
|
||||
<>
|
||||
{(isSAMLAdvancedAttrToRole || isOIDCAdvancedClaimToRole) && (
|
||||
<>
|
||||
|
@ -413,9 +436,7 @@ export const AddMapper = () => {
|
|||
id="target-dropdown"
|
||||
placeholderText={t("realm-settings:placeholderText")}
|
||||
direction="down"
|
||||
onToggle={() =>
|
||||
setTargetOptionsOpen(!targetOptionsOpen)
|
||||
}
|
||||
onToggle={() => setTargetOptionsOpen(!targetOptionsOpen)}
|
||||
onSelect={(_, value) => {
|
||||
onChange(t(`targetOptions.${value}`));
|
||||
setTargetOptionsOpen(false);
|
||||
|
@ -698,9 +719,7 @@ export const AddMapper = () => {
|
|||
: "kc-user-attribute-name"
|
||||
}
|
||||
name={
|
||||
isOIDCclaimToRole
|
||||
? "config.claim"
|
||||
: "config.user-attribute"
|
||||
isOIDCclaimToRole ? "config.claim" : "config.user-attribute"
|
||||
}
|
||||
validated={
|
||||
errors.name
|
||||
|
@ -711,6 +730,79 @@ export const AddMapper = () => {
|
|||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isSocialAttributeImporter && (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("socialProfileJSONFieldPath")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
id="social-profile-JSON-field-path-help-icon"
|
||||
helpText="identity-providers-help:socialProfileJSONFieldPath"
|
||||
forLabel={t("socialProfileJSONFieldPath")}
|
||||
forID={t(`common:helpLabel`, {
|
||||
label: t("socialProfileJSONFieldPath"),
|
||||
})}
|
||||
/>
|
||||
}
|
||||
fieldId="kc-social-profile-JSON-field-path"
|
||||
validated={
|
||||
errors.name
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
defaultValue={currentMapper?.config.attribute}
|
||||
id="kc-social-profile-JSON-field-path"
|
||||
data-testid={"social-profile-JSON-field-path"}
|
||||
name="config.jsonField"
|
||||
validated={
|
||||
errors.config?.role
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("mapperUserAttributeName")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
id="user-attribute-name-help-icon"
|
||||
helpText="identity-providers-help:socialUserAttributeName"
|
||||
forLabel={t("mapperUserAttributeName")}
|
||||
forID={t(`common:helpLabel`, {
|
||||
label: t("mapperUserAttributeName"),
|
||||
})}
|
||||
/>
|
||||
}
|
||||
fieldId="kc-user-session-attribute-value"
|
||||
validated={
|
||||
errors.name
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register()}
|
||||
type="text"
|
||||
defaultValue={currentMapper?.config.userAttribute}
|
||||
data-testid={"user-attribute-name"}
|
||||
id="kc-user-session-attribute-name"
|
||||
name="config.userAttribute"
|
||||
validated={
|
||||
errors.name
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
{(isSAMLAdvancedAttrToRole ||
|
||||
isHardcodedRole ||
|
||||
isSAMLAttributeToRole ||
|
||||
|
@ -758,75 +850,6 @@ export const AddMapper = () => {
|
|||
</FormGroup>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("userSessionAttribute")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
id="user-session-attribute-help-icon"
|
||||
helpText="identity-providers-help:userSessionAttribute"
|
||||
forLabel={t("userSessionAttribute")}
|
||||
forID={t(`common:helpLabel`, {
|
||||
label: t("userSessionAttribute"),
|
||||
})}
|
||||
/>
|
||||
}
|
||||
fieldId="kc-user-session-attribute"
|
||||
isRequired
|
||||
validated={
|
||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: true })}
|
||||
type="text"
|
||||
id="kc-attribute"
|
||||
data-testid="user-session-attribute"
|
||||
name="config.attribute"
|
||||
validated={
|
||||
errors.name
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("userSessionAttributeValue")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
id="user-session-attribute-value-help-icon"
|
||||
helpText="identity-providers-help:userSessionAttributeValue"
|
||||
forLabel={t("userSessionAttributeValue")}
|
||||
forID={t(`common:helpLabel`, {
|
||||
label: t("userSessionAttributeValue"),
|
||||
})}
|
||||
/>
|
||||
}
|
||||
fieldId="kc-user-session-attribute-value"
|
||||
isRequired
|
||||
validated={
|
||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: true })}
|
||||
type="text"
|
||||
data-testid="user-session-attribute-value"
|
||||
id="kc-user-session-attribute-value"
|
||||
name="config.attribute-value"
|
||||
validated={
|
||||
errors.name
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ActionGroup>
|
||||
<Button
|
||||
data-testid="new-mapper-save-button"
|
||||
|
|
|
@ -20,11 +20,11 @@ import type { IdPMapperRepresentationWithAttributes } from "./AddMapper";
|
|||
type AddMapperFormProps = {
|
||||
mapperTypes?: Record<string, IdentityProviderMapperRepresentation>;
|
||||
mapperType: string;
|
||||
providerId: string;
|
||||
id: string;
|
||||
updateMapperType: (mapperType: string) => void;
|
||||
form: UseFormMethods<IdPMapperRepresentationWithAttributes>;
|
||||
formValues: IdPMapperRepresentationWithAttributes;
|
||||
isSocialIdP: boolean;
|
||||
};
|
||||
|
||||
export const AddMapperForm = ({
|
||||
|
@ -34,6 +34,7 @@ export const AddMapperForm = ({
|
|||
id,
|
||||
updateMapperType,
|
||||
formValues,
|
||||
isSocialIdP,
|
||||
}: AddMapperFormProps) => {
|
||||
const { t } = useTranslation("identity-providers");
|
||||
|
||||
|
@ -129,7 +130,9 @@ export const AddMapperForm = ({
|
|||
helpText={
|
||||
formValues.identityProviderMapper ===
|
||||
"saml-user-attribute-idp-mapper" &&
|
||||
(providerId === "oidc" || providerId === "keycloak-oidc")
|
||||
(providerId === "oidc" ||
|
||||
providerId === "keycloak-oidc" ||
|
||||
isSocialIdP)
|
||||
? `identity-providers-help:oidcAttributeImporter`
|
||||
: `identity-providers-help:${mapperType}`
|
||||
}
|
||||
|
@ -142,7 +145,9 @@ export const AddMapperForm = ({
|
|||
<Controller
|
||||
name="identityProviderMapper"
|
||||
defaultValue={
|
||||
providerId === "saml"
|
||||
isSocialIdP
|
||||
? `${providerId.toLowerCase()}-user-attribute-mapper`
|
||||
: providerId === "saml"
|
||||
? "saml-advanced-role-idp-mapper"
|
||||
: "oidc-advanced-role-idp-mapper"
|
||||
}
|
||||
|
|
|
@ -136,6 +136,8 @@ export default {
|
|||
userAttribute: "Name of user attribute you want to hardcode",
|
||||
claim:
|
||||
"Name of claim to search for in token. You can reference nested claims by using a '.', i.e. 'address.locality'. To use dot (.) literally, escape it with backslash. (\\.)",
|
||||
socialProfileJSONFieldPath:
|
||||
"Path of field in Social Provider User Profile JSON data to get value from. You can use dot notation for nesting and square brackets for array index. E.g. 'contact.address[0].country'.",
|
||||
userAttributeValue: "Value you want to hardcode",
|
||||
attributeName:
|
||||
"Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.",
|
||||
|
@ -143,6 +145,7 @@ export default {
|
|||
"Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.",
|
||||
userAttributeName:
|
||||
"User attribute name to store SAML attribute. Use email, lastName, and firstName to map to those predefined user properties.",
|
||||
socialUserAttributeName: "User attribute name to store information.",
|
||||
attributeValue:
|
||||
"Value the attribute must have. If the attribute is a list, then the value must be contained in the list.",
|
||||
attributes:
|
||||
|
|
|
@ -74,6 +74,7 @@ export default {
|
|||
claim: "Claim",
|
||||
claimValue: "Claim Value",
|
||||
claims: "Claims",
|
||||
socialProfileJSONFieldPath: "Social Profile JSON Field Path",
|
||||
mapperAttributeName: "Attribute Name",
|
||||
mapperUserAttributeName: "User Attribute Name",
|
||||
mapperAttributeFriendlyName: "Friendly name",
|
||||
|
|
Loading…
Reference in a new issue