From b4d3b75434e4cfdffd1462177197dc2a658d86a6 Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Tue, 1 Aug 2023 16:16:57 -0700 Subject: [PATCH] Now, it's starting to look like a complete package. The LDAP method is working, but there is a bug: the radio is sending the wrong value !?!?!?. Track that down, dammit. The search wrappers now resend their events as standard `input` events, and that actually seems to work well; the browser is decorating it with the right target, with the right `name` attribute, and since we have good definitions of the `value` as a string (the real value of any search object is its UUID4), that works quite well. Added search wrappers for CoreGroup and CryptoCertificate (CertificateKeyPairs), and the latter has flags for "use the first one if it's the only one" and "allow the display of keyless certificates." Not sure why `state()` is blocking the transmission of typing information from the typed element to the context handler, but it's a bug in the typechecker, and it's not a problem so far. --- .../wizard/ApplicationWizardPageBase.ts | 29 ++++++++ ...lication-wizard-application-details.css.ts | 13 ++-- ...-application-wizard-application-details.ts | 23 +------ ...ion-wizard-authentication-method-choice.ts | 68 +++++++++++++++++++ .../wizard/ak-application-wizard-context.ts | 2 + ...tion-wizard-application-details.stories.ts | 43 ++++++++++++ 6 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 web/src/admin/applications/wizard/ApplicationWizardPageBase.ts create mode 100644 web/src/admin/applications/wizard/ak-application-wizard-authentication-method-choice.ts diff --git a/web/src/admin/applications/wizard/ApplicationWizardPageBase.ts b/web/src/admin/applications/wizard/ApplicationWizardPageBase.ts new file mode 100644 index 000000000..c21c3a357 --- /dev/null +++ b/web/src/admin/applications/wizard/ApplicationWizardPageBase.ts @@ -0,0 +1,29 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { consume } from "@lit-labs/context"; +import { state } from "@lit/reactive-element/decorators/state.js"; + +import { styles as AwadStyles } from "./ak-application-wizard-application-details.css"; + +import type { WizardState } from "./ak-application-wizard-context"; +import applicationWizardContext from "./ak-application-wizard-context-name"; + +export class ApplicationWizardPageBase extends CustomEmitterElement(AKElement) { + static get styles() { + return AwadStyles; + } + + @consume({ context: applicationWizardContext, subscribe: true }) + @state() + private wizard!: WizardState; + + dispatchWizardUpdate(update: Partial) { + this.dispatchCustomEvent("ak-wizard-update", { + ...this.wizard, + ...update, + }); + } +} + +export default ApplicationWizardPageBase; diff --git a/web/src/admin/applications/wizard/ak-application-wizard-application-details.css.ts b/web/src/admin/applications/wizard/ak-application-wizard-application-details.css.ts index 141d32dee..698b6cbac 100644 --- a/web/src/admin/applications/wizard/ak-application-wizard-application-details.css.ts +++ b/web/src/admin/applications/wizard/ak-application-wizard-application-details.css.ts @@ -6,6 +6,7 @@ import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css"; +import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; @@ -15,12 +16,16 @@ export const styles = [ PFButton, PFForm, PFAlert, + PFRadio, PFInputGroup, PFFormControl, PFSwitch, css` -select[multiple] { -height: 15em; -} -`, + .pf-c-radio__label { + color: #212427; + } + select[multiple] { + height: 15em; + } + `, ]; diff --git a/web/src/admin/applications/wizard/ak-application-wizard-application-details.ts b/web/src/admin/applications/wizard/ak-application-wizard-application-details.ts index f10cd3006..c7d0f4866 100644 --- a/web/src/admin/applications/wizard/ak-application-wizard-application-details.ts +++ b/web/src/admin/applications/wizard/ak-application-wizard-application-details.ts @@ -3,39 +3,22 @@ import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; -import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; -import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; -import { consume } from "@lit-labs/context"; import { msg } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { state } from "@lit/reactive-element/decorators/state.js"; import { TemplateResult, html } from "lit"; import { ifDefined } from "lit/directives/if-defined.js"; -import { styles as AwadStyles } from "./ak-application-wizard-application-details.css"; - -import type { WizardState } from "./ak-application-wizard-context"; -import applicationWizardContext from "./ak-application-wizard-context-name"; +import ApplicationWizardPageBase from "./ApplicationWizardPageBase"; @customElement("ak-application-wizard-application-details") -export class ApplicationWizardApplicationDetails extends CustomEmitterElement(AKElement) { - static get styles() { - return AwadStyles; - } - - @consume({ context: applicationWizardContext, subscribe: true }) - @state() - private wizard!: WizardState; - +export class ApplicationWizardApplicationDetails extends ApplicationWizardPageBase { handleChange(ev: Event) { const value = ev.target.type === "checkbox" ? ev.target.checked : ev.target.value; - - this.dispatchCustomEvent("ak-wizard-update", { - ...this.wizard, + this.dispatchWizardUpdate({ application: { ...this.wizard.application, [ev.target.name]: value, diff --git a/web/src/admin/applications/wizard/ak-application-wizard-authentication-method-choice.ts b/web/src/admin/applications/wizard/ak-application-wizard-authentication-method-choice.ts new file mode 100644 index 000000000..f544bb5d8 --- /dev/null +++ b/web/src/admin/applications/wizard/ak-application-wizard-authentication-method-choice.ts @@ -0,0 +1,68 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; +import { html } from "lit"; +import { state } from "lit/decorators.js"; +import { map } from "lit/directives/map.js"; + +import { ProvidersApi } from "@goauthentik/api"; +import type { TypeCreate } from "@goauthentik/api"; + +import ApplicationWizardPageBase from "./ApplicationWizardPageBase"; + +@customElement("ak-application-wizard-authentication-method-choice") +export class ApplicationWizardAuthenticationMethodChoice extends ApplicationWizardPageBase { + @state() + providerTypes: TypeCreate[] = []; + + constructor() { + super(); + this.handleChoice = this.handleChoice.bind(this); + this.renderProvider = this.renderProvider.bind(this); + new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => { + this.providerTypes = types; + }); + } + + handleChoice(ev: Event) { + this.dispatchWizardUpdate({ providerType: ev.target.value }); + } + + renderProvider(type: Provider) { + // Special case; the SAML-by-import method is handled differently + // prettier-ignore + const model = /^SAML/.test(type.name) && type.modelName === "" + ? "samlimporter" + : type.modelName; + + return html`
+ + + ${type.description} +
`; + } + + render() { + return this.providerTypes.length > 0 + ? html`
+ ${map(this.providerTypes, this.renderProvider)} +
` + : html``; + } +} + +export default ApplicationWizardAuthenticationMethodChoice; diff --git a/web/src/admin/applications/wizard/ak-application-wizard-context.ts b/web/src/admin/applications/wizard/ak-application-wizard-context.ts index 3279cda78..72593d6b0 100644 --- a/web/src/admin/applications/wizard/ak-application-wizard-context.ts +++ b/web/src/admin/applications/wizard/ak-application-wizard-context.ts @@ -29,6 +29,7 @@ type OneOfProvider = export type WizardState = { step: number; + providerType: string; application: Partial; provider: OneOfProvider; }; @@ -42,6 +43,7 @@ export class AkApplicationWizardContext extends CustomListenerElement(LitElement @property({ attribute: false }) wizardState: WizardState = { step: 0, + providerType: "", application: {}, provider: {}, }; diff --git a/web/src/admin/applications/wizard/stories/ak-application-wizard-application-details.stories.ts b/web/src/admin/applications/wizard/stories/ak-application-wizard-application-details.stories.ts index 18beccf20..9e0142842 100644 --- a/web/src/admin/applications/wizard/stories/ak-application-wizard-application-details.stories.ts +++ b/web/src/admin/applications/wizard/stories/ak-application-wizard-application-details.stories.ts @@ -4,9 +4,35 @@ import { TemplateResult, html } from "lit"; import "../ak-application-wizard-application-details"; import AkApplicationWizardApplicationDetails from "../ak-application-wizard-application-details"; +import "../ak-application-wizard-authentication-method-choice"; import "../ak-application-wizard-context"; import "./ak-application-context-display-for-test"; +// prettier-ignore +const providerTypes = [ + ["LDAP Provider", "ldapprovider", + "Allow applications to authenticate against authentik's users using LDAP.", + ], + ["OAuth2/OpenID Provider", "oauth2provider", + "OAuth2 Provider for generic OAuth and OpenID Connect Applications.", + ], + ["Proxy Provider", "proxyprovider", + "Protect applications that don't support any of the other\n Protocols by using a Reverse-Proxy.", + ], + ["Radius Provider", "radiusprovider", + "Allow applications to authenticate against authentik's users using Radius.", + ], + ["SAML Provider", "samlprovider", + "SAML 2.0 Endpoint for applications which support SAML.", + ], + ["SCIM Provider", "scimprovider", + "SCIM 2.0 provider to create users and groups in external applications", + ], + ["SAML Provider from Metadata", "", + "Create a SAML Provider by importing its Metadata.", + ], +].map(([name, model_name, description]) => ({ name, description, model_name })); + const metadata: Meta = { title: "Elements / Application Wizard / Page 1", component: "ak-application-wizard-application-details", @@ -16,6 +42,14 @@ const metadata: Meta = { component: "The first page of the application wizard", }, }, + mockData: [ + { + url: "/api/v3/providers/all/types/", + method: "GET", + status: 200, + response: providerTypes, + }, + ], }, }; @@ -42,3 +76,12 @@ export const PageOne = () => { ` ); }; + +export const PageTwo = () => { + return container( + html` + + + ` + ); +};