This commit continues the application wizard buildout. In this commit are the following changes:

- Added SCIM to the list of available providers
- Fixed ForwardProxy so that its mode is set correctly.  (This is a special case in the committer;
  I'm unhappy with that.)
- Fixed the commit messages so that:
  - icons are set correctly (Success, Danger, Working)
  - icons are colored correctly according to state
  - commit message includes a `data-commit-state` field so tests can find it!
- Merged the application wizard tests into a single test pass
- Isolated common parts of the application wizard tests to reduce unnecessary repetition.  All
  application tests are the same until you reach the provider section anyway.
- Fixed the unit tests so they're finding the right error messages and are enabled to display them
  correctly.
- Moved the test Form handlers into their own folder so they're not cluttering up the Pages folder.
This commit is contained in:
Ken Sternberg 2023-09-26 15:15:47 -07:00
parent 7e6a9fd3d2
commit f2ba927d34
18 changed files with 531 additions and 66 deletions

View file

@ -37,5 +37,5 @@ test-bad-login: node_modules admin-user ## Test that bad usernames and password
$(SPEC)/bad-logins.ts
test-application-wizard-ldap: node_modules admin-user
$(SPEC)/new-wizard-ldap-application.ts
test-application-wizard:
$(SPEC)/new-application-by-wizard.ts

View file

@ -4,8 +4,9 @@ import Page from "../pageobjects/page.js";
const CLICK_TIME_DELAY = 250;
export default class AdminPage extends Page {
public get pageHeader() {
return $(">>>ak-page-header h1");
return $('>>>ak-page-header slot[name="header"]');
}
async openApplicationsListPage() {

View file

@ -1,12 +1,17 @@
import { $ } from "@wdio/globals";
import AdminPage from "./admin.page.js";
import ApplicationForm from "./application-form.view.js";
import LdapForm from "./ldap-form.view.js";
import OauthForm from "./oauth-form.view.js";
import ApplicationForm from "./forms/application.form.js";
import LdapForm from "./forms/ldap.form.js";
import OauthForm from "./forms/oauth.form.js";
import TransparentProxyForm from "./forms/transparent-proxy.form.js";
import ForwardProxyForm from "./forms/forward-proxy.form.js";
import SamlForm from "./forms/saml.form.js";
import ScimForm from "./forms/scim.form.js";
import { $ } from "@wdio/globals";
/**
* sub page containing specific selectors and methods for a specific page
*/
class ApplicationWizardView extends AdminPage {
/**
* define selectors using getter methods
@ -14,6 +19,10 @@ class ApplicationWizardView extends AdminPage {
ldap = LdapForm;
oauth = OauthForm;
transparentProxy = TransparentProxyForm;
forwardProxy = ForwardProxyForm;
saml = SamlForm;
scim = ScimForm;
app = ApplicationForm;
get wizardTitle() {
@ -32,9 +41,33 @@ class ApplicationWizardView extends AdminPage {
return await this.providerList.$(`>>>input[value="${type}"]`);
}
get commitMessage() {
return $(">>>ak-application-wizard-commit-application h1.pf-c-title");
get successMessage() {
return $('>>>[data-commit-state="success"]')
}
}
type Pair = [string, string];
// Define a getter for each provider type in the radio button collection.
const providerValues: Pair[] = [
["oauth2provider", "oauth2Provider"],
["ldapprovider", "ldapProvider"],
["proxyprovider-proxy", "proxyProviderProxy"],
["proxyprovider-forwardsingle", "proxyProviderForwardsingle"],
["radiusprovider", "radiusProvider"],
["samlprovider", "samlProvider"],
["scimprovider", "scimProvider"],
];
providerValues.forEach(([value, name]: Pair) => {
Object.defineProperties(ApplicationWizardView.prototype, {
[name]: {
get: function () {
return this.providerList.$(`>>>input[value="${value}"]`);
},
},
});
});
export default new ApplicationWizardView();

View file

@ -1,5 +1,5 @@
import { $ } from "@wdio/globals";
import Page from "./page.js";
import Page from "../page.js";
export class ApplicationForm extends Page {
get name() {

View file

@ -0,0 +1,18 @@
import { $ } from "@wdio/globals";
import Page from "../page.js";
export class ForwardProxyForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`
);
}
get externalHost() {
return $('>>>input[name="externalHost"]');
}
}
export default new ForwardProxyForm();

View file

@ -1,4 +1,4 @@
import Page from "./page.js";
import Page from "../page.js";
export class LdapForm extends Page {
async setBindFlow(selector: string) {

View file

@ -1,4 +1,4 @@
import Page from "./page.js";
import Page from "../page.js";
export class OauthForm extends Page {
async setAuthorizationFlow(selector: string) {

View file

@ -0,0 +1,18 @@
import { $ } from "@wdio/globals";
import Page from "../page.js";
export class SamlForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`
);
}
get acsUrl() {
return $('>>>input[name="acsUrl"]');
}
}
export default new SamlForm();

View file

@ -0,0 +1,14 @@
import Page from "../page.js";
export class ScimForm extends Page {
get url() {
return $('>>>input[name="url"]');
}
get token() {
return $('>>>input[name="token"]');
}
}
export default new ScimForm();

View file

@ -0,0 +1,22 @@
import { $ } from "@wdio/globals";
import Page from "../page.js";
export class TransparentProxyForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`
);
}
get externalHost() {
return $('>>>input[name="externalHost"]');
}
get internalHost() {
return $('>>>input[name="internalHost"]');
}
}
export default new TransparentProxyForm();

View file

@ -38,4 +38,9 @@ export default class Page {
const target = searchBlock.$(buttonSelector);
return await target.click();
}
public async logout() {
await browser.url('http://localhost:9000/flows/-/default/invalidation/');
return await this.pause()
}
}

View file

@ -0,0 +1,148 @@
import ApplicationWizardView from "../pageobjects/application-wizard.page.js";
import ApplicationsListPage from "../pageobjects/applications-list.page.js";
import { randomId } from "../utils/index.js";
import { login } from "../utils/login.js";
import { expect } from "@wdio/globals";
async function reachTheProvider(title: string) {
const newPrefix = randomId();
await ApplicationsListPage.logout();
await login();
await ApplicationsListPage.open();
await expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
await ApplicationsListPage.startWizardButton.click();
await ApplicationWizardView.wizardTitle.waitForDisplayed();
await expect(await ApplicationWizardView.wizardTitle).toHaveText("New application");
await ApplicationWizardView.app.name.setValue(`${title} - ${newPrefix}`);
await ApplicationWizardView.nextButton.click();
return await ApplicationWizardView.pause();
}
async function getCommitMessage() {
await ApplicationWizardView.successMessage.waitForDisplayed();
return await ApplicationWizardView.successMessage;
}
describe("Configure Applications with the Application Wizard", () => {
it("Should configure a simple LDAP Application", async () => {
await reachTheProvider("New LDAP Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.ldapProvider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.ldap.setBindFlow("default-authentication-flow");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(
"Your application has been saved"
);
});
it("Should configure a simple Oauth2 Application", async () => {
await reachTheProvider("New Oauth2 Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.oauth2Provider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.oauth.setAuthorizationFlow(
"default-provider-authorization-explicit-consent"
);
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(
"Your application has been saved"
);
});
it("Should configure a simple SAML Application", async () => {
await reachTheProvider("New SAML Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.samlProvider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.saml.setAuthorizationFlow(
"default-provider-authorization-explicit-consent"
);
await ApplicationWizardView.saml.acsUrl.setValue("http://example.com:8000/");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(
"Your application has been saved"
);
});
it("Should configure a simple SCIM Application", async () => {
await reachTheProvider("New SCIM Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.scimProvider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.scim.url.setValue("http://example.com:8000/");
await ApplicationWizardView.scim.token.setValue("a-very-basic-token");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(
"Your application has been saved"
);
});
it("Should configure a simple Transparent Proxy Application", async () => {
await reachTheProvider("New Transparent Proxy Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.proxyProviderProxy.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.transparentProxy.setAuthorizationFlow(
"default-provider-authorization-explicit-consent"
);
await ApplicationWizardView.transparentProxy.externalHost.setValue("http://external.example.com");
await ApplicationWizardView.transparentProxy.internalHost.setValue("http://internal.example.com");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(
"Your application has been saved"
);
});
it("Should configure a simple Forward Proxy Application", async () => {
await reachTheProvider("New Forward Proxy Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.proxyProviderForwardsingle.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.forwardProxy.setAuthorizationFlow(
"default-provider-authorization-explicit-consent"
);
await ApplicationWizardView.forwardProxy.externalHost.setValue("http://external.example.com");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(
"Your application has been saved"
);
});
});

View file

@ -1,35 +0,0 @@
import { login } from "../utils/login.js";
import { randomId } from "../utils/index.js";
import ApplicationsListPage from "../pageobjects/applications-list.page.js";
import ApplicationWizardView from "../pageobjects/application-wizard.page.js";
import { expect } from "@wdio/globals";
describe("Configure LDAP Application with Wizard", () => {
it("Should configure a simple LDAP Application", async () => {
const newPrefix = randomId();
await login();
await ApplicationsListPage.open();
expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
await ApplicationsListPage.startWizardButton.click();
await ApplicationWizardView.wizardTitle.waitForDisplayed();
expect(await ApplicationWizardView.wizardTitle).toHaveText("New Application");
await ApplicationWizardView.app.name.setValue(`New LDAP Application - ${newPrefix}`);
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause()
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.providerList.$('>>>input[value=ldapprovider]').click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause()
await ApplicationWizardView.ldap.setBindFlow('default-authentication-flow');
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause()
await ApplicationWizardView.commitMessage.waitForDisplayed();
expect(await ApplicationWizardView.commitMessage).toHaveText("Your application has been saved");
})
});

View file

@ -15,7 +15,7 @@
"build-proxy": "run-s build-locales rollup:build-proxy",
"watch": "run-s build-locales rollup:watch",
"lint": "eslint . --max-warnings 0 --fix",
"lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain | grep '^[M?][M?]' | cut -c8- | grep -E '\\.(ts|js|tsx|jsx)$') ",
"lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[M?][M?]' | cut -c8- | grep -E '\\.(ts|js|tsx|jsx)$') ",
"lint:spelling": "codespell -D - -D ../.github/codespell-dictionary.txt -I ../.github/codespell-words.txt -S './src/locales/**' ./src -s",
"lit-analyse": "lit-analyzer src",
"precommit": "run-s tsc lit-analyse lint:precommit lint:spelling prettier",

View file

@ -9,6 +9,7 @@ import type {
OAuth2ProviderRequest,
ProxyProviderRequest,
SAMLProviderRequest,
SCIMProviderRequest,
} from "@goauthentik/api";
import { OneOfProvider } from "../types";
@ -57,18 +58,18 @@ const _providerModelsTable: ProviderType[] = [
],
[
"samlprovider-manual",
"samlprovider",
msg("SAML Manual configuration"),
msg("Configure SAML provider manually"),
() => html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
ProviderModelEnum.SamlSamlprovider
],
[
"samlprovider-import",
msg("SAML Import Configuration"),
msg("Create a SAML provider by importing its metadata"),
() => html`<ak-application-wizard-authentication-by-saml-import></ak-application-wizard-authentication-by-saml-import>`,
ProviderModelEnum.SamlSamlprovider
"scimprovider",
msg("SCIM Manual configuration"),
msg("Configure SCIM provider manually"),
() => html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
ProviderModelEnum.ScimScimprovider
],
];
@ -101,6 +102,13 @@ const converters = new Map<ProviderModelEnumType, ModelConverter>([
...(provider as SAMLProviderRequest),
}),
],
[
ProviderModelEnum.ScimScimprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ScimScimprovider,
...(provider as SCIMProviderRequest),
}),
],
]);
// Contract enforcement

View file

@ -10,6 +10,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement, state } from "@lit/reactive-element/decorators.js";
import { TemplateResult, css, html, nothing } from "lit";
import { classMap } from "lit/directives/class-map.js";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";
@ -19,6 +20,7 @@ import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
import {
ApplicationRequest,
CoreApi,
ProxyMode,
TransactionApplicationRequest,
TransactionApplicationResponse,
} from "@goauthentik/api";
@ -37,15 +39,34 @@ function cleanApplication(app: Partial<ApplicationRequest>): ApplicationRequest
type ProviderModelType = Exclude<ModelRequest["providerModel"], "11184809">;
type State = { state: "idle" | "running" | "error" | "done"; label: string | TemplateResult };
type State = {
state: "idle" | "running" | "error" | "success";
label: string | TemplateResult;
icon: string[];
};
const idleState: State = { state: "idle", label: "" };
const runningState: State = { state: "running", label: msg("Saving Application...") };
const idleState: State = {
state: "idle",
label: "",
icon: ["fa-cogs", "pf-m-pending"],
};
const runningState: State = {
state: "running",
label: msg("Saving Application..."),
icon: ["fa-cogs", "pf-m-info"],
};
const errorState: State = {
state: "error",
label: msg("There was an error in saving your application:"),
icon: ["fa-times-circle", "pf-m-danger"],
};
const successState: State = {
state: "success",
label: msg("Your application has been saved"),
icon: ["fa-check-circle", "pf-m-success"],
};
const doneState: State = { state: "done", label: msg("Your application has been saved") };
function extract(o: Record<string, any>): string[] {
function inner(o: Record<string, any>): string[] {
@ -92,10 +113,10 @@ export class ApplicationWizardCommitApplication extends BasePanel {
if (this.commitState === idleState) {
this.response = undefined;
this.commitState = runningState;
const provider = providerModelsList.find(
const providerModel = providerModelsList.find(
({ formName }) => formName === this.wizard.providerModel,
);
if (!provider) {
if (!providerModel) {
throw new Error(
`Could not determine provider model from user request: ${JSON.stringify(
this.wizard,
@ -105,10 +126,26 @@ export class ApplicationWizardCommitApplication extends BasePanel {
);
}
const provider = (() => {
if (this.wizard.providerModel === "proxyprovider-forwardsingle") {
return {
...providerModel.converter(this.wizard.provider),
mode: ProxyMode.ForwardSingle,
};
}
if (this.wizard.providerModel === "proxyprovider-proxy") {
return {
...providerModel.converter(this.wizard.provider),
mode: ProxyMode.Proxy,
};
}
return providerModel.converter(this.wizard.provider);
})();
const request: TransactionApplicationRequest = {
providerModel: provider.modelName as ProviderModelType,
providerModel: providerModel.modelName as ProviderModelType,
app: cleanApplication(this.wizard.app),
provider: provider.converter(this.wizard.provider),
provider,
};
this.send(request);
@ -129,7 +166,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
this.response = response;
this.dispatchCustomEvent(EVENT_REFRESH);
this.dispatchWizardUpdate({ status: "submitted" });
this.commitState = doneState;
this.commitState = successState;
})
.catch((resolution: any) => {
resolution.response.json().then((body: Record<string, any>) => {
@ -140,16 +177,23 @@ export class ApplicationWizardCommitApplication extends BasePanel {
}
render(): TemplateResult {
const icon = classMap(this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {}));
return html`
<div>
<div class="pf-l-bullseye">
<div class="pf-c-empty-state pf-m-lg">
<div class="pf-c-empty-state__content">
<i
class="fas fa- fa-cogs pf-c-empty-state__icon"
class="fas fa- ${icon} pf-c-empty-state__icon"
aria-hidden="true"
></i>
<h1 class="pf-c-title pf-m-lg">${this.commitState.label}</h1>
<h1
data-commit-state=${this.commitState.state}
class="pf-c-title pf-m-lg"
>
${this.commitState.label}
</h1>
${this.errors.length > 0
? html`<ul>
${this.errors.map(

View file

@ -7,7 +7,7 @@ import "./oauth/ak-application-wizard-authentication-by-oauth";
import "./proxy/ak-application-wizard-authentication-for-reverse-proxy";
import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy";
import "./saml/ak-application-wizard-authentication-by-saml-configuration";
import "./saml/ak-application-wizard-authentication-by-saml-import";
import "./scim/ak-application-wizard-authentication-by-scim";
// prettier-ignore

View file

@ -0,0 +1,189 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement, state } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
CoreApi,
CoreGroupsListRequest,
type Group,
PaginatedSCIMMappingList,
PropertymappingsApi,
type SCIMProvider,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-scim")
export class ApplicationWizardAuthenticationBySCIM extends BaseProviderPanel {
@state()
propertyMappings?: PaginatedSCIMMappingList;
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({
ordering: "scope_name",
})
.then((propertyMappings: PaginatedSCIMMappingList) => {
this.propertyMappings = propertyMappings;
});
}
render() {
const provider = this.wizard.provider as SCIMProvider | undefined;
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
label=${msg("Name")}
value=${ifDefined(provider?.name)}
required
></ak-text-input>
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="url"
label=${msg("URL")}
value="${first(provider?.url, "")}"
required
help=${msg("SCIM base url, usually ends in /v2.")}
>
</ak-text-input>
<ak-text-input
name="token"
label=${msg("Token")}
value="${first(provider?.token, "")}"
required
help=${msg(
"Token to authenticate with. Currently only bearer authentication is supported.",
)}
>
</ak-text-input>
</div>
</ak-form-group>
<ak-form-group expanded>
<span slot="header">${msg("User filtering")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="excludeUsersServiceAccount"
?checked=${first(provider?.excludeUsersServiceAccount, true)}
label=${msg("Exclude service accounts")}
></ak-switch-input>
<ak-form-element-horizontal label=${msg("Group")} name="filterGroup">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(
args,
);
return groups.results;
}}
.renderElement=${(group: Group): string => {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group ? group.pk : undefined;
}}
.selected=${(group: Group): boolean => {
return group.pk === provider?.filterGroup;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${msg("Only sync users within the selected group.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group ?expanded=${true}>
<span slot="header"> ${msg("Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
?required=${true}
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!provider?.propertyMappings) {
selected =
mapping.managed === "goauthentik.io/providers/scim/user" ||
false;
} else {
selected = Array.from(provider?.propertyMappings).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Property mappings used to user mapping.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group Property Mappings")}
?required=${true}
name="propertyMappingsGroup"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!provider?.propertyMappingsGroup) {
selected =
mapping.managed === "goauthentik.io/providers/scim/group";
} else {
selected = Array.from(provider?.propertyMappingsGroup).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Property mappings used to group creation.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardAuthenticationBySCIM;