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.
This commit is contained in:
Ken Sternberg 2024-01-12 10:21:02 -08:00
parent be66ee52cd
commit 66fa4e3085
6 changed files with 199 additions and 11 deletions

View File

@ -1,3 +1,5 @@
import "@goauthentik/admin/common/ak-license-notice";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
@ -8,6 +10,7 @@ import type {
ModelRequest, ModelRequest,
OAuth2ProviderRequest, OAuth2ProviderRequest,
ProxyProviderRequest, ProxyProviderRequest,
RACProviderRequest,
RadiusProviderRequest, RadiusProviderRequest,
SAMLProviderRequest, SAMLProviderRequest,
SCIMProviderRequest, SCIMProviderRequest,
@ -19,6 +22,9 @@ type ProviderRenderer = () => TemplateResult;
type ModelConverter = (provider: OneOfProvider) => ModelRequest; 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. * 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 ProviderRenderer, // Function that returns the provider's wizard panel as a TemplateResult
ProviderModelEnumType, // key used by the API to distinguish between providers 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 ModelConverter, // Handler that takes a generic provider and returns one specifically typed to its panel
ProviderNote?,
]; ];
export type LocalTypeCreate = TypeCreate & { export type LocalTypeCreate = TypeCreate & {
formName: string; formName: string;
modelName: ProviderModelEnumType; modelName: ProviderModelEnumType;
converter: ModelConverter; converter: ModelConverter;
note?: ProviderNote;
}; };
// prettier-ignore // prettier-ignore
@ -103,6 +111,19 @@ const _providerModelsTable: ProviderType[] = [
mode: ProxyMode.ForwardDomain, 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", "samlprovider",
msg("SAML (Security Assertion Markup Language)"), msg("SAML (Security Assertion Markup Language)"),
@ -148,6 +169,7 @@ function mapProviders([
_, _,
modelName, modelName,
converter, converter,
note,
]: ProviderType): LocalTypeCreate { ]: ProviderType): LocalTypeCreate {
return { return {
formName, formName,
@ -156,6 +178,7 @@ function mapProviders([
component: "", component: "",
modelName, modelName,
converter, converter,
note,
}; };
} }

View File

@ -7,7 +7,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; 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 { map } from "lit/directives/map.js";
import BasePanel from "../BasePanel"; import BasePanel from "../BasePanel";
@ -48,7 +48,9 @@ export class ApplicationWizardAuthenticationMethodChoice extends BasePanel {
@change=${this.handleChoice} @change=${this.handleChoice}
/> />
<label class="pf-c-radio__label" for="provider-${type.formName}">${type.name}</label> <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>`; </div>`;
} }

View File

@ -0,0 +1,128 @@
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 YAML from "yaml";
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,
PaginatedEndpointList,
PaginatedRACPropertyMappingList,
PropertymappingsApi,
RACProvider,
RacApi,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-rac")
export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel {
@state()
endpoints?: PaginatedEndpointList;
@state()
propertyMappings?: PaginatedRACPropertyMappingList;
constructor() {
super();
new RacApi(DEFAULT_CONFIG).racEndpointsList({}).then((endpoints) => {
this.endpoints = endpoints;
});
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.",
)}
required
></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>
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
<ak-codemirror
mode="yaml"
value="${YAML.stringify(provider?.settings ?? {})}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${msg("Connection settings.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardAuthenticationByRAC;

View File

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

View File

@ -0,0 +1,35 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/Alert";
import { AKElement } from "@goauthentik/elements/Base";
import { msg } from "@lit/localize";
import { html, nothing } from "lit";
import { customElement, state } from "lit/decorators.js";
import { EnterpriseApi } from "@goauthentik/api";
@customElement("ak-license-notice")
export class AkLicenceNotice extends AKElement {
@state()
hasLicense = false;
constructor() {
console.log("Notice constructed");
super();
new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => {
this.hasLicense = enterprise.hasLicense;
});
}
render() {
console.log(this.hasLicense);
return this.hasLicense
? nothing
: html`
<ak-alert class="pf-c-radio__description" inline>
${msg("Provider requires enterprise.")}
<a href="#/enterprise/licenses">${msg("Learn more")}</a>
</ak-alert>
`;
}
}

View File

@ -1,3 +1,4 @@
import "@goauthentik/admin/common/ak-license-notice";
import "@goauthentik/admin/providers/ldap/LDAPProviderForm"; import "@goauthentik/admin/providers/ldap/LDAPProviderForm";
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
import "@goauthentik/admin/providers/proxy/ProxyProviderForm"; import "@goauthentik/admin/providers/proxy/ProxyProviderForm";
@ -86,15 +87,12 @@ export class InitialProviderWizardPage extends WizardPage {
?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false} ?disabled=${type.requiresEnterprise ? !this.enterprise?.hasLicense : false}
/> />
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label> <label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
<span class="pf-c-radio__description">${type.description}</span> <span class="pf-c-radio__description"
${type.requiresEnterprise && !this.enterprise?.hasLicense >${type.description}
? html` ${type.requiresEnterprise
<ak-alert class="pf-c-radio__description" ?inline=${true}> ? html`<ak-license-notice></ak-license-notice>`
${msg("Provider require enterprise.")} : nothing}
<a href="#/enterprise/licenses">${msg("Learn more")}</a> </span>
</ak-alert>
`
: nothing}
</div>`; </div>`;
})} })}
</form>`; </form>`;