web: add RAC Provider to the list of providers understood by the wizard (#8149)

* web: add RAC Provider to the list of providers understood by the wizard

This commit also creates a new, simple alert that knows how to look up the enterprise requirements
and chooses to fill itself in with a notice saying "A license is required for this provider," or
nothing.  That harmonizes the display across both wizards, and reduces the demands on the wizards
themselves to "know" about enterprise features.

* web: remove console.log() from ak-license-notice

* web: fix inconsistencies in identity passing.

* web: move the license summary information into a top-level context.

Rather than repeatedly fetching the license summary, this commit
fetches it once at the top-level and keeps it until an EVENT_REFRESH
reaches the top level.  This prevents the FOUC (Flash Of Unavailable
Content) while loading and awaiting the end of the load.

* Remove some debugging info, fix a misspelling.

* web: provide a context for enterprise license status

There are a few places (currently 5) in our code where we have checks for the current enterprise
licensing status of our product. While not particularly heavy or onerous, there's no reason to
repeat those same lines, and since our UI is always running in the context of authentik, may as well
make that status a client-side context in its own right. The status will update with an
EVENT_REFRESH request.

A context-aware custom alert has also been provided; it draws itself (or `nothing`) depending on the
state of the license, and the default message, "This feature requires an enterprise license," can be
overriden with the `notice` property.

These two changes reduce the amount of code needed to manage our license alerting from 67 to 38
lines code, and while removing 29 lines from a product with 54,145 lines of code (a savings of
0.05%, oh boy!) isn't a miracle, it does mean there's a single source of truth for "Is this instance
enterprise-licensed?" that's easy to access and use.

* web: [x] The translation files have been updated

* web: add RAC Provider to the list of providers understood by the wizard

This commit also creates a new, simple alert that knows how to look up the enterprise requirements
and chooses to fill itself in with a notice saying "A license is required for this provider," or
nothing.  That harmonizes the display across both wizards, and reduces the demands on the wizards
themselves to "know" about enterprise features.

* web: fix inconsistencies in identity passing.

* web: move the license summary information into a top-level context.

Rather than repeatedly fetching the license summary, this commit
fetches it once at the top-level and keeps it until an EVENT_REFRESH
reaches the top level.  This prevents the FOUC (Flash Of Unavailable
Content) while loading and awaiting the end of the load.

* Remove some debugging info, fix a misspelling.

* remmove endpoint fetch from both rac provider forms since its not used

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* i18n

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web: RAC updates

- special case: disable RAC provider in the wizard if enterprise is not enabled
- remove `settings` YAML editor from the RAC provider in the wizard

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Ken Sternberg
2024-02-11 10:57:37 -08:00
committed by GitHub
parent 1ef224f5fd
commit 7db598c04e
6 changed files with 143 additions and 8 deletions

View File

@ -1,3 +1,5 @@
import "@goauthentik/admin/common/ak-license-notice";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
@ -8,6 +10,7 @@ import type {
ModelRequest,
OAuth2ProviderRequest,
ProxyProviderRequest,
RACProviderRequest,
RadiusProviderRequest,
SAMLProviderRequest,
SCIMProviderRequest,
@ -19,6 +22,9 @@ type ProviderRenderer = () => TemplateResult;
type ModelConverter = (provider: OneOfProvider) => ModelRequest;
type ProviderNoteProvider = () => TemplateResult | undefined;
type ProviderNote = ProviderNoteProvider | undefined;
/**
* There's an internal key and an API key because "Proxy" has three different subtypes.
*/
@ -30,12 +36,14 @@ type ProviderType = [
ProviderRenderer, // Function that returns the provider's wizard panel as a TemplateResult
ProviderModelEnumType, // key used by the API to distinguish between providers
ModelConverter, // Handler that takes a generic provider and returns one specifically typed to its panel
ProviderNote?,
];
export type LocalTypeCreate = TypeCreate & {
formName: string;
modelName: ProviderModelEnumType;
converter: ModelConverter;
note?: ProviderNote;
};
// prettier-ignore
@ -103,6 +111,19 @@ const _providerModelsTable: ProviderType[] = [
mode: ProxyMode.ForwardDomain,
}),
],
[
"racprovider",
msg("Remote Access Provider"),
msg("Remotely access computers/servers via RDP/SSH/VNC"),
() =>
html`<ak-application-wizard-authentication-for-rac></ak-application-wizard-authentication-for-rac>`,
ProviderModelEnum.RacRacprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.RacRacprovider,
...(provider as RACProviderRequest),
}),
() => html`<ak-license-notice></ak-license-notice>`
],
[
"samlprovider",
msg("SAML (Security Assertion Markup Language)"),
@ -148,6 +169,7 @@ function mapProviders([
_,
modelName,
converter,
note,
]: ProviderType): LocalTypeCreate {
return {
formName,
@ -156,6 +178,7 @@ function mapProviders([
component: "",
modelName,
converter,
note,
};
}

View File

@ -1,3 +1,4 @@
import { WithLicenseSummary } from "@goauthentik/app/elements/Interface/licenseSummaryProvider";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
@ -7,7 +8,7 @@ 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 { html, nothing } from "lit";
import { map } from "lit/directives/map.js";
import BasePanel from "../BasePanel";
@ -15,7 +16,7 @@ import providerModelsList from "./ak-application-wizard-authentication-method-ch
import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices";
@customElement("ak-application-wizard-authentication-method-choice")
export class ApplicationWizardAuthenticationMethodChoice extends BasePanel {
export class ApplicationWizardAuthenticationMethodChoice extends WithLicenseSummary(BasePanel) {
constructor() {
super();
this.handleChoice = this.handleChoice.bind(this);
@ -43,12 +44,15 @@ export class ApplicationWizardAuthenticationMethodChoice extends BasePanel {
type="radio"
name="type"
id="provider-${type.formName}"
?disabled=${type.formName === "racprovider" && !this.hasEnterpriseLicense}
value=${type.formName}
?checked=${type.formName === method}
@change=${this.handleChoice}
/>
<label class="pf-c-radio__label" for="provider-${type.formName}">${type.name}</label>
<span class="pf-c-radio__description">${type.description}</span>
<span class="pf-c-radio__description"
>${type.description}${type.note ? type.note() : nothing}</span
>
</div>`;
}

View File

@ -7,6 +7,7 @@ import "./oauth/ak-application-wizard-authentication-by-oauth";
import "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy";
import "./proxy/ak-application-wizard-authentication-for-reverse-proxy";
import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy";
import "./rac/ak-application-wizard-authentication-for-rac";
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";

View File

@ -0,0 +1,110 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
PaginatedRACPropertyMappingList,
PropertymappingsApi,
RACProvider,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-for-rac")
export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel {
@state()
propertyMappings?: PaginatedRACPropertyMappingList;
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsRacList({
ordering: "name",
})
.then((propertyMappings) => {
this.propertyMappings = propertyMappings;
});
}
render() {
const provider = this.wizard.provider as RACProvider | undefined;
const selected = new Set(Array.from(provider?.propertyMappings ?? []));
const errors = this.wizard.errors.provider;
return html`<ak-wizard-title
>${msg("Configure Remote Access Provider Provider")}</ak-wizard-title
>
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
label=${msg("Name")}
value=${ifDefined(provider?.name)}
.errorMessages=${errors?.name ?? []}
required
></ak-text-input>
<ak-form-element-horizontal
name="authorizationFlow"
label=${msg("Authorization flow")}
?required=${true}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-text-input
name="connectionExpiry"
label=${msg("Connection expiry")}
required
value="${provider?.connectionExpiry ?? "hours=8"}"
help=${msg(
"Determines how long a session lasts before being disconnected and requiring re-authorization.",
)}
></ak-text-input>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Property mappings")}
?required=${true}
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map(
(mapping) =>
html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected.has(mapping.pk)}
>
${mapping.name}
</option>`,
)}
</select>
<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 ApplicationWizardAuthenticationByRAC;

View File

@ -6,6 +6,7 @@ import {
type OAuth2ProviderRequest,
type ProvidersSamlImportMetadataCreateRequest,
type ProxyProviderRequest,
type RACProviderRequest,
type RadiusProviderRequest,
type SAMLProviderRequest,
type SCIMProviderRequest,
@ -16,6 +17,7 @@ export type OneOfProvider =
| Partial<SCIMProviderRequest>
| Partial<SAMLProviderRequest>
| Partial<ProvidersSamlImportMetadataCreateRequest>
| Partial<RACProviderRequest>
| Partial<RadiusProviderRequest>
| Partial<ProxyProviderRequest>
| Partial<OAuth2ProviderRequest>

View File

@ -18,23 +18,18 @@ import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
PaginatedEndpointList,
PaginatedRACPropertyMappingList,
PropertymappingsApi,
ProvidersApi,
RACProvider,
RacApi,
} from "@goauthentik/api";
@customElement("ak-provider-rac-form")
export class RACProviderFormPage extends ModelForm<RACProvider, number> {
@state()
endpoints?: PaginatedEndpointList;
propertyMappings?: PaginatedRACPropertyMappingList;
async load(): Promise<void> {
this.endpoints = await new RacApi(DEFAULT_CONFIG).racEndpointsList({});
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsRacList({