From f2ba927d34739f959552f1241c8b8ff2c3c12440 Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Tue, 26 Sep 2023 15:15:47 -0700 Subject: [PATCH] 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. --- tests/wdio/Makefile | 4 +- tests/wdio/test/pageobjects/admin.page.ts | 3 +- .../pageobjects/application-wizard.page.ts | 45 ++++- .../application.form.ts} | 2 +- .../pageobjects/forms/forward-proxy.form.ts | 18 ++ .../{ldap-form.view.ts => forms/ldap.form.ts} | 2 +- .../oauth.form.ts} | 2 +- .../wdio/test/pageobjects/forms/saml.form.ts | 18 ++ .../wdio/test/pageobjects/forms/scim.form.ts | 14 ++ .../forms/transparent-proxy.form.ts | 22 ++ tests/wdio/test/pageobjects/page.ts | 5 + .../test/specs/new-application-by-wizard.ts | 148 ++++++++++++++ .../test/specs/new-wizard-ldap-application.ts | 35 ---- web/package.json | 2 +- ...rd-authentication-method-choice.choices.ts | 20 +- ...k-application-wizard-commit-application.ts | 66 +++++- ...pplication-wizard-authentication-method.ts | 2 +- ...plication-wizard-authentication-by-scim.ts | 189 ++++++++++++++++++ 18 files changed, 531 insertions(+), 66 deletions(-) rename tests/wdio/test/pageobjects/{application-form.view.ts => forms/application.form.ts} (87%) create mode 100644 tests/wdio/test/pageobjects/forms/forward-proxy.form.ts rename tests/wdio/test/pageobjects/{ldap-form.view.ts => forms/ldap.form.ts} (91%) rename tests/wdio/test/pageobjects/{oauth-form.view.ts => forms/oauth.form.ts} (91%) create mode 100644 tests/wdio/test/pageobjects/forms/saml.form.ts create mode 100644 tests/wdio/test/pageobjects/forms/scim.form.ts create mode 100644 tests/wdio/test/pageobjects/forms/transparent-proxy.form.ts create mode 100644 tests/wdio/test/specs/new-application-by-wizard.ts delete mode 100644 tests/wdio/test/specs/new-wizard-ldap-application.ts create mode 100644 web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts diff --git a/tests/wdio/Makefile b/tests/wdio/Makefile index 172269f8b..51eb3700b 100644 --- a/tests/wdio/Makefile +++ b/tests/wdio/Makefile @@ -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 diff --git a/tests/wdio/test/pageobjects/admin.page.ts b/tests/wdio/test/pageobjects/admin.page.ts index 9335cd670..d0441ea13 100644 --- a/tests/wdio/test/pageobjects/admin.page.ts +++ b/tests/wdio/test/pageobjects/admin.page.ts @@ -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() { diff --git a/tests/wdio/test/pageobjects/application-wizard.page.ts b/tests/wdio/test/pageobjects/application-wizard.page.ts index 552753737..397d6a790 100644 --- a/tests/wdio/test/pageobjects/application-wizard.page.ts +++ b/tests/wdio/test/pageobjects/application-wizard.page.ts @@ -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(); diff --git a/tests/wdio/test/pageobjects/application-form.view.ts b/tests/wdio/test/pageobjects/forms/application.form.ts similarity index 87% rename from tests/wdio/test/pageobjects/application-form.view.ts rename to tests/wdio/test/pageobjects/forms/application.form.ts index 695eac66f..37d16eb7b 100644 --- a/tests/wdio/test/pageobjects/application-form.view.ts +++ b/tests/wdio/test/pageobjects/forms/application.form.ts @@ -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() { diff --git a/tests/wdio/test/pageobjects/forms/forward-proxy.form.ts b/tests/wdio/test/pageobjects/forms/forward-proxy.form.ts new file mode 100644 index 000000000..05769d00f --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/forward-proxy.form.ts @@ -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(); diff --git a/tests/wdio/test/pageobjects/ldap-form.view.ts b/tests/wdio/test/pageobjects/forms/ldap.form.ts similarity index 91% rename from tests/wdio/test/pageobjects/ldap-form.view.ts rename to tests/wdio/test/pageobjects/forms/ldap.form.ts index 33f8f9d6a..aa0b43ccd 100644 --- a/tests/wdio/test/pageobjects/ldap-form.view.ts +++ b/tests/wdio/test/pageobjects/forms/ldap.form.ts @@ -1,4 +1,4 @@ -import Page from "./page.js"; +import Page from "../page.js"; export class LdapForm extends Page { async setBindFlow(selector: string) { diff --git a/tests/wdio/test/pageobjects/oauth-form.view.ts b/tests/wdio/test/pageobjects/forms/oauth.form.ts similarity index 91% rename from tests/wdio/test/pageobjects/oauth-form.view.ts rename to tests/wdio/test/pageobjects/forms/oauth.form.ts index fb4c6acbb..d69dd901b 100644 --- a/tests/wdio/test/pageobjects/oauth-form.view.ts +++ b/tests/wdio/test/pageobjects/forms/oauth.form.ts @@ -1,4 +1,4 @@ -import Page from "./page.js"; +import Page from "../page.js"; export class OauthForm extends Page { async setAuthorizationFlow(selector: string) { diff --git a/tests/wdio/test/pageobjects/forms/saml.form.ts b/tests/wdio/test/pageobjects/forms/saml.form.ts new file mode 100644 index 000000000..813c19d8c --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/saml.form.ts @@ -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(); diff --git a/tests/wdio/test/pageobjects/forms/scim.form.ts b/tests/wdio/test/pageobjects/forms/scim.form.ts new file mode 100644 index 000000000..7653f6961 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/scim.form.ts @@ -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(); diff --git a/tests/wdio/test/pageobjects/forms/transparent-proxy.form.ts b/tests/wdio/test/pageobjects/forms/transparent-proxy.form.ts new file mode 100644 index 000000000..a25ec3c44 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/transparent-proxy.form.ts @@ -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(); diff --git a/tests/wdio/test/pageobjects/page.ts b/tests/wdio/test/pageobjects/page.ts index ef915c6c5..cc9a6b803 100644 --- a/tests/wdio/test/pageobjects/page.ts +++ b/tests/wdio/test/pageobjects/page.ts @@ -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() + } } diff --git a/tests/wdio/test/specs/new-application-by-wizard.ts b/tests/wdio/test/specs/new-application-by-wizard.ts new file mode 100644 index 000000000..4f69dff2f --- /dev/null +++ b/tests/wdio/test/specs/new-application-by-wizard.ts @@ -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" + ); + }); +}); diff --git a/tests/wdio/test/specs/new-wizard-ldap-application.ts b/tests/wdio/test/specs/new-wizard-ldap-application.ts deleted file mode 100644 index 8fc821dd6..000000000 --- a/tests/wdio/test/specs/new-wizard-ldap-application.ts +++ /dev/null @@ -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"); - }) -}); diff --git a/web/package.json b/web/package.json index 97c20302c..9ea7e0061 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts index 39668d6b5..5dceed8ac 100644 --- a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts +++ b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts @@ -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``, ProviderModelEnum.SamlSamlprovider ], [ - "samlprovider-import", - msg("SAML Import Configuration"), - msg("Create a SAML provider by importing its metadata"), - () => html``, - ProviderModelEnum.SamlSamlprovider + "scimprovider", + msg("SCIM Manual configuration"), + msg("Configure SCIM provider manually"), + () => html``, + ProviderModelEnum.ScimScimprovider ], ]; @@ -101,6 +102,13 @@ const converters = new Map([ ...(provider as SAMLProviderRequest), }), ], + [ + ProviderModelEnum.ScimScimprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.ScimScimprovider, + ...(provider as SCIMProviderRequest), + }), + ], ]); // Contract enforcement diff --git a/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts b/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts index 7bade4ca3..ea8f1a5b4 100644 --- a/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts +++ b/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts @@ -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 type ProviderModelType = Exclude; -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[] { function inner(o: Record): 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) => { @@ -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`
-

${this.commitState.label}

+

+ ${this.commitState.label} +

${this.errors.length > 0 ? html`
    ${this.errors.map( diff --git a/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts index 682958a35..acb0ec8a9 100644 --- a/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts +++ b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts @@ -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 diff --git a/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts b/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts new file mode 100644 index 000000000..493c740d1 --- /dev/null +++ b/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts @@ -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`
    + + + ${msg("Protocol settings")} +
    + + + + +
    +
    + + ${msg("User filtering")} +
    + + + => { + 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} + > + +

    + ${msg("Only sync users within the selected group.")} +

    +
    +
    +
    + + ${msg("Attribute mapping")} +
    + + +

    + ${msg("Property mappings used to user mapping.")} +

    +

    + ${msg("Hold control/command to select multiple items.")} +

    +
    + + +

    + ${msg("Property mappings used to group creation.")} +

    +

    + ${msg("Hold control/command to select multiple items.")} +

    +
    +
    +
    +
    `; + } +} + +export default ApplicationWizardAuthenticationBySCIM;