From 3b677c6e8ee344590f257c1347266026fad0d54a Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Fri, 1 Oct 2021 11:03:38 -0400 Subject: [PATCH] Identity providers(mappers): update "create" form fields for all SAML mapper types (#1282) --- .../integration/identity_providers.spec.ts | 76 +++- .../identity_providers/AddMapperPage.ts | 137 ++++++- src/identity-providers/add/AddMapper.tsx | 346 +++++++++--------- src/identity-providers/add/AddMapperForm.tsx | 198 ++++++++++ src/identity-providers/messages.ts | 4 + 5 files changed, 586 insertions(+), 175 deletions(-) create mode 100644 src/identity-providers/add/AddMapperForm.tsx diff --git a/cypress/integration/identity_providers.spec.ts b/cypress/integration/identity_providers.spec.ts index 6749ea5a88..1de2eda88d 100644 --- a/cypress/integration/identity_providers.spec.ts +++ b/cypress/integration/identity_providers.spec.ts @@ -142,7 +142,7 @@ describe("Identity provider test", () => { masthead.checkNotificationMessage(createMapperSuccessMsg); }); - it("should add SAML mapper", () => { + it("should add SAML mapper of type Advanced Attribute to Role", () => { sidebarPage.goToIdentityProviders(); listingPage.goToItemDetails("saml"); @@ -151,7 +151,7 @@ describe("Identity provider test", () => { addMapperPage.emptyStateAddMapper(); - addMapperPage.fillSAMLorOIDCMapper("SAML mapper"); + addMapperPage.addAdvancedAttrToRoleMapper("SAML mapper"); masthead.checkNotificationMessage(createMapperSuccessMsg); }); @@ -172,6 +172,78 @@ describe("Identity provider test", () => { masthead.checkNotificationMessage(createMapperSuccessMsg); }); + it("should add SAML mapper of type Hardcoded User Session Attribute", () => { + sidebarPage.goToIdentityProviders(); + + listingPage.goToItemDetails("saml"); + + addMapperPage.goToMappersTab(); + + addMapperPage.addMapper(); + + addMapperPage.addHardcodedUserSessionAttrMapper( + "Hardcoded User Session Attribute" + ); + + masthead.checkNotificationMessage(createMapperSuccessMsg); + }); + + it("should add SAML mapper of type Attribute Importer", () => { + sidebarPage.goToIdentityProviders(); + + listingPage.goToItemDetails("saml"); + + addMapperPage.goToMappersTab(); + + addMapperPage.addMapper(); + + addMapperPage.addAttrImporterMapper("Attribute Importer"); + + masthead.checkNotificationMessage(createMapperSuccessMsg); + }); + + it("should add SAML mapper of type Hardcoded Role", () => { + sidebarPage.goToIdentityProviders(); + + listingPage.goToItemDetails("saml"); + + addMapperPage.goToMappersTab(); + + addMapperPage.addMapper(); + + addMapperPage.addHardcodedRoleMapper("Hardcoded Role"); + + masthead.checkNotificationMessage(createMapperSuccessMsg); + }); + + it("should add SAML mapper of type Hardcoded Attribute", () => { + sidebarPage.goToIdentityProviders(); + + listingPage.goToItemDetails("saml"); + + addMapperPage.goToMappersTab(); + + addMapperPage.addMapper(); + + addMapperPage.addHardcodedAttrMapper("Hardcoded Attribute"); + + masthead.checkNotificationMessage(createMapperSuccessMsg); + }); + + it("should add SAML mapper of type SAML Attribute To Role", () => { + sidebarPage.goToIdentityProviders(); + + listingPage.goToItemDetails("saml"); + + addMapperPage.goToMappersTab(); + + addMapperPage.addMapper(); + + addMapperPage.addSAMLAttributeToRoleMapper("SAML Attribute To Role"); + + masthead.checkNotificationMessage(createMapperSuccessMsg); + }); + it("should edit Username Template Importer mapper", () => { sidebarPage.goToIdentityProviders(); diff --git a/cypress/support/pages/admin_console/manage/identity_providers/AddMapperPage.ts b/cypress/support/pages/admin_console/manage/identity_providers/AddMapperPage.ts index a054e131a6..f02e163165 100644 --- a/cypress/support/pages/admin_console/manage/identity_providers/AddMapperPage.ts +++ b/cypress/support/pages/admin_console/manage/identity_providers/AddMapperPage.ts @@ -7,6 +7,12 @@ export default class AddMapperPage { private mapperNameInput = "#kc-name"; private mapperRoleInput = "mapper-role-input"; + private attributeName = "attribute-name"; + private attributeFriendlyName = "attribute-friendly-name"; + private attributeValue = "attribute-value"; + private userAttribute = "user-attribute"; + private userAttributeName = "user-attribute-name"; + private userAttributeValue = "user-attribute-value"; private userSessionAttribute = "user-session-attribute"; private userSessionAttributeValue = "user-session-attribute-value"; private newMapperSaveButton = "new-mapper-save-button"; @@ -91,7 +97,7 @@ export default class AddMapperPage { return this; } - fillSAMLorOIDCMapper(name: string) { + addAdvancedAttrToRoleMapper(name: string) { cy.get(this.mapperNameInput).clear(); cy.get(this.mapperNameInput).clear().type(name); @@ -150,6 +156,135 @@ export default class AddMapperPage { return this; } + addHardcodedUserSessionAttrMapper(name: string) { + cy.get(this.mapperNameInput).clear(); + + cy.get(this.mapperNameInput).clear().type(name); + + cy.get(this.syncmodeSelectToggle).click(); + + cy.findByTestId("inherit").click(); + + cy.get(this.idpMapperSelectToggle).click(); + + cy.findByTestId(this.idpMapperSelect) + .contains("Hardcoded User Session Attribute") + .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" + ); + + this.saveNewMapper(); + + return this; + } + + addAttrImporterMapper(name: string) { + cy.get(this.mapperNameInput).clear(); + + cy.get(this.mapperNameInput).clear().type(name); + + cy.get(this.syncmodeSelectToggle).click(); + + cy.findByTestId("inherit").click(); + + cy.get(this.idpMapperSelectToggle).click(); + + cy.findByTestId(this.idpMapperSelect) + .contains("Attribute Importer") + .click(); + + cy.findByTestId(this.attributeName).clear(); + cy.findByTestId(this.attributeName).type("attribute name"); + + cy.findByTestId(this.attributeFriendlyName).clear(); + cy.findByTestId(this.attributeFriendlyName).type("friendly name"); + + cy.findByTestId(this.userAttributeName).clear(); + cy.findByTestId(this.userAttributeName).type("user attribute name"); + + this.saveNewMapper(); + + return this; + } + + addHardcodedRoleMapper(name: string) { + cy.get(this.mapperNameInput).clear(); + + cy.get(this.mapperNameInput).clear().type(name); + + cy.get(this.syncmodeSelectToggle).click(); + + cy.findByTestId("inherit").click(); + + cy.get(this.idpMapperSelectToggle).click(); + + cy.findByTestId(this.idpMapperSelect).contains("Hardcoded Role").click(); + + cy.findByTestId(this.mapperRoleInput).clear(); + cy.findByTestId(this.mapperRoleInput).type("admin"); + + this.saveNewMapper(); + + return this; + } + + addHardcodedAttrMapper(name: string) { + cy.get(this.mapperNameInput).clear(); + + cy.get(this.mapperNameInput).clear().type(name); + + cy.get(this.syncmodeSelectToggle).click(); + + cy.findByTestId("inherit").click(); + + cy.get(this.idpMapperSelectToggle).click(); + + cy.findByTestId(this.idpMapperSelect) + .contains("Hardcoded Attribute") + .click(); + + cy.findByTestId(this.userAttribute).clear(); + cy.findByTestId(this.userAttribute).type("user session attribute"); + + cy.findByTestId(this.userAttributeValue).clear(); + cy.findByTestId(this.userAttributeValue).type( + "user session attribute value" + ); + + this.saveNewMapper(); + + return this; + } + + addSAMLAttributeToRoleMapper(name: string) { + cy.get(this.mapperNameInput).clear(); + + cy.get(this.mapperNameInput).clear().type(name); + + cy.get(this.syncmodeSelectToggle).click(); + + cy.findByTestId("inherit").click(); + + cy.get(this.idpMapperSelectToggle).click(); + + cy.findByTestId(this.idpMapperSelect) + .contains("SAML Attribute To Role") + .click(); + + cy.findByTestId(this.mapperRoleInput).clear(); + cy.findByTestId(this.mapperRoleInput).type("admin"); + + this.saveNewMapper(); + + return this; + } + editUsernameTemplateImporterMapper() { cy.get(this.syncmodeSelectToggle).click(); diff --git a/src/identity-providers/add/AddMapper.tsx b/src/identity-providers/add/AddMapper.tsx index 46f6463df4..642ef12fbc 100644 --- a/src/identity-providers/add/AddMapper.tsx +++ b/src/identity-providers/add/AddMapper.tsx @@ -27,7 +27,6 @@ import { import { FormAccess } from "../../components/form-access/FormAccess"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import type { IdentityProviderAddMapperParams } from "../routes/AddMapper"; -import _ from "lodash"; import { AssociatedRolesModal } from "../../realm-roles/AssociatedRolesModal"; import type { RoleRepresentation } from "../../model/role-model"; import { useAlerts } from "../../components/alert/Alerts"; @@ -35,8 +34,9 @@ import type { IdentityProviderEditMapperParams } from "../routes/EditMapper"; import { convertToFormValues } from "../../util"; import { toIdentityProvider } from "../routes/IdentityProvider"; import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation"; +import { AddMapperForm } from "./AddMapperForm"; -type IdPMapperRepresentationWithAttributes = +export type IdPMapperRepresentationWithAttributes = IdentityProviderMapperRepresentation & { attributes: KeyValueType[]; }; @@ -166,19 +166,39 @@ export const AddMapper = () => { }); }; - const syncModes = ["inherit", "import", "legacy", "force"]; - const [syncModeOpen, setSyncModeOpen] = useState(false); const targetOptions = ["local", "brokerId", "brokerUsername"]; const [targetOptionsOpen, setTargetOptionsOpen] = useState(false); - const [mapperTypeOpen, setMapperTypeOpen] = useState(false); const [selectedRole, setSelectedRole] = useState([]); + const formValues = form.getValues(); + + const isAdvancedAttrToRole = + formValues.identityProviderMapper === "saml-advanced-role-idp-mapper"; + + const isAttributeImporter = + formValues.identityProviderMapper === "saml-user-attribute-idp-mapper"; + + const isHardcodedAttribute = + form.getValues().identityProviderMapper === + "hardcoded-attribute-idp-mapper"; + + const isHardcodedRole = + formValues.identityProviderMapper === "oidc-hardcoded-role-idp-mapper"; + + const isHardcodedUserSessionAttribute = + formValues.identityProviderMapper === + "hardcoded-user-session-attribute-idp-mapper"; + + const isSAMLAttributeToRole = + formValues.identityProviderMapper === "saml-role-idp-mapper"; + + const isUsernameTemplateImporter = + formValues.identityProviderMapper === "saml-username-idp-mapper"; + const toggleModal = () => { setRolesModalOpen(!rolesModalOpen); }; - const formValues = form.getValues(); - return ( { /> )} - - } - fieldId="kc-name" - isRequired - validated={ - errors.name ? ValidatedOptions.error : ValidatedOptions.default - } - helperTextInvalid={t("common:required")} - > - - - - } - fieldId="syncMode" - > - ( - - )} - /> - - - } - fieldId="identityProviderMapper" - > - ( - - )} - /> - + {isSAMLorOIDC ? ( <> - {formValues.identityProviderMapper === - "saml-advanced-role-idp-mapper" && ( + {isAdvancedAttrToRole && ( <> { )} - {formValues.identityProviderMapper === - "saml-username-idp-mapper" && ( + {isUsernameTemplateImporter && ( <> { )} - {[ - "saml-advanced-role-idp-mapper", - "oidc-hardcoded-role-idp-mapper", - "saml-role-idp-mapper", - ].includes(formValues.identityProviderMapper!) && ( + {(isAdvancedAttrToRole || + isHardcodedRole || + isSAMLAttributeToRole) && ( { )} - {[ - "hardcoded-user-session-attribute-idp-mapper", - "hardcoded-attribute-idp-mapper", - ].includes(formValues.identityProviderMapper!) && ( + {(isHardcodedAttribute || isHardcodedUserSessionAttribute) && ( <> { type="text" defaultValue={currentMapper?.config.attribute} id="kc-attribute" - data-testid="user-session-attribute" + data-testid={ + isHardcodedUserSessionAttribute + ? "user-session-attribute" + : "user-attribute" + } name="config.attribute" validated={ errors.name @@ -617,14 +497,24 @@ export const AddMapper = () => { /> } @@ -640,7 +530,11 @@ export const AddMapper = () => { ref={register()} type="text" defaultValue={currentMapper?.config["attribute-value"]} - data-testid="user-session-attribute-value" + data-testid={ + isHardcodedUserSessionAttribute + ? "user-session-attribute-value" + : "user-attribute-value" + } id="kc-user-session-attribute-value" name="config.attribute-value" validated={ @@ -652,6 +546,114 @@ export const AddMapper = () => { )} + {isAttributeImporter && ( + <> + + } + fieldId="kc-attribute-name" + validated={ + errors.name + ? ValidatedOptions.error + : ValidatedOptions.default + } + helperTextInvalid={t("common:required")} + > + + + + } + fieldId="kc-friendly-name" + validated={ + errors.name + ? ValidatedOptions.error + : ValidatedOptions.default + } + helperTextInvalid={t("common:required")} + > + + + + } + fieldId="kc-user-attribute-name" + validated={ + errors.name + ? ValidatedOptions.error + : ValidatedOptions.default + } + helperTextInvalid={t("common:required")} + > + + + + )} ) : ( <> diff --git a/src/identity-providers/add/AddMapperForm.tsx b/src/identity-providers/add/AddMapperForm.tsx new file mode 100644 index 0000000000..2e1e30f9ce --- /dev/null +++ b/src/identity-providers/add/AddMapperForm.tsx @@ -0,0 +1,198 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Controller, UseFormMethods } from "react-hook-form"; +import { + FormGroup, + Select, + SelectOption, + SelectVariant, + TextInput, + ValidatedOptions, +} from "@patternfly/react-core"; + +import { HelpItem } from "../../components/help-enabler/HelpItem"; +import _ from "lodash"; +import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation"; +import type { IdentityProviderAddMapperParams } from "../routes/AddMapper"; +import { useParams } from "react-router-dom"; +import type { IdPMapperRepresentationWithAttributes } from "./AddMapper"; + +type AddMapperFormProps = { + mapperTypes?: Record; + mapperType: string; + providerId: string; + id: string; + updateMapperType: (mapperType: string) => void; + form: UseFormMethods; + formValues: IdPMapperRepresentationWithAttributes; +}; + +export const AddMapperForm = ({ + mapperTypes, + mapperType, + form, + id, + updateMapperType, + formValues, +}: AddMapperFormProps) => { + const { t } = useTranslation("identity-providers"); + + const { control, register, errors } = form; + + const [mapperTypeOpen, setMapperTypeOpen] = useState(false); + + const syncModes = ["inherit", "import", "legacy", "force"]; + const [syncModeOpen, setSyncModeOpen] = useState(false); + const { providerId } = useParams(); + + return ( + <> + + } + fieldId="kc-name" + isRequired + validated={ + errors.name ? ValidatedOptions.error : ValidatedOptions.default + } + helperTextInvalid={t("common:required")} + > + + + + } + fieldId="syncMode" + > + ( + + )} + /> + + + } + fieldId="identityProviderMapper" + > + ( + + )} + /> + + + ); +}; diff --git a/src/identity-providers/messages.ts b/src/identity-providers/messages.ts index 8d55a17937..4854608f22 100644 --- a/src/identity-providers/messages.ts +++ b/src/identity-providers/messages.ts @@ -71,6 +71,9 @@ export default { subjectNameId: "Subject NameID", attributeName: "Attribute [Name]", attributeFriendlyName: "Attribute [Friendly Name]", + mapperAttributeName: "Attribute Name", + mapperUserAttributeName: "User Attribute Name", + mapperAttributeFriendlyName: "Friendly name", httpPostBindingResponse: "HTTP-POST binding response", httpPostBindingAuthnRequest: "HTTP-POST binding for AuthnRequest", httpPostBindingLogout: "HTTP-POST binding logout", @@ -167,6 +170,7 @@ export default { mapperSaveSuccess: "Mapper saved successfully.", mapperSaveError: "Error saving mapper: {{error}}", userAttribute: "User Attribute", + attributeValue: "Attribute Value", userAttributeValue: "User Attribute Value", userSessionAttribute: "User Session Attribute", userSessionAttributeValue: "User Session Attribute Value",