diff --git a/Makefile b/Makefile index d4e33fec9..daae5c81d 100644 --- a/Makefile +++ b/Makefile @@ -28,10 +28,13 @@ CODESPELL_ARGS = -D - -D .github/codespell-dictionary.txt \ all: lint-fix lint test gen web ## Lint, build, and test everything +HELP_WIDTH := $(shell grep -h '^[a-z][^ ]*:.*\#\#' $(MAKEFILE_LIST) 2>/dev/null | \ + cut -d':' -f1 | awk '{printf "%d\n", length}' | sort -rn | head -1) + help: ## Show this help @echo "\nSpecify a command. The choices are:\n" - @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf " \033[0;36m%-24s\033[m %s\n", $$1, $$2}' | \ + @grep -Eh '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[0;36m%-$(HELP_WIDTH)s \033[m %s\n", $$1, $$2}' | \ sort @echo "" diff --git a/tests/wdio/test/pageobjects/application-wizard.page.ts b/tests/wdio/test/pageobjects/application-wizard.page.ts index 397d6a790..904e65d40 100644 --- a/tests/wdio/test/pageobjects/application-wizard.page.ts +++ b/tests/wdio/test/pageobjects/application-wizard.page.ts @@ -6,6 +6,7 @@ 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 RadiusForm from "./forms/radius.form.js"; import { $ } from "@wdio/globals"; /** @@ -23,6 +24,7 @@ class ApplicationWizardView extends AdminPage { forwardProxy = ForwardProxyForm; saml = SamlForm; scim = ScimForm; + radius = RadiusForm; app = ApplicationForm; get wizardTitle() { diff --git a/tests/wdio/test/pageobjects/forms/application.form.ts b/tests/wdio/test/pageobjects/forms/application.form.ts index 37d16eb7b..73881902c 100644 --- a/tests/wdio/test/pageobjects/forms/application.form.ts +++ b/tests/wdio/test/pageobjects/forms/application.form.ts @@ -5,6 +5,14 @@ export class ApplicationForm extends Page { get name() { return $('>>>ak-form-element-horizontal input[name="name"]'); } + + get uiSettings() { + return $('>>>ak-form-group button[aria-label="UI Settings"]'); + } + + get launchUrl() { + return $('>>>input[name="metaLaunchUrl"]'); + } } export default new ApplicationForm(); diff --git a/tests/wdio/test/pageobjects/forms/radius.form.ts b/tests/wdio/test/pageobjects/forms/radius.form.ts new file mode 100644 index 000000000..771180df1 --- /dev/null +++ b/tests/wdio/test/pageobjects/forms/radius.form.ts @@ -0,0 +1,13 @@ +import Page from "../page.js"; + +export class RadiusForm extends Page { + async setAuthenticationFlow(selector: string) { + await this.searchSelect( + '>>>ak-tenanted-flow-search[name="authorizationFlow"] input[type="text"]', + "authorizationFlow", + `button*=${selector}` + ); + } +} + +export default new RadiusForm(); diff --git a/tests/wdio/test/specs/new-application-by-wizard.ts b/tests/wdio/test/specs/new-application-by-wizard.ts index 4f69dff2f..cda176318 100644 --- a/tests/wdio/test/specs/new-application-by-wizard.ts +++ b/tests/wdio/test/specs/new-application-by-wizard.ts @@ -4,6 +4,8 @@ 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(); @@ -17,6 +19,11 @@ async function reachTheProvider(title: string) { await expect(await ApplicationWizardView.wizardTitle).toHaveText("New application"); await ApplicationWizardView.app.name.setValue(`${title} - ${newPrefix}`); + await ApplicationWizardView.app.uiSettings.scrollIntoView(); + await ApplicationWizardView.app.uiSettings.click(); + await ApplicationWizardView.app.launchUrl.scrollIntoView(); + await ApplicationWizardView.app.launchUrl.setValue('http://example.goauthentik.io'); + await ApplicationWizardView.nextButton.click(); return await ApplicationWizardView.pause(); } @@ -29,11 +36,16 @@ async function getCommitMessage() { 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.scrollIntoView(); await ApplicationWizardView.ldapProvider.click(); + await ApplicationWizardView.nextButton.click(); await ApplicationWizardView.pause(); @@ -50,7 +62,9 @@ describe("Configure Applications with the Application Wizard", () => { await reachTheProvider("New Oauth2 Application"); await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.oauth2Provider.scrollIntoView(); await ApplicationWizardView.oauth2Provider.click(); + await ApplicationWizardView.nextButton.click(); await ApplicationWizardView.pause(); @@ -69,7 +83,9 @@ describe("Configure Applications with the Application Wizard", () => { await reachTheProvider("New SAML Application"); await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.samlProvider.scrollIntoView(); await ApplicationWizardView.samlProvider.click(); + await ApplicationWizardView.nextButton.click(); await ApplicationWizardView.pause(); @@ -89,7 +105,9 @@ describe("Configure Applications with the Application Wizard", () => { await reachTheProvider("New SCIM Application"); await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.scimProvider.scrollIntoView(); await ApplicationWizardView.scimProvider.click(); + await ApplicationWizardView.nextButton.click(); await ApplicationWizardView.pause(); @@ -103,10 +121,32 @@ describe("Configure Applications with the Application Wizard", () => { ); }); + it("Should configure a simple Radius Application", async () => { + await reachTheProvider("New Radius Application"); + + await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.radiusProvider.scrollIntoView(); + await ApplicationWizardView.radiusProvider.click(); + + await ApplicationWizardView.nextButton.click(); + await ApplicationWizardView.pause(); + + await ApplicationWizardView.radius.setAuthenticationFlow( + "default-authentication-flow" + ); + 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.scrollIntoView(); await ApplicationWizardView.proxyProviderProxy.click(); await ApplicationWizardView.nextButton.click(); await ApplicationWizardView.pause(); @@ -129,6 +169,7 @@ describe("Configure Applications with the Application Wizard", () => { await reachTheProvider("New Forward Proxy Application"); await ApplicationWizardView.providerList.waitForDisplayed(); + await ApplicationWizardView.proxyProviderForwardsingle.scrollIntoView(); await ApplicationWizardView.proxyProviderForwardsingle.click(); await ApplicationWizardView.nextButton.click(); await ApplicationWizardView.pause(); diff --git a/tests/wdio/wdio.conf.ts b/tests/wdio/wdio.conf.ts index 9ba17aac8..2f6d51145 100644 --- a/tests/wdio/wdio.conf.ts +++ b/tests/wdio/wdio.conf.ts @@ -1,4 +1,5 @@ import type { Options } from "@wdio/types"; +import { browser } from "@wdio/globals"; export const config: Options.Testrunner = { // @@ -86,7 +87,7 @@ export const config: Options.Testrunner = { // Define all options that are relevant for the WebdriverIO instance here // // Level of logging verbosity: trace | debug | info | warn | error | silent - logLevel: "info", + logLevel: "warn", // // Set specific log levels per logger // loggers: @@ -209,8 +210,8 @@ export const config: Options.Testrunner = { * @param {Array.} specs List of spec file paths that are to be run * @param {object} browser instance of created browser/device session */ - // before: function (capabilities, specs) { - // }, + before: function (capabilities, specs) { + }, /** * Runs before a WebdriverIO command gets executed. * @param {string} commandName hook command name diff --git a/web/src/admin/applications/wizard/application/ak-application-wizard-application-details.ts b/web/src/admin/applications/wizard/application/ak-application-wizard-application-details.ts index 1ee8f22f4..13e439d76 100644 --- a/web/src/admin/applications/wizard/application/ak-application-wizard-application-details.ts +++ b/web/src/admin/applications/wizard/application/ak-application-wizard-application-details.ts @@ -72,8 +72,8 @@ export class ApplicationWizardApplicationDetails extends BasePanel { .options=${policyOptions} .value=${this.wizard.app?.policyEngineMode} > - - ${msg("UI settings")} + + ${msg("UI Settings")}
html``, ProviderModelEnum.SamlSamlprovider ], + [ + "radiusprovider", + msg("RADIUS Configuration"), + msg("Configure RADIUS provider manually"), + () => html``, + ProviderModelEnum.RadiusRadiusprovider + ], [ "scimprovider", msg("SCIM Manual configuration"), @@ -109,6 +117,13 @@ const converters = new Map([ ...(provider as SCIMProviderRequest), }), ], + [ + ProviderModelEnum.RadiusRadiusprovider, + (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.RadiusRadiusprovider, + ...(provider as RadiusProviderRequest), + }), + ], ]); // 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 ea8f1a5b4..906856344 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 @@ -177,7 +177,9 @@ export class ApplicationWizardCommitApplication extends BasePanel { } render(): TemplateResult { - const icon = classMap(this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {})); + const icon = classMap( + this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {}), + ); return html`
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 acb0ec8a9..f308bcbab 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 @@ -6,6 +6,7 @@ import "./ldap/ak-application-wizard-authentication-by-ldap"; 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 "./radius/ak-application-wizard-authentication-by-radius"; import "./saml/ak-application-wizard-authentication-by-saml-configuration"; import "./scim/ak-application-wizard-authentication-by-scim"; diff --git a/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts new file mode 100644 index 000000000..d107eab0f --- /dev/null +++ b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts @@ -0,0 +1,73 @@ +import "@goauthentik/admin/common/ak-crypto-certificate-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-text-input"; +import { rootInterface } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators.js"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/api"; + +import BaseProviderPanel from "../BaseProviderPanel"; + +@customElement("ak-application-wizard-authentication-by-radius") +export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel { + render() { + const provider = this.wizard.provider as RadiusProvider | undefined; + + return html`
+ + + + + +

${msg("Flow used for users to authenticate.")}

+
+ + + ${msg("Protocol settings")} +
+ + +
+
+
`; + } +} + +export default ApplicationWizardAuthenticationByRadius; diff --git a/web/src/elements/forms/FormGroup.ts b/web/src/elements/forms/FormGroup.ts index af92a0d26..0c867d719 100644 --- a/web/src/elements/forms/FormGroup.ts +++ b/web/src/elements/forms/FormGroup.ts @@ -25,6 +25,9 @@ export class FormGroup extends AKElement { @property({ type: Boolean, reflect: true }) expanded = false; + @property({ type: String, attribute: "aria-label", reflect: true }) + ariaLabel = "Details"; + static get styles(): CSSResult[] { return [ PFBase, @@ -47,7 +50,7 @@ export class FormGroup extends AKElement { class="pf-c-button pf-m-plain" type="button" aria-expanded="${this.expanded}" - aria-label="Details" + aria-label=${this.ariaLabel} @click=${() => { this.expanded = !this.expanded; }}