web: re-organise frontend and cleanup common code (#3572)
* fix repo in api client Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: re-organise files to match their interface Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: include version in script tags Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * cleanup maybe broken Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * revert rename Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: get rid of Client.ts Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * move more to common Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * more moving Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * format Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * unfuck files that vscode fucked, thanks Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * move more Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * finish moving (maybe) Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * ok more moving Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix more stuff that vs code destroyed Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * get rid "web" prefix for virtual package Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix locales Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * use custom base element Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix css file Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * don't run autoDetectLanguage when importing locale Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix circular dependencies Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: fix build Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
116
web/src/admin/providers/ProviderListPage.ts
Normal file
116
web/src/admin/providers/ProviderListPage.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import "@goauthentik/admin/providers/ProviderWizard";
|
||||
import "@goauthentik/admin/providers/ldap/LDAPProviderForm";
|
||||
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
|
||||
import "@goauthentik/admin/providers/proxy/ProxyProviderForm";
|
||||
import "@goauthentik/admin/providers/saml/SAMLProviderForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { Provider, ProvidersApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-list")
|
||||
export class ProviderListPage extends TablePage<Provider> {
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
pageTitle(): string {
|
||||
return t`Providers`;
|
||||
}
|
||||
pageDescription(): string {
|
||||
return t`Provide support for protocols like SAML and OAuth to assigned applications.`;
|
||||
}
|
||||
pageIcon(): string {
|
||||
return "pf-icon pf-icon-integration";
|
||||
}
|
||||
|
||||
checkbox = true;
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<Provider>> {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersAllList({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(t`Name`, "name"),
|
||||
new TableColumn(t`Application`),
|
||||
new TableColumn(t`Type`),
|
||||
new TableColumn(t`Actions`),
|
||||
];
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${t`Provider(s)`}
|
||||
.objects=${this.selectedElements}
|
||||
.usedBy=${(item: Provider) => {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersAllUsedByList({
|
||||
id: item.pk,
|
||||
});
|
||||
}}
|
||||
.delete=${(item: Provider) => {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersAllDestroy({
|
||||
id: item.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${t`Delete`}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: Provider): TemplateResult[] {
|
||||
return [
|
||||
html`<a href="#/core/providers/${item.pk}"> ${item.name} </a>`,
|
||||
item.assignedApplicationName
|
||||
? html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
|
||||
${t`Assigned to application `}
|
||||
<a href="#/core/applications/${item.assignedApplicationSlug}"
|
||||
>${item.assignedApplicationName}</a
|
||||
>`
|
||||
: html`<i class="pf-icon pf-icon-warning-triangle pf-m-warning"></i>
|
||||
${t`Warning: Provider not assigned to any application.`}`,
|
||||
html`${item.verboseName}`,
|
||||
html`<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
|
||||
<ak-proxy-form
|
||||
slot="form"
|
||||
.args=${{
|
||||
instancePk: item.pk,
|
||||
}}
|
||||
type=${item.component}
|
||||
>
|
||||
</ak-proxy-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</ak-forms-modal>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-provider-wizard> </ak-provider-wizard> `;
|
||||
}
|
||||
}
|
||||
94
web/src/admin/providers/ProviderViewPage.ts
Normal file
94
web/src/admin/providers/ProviderViewPage.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import "@goauthentik/admin/providers/ldap/LDAPProviderViewPage";
|
||||
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderViewPage";
|
||||
import "@goauthentik/admin/providers/proxy/ProxyProviderViewPage";
|
||||
import "@goauthentik/admin/providers/saml/SAMLProviderViewPage";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import "@goauthentik/elements/PageHeader";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
|
||||
import { Provider, ProvidersApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-view")
|
||||
export class ProviderViewPage extends AKElement {
|
||||
@property({ type: Number })
|
||||
set providerID(value: number) {
|
||||
new ProvidersApi(DEFAULT_CONFIG)
|
||||
.providersAllRetrieve({
|
||||
id: value,
|
||||
})
|
||||
.then((prov) => (this.provider = prov));
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
provider?: Provider;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFPage, AKGlobal];
|
||||
}
|
||||
|
||||
renderProvider(): TemplateResult {
|
||||
if (!this.provider) {
|
||||
return html`<ak-empty-state ?loading=${true} ?fullHeight=${true}></ak-empty-state>`;
|
||||
}
|
||||
switch (this.provider?.component) {
|
||||
case "ak-provider-saml-form":
|
||||
return html`<ak-provider-saml-view
|
||||
providerID=${ifDefined(this.provider.pk)}
|
||||
></ak-provider-saml-view>`;
|
||||
case "ak-provider-oauth2-form":
|
||||
return html`<ak-provider-oauth2-view
|
||||
providerID=${ifDefined(this.provider.pk)}
|
||||
></ak-provider-oauth2-view>`;
|
||||
case "ak-provider-proxy-form":
|
||||
return html`<ak-provider-proxy-view
|
||||
providerID=${ifDefined(this.provider.pk)}
|
||||
></ak-provider-proxy-view>`;
|
||||
case "ak-provider-ldap-form":
|
||||
return html`<ak-provider-ldap-view
|
||||
providerID=${ifDefined(this.provider.pk)}
|
||||
></ak-provider-ldap-view>`;
|
||||
default:
|
||||
return html`<p>Invalid provider type ${this.provider?.component}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<ak-page-header
|
||||
icon="pf-icon pf-icon-integration"
|
||||
header=${ifDefined(this.provider?.name)}
|
||||
description=${ifDefined(this.provider?.verboseName)}
|
||||
>
|
||||
</ak-page-header>
|
||||
<ak-tabs>
|
||||
<section slot="page-overview" data-tab-title="${t`Overview`}">
|
||||
${this.renderProvider()}
|
||||
</section>
|
||||
<section
|
||||
slot="page-changelog"
|
||||
data-tab-title="${t`Changelog`}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-object-changelog
|
||||
targetModelPk=${this.provider?.pk || ""}
|
||||
targetModelName=${this.provider?.metaModelName || ""}
|
||||
>
|
||||
</ak-object-changelog>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ak-tabs>`;
|
||||
}
|
||||
}
|
||||
129
web/src/admin/providers/ProviderWizard.ts
Normal file
129
web/src/admin/providers/ProviderWizard.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import "@goauthentik/admin/providers/ldap/LDAPProviderForm";
|
||||
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
|
||||
import "@goauthentik/admin/providers/proxy/ProxyProviderForm";
|
||||
import "@goauthentik/admin/providers/saml/SAMLProviderForm";
|
||||
import "@goauthentik/admin/providers/saml/SAMLProviderImportForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFHint from "@patternfly/patternfly/components/Hint/hint.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { ProvidersApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-wizard-initial")
|
||||
export class InitialProviderWizardPage extends WizardPage {
|
||||
@property({ attribute: false })
|
||||
providerTypes: TypeCreate[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFHint, PFButton, AKGlobal, PFRadio];
|
||||
}
|
||||
sidebarLabel = () => t`Select type`;
|
||||
|
||||
renderHint(): TemplateResult {
|
||||
return html`<div class="pf-c-hint">
|
||||
<div class="pf-c-hint__title">${t`Try the new application wizard`}</div>
|
||||
<div class="pf-c-hint__body">
|
||||
${t`The new application wizard greatly simplifies the steps required to create applications and providers.`}
|
||||
</div>
|
||||
<div class="pf-c-hint__footer">
|
||||
<a
|
||||
class="pf-c-button pf-m-link pf-m-inline"
|
||||
href=${paramURL("/core/applications", {
|
||||
createForm: true,
|
||||
})}
|
||||
>${t`Try it now`}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<br />`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <form class="pf-c-form pf-m-horizontal">
|
||||
${this.providerTypes.map((type) => {
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${type.component}
|
||||
@change=${() => {
|
||||
this.host.steps = ["initial", `type-${type.component}`];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
|
||||
<span class="pf-c-radio__description">${type.description}</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-provider-wizard")
|
||||
export class ProviderWizard extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton, AKGlobal, PFRadio];
|
||||
}
|
||||
|
||||
@property()
|
||||
createText = t`Create`;
|
||||
|
||||
@property({ attribute: false })
|
||||
providerTypes: TypeCreate[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
finalHandler: () => Promise<void> = () => {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
firstUpdated(): void {
|
||||
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => {
|
||||
this.providerTypes = types;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-wizard
|
||||
.steps=${["initial"]}
|
||||
header=${t`New provider`}
|
||||
description=${t`Create a new provider.`}
|
||||
.finalHandler=${() => {
|
||||
return this.finalHandler();
|
||||
}}
|
||||
>
|
||||
<ak-provider-wizard-initial slot="initial" .providerTypes=${this.providerTypes}>
|
||||
</ak-provider-wizard-initial>
|
||||
${this.providerTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
slot=${`type-${type.component}`}
|
||||
.sidebarLabel=${() => t`Create ${type.name}`}
|
||||
>
|
||||
<ak-proxy-form type=${type.component}></ak-proxy-form>
|
||||
</ak-wizard-page-form>
|
||||
`;
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
|
||||
</ak-wizard>
|
||||
`;
|
||||
}
|
||||
}
|
||||
38
web/src/admin/providers/RelatedApplicationButton.ts
Normal file
38
web/src/admin/providers/RelatedApplicationButton.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import "@goauthentik/admin/applications/ApplicationForm";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/Spinner";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { Provider } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-related-application")
|
||||
export class RelatedApplicationButton extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
provider?: Provider;
|
||||
|
||||
render(): TemplateResult {
|
||||
if (this.provider?.assignedApplicationSlug) {
|
||||
return html`<a href="#/core/applications/${this.provider.assignedApplicationSlug}">
|
||||
${this.provider.assignedApplicationName}
|
||||
</a>`;
|
||||
}
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Create`} </span>
|
||||
<span slot="header"> ${t`Create Application`} </span>
|
||||
<ak-application-form slot="form" .provider=${this.provider?.pk}> </ak-application-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
|
||||
</ak-forms-modal>`;
|
||||
}
|
||||
}
|
||||
245
web/src/admin/providers/ldap/LDAPProviderForm.ts
Normal file
245
web/src/admin/providers/ldap/LDAPProviderForm.ts
Normal file
@ -0,0 +1,245 @@
|
||||
import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import {
|
||||
CoreApi,
|
||||
CryptoApi,
|
||||
FlowsApi,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
LDAPAPIAccessMode,
|
||||
LDAPProvider,
|
||||
ProvidersApi,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-ldap-form")
|
||||
export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
|
||||
loadInstance(pk: number): Promise<LDAPProvider> {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
|
||||
id: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated provider.`;
|
||||
} else {
|
||||
return t`Successfully created provider.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: LDAPProvider): Promise<LDAPProvider> => {
|
||||
if (this.instance) {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersLdapUpdate({
|
||||
id: this.instance.pk || 0,
|
||||
lDAPProviderRequest: data,
|
||||
});
|
||||
} else {
|
||||
data.tlsServerName = "";
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersLdapCreate({
|
||||
lDAPProviderRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Bind flow`}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
${until(
|
||||
tenant().then((t) => {
|
||||
return new FlowsApi(DEFAULT_CONFIG)
|
||||
.flowsInstancesList({
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authentication,
|
||||
})
|
||||
.then((flows) => {
|
||||
return flows.results.map((flow) => {
|
||||
let selected = flow.pk === t.flowAuthentication;
|
||||
if (this.instance?.authorizationFlow === flow.pk) {
|
||||
selected = true;
|
||||
}
|
||||
return html`<option
|
||||
value=${ifDefined(flow.pk)}
|
||||
?selected=${selected}
|
||||
>
|
||||
${flow.name} (${flow.slug})
|
||||
</option>`;
|
||||
});
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Search group`} name="searchGroup">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.searchGroup === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then((groups) => {
|
||||
return groups.results.map((group) => {
|
||||
return html`<option
|
||||
value=${ifDefined(group.pk)}
|
||||
?selected=${this.instance?.searchGroup === group.pk}
|
||||
>
|
||||
${group.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Bind mode`} name="bindMode">
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value="${LDAPAPIAccessMode.Cached}"
|
||||
?selected=${this.instance?.bindMode === LDAPAPIAccessMode.Cached}
|
||||
>
|
||||
${t`Cached binding, flow is executed and session is cached in memory. Flow is executed when session expires.`}
|
||||
</option>
|
||||
<option
|
||||
value="${LDAPAPIAccessMode.Direct}"
|
||||
?selected=${this.instance?.bindMode === LDAPAPIAccessMode.Direct}
|
||||
>
|
||||
${t`Direct binding, always execute the configured bind flow to authenticate the user.`}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure how the outpost authenticates requests.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Search mode`} name="searchMode">
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value="${LDAPAPIAccessMode.Cached}"
|
||||
?selected=${this.instance?.searchMode === LDAPAPIAccessMode.Cached}
|
||||
>
|
||||
${t`Cached querying, the outpost holds all users and groups in-memory and will refresh every 5 Minutes.`}
|
||||
</option>
|
||||
<option
|
||||
value="${LDAPAPIAccessMode.Direct}"
|
||||
?selected=${this.instance?.searchMode === LDAPAPIAccessMode.Direct}
|
||||
>
|
||||
${t`Direct querying, always returns the latest data, but slower than cached querying.`}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure how the outpost queries the core authentik server's users.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Protocol settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${t`Base DN`} ?required=${true} name="baseDn">
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`LDAP DN under which bind requests and search requests can be made.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Certificate`} name="certificate">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.certificate === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new CryptoApi(DEFAULT_CONFIG)
|
||||
.cryptoCertificatekeypairsList({
|
||||
ordering: "name",
|
||||
hasKey: true,
|
||||
})
|
||||
.then((keys) => {
|
||||
return keys.results.map((key) => {
|
||||
return html`<option
|
||||
value=${ifDefined(key.pk)}
|
||||
?selected=${this.instance?.certificate === key.pk}
|
||||
>
|
||||
${key.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option
|
||||
value=${ifDefined(this.instance?.certificate || undefined)}
|
||||
?selected=${this.instance?.certificate !== undefined}
|
||||
>
|
||||
${t`Loading...`}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Due to protocol limitations, this certificate is only used when the outpost has a single provider.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`If multiple providers share an outpost, a self-signed certificate is used.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`UID start number`}
|
||||
?required=${true}
|
||||
name="uidStartNumber"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.uidStartNumber, 2000)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`GID start number`}
|
||||
?required=${true}
|
||||
name="gidStartNumber"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.gidStartNumber, 4000)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
210
web/src/admin/providers/ldap/LDAPProviderViewPage.ts
Normal file
210
web/src/admin/providers/ldap/LDAPProviderViewPage.ts
Normal file
@ -0,0 +1,210 @@
|
||||
import "@goauthentik/admin/providers/RelatedApplicationButton";
|
||||
import "@goauthentik/admin/providers/ldap/LDAPProviderForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/events/ObjectChangelog";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { until } from "lit-html/directives/until.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { LDAPProvider, ProvidersApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-ldap-view")
|
||||
export class LDAPProviderViewPage extends AKElement {
|
||||
@property()
|
||||
set args(value: { [key: string]: number }) {
|
||||
this.providerID = value.id;
|
||||
}
|
||||
|
||||
@property({ type: Number })
|
||||
set providerID(value: number) {
|
||||
new ProvidersApi(DEFAULT_CONFIG)
|
||||
.providersLdapRetrieve({
|
||||
id: value,
|
||||
})
|
||||
.then((prov) => (this.provider = prov));
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
provider?: LDAPProvider;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFButton,
|
||||
PFBanner,
|
||||
PFForm,
|
||||
PFFormControl,
|
||||
PFList,
|
||||
PFGrid,
|
||||
PFPage,
|
||||
PFContent,
|
||||
PFCard,
|
||||
PFDescriptionList,
|
||||
AKGlobal,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener(EVENT_REFRESH, () => {
|
||||
if (!this.provider?.pk) return;
|
||||
this.providerID = this.provider?.pk;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.provider) {
|
||||
return html``;
|
||||
}
|
||||
return html`${
|
||||
this.provider?.assignedApplicationName
|
||||
? html``
|
||||
: html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
${t`Warning: Provider is not used by an Application.`}
|
||||
</div>`
|
||||
}
|
||||
${
|
||||
this.provider?.outpostSet.length < 1
|
||||
? html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
${t`Warning: Provider is not used by any Outpost.`}
|
||||
</div>`
|
||||
: html``
|
||||
}
|
||||
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__body">
|
||||
<dl class="pf-c-description-list pf-m-3-col-on-lg">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Name`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.name}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Assigned to application`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-provider-related-application
|
||||
.provider=${this.provider}
|
||||
></ak-provider-related-application>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Base DN`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.baseDn}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update LDAP Provider`} </span>
|
||||
<ak-provider-ldap-form slot="form" .instancePk=${this.provider.pk}>
|
||||
</ak-provider-ldap-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${t`Edit`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">
|
||||
${t`How to connect`}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<p>
|
||||
${t`Connect to the LDAP Server on port 389:`}
|
||||
</p>
|
||||
<ul class="pf-c-list">
|
||||
<li>${t`Check the IP of the Kubernetes service, or`}</li>
|
||||
<li>${t`The Host IP of the docker host`}</li>
|
||||
</ul>
|
||||
<form class="pf-c-form">
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">${t`Bind DN`}</span>
|
||||
</label>
|
||||
<!-- @ts-ignore -->
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value=${until(
|
||||
me().then((m) => {
|
||||
return `cn=${
|
||||
m.user.username
|
||||
},ou=users,${this.provider?.baseDn?.toLowerCase()}`;
|
||||
}),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">${t`Bind Password`}</span>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="Your authentik password"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">${t`Search base`}</span>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value=${ifDefined(this.provider?.baseDn?.toLowerCase())}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
417
web/src/admin/providers/oauth2/OAuth2ProviderForm.ts
Normal file
417
web/src/admin/providers/oauth2/OAuth2ProviderForm.ts
Normal file
@ -0,0 +1,417 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first, randomString } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import {
|
||||
ClientTypeEnum,
|
||||
CryptoApi,
|
||||
FlowsApi,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
IssuerModeEnum,
|
||||
OAuth2Provider,
|
||||
PropertymappingsApi,
|
||||
ProvidersApi,
|
||||
SourcesApi,
|
||||
SubModeEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-oauth2-form")
|
||||
export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
|
||||
loadInstance(pk: number): Promise<OAuth2Provider> {
|
||||
return new ProvidersApi(DEFAULT_CONFIG)
|
||||
.providersOauth2Retrieve({
|
||||
id: pk,
|
||||
})
|
||||
.then((provider) => {
|
||||
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
|
||||
return provider;
|
||||
});
|
||||
}
|
||||
|
||||
@property({ type: Boolean })
|
||||
showClientSecret = true;
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated provider.`;
|
||||
} else {
|
||||
return t`Successfully created provider.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: OAuth2Provider): Promise<OAuth2Provider> => {
|
||||
if (this.instance) {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({
|
||||
id: this.instance.pk || 0,
|
||||
oAuth2ProviderRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
|
||||
oAuth2ProviderRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authorization flow`}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
${until(
|
||||
new FlowsApi(DEFAULT_CONFIG)
|
||||
.flowsInstancesList({
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authorization,
|
||||
})
|
||||
.then((flows) => {
|
||||
return flows.results.map((flow) => {
|
||||
return html`<option
|
||||
value=${ifDefined(flow.pk)}
|
||||
?selected=${this.instance?.authorizationFlow === flow.pk}
|
||||
>
|
||||
${flow.name} (${flow.slug})
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used when authorizing this provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Protocol settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Client type`}
|
||||
?required=${true}
|
||||
name="clientType"
|
||||
>
|
||||
<select
|
||||
class="pf-c-form-control"
|
||||
@change=${(ev: Event) => {
|
||||
const target = ev.target as HTMLSelectElement;
|
||||
if (target.selectedOptions[0].value === ClientTypeEnum.Public) {
|
||||
this.showClientSecret = false;
|
||||
} else {
|
||||
this.showClientSecret = true;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option
|
||||
value=${ClientTypeEnum.Confidential}
|
||||
?selected=${this.instance?.clientType ===
|
||||
ClientTypeEnum.Confidential}
|
||||
>
|
||||
${t`Confidential`}
|
||||
</option>
|
||||
<option
|
||||
value=${ClientTypeEnum.Public}
|
||||
?selected=${this.instance?.clientType === ClientTypeEnum.Public}
|
||||
>
|
||||
${t`Public`}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Confidential clients are capable of maintaining the confidentiality of their credentials. Public clients are incapable.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Client ID`}
|
||||
?required=${true}
|
||||
name="clientId"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.clientId, randomString(40))}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
?hidden=${!this.showClientSecret}
|
||||
label=${t`Client Secret`}
|
||||
name="clientSecret"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.clientSecret, randomString(128))}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Redirect URIs/Origins (RegEx)`}
|
||||
name="redirectUris"
|
||||
>
|
||||
<textarea class="pf-c-form-control">
|
||||
${this.instance?.redirectUris}</textarea
|
||||
>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`If no explicit redirect URIs are specified, the first successfully used redirect URI will be saved.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Signing Key`} name="signingKey">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.signingKey === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new CryptoApi(DEFAULT_CONFIG)
|
||||
.cryptoCertificatekeypairsList({
|
||||
ordering: "name",
|
||||
hasKey: true,
|
||||
})
|
||||
.then((keys) => {
|
||||
return keys.results.map((key) => {
|
||||
let selected = this.instance?.signingKey === key.pk;
|
||||
if (!this.instance && keys.results.length === 1) {
|
||||
selected = true;
|
||||
}
|
||||
return html`<option
|
||||
value=${ifDefined(key.pk)}
|
||||
?selected=${selected}
|
||||
>
|
||||
${key.name} (${key.privateKeyType?.toUpperCase()})
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option
|
||||
value=${ifDefined(this.instance?.signingKey || undefined)}
|
||||
?selected=${this.instance?.signingKey !== undefined}
|
||||
>
|
||||
${t`Loading...`}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">${t`Key used to sign the tokens.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${t`Advanced protocol settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Access token validity`}
|
||||
?required=${true}
|
||||
name="accessCodeValidity"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.accessCodeValidity, "minutes=1")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure how long access tokens are valid for.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`If you are using an Implicit, client-side flow (where the token-endpoint isn't used), you probably want to increase this time.`}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Token validity`}
|
||||
?required=${true}
|
||||
name="tokenValidity"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.tokenValidity, "days=30")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure how long refresh tokens and their id_tokens are valid for.`}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsScopeList({
|
||||
ordering: "scope_name",
|
||||
})
|
||||
.then((scopes) => {
|
||||
return scopes.results.map((scope) => {
|
||||
let selected = false;
|
||||
if (!this.instance?.propertyMappings) {
|
||||
selected =
|
||||
scope.managed?.startsWith(
|
||||
"goauthentik.io/providers/oauth2/scope-",
|
||||
) || false;
|
||||
} else {
|
||||
selected = Array.from(
|
||||
this.instance?.propertyMappings,
|
||||
).some((su) => {
|
||||
return su == scope.pk;
|
||||
});
|
||||
}
|
||||
return html`<option
|
||||
value=${ifDefined(scope.pk)}
|
||||
?selected=${selected}
|
||||
>
|
||||
${scope.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Select which scopes can be used by the client. The client still has to specify the scope to access the data.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Hold control/command to select multiple items.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Subject mode`}
|
||||
?required=${true}
|
||||
name="subMode"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value="${SubModeEnum.HashedUserId}"
|
||||
?selected=${this.instance?.subMode === SubModeEnum.HashedUserId}
|
||||
>
|
||||
${t`Based on the Hashed User ID`}
|
||||
</option>
|
||||
<option
|
||||
value="${SubModeEnum.UserUsername}"
|
||||
?selected=${this.instance?.subMode === SubModeEnum.UserUsername}
|
||||
>
|
||||
${t`Based on the username`}
|
||||
</option>
|
||||
<option
|
||||
value="${SubModeEnum.UserEmail}"
|
||||
?selected=${this.instance?.subMode === SubModeEnum.UserEmail}
|
||||
>
|
||||
${t`Based on the User's Email. This is recommended over the UPN method.`}
|
||||
</option>
|
||||
<option
|
||||
value="${SubModeEnum.UserUpn}"
|
||||
?selected=${this.instance?.subMode === SubModeEnum.UserUpn}
|
||||
>
|
||||
${t`Based on the User's UPN, only works if user has a 'upn' attribute set. Use this method only if you have different UPN and Mail domains.`}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure what data should be used as unique User Identifier. For most cases, the default should be fine.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="includeClaimsInIdToken">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.includeClaimsInIdToken, true)}
|
||||
/>
|
||||
<label class="pf-c-check__label">
|
||||
${t`Include claims in id_token`}
|
||||
</label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Issuer mode`}
|
||||
?required=${true}
|
||||
name="issuerMode"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value="${IssuerModeEnum.PerProvider}"
|
||||
?selected=${this.instance?.issuerMode ===
|
||||
IssuerModeEnum.PerProvider}
|
||||
>
|
||||
${t`Each provider has a different issuer, based on the application slug.`}
|
||||
</option>
|
||||
<option
|
||||
value="${IssuerModeEnum.Global}"
|
||||
?selected=${this.instance?.issuerMode === IssuerModeEnum.Global}
|
||||
>
|
||||
${t`Same identifier is used for all providers`}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure how the issuer field of the ID Token should be filled.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${t`Machine-to-Machine authentication settings`}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${t`Trusted OIDC Sources`} name="jwksSources">
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
new SourcesApi(DEFAULT_CONFIG)
|
||||
.sourcesOauthList({
|
||||
ordering: "name",
|
||||
})
|
||||
.then((sources) => {
|
||||
return sources.results.map((source) => {
|
||||
const selected = (
|
||||
this.instance?.jwksSources || []
|
||||
).some((su) => {
|
||||
return su == source.pk;
|
||||
});
|
||||
return html`<option
|
||||
value=${source.pk}
|
||||
?selected=${selected}
|
||||
>
|
||||
${source.name} (${source.slug})
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Deprecated. Instead of using this field, configure the JWKS data/URL in Sources.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`JWTs signed by certificates configured here can be used to authenticate to the provider.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Hold control/command to select multiple items.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
281
web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts
Normal file
281
web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts
Normal file
@ -0,0 +1,281 @@
|
||||
import "@goauthentik/admin/providers/RelatedApplicationButton";
|
||||
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { convertToTitle } from "@goauthentik/common/utils";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/events/ObjectChangelog";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { OAuth2Provider, OAuth2ProviderSetupURLs, ProvidersApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-oauth2-view")
|
||||
export class OAuth2ProviderViewPage extends AKElement {
|
||||
@property({ type: Number })
|
||||
set providerID(value: number) {
|
||||
const api = new ProvidersApi(DEFAULT_CONFIG);
|
||||
api.providersOauth2Retrieve({
|
||||
id: value,
|
||||
}).then((prov) => {
|
||||
this.provider = prov;
|
||||
});
|
||||
api.providersOauth2SetupUrlsRetrieve({
|
||||
id: value,
|
||||
}).then((prov) => {
|
||||
this.providerUrls = prov;
|
||||
});
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
provider?: OAuth2Provider;
|
||||
|
||||
@property({ attribute: false })
|
||||
providerUrls?: OAuth2ProviderSetupURLs;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFButton,
|
||||
PFPage,
|
||||
PFGrid,
|
||||
PFContent,
|
||||
PFCard,
|
||||
PFDescriptionList,
|
||||
PFForm,
|
||||
PFFormControl,
|
||||
PFBanner,
|
||||
AKGlobal,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener(EVENT_REFRESH, () => {
|
||||
if (!this.provider?.pk) return;
|
||||
this.providerID = this.provider?.pk;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.provider) {
|
||||
return html``;
|
||||
}
|
||||
return html` ${
|
||||
this.provider?.assignedApplicationName
|
||||
? html``
|
||||
: html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
${t`Warning: Provider is not used by an Application.`}
|
||||
</div>`
|
||||
}
|
||||
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<dl class="pf-c-description-list pf-m-2-col-on-lg">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Name`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.name}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Assigned to application`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-provider-related-application
|
||||
.provider=${this.provider}
|
||||
></ak-provider-related-application>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Client type`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${convertToTitle(
|
||||
this.provider.clientType || "",
|
||||
)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Client ID`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.clientId}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Redirect URIs`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.redirectUris}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update OAuth2 Provider`} </span>
|
||||
<ak-provider-oauth2-form
|
||||
slot="form"
|
||||
.instancePk=${this.provider.pk || 0}
|
||||
>
|
||||
</ak-provider-oauth2-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${t`Edit`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<form class="pf-c-form">
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`OpenID Configuration URL`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.providerInfo || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`OpenID Configuration Issuer`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.issuer || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`Authorize URL`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.authorize || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`Token URL`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.token || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`Userinfo URL`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.userInfo || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`Logout URL`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.logout || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`JWKS URL`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${this.providerUrls?.jwks || t`-`}"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ak-tabs>`;
|
||||
}
|
||||
}
|
||||
449
web/src/admin/providers/proxy/ProxyProviderForm.ts
Normal file
449
web/src/admin/providers/proxy/ProxyProviderForm.ts
Normal file
@ -0,0 +1,449 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, css } from "lit";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
|
||||
import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
|
||||
|
||||
import {
|
||||
CryptoApi,
|
||||
FlowsApi,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PropertymappingsApi,
|
||||
ProvidersApi,
|
||||
ProxyMode,
|
||||
ProxyProvider,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-proxy-form")
|
||||
export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(
|
||||
PFToggleGroup,
|
||||
PFContent,
|
||||
PFList,
|
||||
PFSpacing,
|
||||
css`
|
||||
.pf-c-toggle-group {
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
loadInstance(pk: number): Promise<ProxyProvider> {
|
||||
return new ProvidersApi(DEFAULT_CONFIG)
|
||||
.providersProxyRetrieve({
|
||||
id: pk,
|
||||
})
|
||||
.then((provider) => {
|
||||
this.showHttpBasic = first(provider.basicAuthEnabled, true);
|
||||
this.mode = first(provider.mode, ProxyMode.Proxy);
|
||||
return provider;
|
||||
});
|
||||
}
|
||||
|
||||
@property({ type: Boolean })
|
||||
showHttpBasic = true;
|
||||
|
||||
@property({ attribute: false })
|
||||
mode: ProxyMode = ProxyMode.Proxy;
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated provider.`;
|
||||
} else {
|
||||
return t`Successfully created provider.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: ProxyProvider): Promise<ProxyProvider> => {
|
||||
data.mode = this.mode;
|
||||
if (this.instance) {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({
|
||||
id: this.instance.pk || 0,
|
||||
proxyProviderRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersProxyCreate({
|
||||
proxyProviderRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderHttpBasic(): TemplateResult {
|
||||
if (!this.showHttpBasic) {
|
||||
return html``;
|
||||
}
|
||||
return html`<ak-form-element-horizontal
|
||||
label=${t`HTTP-Basic Username Key`}
|
||||
name="basicAuthUserAttribute"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.basicAuthUserAttribute)}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`HTTP-Basic Password Key`}
|
||||
name="basicAuthPasswordAttribute"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.basicAuthPasswordAttribute)}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`User/Group Attribute used for the password part of the HTTP-Basic Header.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
renderModeSelector(): TemplateResult {
|
||||
return html` <div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.mode = ProxyMode.Proxy;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${t`Proxy`}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardSingle
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.mode = ProxyMode.ForwardSingle;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text"
|
||||
>${t`Forward auth (single application)`}</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardDomain
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.mode = ProxyMode.ForwardDomain;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${t`Forward auth (domain level)`}</span>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderSettings(): TemplateResult {
|
||||
switch (this.mode) {
|
||||
case ProxyMode.Proxy:
|
||||
return html`<p class="pf-u-mb-xl">
|
||||
${t`This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.`}
|
||||
</p>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External host`}
|
||||
?required=${true}
|
||||
name="externalHost"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.externalHost)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`The external URL you'll access the application at. Include any non-standard port.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Internal host`}
|
||||
?required=${true}
|
||||
name="internalHost"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.internalHost)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Upstream host that the requests are forwarded to.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="internalHostSslValidation">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.internalHostSslValidation, true)}
|
||||
/>
|
||||
<label class="pf-c-check__label">
|
||||
${t`Internal host SSL Validation`}
|
||||
</label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Validate SSL Certificates of upstream servers.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
case ProxyMode.ForwardSingle:
|
||||
return html`<p class="pf-u-mb-xl">
|
||||
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you).`}
|
||||
</p>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External host`}
|
||||
?required=${true}
|
||||
name="externalHost"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.externalHost)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`The external URL you'll access the application at. Include any non-standard port.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
case ProxyMode.ForwardDomain:
|
||||
return html`<p class="pf-u-mb-xl">
|
||||
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.`}
|
||||
</p>
|
||||
<div class="pf-u-mb-xl">
|
||||
${t`An example setup can look like this:`}
|
||||
<ul class="pf-c-list">
|
||||
<li>${t`authentik running on auth.example.com`}</li>
|
||||
<li>${t`app1 running on app1.example.com`}</li>
|
||||
</ul>
|
||||
${t`In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authentication URL`}
|
||||
?required=${true}
|
||||
name="externalHost"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.externalHost, window.location.origin)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`The external URL you'll authenticate at. The authentik core server should be reachable under this URL.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Cookie domain`}
|
||||
name="cookieDomain"
|
||||
?required=${true}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.cookieDomain)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authorization flow`}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
${until(
|
||||
new FlowsApi(DEFAULT_CONFIG)
|
||||
.flowsInstancesList({
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authorization,
|
||||
})
|
||||
.then((flows) => {
|
||||
return flows.results.map((flow) => {
|
||||
return html`<option
|
||||
value=${ifDefined(flow.pk)}
|
||||
?selected=${this.instance?.authorizationFlow === flow.pk}
|
||||
>
|
||||
${flow.name} (${flow.slug})
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used when authorizing this provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-c-toggle-group">${this.renderModeSelector()}</div>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">${this.renderSettings()}</div>
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Token validity`} name="tokenValidity">
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.tokenValidity, "hours=24")}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${t`Configure how long tokens are valid for.`}</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">${t`Advanced protocol settings`}</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${t`Certificate`} name="certificate">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.certificate === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new CryptoApi(DEFAULT_CONFIG)
|
||||
.cryptoCertificatekeypairsList({
|
||||
ordering: "name",
|
||||
hasKey: true,
|
||||
})
|
||||
.then((keys) => {
|
||||
return keys.results.map((key) => {
|
||||
return html`<option
|
||||
value=${ifDefined(key.pk)}
|
||||
?selected=${this.instance?.certificate === key.pk}
|
||||
>
|
||||
${key.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option
|
||||
value=${ifDefined(this.instance?.certificate || undefined)}
|
||||
?selected=${this.instance?.certificate !== undefined}
|
||||
>
|
||||
${t`Loading...`}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsScopeList({
|
||||
ordering: "scope_name",
|
||||
})
|
||||
.then((scopes) => {
|
||||
return scopes.results
|
||||
.filter((scope) => {
|
||||
return !scope.managed?.startsWith(
|
||||
"goauthentik.io/providers",
|
||||
);
|
||||
})
|
||||
.map((scope) => {
|
||||
const selected = (
|
||||
this.instance?.propertyMappings || []
|
||||
).some((su) => {
|
||||
return su == scope.pk;
|
||||
});
|
||||
return html`<option
|
||||
value=${ifDefined(scope.pk)}
|
||||
?selected=${selected}
|
||||
>
|
||||
${scope.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Additional scope mappings, which are passed to the proxy.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Hold control/command to select multiple items.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label="${this.mode === ProxyMode.ForwardDomain
|
||||
? t`Unauthenticated URLs`
|
||||
: t`Unauthenticated Paths`}"
|
||||
name="skipPathRegex"
|
||||
>
|
||||
<textarea class="pf-c-form-control">
|
||||
${this.instance?.skipPathRegex}</textarea
|
||||
>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal name="basicAuthEnabled">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.basicAuthEnabled, false)}
|
||||
@change=${(ev: Event) => {
|
||||
const el = ev.target as HTMLInputElement;
|
||||
this.showHttpBasic = el.checked;
|
||||
}}
|
||||
/>
|
||||
<label class="pf-c-check__label">
|
||||
${t`Set HTTP-Basic Authentication`}
|
||||
</label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Set a custom HTTP-Basic Authentication header based on values from authentik.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.renderHttpBasic()}
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
313
web/src/admin/providers/proxy/ProxyProviderViewPage.ts
Normal file
313
web/src/admin/providers/proxy/ProxyProviderViewPage.ts
Normal file
@ -0,0 +1,313 @@
|
||||
import "@goauthentik/admin/providers/RelatedApplicationButton";
|
||||
import "@goauthentik/admin/providers/proxy/ProxyProviderForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { convertToSlug } from "@goauthentik/common/utils";
|
||||
import MDCaddyStandalone from "@goauthentik/docs/providers/proxy/_caddy_standalone.md";
|
||||
import MDNginxIngress from "@goauthentik/docs/providers/proxy/_nginx_ingress.md";
|
||||
import MDNginxPM from "@goauthentik/docs/providers/proxy/_nginx_proxy_manager.md";
|
||||
import MDNginxStandalone from "@goauthentik/docs/providers/proxy/_nginx_standalone.md";
|
||||
import MDTraefikCompose from "@goauthentik/docs/providers/proxy/_traefik_compose.md";
|
||||
import MDTraefikIngress from "@goauthentik/docs/providers/proxy/_traefik_ingress.md";
|
||||
import MDTraefikStandalone from "@goauthentik/docs/providers/proxy/_traefik_standalone.md";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { PFColor } from "@goauthentik/elements/Label";
|
||||
import { MarkdownDocument } from "@goauthentik/elements/Markdown";
|
||||
import "@goauthentik/elements/Markdown";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/events/ObjectChangelog";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { ProvidersApi, ProxyMode, ProxyProvider } from "@goauthentik/api";
|
||||
|
||||
export function ModeToLabel(action?: ProxyMode): string {
|
||||
if (!action) return "";
|
||||
switch (action) {
|
||||
case ProxyMode.Proxy:
|
||||
return t`Proxy`;
|
||||
case ProxyMode.ForwardSingle:
|
||||
return t`Forward auth (single application)`;
|
||||
case ProxyMode.ForwardDomain:
|
||||
return t`Forward auth (domain-level)`;
|
||||
}
|
||||
}
|
||||
|
||||
export function isForward(mode: ProxyMode): boolean {
|
||||
switch (mode) {
|
||||
case ProxyMode.Proxy:
|
||||
return false;
|
||||
case ProxyMode.ForwardSingle:
|
||||
case ProxyMode.ForwardDomain:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-provider-proxy-view")
|
||||
export class ProxyProviderViewPage extends AKElement {
|
||||
@property()
|
||||
set args(value: { [key: string]: number }) {
|
||||
this.providerID = value.id;
|
||||
}
|
||||
|
||||
@property({ type: Number })
|
||||
set providerID(value: number) {
|
||||
new ProvidersApi(DEFAULT_CONFIG)
|
||||
.providersProxyRetrieve({
|
||||
id: value,
|
||||
})
|
||||
.then((prov) => (this.provider = prov));
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
provider?: ProxyProvider;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFButton,
|
||||
PFPage,
|
||||
PFGrid,
|
||||
PFContent,
|
||||
PFForm,
|
||||
PFFormControl,
|
||||
PFCard,
|
||||
PFDescriptionList,
|
||||
PFBanner,
|
||||
AKGlobal,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener(EVENT_REFRESH, () => {
|
||||
if (!this.provider?.pk) return;
|
||||
this.providerID = this.provider?.pk;
|
||||
});
|
||||
}
|
||||
|
||||
renderConfigTemplate(markdown: MarkdownDocument): MarkdownDocument {
|
||||
const extHost = new URL(this.provider?.externalHost || "http://a");
|
||||
// See website/docs/providers/proxy/forward_auth.mdx
|
||||
if (this.provider?.mode === ProxyMode.ForwardSingle) {
|
||||
markdown.html = markdown.html
|
||||
.replaceAll("authentik.company", window.location.hostname)
|
||||
.replaceAll("outpost.company:9000", window.location.hostname)
|
||||
.replaceAll("https://app.company", extHost.toString())
|
||||
.replaceAll("app.company", extHost.hostname);
|
||||
} else if (this.provider?.mode == ProxyMode.ForwardDomain) {
|
||||
markdown.html = markdown.html
|
||||
.replaceAll("authentik.company", window.location.hostname)
|
||||
.replaceAll("outpost.company:9000", extHost.toString())
|
||||
.replaceAll("https://app.company", extHost.toString())
|
||||
.replaceAll("app.company", extHost.hostname);
|
||||
}
|
||||
return markdown;
|
||||
}
|
||||
|
||||
renderConfig(): TemplateResult {
|
||||
const serves = [
|
||||
{
|
||||
label: t`Nginx (Ingress)`,
|
||||
md: MDNginxIngress,
|
||||
},
|
||||
{
|
||||
label: t`Nginx (Proxy Manager)`,
|
||||
md: MDNginxPM,
|
||||
},
|
||||
{
|
||||
label: t`Nginx (standalone)`,
|
||||
md: MDNginxStandalone,
|
||||
},
|
||||
{
|
||||
label: t`Traefik (Ingress)`,
|
||||
md: MDTraefikIngress,
|
||||
},
|
||||
{
|
||||
label: t`Traefik (Compose)`,
|
||||
md: MDTraefikCompose,
|
||||
},
|
||||
{
|
||||
label: t`Traefik (Standalone)`,
|
||||
md: MDTraefikStandalone,
|
||||
},
|
||||
{
|
||||
label: t`Caddy (Standalone)`,
|
||||
md: MDCaddyStandalone,
|
||||
},
|
||||
];
|
||||
return html`<ak-tabs pageIdentifier="proxy-setup">
|
||||
${serves.map((server) => {
|
||||
return html`<section
|
||||
slot="page-${convertToSlug(server.label)}"
|
||||
data-tab-title="${server.label}"
|
||||
class="pf-c-page__main-section pf-m-light pf-m-no-padding-mobile"
|
||||
>
|
||||
<ak-markdown .md=${this.renderConfigTemplate(server.md)}></ak-markdown>
|
||||
</section>`;
|
||||
})}</ak-tabs
|
||||
>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.provider) {
|
||||
return html``;
|
||||
}
|
||||
return html`${this.provider?.assignedApplicationName
|
||||
? html``
|
||||
: html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
${t`Warning: Provider is not used by an Application.`}
|
||||
</div>`}
|
||||
${this.provider?.outpostSet.length < 1
|
||||
? html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
${t`Warning: Provider is not used by any Outpost.`}
|
||||
</div>`
|
||||
: html``}
|
||||
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__body">
|
||||
<dl class="pf-c-description-list pf-m-3-col-on-lg">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Name`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.name}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Assigned to application`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-provider-related-application
|
||||
.provider=${this.provider}
|
||||
></ak-provider-related-application>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Internal Host`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.internalHost}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`External Host`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<a target="_blank" href="${this.provider.externalHost}"
|
||||
>${this.provider.externalHost}</a
|
||||
>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Basic-Auth`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-label
|
||||
color=${this.provider.basicAuthEnabled
|
||||
? PFColor.Green
|
||||
: PFColor.Grey}
|
||||
>
|
||||
${this.provider.basicAuthEnabled ? t`Yes` : t`No`}
|
||||
</ak-label>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Mode`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${ModeToLabel(this.provider.mode || ProxyMode.Proxy)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update Proxy Provider`} </span>
|
||||
<ak-provider-proxy-form
|
||||
slot="form"
|
||||
.instancePk=${this.provider.pk || 0}
|
||||
>
|
||||
</ak-provider-proxy-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${t`Edit`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">${t`Protocol Settings`}</div>
|
||||
<div class="pf-c-card__body">
|
||||
<form class="pf-c-form">
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`Allowed Redirect URIs`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value=${this.provider.redirectUris}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">${t`Setup`}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${isForward(this.provider?.mode || ProxyMode.Proxy)
|
||||
? html` ${this.renderConfig()} `
|
||||
: html` <p>${t`No additional setup is required.`}</p> `}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
434
web/src/admin/providers/saml/SAMLProviderForm.ts
Normal file
434
web/src/admin/providers/saml/SAMLProviderForm.ts
Normal file
@ -0,0 +1,434 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import {
|
||||
CryptoApi,
|
||||
DigestAlgorithmEnum,
|
||||
FlowsApi,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PropertymappingsApi,
|
||||
ProvidersApi,
|
||||
SAMLProvider,
|
||||
SignatureAlgorithmEnum,
|
||||
SpBindingEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-saml-form")
|
||||
export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
|
||||
loadInstance(pk: number): Promise<SAMLProvider> {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersSamlRetrieve({
|
||||
id: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated provider.`;
|
||||
} else {
|
||||
return t`Successfully created provider.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: SAMLProvider): Promise<SAMLProvider> => {
|
||||
if (this.instance) {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersSamlUpdate({
|
||||
id: this.instance.pk || 0,
|
||||
sAMLProviderRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersSamlCreate({
|
||||
sAMLProviderRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authorization flow`}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
${until(
|
||||
new FlowsApi(DEFAULT_CONFIG)
|
||||
.flowsInstancesList({
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authorization,
|
||||
})
|
||||
.then((flows) => {
|
||||
return flows.results.map((flow) => {
|
||||
return html`<option
|
||||
value=${ifDefined(flow.pk)}
|
||||
?selected=${this.instance?.authorizationFlow === flow.pk}
|
||||
>
|
||||
${flow.name} (${flow.slug})
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used when authorizing this provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Protocol settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${t`ACS URL`} ?required=${true} name="acsUrl">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.acsUrl)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Issuer`} ?required=${true} name="issuer">
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.issuer || "authentik"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${t`Also known as EntityID.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Service Provider Binding`}
|
||||
?required=${true}
|
||||
name="spBinding"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=${SpBindingEnum.Redirect}
|
||||
?selected=${this.instance?.spBinding === SpBindingEnum.Redirect}
|
||||
>
|
||||
${t`Redirect`}
|
||||
</option>
|
||||
<option
|
||||
value=${SpBindingEnum.Post}
|
||||
?selected=${this.instance?.spBinding === SpBindingEnum.Post}
|
||||
>
|
||||
${t`Post`}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Determines how authentik sends the response back to the Service Provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Audience`} name="audience">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.audience)}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${t`Advanced protocol settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${t`Signing Certificate`} name="signingKp">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.signingKp === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new CryptoApi(DEFAULT_CONFIG)
|
||||
.cryptoCertificatekeypairsList({
|
||||
ordering: "name",
|
||||
hasKey: true,
|
||||
})
|
||||
.then((keys) => {
|
||||
return keys.results.map((key) => {
|
||||
return html`<option
|
||||
value=${ifDefined(key.pk)}
|
||||
?selected=${this.instance?.signingKp === key.pk}
|
||||
>
|
||||
${key.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option
|
||||
value=${ifDefined(this.instance?.signingKp || undefined)}
|
||||
?selected=${this.instance?.signingKp !== undefined}
|
||||
>
|
||||
${t`Loading...`}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Certificate used to sign outgoing Responses going to the Service Provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Verification Certificate`}
|
||||
name="verificationKp"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=""
|
||||
?selected=${this.instance?.verificationKp === undefined}
|
||||
>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new CryptoApi(DEFAULT_CONFIG)
|
||||
.cryptoCertificatekeypairsList({
|
||||
ordering: "name",
|
||||
})
|
||||
.then((keys) => {
|
||||
return keys.results.map((key) => {
|
||||
return html`<option
|
||||
value=${ifDefined(key.pk)}
|
||||
?selected=${this.instance?.verificationKp ===
|
||||
key.pk}
|
||||
>
|
||||
${key.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option
|
||||
value=${ifDefined(this.instance?.verificationKp || undefined)}
|
||||
?selected=${this.instance?.verificationKp !== undefined}
|
||||
>
|
||||
${t`Loading...`}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Property mappings`}
|
||||
?required=${true}
|
||||
name="propertyMappings"
|
||||
>
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsSamlList({
|
||||
ordering: "saml_name",
|
||||
})
|
||||
.then((mappings) => {
|
||||
return mappings.results.map((mapping) => {
|
||||
let selected = false;
|
||||
if (!this.instance?.propertyMappings) {
|
||||
selected =
|
||||
mapping.managed?.startsWith(
|
||||
"goauthentik.io/providers/saml",
|
||||
) || false;
|
||||
} else {
|
||||
selected = Array.from(
|
||||
this.instance?.propertyMappings,
|
||||
).some((su) => {
|
||||
return su == mapping.pk;
|
||||
});
|
||||
}
|
||||
return html`<option
|
||||
value=${ifDefined(mapping.pk)}
|
||||
?selected=${selected}
|
||||
>
|
||||
${mapping.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Hold control/command to select multiple items.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`NameID Property Mapping`}
|
||||
name="nameIdMapping"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=""
|
||||
?selected=${this.instance?.nameIdMapping === undefined}
|
||||
>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsSamlList({
|
||||
ordering: "saml_name",
|
||||
})
|
||||
.then((mappings) => {
|
||||
return mappings.results.map((mapping) => {
|
||||
return html`<option
|
||||
value=${ifDefined(mapping.pk)}
|
||||
?selected=${this.instance?.nameIdMapping ===
|
||||
mapping.pk}
|
||||
>
|
||||
${mapping.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Assertion valid not before`}
|
||||
?required=${true}
|
||||
name="assertionValidNotBefore"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.assertionValidNotBefore || "minutes=-5"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Configure the maximum allowed time drift for an assertion.`}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Assertion valid not on or after`}
|
||||
?required=${true}
|
||||
name="assertionValidNotOnOrAfter"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.assertionValidNotOnOrAfter || "minutes=5"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Assertion not valid on or after current time + this value.`}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Session valid not on or after`}
|
||||
?required=${true}
|
||||
name="sessionValidNotOnOrAfter"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.sessionValidNotOnOrAfter || "minutes=86400"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Session not valid on or after current time + this value.`}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Digest algorithm`}
|
||||
?required=${true}
|
||||
name="digestAlgorithm"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=${DigestAlgorithmEnum._200009Xmldsigsha1}
|
||||
?selected=${this.instance?.digestAlgorithm ===
|
||||
DigestAlgorithmEnum._200009Xmldsigsha1}
|
||||
>
|
||||
${t`SHA1`}
|
||||
</option>
|
||||
<option
|
||||
value=${DigestAlgorithmEnum._200104Xmlencsha256}
|
||||
?selected=${this.instance?.digestAlgorithm ===
|
||||
DigestAlgorithmEnum._200104Xmlencsha256 ||
|
||||
this.instance?.digestAlgorithm === undefined}
|
||||
>
|
||||
${t`SHA256`}
|
||||
</option>
|
||||
<option
|
||||
value=${DigestAlgorithmEnum._200104XmldsigMoresha384}
|
||||
?selected=${this.instance?.digestAlgorithm ===
|
||||
DigestAlgorithmEnum._200104XmldsigMoresha384}
|
||||
>
|
||||
${t`SHA384`}
|
||||
</option>
|
||||
<option
|
||||
value=${DigestAlgorithmEnum._200104Xmlencsha512}
|
||||
?selected=${this.instance?.digestAlgorithm ===
|
||||
DigestAlgorithmEnum._200104Xmlencsha512}
|
||||
>
|
||||
${t`SHA512`}
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Signature algorithm`}
|
||||
?required=${true}
|
||||
name="signatureAlgorithm"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=${SignatureAlgorithmEnum._200009XmldsigrsaSha1}
|
||||
?selected=${this.instance?.signatureAlgorithm ===
|
||||
SignatureAlgorithmEnum._200009XmldsigrsaSha1}
|
||||
>
|
||||
${t`RSA-SHA1`}
|
||||
</option>
|
||||
<option
|
||||
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha256}
|
||||
?selected=${this.instance?.signatureAlgorithm ===
|
||||
SignatureAlgorithmEnum._200104XmldsigMorersaSha256 ||
|
||||
this.instance?.signatureAlgorithm === undefined}
|
||||
>
|
||||
${t`RSA-SHA256`}
|
||||
</option>
|
||||
<option
|
||||
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
|
||||
?selected=${this.instance?.signatureAlgorithm ===
|
||||
SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
|
||||
>
|
||||
${t`RSA-SHA384`}
|
||||
</option>
|
||||
<option
|
||||
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
|
||||
?selected=${this.instance?.signatureAlgorithm ===
|
||||
SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
|
||||
>
|
||||
${t`RSA-SHA512`}
|
||||
</option>
|
||||
<option
|
||||
value=${SignatureAlgorithmEnum._200009XmldsigdsaSha1}
|
||||
?selected=${this.instance?.signatureAlgorithm ===
|
||||
SignatureAlgorithmEnum._200009XmldsigdsaSha1}
|
||||
>
|
||||
${t`DSA-SHA1`}
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
75
web/src/admin/providers/saml/SAMLProviderImportForm.ts
Normal file
75
web/src/admin/providers/saml/SAMLProviderImportForm.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
||||
import { Form } from "@goauthentik/elements/forms/Form";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import {
|
||||
FlowsApi,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
ProvidersApi,
|
||||
SAMLProvider,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-saml-import-form")
|
||||
export class SAMLProviderImportForm extends Form<SAMLProvider> {
|
||||
getSuccessMessage(): string {
|
||||
return t`Successfully imported provider.`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
send = (data: SAMLProvider): Promise<void> => {
|
||||
const file = this.getFormFiles()["metadata"];
|
||||
if (!file) {
|
||||
throw new SentryIgnoredError("No form data");
|
||||
}
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersSamlImportMetadataCreate({
|
||||
file: file,
|
||||
name: data.name,
|
||||
authorizationFlow: data.authorizationFlow,
|
||||
});
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input type="text" class="pf-c-form-control" required />
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authorization flow`}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
${until(
|
||||
new FlowsApi(DEFAULT_CONFIG)
|
||||
.flowsInstancesList({
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authorization,
|
||||
})
|
||||
.then((flows) => {
|
||||
return flows.results.map((flow) => {
|
||||
return html`<option value=${flow.slug}>
|
||||
${flow.name} (${flow.slug})
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used when authorizing this provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal label=${t`Metadata`} name="metadata">
|
||||
<input type="file" value="" class="pf-c-form-control" />
|
||||
</ak-form-element-horizontal>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
296
web/src/admin/providers/saml/SAMLProviderViewPage.ts
Normal file
296
web/src/admin/providers/saml/SAMLProviderViewPage.ts
Normal file
@ -0,0 +1,296 @@
|
||||
import "@goauthentik/admin/providers/RelatedApplicationButton";
|
||||
import "@goauthentik/admin/providers/saml/SAMLProviderForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/events/ObjectChangelog";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { CryptoApi, ProvidersApi, SAMLProvider } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-saml-view")
|
||||
export class SAMLProviderViewPage extends AKElement {
|
||||
@property()
|
||||
set args(value: { [key: string]: number }) {
|
||||
this.providerID = value.id;
|
||||
}
|
||||
|
||||
@property({ type: Number })
|
||||
set providerID(value: number) {
|
||||
new ProvidersApi(DEFAULT_CONFIG)
|
||||
.providersSamlRetrieve({
|
||||
id: value,
|
||||
})
|
||||
.then((prov) => (this.provider = prov));
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
provider?: SAMLProvider;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFButton,
|
||||
PFPage,
|
||||
PFGrid,
|
||||
PFContent,
|
||||
PFCard,
|
||||
PFDescriptionList,
|
||||
PFForm,
|
||||
PFFormControl,
|
||||
PFBanner,
|
||||
AKGlobal,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener(EVENT_REFRESH, () => {
|
||||
if (!this.provider?.pk) return;
|
||||
this.providerID = this.provider?.pk;
|
||||
});
|
||||
}
|
||||
|
||||
async renderRelatedObjects(): Promise<TemplateResult> {
|
||||
if (!this.provider?.signingKp) {
|
||||
return Promise.resolve(html``);
|
||||
}
|
||||
const kp = await new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsRetrieve({
|
||||
kpUuid: this.provider.signingKp,
|
||||
});
|
||||
return html` <div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">${t`Related objects`}</div>
|
||||
<div class="pf-c-card__body">
|
||||
<dl class="pf-c-description-list pf-m-2-col">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Download signing certificate`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<a
|
||||
class="pf-c-button pf-m-primary"
|
||||
href=${kp.certificateDownloadUrl}
|
||||
>${t`Download`}</a
|
||||
>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.provider) {
|
||||
return html``;
|
||||
}
|
||||
return html`${
|
||||
this.provider?.assignedApplicationName
|
||||
? html``
|
||||
: html`<div slot="header" class="pf-c-banner pf-m-warning">
|
||||
${t`Warning: Provider is not used by an Application.`}
|
||||
</div>`
|
||||
}
|
||||
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__body">
|
||||
<dl class="pf-c-description-list pf-m-3-col-on-lg">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Name`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.name}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Assigned to application`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-provider-related-application
|
||||
.provider=${this.provider}
|
||||
></ak-provider-related-application>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`ACS URL`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.acsUrl}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Audience`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.audience || "-"}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Issuer`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.issuer}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update SAML Provider`} </span>
|
||||
<ak-provider-saml-form slot="form" .instancePk=${this.provider.pk || 0}>
|
||||
</ak-provider-saml-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${t`Edit`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
</div>
|
||||
${until(this.renderRelatedObjects())}
|
||||
${
|
||||
this.provider.assignedApplicationName
|
||||
? html` <div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">${t`SAML Configuration`}</div>
|
||||
<div class="pf-c-card__body">
|
||||
<form class="pf-c-form">
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`EntityID/Issuer`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider?.issuer)}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`SSO URL (Post)`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider.urlSsoPost)}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`SSO URL (Redirect)`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider.urlSsoRedirect)}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${t`SSO URL (IdP-initiated Login)`}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider.urlSsoInit)}"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
|
||||
<div class="pf-c-card__title">${t`SAML Metadata`}</div>
|
||||
<div class="pf-c-card__body">
|
||||
${until(
|
||||
new ProvidersApi(DEFAULT_CONFIG)
|
||||
.providersSamlMetadataRetrieve({
|
||||
id: this.provider.pk || 0,
|
||||
})
|
||||
.then((m) => {
|
||||
return html`<ak-codemirror
|
||||
mode="xml"
|
||||
?readOnly=${true}
|
||||
value="${ifDefined(m.metadata)}"
|
||||
></ak-codemirror>`;
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<a
|
||||
class="pf-c-button pf-m-primary"
|
||||
target="_blank"
|
||||
href=${this.provider.urlDownloadMetadata}
|
||||
>
|
||||
${t`Download`}
|
||||
</a>
|
||||
<ak-action-button
|
||||
class="pf-m-secondary"
|
||||
.apiRequest=${() => {
|
||||
return navigator.clipboard.writeText(
|
||||
this.provider?.urlDownloadMetadata || "",
|
||||
);
|
||||
}}
|
||||
>
|
||||
${t`Copy download URL`}
|
||||
</ak-action-button>
|
||||
</div>
|
||||
</div>`
|
||||
: html``
|
||||
}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user