Identity Providers(Mappers): Add create/edit functionality for mappers of type Username Template Importer (#1248)

This commit is contained in:
Jenny 2021-09-30 05:26:36 -04:00 committed by GitHub
parent bd8fc558d5
commit 4d5c8f3f18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 387 additions and 87 deletions

View file

@ -133,7 +133,7 @@ describe("Identity provider test", () => {
addMapperPage.goToMappersTab(); addMapperPage.goToMappersTab();
addMapperPage.clickAdd(); addMapperPage.emptyStateAddMapper();
addMapperPage.fillSocialMapper("facebook mapper"); addMapperPage.fillSocialMapper("facebook mapper");
@ -149,13 +149,43 @@ describe("Identity provider test", () => {
addMapperPage.goToMappersTab(); addMapperPage.goToMappersTab();
addMapperPage.clickAdd(); addMapperPage.emptyStateAddMapper();
addMapperPage.fillSAMLorOIDCMapper("SAML mapper"); addMapperPage.fillSAMLorOIDCMapper("SAML mapper");
masthead.checkNotificationMessage(createMapperSuccessMsg); masthead.checkNotificationMessage(createMapperSuccessMsg);
}); });
it("should add SAML mapper of type Username Template Importer", () => {
sidebarPage.goToIdentityProviders();
listingPage.goToItemDetails("saml");
addMapperPage.goToMappersTab();
addMapperPage.addMapper();
addMapperPage.addUsernameTemplateImporterMapper(
"SAML Username Template Importer Mapper"
);
masthead.checkNotificationMessage(createMapperSuccessMsg);
});
it("should edit Username Template Importer mapper", () => {
sidebarPage.goToIdentityProviders();
listingPage.goToItemDetails("saml");
addMapperPage.goToMappersTab();
listingPage.goToItemDetails("SAML Username Template Importer Mapper");
addMapperPage.editUsernameTemplateImporterMapper();
masthead.checkNotificationMessage(saveMapperSuccessMsg);
});
it("should edit facebook mapper", () => { it("should edit facebook mapper", () => {
sidebarPage.goToIdentityProviders(); sidebarPage.goToIdentityProviders();

View file

@ -3,6 +3,7 @@ export default class AddMapperPage {
private noMappersAddMapperButton = "no-mappers-empty-action"; private noMappersAddMapperButton = "no-mappers-empty-action";
private idpMapperSelectToggle = "#identityProviderMapper"; private idpMapperSelectToggle = "#identityProviderMapper";
private idpMapperSelect = "idp-mapper-select"; private idpMapperSelect = "idp-mapper-select";
private addMapperButton = "#add-mapper-button";
private mapperNameInput = "#kc-name"; private mapperNameInput = "#kc-name";
private mapperRoleInput = "mapper-role-input"; private mapperRoleInput = "mapper-role-input";
@ -13,6 +14,9 @@ export default class AddMapperPage {
private syncmodeSelectToggle = "#syncMode"; private syncmodeSelectToggle = "#syncMode";
private attributesKeyInput = 'input[name="config.attributes[0].key"]'; private attributesKeyInput = 'input[name="config.attributes[0].key"]';
private attributesValueInput = 'input[name="config.attributes[0].value"]'; private attributesValueInput = 'input[name="config.attributes[0].value"]';
private template = "template";
private target = "#target";
private targetDropdown = "#target-dropdown";
private selectRoleButton = "select-role-button"; private selectRoleButton = "select-role-button";
private radio = "[type=radio]"; private radio = "[type=radio]";
private addAssociatedRolesModalButton = "add-associated-roles-button"; private addAssociatedRolesModalButton = "add-associated-roles-button";
@ -22,11 +26,16 @@ export default class AddMapperPage {
return this; return this;
} }
clickAdd() { emptyStateAddMapper() {
cy.findByTestId(this.noMappersAddMapperButton).click(); cy.findByTestId(this.noMappersAddMapperButton).click();
return this; return this;
} }
addMapper() {
cy.get(this.addMapperButton).click();
return this;
}
clickCreateDropdown() { clickCreateDropdown() {
cy.contains("Add provider").click(); cy.contains("Add provider").click();
return this; return this;
@ -94,7 +103,7 @@ export default class AddMapperPage {
cy.get(this.idpMapperSelectToggle).click(); cy.get(this.idpMapperSelectToggle).click();
cy.findByTestId(this.idpMapperSelect) cy.findByTestId(this.idpMapperSelect)
.contains("Hardcoded User Session Attribute") .contains("Advanced Attribute To Role")
.click(); .click();
cy.get(this.attributesKeyInput).clear(); cy.get(this.attributesKeyInput).clear();
@ -114,6 +123,49 @@ export default class AddMapperPage {
return this; return this;
} }
addUsernameTemplateImporterMapper(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("Username Template Importer")
.click();
cy.findByTestId(this.template).clear();
cy.findByTestId(this.template).type("Template");
cy.get(this.target).click();
cy.get(this.targetDropdown).contains("LOCAL").click();
this.saveNewMapper();
return this;
}
editUsernameTemplateImporterMapper() {
cy.get(this.syncmodeSelectToggle).click();
cy.findByTestId("legacy").click();
cy.findByTestId(this.template).type("_edited");
cy.get(this.target).click();
cy.get(this.targetDropdown).contains("BROKER_ID").click();
this.saveNewMapper();
return this;
}
editSocialMapper() { editSocialMapper() {
cy.get(this.syncmodeSelectToggle).click(); cy.get(this.syncmodeSelectToggle).click();

View file

@ -147,6 +147,10 @@ export const AddMapper = () => {
); );
} }
if (mapper.config?.attribute) {
form.setValue("config.attributes", value.attribute);
}
if (mapper.config?.attributes) { if (mapper.config?.attributes) {
form.setValue("config.attributes", JSON.parse(value.attributes)); form.setValue("config.attributes", JSON.parse(value.attributes));
} }
@ -164,6 +168,8 @@ export const AddMapper = () => {
const syncModes = ["inherit", "import", "legacy", "force"]; const syncModes = ["inherit", "import", "legacy", "force"];
const [syncModeOpen, setSyncModeOpen] = useState(false); const [syncModeOpen, setSyncModeOpen] = useState(false);
const targetOptions = ["local", "brokerId", "brokerUsername"];
const [targetOptionsOpen, setTargetOptionsOpen] = useState(false);
const [mapperTypeOpen, setMapperTypeOpen] = useState(false); const [mapperTypeOpen, setMapperTypeOpen] = useState(false);
const [selectedRole, setSelectedRole] = useState<RoleRepresentation[]>([]); const [selectedRole, setSelectedRole] = useState<RoleRepresentation[]>([]);
@ -171,6 +177,8 @@ export const AddMapper = () => {
setRolesModalOpen(!rolesModalOpen); setRolesModalOpen(!rolesModalOpen);
}; };
const formValues = form.getValues();
return ( return (
<PageSection variant="light"> <PageSection variant="light">
<ViewHeader <ViewHeader
@ -371,93 +379,279 @@ export const AddMapper = () => {
</FormGroup> </FormGroup>
{isSAMLorOIDC ? ( {isSAMLorOIDC ? (
<> <>
{" "} {formValues.identityProviderMapper ===
<FormGroup "saml-advanced-role-idp-mapper" && (
label={t("common:attributes")} <>
labelIcon={ <FormGroup
<HelpItem label={t("common:attributes")}
helpText="identity-providers-help:attributes" labelIcon={
forLabel={t("attributes")} <HelpItem
forID={t(`common:helpLabel`, { label: t("attributes") })} helpText="identity-providers-help:attributes"
/> forLabel={t("attributes")}
} forID={t(`common:helpLabel`, { label: t("attributes") })}
fieldId="kc-gui-order" />
> }
<AttributesForm fieldId="kc-gui-order"
form={form} >
inConfig <AttributesForm
array={{ fields, append, remove }} form={form}
/> inConfig
</FormGroup> array={{ fields, append, remove }}
<FormGroup
label={t("regexAttributeValues")}
labelIcon={
<HelpItem
helpText="identity-providers-help:regexAttributeValues"
forLabel={t("regexAttributeValues")}
forID={t(`common:helpLabel`, {
label: t("regexAttributeValues"),
})}
/>
}
fieldId="regexAttributeValues"
>
<Controller
name="config.are-attribute-values-regex"
control={control}
defaultValue="false"
render={({ onChange, value }) => (
<Switch
id="regexAttributeValues"
data-testid="regex-attribute-values-switch"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value === "true"}
onChange={(value) => onChange("" + value)}
/> />
)} </FormGroup>
/> <FormGroup
</FormGroup> label={t("regexAttributeValues")}
<FormGroup labelIcon={
label={t("common:role")} <HelpItem
labelIcon={ helpText="identity-providers-help:regexAttributeValues"
<HelpItem forLabel={t("regexAttributeValues")}
id="name-help-icon" forID={t(`common:helpLabel`, {
helpText="identity-providers-help:role" label: t("regexAttributeValues"),
forLabel={t("identity-providers-help:role")} })}
forID={t(`identity-providers:helpLabel`, { />
label: t("role"), }
})} fieldId="regexAttributeValues"
/> >
} <Controller
fieldId="kc-role" name="config.are-attribute-values-regex"
validated={ control={control}
errors.config?.role defaultValue="false"
? ValidatedOptions.error render={({ onChange, value }) => (
: ValidatedOptions.default <Switch
} id="regexAttributeValues"
helperTextInvalid={t("common:required")} data-testid="regex-attribute-values-switch"
> label={t("common:on")}
<TextInput labelOff={t("common:off")}
ref={register()} isChecked={value === "true"}
type="text" onChange={(value) => onChange("" + value)}
id="kc-role" />
data-testid="mapper-role-input" )}
name="config.role" />
value={selectedRole[0]?.name} </FormGroup>
</>
)}
{formValues.identityProviderMapper ===
"saml-username-idp-mapper" && (
<>
<FormGroup
label={t("template")}
labelIcon={
<HelpItem
id="target-help-icon"
helpText="identity-providers-help:template"
forLabel={t("template")}
forID={t(`common:helpLabel`, {
label: t("template"),
})}
/>
}
fieldId="kc-user-session-attribute"
validated={
errors.name
? ValidatedOptions.error
: ValidatedOptions.default
}
helperTextInvalid={t("common:required")}
>
<TextInput
ref={register()}
type="text"
id="kc-template"
data-testid="template"
name="config.template"
defaultValue={currentMapper?.config.template}
validated={
errors.name
? ValidatedOptions.error
: ValidatedOptions.default
}
/>
</FormGroup>
<FormGroup
label={t("target")}
labelIcon={
<HelpItem
id="user-session-attribute-help-icon"
helpText="identity-providers-help:target"
forLabel={t("target")}
forID={t(`common:helpLabel`, {
label: t("target"),
})}
/>
}
fieldId="kc-target"
validated={
errors.name
? ValidatedOptions.error
: ValidatedOptions.default
}
helperTextInvalid={t("common:required")}
>
<Controller
name="config.target"
defaultValue={currentMapper?.config.target}
control={control}
render={({ onChange, value }) => (
<Select
toggleId="target"
datatest-id="target-select"
id="target-dropdown"
placeholderText={t("realm-settings:placeholderText")}
direction="down"
onToggle={() =>
setTargetOptionsOpen(!targetOptionsOpen)
}
onSelect={(_, value) => {
onChange(t(`targetOptions.${value}`));
setTargetOptionsOpen(false);
}}
selections={value}
variant={SelectVariant.single}
aria-label={t("target")}
isOpen={targetOptionsOpen}
>
{targetOptions.map((option) => (
<SelectOption
selected={option === value}
key={option}
data-testid={option}
value={option}
>
{t(`targetOptions.${option}`)}
</SelectOption>
))}
</Select>
)}
/>
</FormGroup>
</>
)}
{[
"saml-advanced-role-idp-mapper",
"oidc-hardcoded-role-idp-mapper",
"saml-role-idp-mapper",
].includes(formValues.identityProviderMapper!) && (
<FormGroup
label={t("common:role")}
labelIcon={
<HelpItem
id="name-help-icon"
helpText="identity-providers-help:role"
forLabel={t("identity-providers-help:role")}
forID={t(`identity-providers:helpLabel`, {
label: t("role"),
})}
/>
}
fieldId="kc-role"
validated={ validated={
errors.config?.role errors.config?.role
? ValidatedOptions.error ? ValidatedOptions.error
: ValidatedOptions.default : ValidatedOptions.default
} }
/> helperTextInvalid={t("common:required")}
<Button
data-testid="select-role-button"
onClick={() => toggleModal()}
> >
{t("selectRole")} <TextInput
</Button> ref={register()}
</FormGroup>{" "} type="text"
id="kc-role"
data-testid="mapper-role-input"
name="config.role"
value={selectedRole[0]?.name}
validated={
errors.config?.role
? ValidatedOptions.error
: ValidatedOptions.default
}
/>
<Button
data-testid="select-role-button"
onClick={() => toggleModal()}
>
{t("selectRole")}
</Button>
</FormGroup>
)}
{[
"hardcoded-user-session-attribute-idp-mapper",
"hardcoded-attribute-idp-mapper",
].includes(formValues.identityProviderMapper!) && (
<>
<FormGroup
label={
formValues.identityProviderMapper ===
"hardcoded-user-session-attribute-idp-mapper"
? t("userSessionAttribute")
: t("userAttribute")
}
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"
validated={
errors.name
? ValidatedOptions.error
: ValidatedOptions.default
}
helperTextInvalid={t("common:required")}
>
<TextInput
ref={register()}
type="text"
defaultValue={currentMapper?.config.attribute}
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:userAttributeValue"
forLabel={t("userSessionAttributeValue")}
forID={t(`common:helpLabel`, {
label: t("userSessionAttributeValue"),
})}
/>
}
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["attribute-value"]}
data-testid="user-session-attribute-value"
id="kc-user-session-attribute-value"
name="config.attribute-value"
validated={
errors.name
? ValidatedOptions.error
: ValidatedOptions.default
}
/>
</FormGroup>
</>
)}
</> </>
) : ( ) : (
<> <>
@ -527,6 +721,7 @@ export const AddMapper = () => {
</FormGroup> </FormGroup>
</> </>
)} )}
<ActionGroup> <ActionGroup>
<Button <Button
data-testid="new-mapper-save-button" data-testid="new-mapper-save-button"

View file

@ -328,7 +328,7 @@ export const DetailSettings = () => {
providerId: provider.providerId!, providerId: provider.providerId!,
tab: "mappers", tab: "mappers",
})} })}
datatest-id="add-mapper-button" id="add-mapper-button"
> >
{t("addMapper")} {t("addMapper")}
</Link> </Link>

View file

@ -128,12 +128,26 @@ export default {
"When user is imported from provider, hardcode a value to a specific user attribute.", "When user is imported from provider, hardcode a value to a specific user attribute.",
samlAttributeToRole: samlAttributeToRole:
"If an attribute exists, grant the user the specified realm or client role.", "If an attribute exists, grant the user the specified realm or client role.",
template:
"Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${CLAIM.sub}'. ALIAS is the provider alias. CLAIM.<NAME> references an ID or Access token claim. The substitution can be converted to upper or lower case by appending |uppercase or |lowercase to the substituted value, e.g. '${CLAIM.sub | lowercase}",
target:
"Destination field for the mapper. LOCAL (default) means that the changes are applied to the username stored in local database upon user import. BROKER_ID and BROKER_USERNAME means that the changes are stored into the ID or username used for federation user lookup, respectively.",
userSessionAttribute: "Name of user session attribute you want to hardcode",
userAttribute: "Name of user attribute you want to hardcode",
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.",
friendlyName:
"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.",
attributeValue:
"Value the attribute must have. If the attribute is a list, then the value must be contained in the list.",
attributes: attributes:
"Name and (regex) value of the attributes to search for in token. The configured name of an attribute is searched in SAML attribute name and attribute friendly name fields. Every given attribute description must be met to set the role. If the attribute is an array, then the value must be contained in the array. If an attribute can be found several times, then one match is sufficient.", "Name and (regex) value of the attributes to search for in token. The configured name of an attribute is searched in SAML attribute name and attribute friendly name fields. Every given attribute description must be met to set the role. If the attribute is an array, then the value must be contained in the array. If an attribute can be found several times, then one match is sufficient.",
regexAttributeValues: regexAttributeValues:
"If enabled attribute values are interpreted as regular expressions.", "If enabled attribute values are interpreted as regular expressions.",
role: "Role to grant to user if all attributes are present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference a client role the syntax is clientname.clientrole, i.e. myclient.myrole", role: "Role to grant to user if all attributes are present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference a client role the syntax is clientname.clientrole, i.e. myclient.myrole",
userSessionAttribute: "Name of user session attribute you want to hardcode",
userSessionAttributeValue: "Value you want to hardcode",
}, },
}; };

View file

@ -166,7 +166,16 @@ export default {
mapperCreateError: "Error creating mapper.", mapperCreateError: "Error creating mapper.",
mapperSaveSuccess: "Mapper saved successfully.", mapperSaveSuccess: "Mapper saved successfully.",
mapperSaveError: "Error saving mapper: {{error}}", mapperSaveError: "Error saving mapper: {{error}}",
userAttribute: "User Attribute",
userAttributeValue: "User Attribute Value",
userSessionAttribute: "User Session Attribute", userSessionAttribute: "User Session Attribute",
userSessionAttributeValue: "User Session Attribute Value", userSessionAttributeValue: "User Session Attribute Value",
template: "Template",
target: "Target",
targetOptions: {
local: "LOCAL",
brokerId: "BROKER_ID",
brokerUsername: "BROKER_USERNAME",
},
}, },
}; };