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:
Jenny 2021-10-06 07:04:17 -04:00 committed by GitHub
parent c71d21c748
commit 8917744c04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 508 additions and 456 deletions

View file

@ -272,6 +272,20 @@ describe("Identity provider test", () => {
masthead.checkNotificationMessage(createMapperSuccessMsg); 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", () => { it("should edit Username Template Importer mapper", () => {
sidebarPage.goToIdentityProviders(); sidebarPage.goToIdentityProviders();
@ -293,7 +307,7 @@ describe("Identity provider test", () => {
addMapperPage.goToMappersTab(); addMapperPage.goToMappersTab();
listingPage.goToItemDetails("facebook mapper"); listingPage.goToItemDetails("facebook attribute importer");
addMapperPage.editSocialMapper(); addMapperPage.editSocialMapper();
}); });

View file

@ -9,7 +9,10 @@ export default class AddMapperPage {
private mapperRoleInput = "mapper-role-input"; private mapperRoleInput = "mapper-role-input";
private attributeName = "attribute-name"; private attributeName = "attribute-name";
private attributeFriendlyName = "attribute-friendly-name"; private attributeFriendlyName = "attribute-friendly-name";
private attributeValue = "attribute-value";
private claimInput = "claim"; private claimInput = "claim";
private claimValueInput = "claim-value-input";
private socialProfileJSONfieldPath = "social-profile-JSON-field-path";
private userAttribute = "user-attribute"; private userAttribute = "user-attribute";
private userAttributeName = "user-attribute-name"; private userAttributeName = "user-attribute-name";
private userAttributeValue = "user-attribute-value"; private userAttributeValue = "user-attribute-value";
@ -73,14 +76,17 @@ export default class AddMapperPage {
.contains("Attribute Importer") .contains("Attribute Importer")
.click(); .click();
cy.findByTestId(this.userSessionAttribute).clear(); cy.findByTestId(this.socialProfileJSONfieldPath).clear();
cy.findByTestId(this.userSessionAttribute).type("user session attribute"); cy.findByTestId(this.socialProfileJSONfieldPath).type(
cy.findByTestId(this.userSessionAttributeValue).clear(); "social profile JSON field path"
cy.findByTestId(this.userSessionAttributeValue).type(
"user session attribute value"
); );
cy.findByTestId(this.userAttributeName).clear();
cy.findByTestId(this.userAttributeName).type("user attribute name");
this.saveNewMapper();
return this; return this;
} }
@ -332,16 +338,16 @@ export default class AddMapperPage {
cy.findByTestId("inherit").click(); cy.findByTestId("inherit").click();
cy.findByTestId(this.userSessionAttribute).clear(); cy.findByTestId(this.socialProfileJSONfieldPath).clear();
cy.findByTestId(this.userSessionAttribute).type(
"user session attribute_edited"
);
cy.findByTestId(this.userSessionAttributeValue).clear();
cy.findByTestId(this.userSessionAttributeValue).type( cy.findByTestId(this.socialProfileJSONfieldPath).type(
"user session attribute value_edited" "social profile JSON field path edited"
); );
cy.findByTestId(this.userAttributeName).clear();
cy.findByTestId(this.userAttributeName).type("user attribute name edited");
this.saveNewMapper(); this.saveNewMapper();
return this; return this;

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useMemo, useState } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
@ -35,6 +35,8 @@ import { convertToFormValues } from "../../util";
import { toIdentityProvider } from "../routes/IdentityProvider"; import { toIdentityProvider } from "../routes/IdentityProvider";
import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation"; import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation";
import { AddMapperForm } from "./AddMapperForm"; import { AddMapperForm } from "./AddMapperForm";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { groupBy } from "lodash";
export type IdPMapperRepresentationWithAttributes = export type IdPMapperRepresentationWithAttributes =
IdentityProviderMapperRepresentation & { IdentityProviderMapperRepresentation & {
@ -54,11 +56,26 @@ export const AddMapper = () => {
const { providerId, alias } = useParams<IdentityProviderAddMapperParams>(); const { providerId, alias } = useParams<IdentityProviderAddMapperParams>();
const { id } = useParams<IdentityProviderEditMapperParams>(); 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] = const [mapperTypes, setMapperTypes] =
useState<Record<string, IdentityProviderMapperRepresentation>>(); useState<Record<string, IdentityProviderMapperRepresentation>>();
const [mapperType, setMapperType] = useState("advancedAttributeToRole"); const [mapperType, setMapperType] = useState(
isSocialIdP ? "attributeImporter" : "hardcodedRole"
);
const [currentMapper, setCurrentMapper] = const [currentMapper, setCurrentMapper] =
useState<IdentityProviderMapperRepresentation>(); useState<IdentityProviderMapperRepresentation>();
const [roles, setRoles] = useState<RoleRepresentation[]>([]); const [roles, setRoles] = useState<RoleRepresentation[]>([]);
@ -207,6 +224,11 @@ export const AddMapper = () => {
const isOIDCUsernameTemplateImporter = const isOIDCUsernameTemplateImporter =
formValues.identityProviderMapper === "oidc-username-idp-mapper"; formValues.identityProviderMapper === "oidc-username-idp-mapper";
const isSocialAttributeImporter = useMemo(
() => formValues.identityProviderMapper?.includes("user-attribute-mapper"),
[formValues.identityProviderMapper]
);
const toggleModal = () => { const toggleModal = () => {
setRolesModalOpen(!rolesModalOpen); setRolesModalOpen(!rolesModalOpen);
}; };
@ -218,10 +240,12 @@ export const AddMapper = () => {
titleKey={ titleKey={
id id
? t("editIdPMapper", { ? t("editIdPMapper", {
providerId: providerId.toUpperCase(), providerId:
providerId[0].toUpperCase() + providerId.substring(1),
}) })
: t("addIdPMapper", { : t("addIdPMapper", {
providerId: providerId.toUpperCase(), providerId:
providerId[0].toUpperCase() + providerId.substring(1),
}) })
} }
divider divider
@ -267,13 +291,12 @@ export const AddMapper = () => {
<AddMapperForm <AddMapperForm
form={form} form={form}
id={id} id={id}
providerId={providerId}
mapperTypes={mapperTypes} mapperTypes={mapperTypes}
updateMapperType={setMapperType} updateMapperType={setMapperType}
formValues={formValues} formValues={formValues}
mapperType={mapperType} mapperType={mapperType}
isSocialIdP={isSocialIdP}
/> />
{isSAMLorOIDC ? (
<> <>
{(isSAMLAdvancedAttrToRole || isOIDCAdvancedClaimToRole) && ( {(isSAMLAdvancedAttrToRole || isOIDCAdvancedClaimToRole) && (
<> <>
@ -413,9 +436,7 @@ export const AddMapper = () => {
id="target-dropdown" id="target-dropdown"
placeholderText={t("realm-settings:placeholderText")} placeholderText={t("realm-settings:placeholderText")}
direction="down" direction="down"
onToggle={() => onToggle={() => setTargetOptionsOpen(!targetOptionsOpen)}
setTargetOptionsOpen(!targetOptionsOpen)
}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(t(`targetOptions.${value}`)); onChange(t(`targetOptions.${value}`));
setTargetOptionsOpen(false); setTargetOptionsOpen(false);
@ -698,9 +719,7 @@ export const AddMapper = () => {
: "kc-user-attribute-name" : "kc-user-attribute-name"
} }
name={ name={
isOIDCclaimToRole isOIDCclaimToRole ? "config.claim" : "config.user-attribute"
? "config.claim"
: "config.user-attribute"
} }
validated={ validated={
errors.name errors.name
@ -711,6 +730,79 @@ export const AddMapper = () => {
</FormGroup> </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 || {(isSAMLAdvancedAttrToRole ||
isHardcodedRole || isHardcodedRole ||
isSAMLAttributeToRole || isSAMLAttributeToRole ||
@ -758,75 +850,6 @@ export const AddMapper = () => {
</FormGroup> </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> <ActionGroup>
<Button <Button
data-testid="new-mapper-save-button" data-testid="new-mapper-save-button"

View file

@ -20,11 +20,11 @@ import type { IdPMapperRepresentationWithAttributes } from "./AddMapper";
type AddMapperFormProps = { type AddMapperFormProps = {
mapperTypes?: Record<string, IdentityProviderMapperRepresentation>; mapperTypes?: Record<string, IdentityProviderMapperRepresentation>;
mapperType: string; mapperType: string;
providerId: string;
id: string; id: string;
updateMapperType: (mapperType: string) => void; updateMapperType: (mapperType: string) => void;
form: UseFormMethods<IdPMapperRepresentationWithAttributes>; form: UseFormMethods<IdPMapperRepresentationWithAttributes>;
formValues: IdPMapperRepresentationWithAttributes; formValues: IdPMapperRepresentationWithAttributes;
isSocialIdP: boolean;
}; };
export const AddMapperForm = ({ export const AddMapperForm = ({
@ -34,6 +34,7 @@ export const AddMapperForm = ({
id, id,
updateMapperType, updateMapperType,
formValues, formValues,
isSocialIdP,
}: AddMapperFormProps) => { }: AddMapperFormProps) => {
const { t } = useTranslation("identity-providers"); const { t } = useTranslation("identity-providers");
@ -129,7 +130,9 @@ export const AddMapperForm = ({
helpText={ helpText={
formValues.identityProviderMapper === formValues.identityProviderMapper ===
"saml-user-attribute-idp-mapper" && "saml-user-attribute-idp-mapper" &&
(providerId === "oidc" || providerId === "keycloak-oidc") (providerId === "oidc" ||
providerId === "keycloak-oidc" ||
isSocialIdP)
? `identity-providers-help:oidcAttributeImporter` ? `identity-providers-help:oidcAttributeImporter`
: `identity-providers-help:${mapperType}` : `identity-providers-help:${mapperType}`
} }
@ -142,7 +145,9 @@ export const AddMapperForm = ({
<Controller <Controller
name="identityProviderMapper" name="identityProviderMapper"
defaultValue={ defaultValue={
providerId === "saml" isSocialIdP
? `${providerId.toLowerCase()}-user-attribute-mapper`
: providerId === "saml"
? "saml-advanced-role-idp-mapper" ? "saml-advanced-role-idp-mapper"
: "oidc-advanced-role-idp-mapper" : "oidc-advanced-role-idp-mapper"
} }

View file

@ -136,6 +136,8 @@ export default {
userAttribute: "Name of user attribute you want to hardcode", userAttribute: "Name of user attribute you want to hardcode",
claim: 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. (\\.)", "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", userAttributeValue: "Value you want to hardcode",
attributeName: attributeName:
"Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.", "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.", "Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.",
userAttributeName: userAttributeName:
"User attribute name to store SAML attribute. Use email, lastName, and firstName to map to those predefined user properties.", "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: attributeValue:
"Value the attribute must have. If the attribute is a list, then the value must be contained in the list.", "Value the attribute must have. If the attribute is a list, then the value must be contained in the list.",
attributes: attributes:

View file

@ -74,6 +74,7 @@ export default {
claim: "Claim", claim: "Claim",
claimValue: "Claim Value", claimValue: "Claim Value",
claims: "Claims", claims: "Claims",
socialProfileJSONFieldPath: "Social Profile JSON Field Path",
mapperAttributeName: "Attribute Name", mapperAttributeName: "Attribute Name",
mapperUserAttributeName: "User Attribute Name", mapperUserAttributeName: "User Attribute Name",
mapperAttributeFriendlyName: "Friendly name", mapperAttributeFriendlyName: "Friendly name",