web/admin: migrate provider forms

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-04-01 15:39:59 +02:00
parent 7a0ebbdc53
commit 4e3701ca8d
15 changed files with 725 additions and 97 deletions

View File

@ -14652,7 +14652,6 @@ definitions:
Provider: Provider:
required: required:
- name - name
- application
- authorization_flow - authorization_flow
type: object type: object
properties: properties:
@ -14664,9 +14663,6 @@ definitions:
title: Name title: Name
type: string type: string
minLength: 1 minLength: 1
application:
title: Application
type: string
authorization_flow: authorization_flow:
title: Authorization flow title: Authorization flow
description: Flow used when authorizing this provider. description: Flow used when authorizing this provider.
@ -15456,7 +15452,6 @@ definitions:
OAuth2Provider: OAuth2Provider:
required: required:
- name - name
- application
- authorization_flow - authorization_flow
type: object type: object
properties: properties:
@ -15468,9 +15463,6 @@ definitions:
title: Name title: Name
type: string type: string
minLength: 1 minLength: 1
application:
title: Application
type: string
authorization_flow: authorization_flow:
title: Authorization flow title: Authorization flow
description: Flow used when authorizing this provider. description: Flow used when authorizing this provider.
@ -16686,7 +16678,6 @@ definitions:
ProxyProvider: ProxyProvider:
required: required:
- name - name
- application
- authorization_flow - authorization_flow
- internal_host - internal_host
- external_host - external_host
@ -16700,9 +16691,6 @@ definitions:
title: Name title: Name
type: string type: string
minLength: 1 minLength: 1
application:
title: Application
type: string
authorization_flow: authorization_flow:
title: Authorization flow title: Authorization flow
description: Flow used when authorizing this provider. description: Flow used when authorizing this provider.
@ -16774,7 +16762,6 @@ definitions:
SAMLProvider: SAMLProvider:
required: required:
- name - name
- application
- authorization_flow - authorization_flow
- acs_url - acs_url
type: object type: object
@ -16787,9 +16774,6 @@ definitions:
title: Name title: Name
type: string type: string
minLength: 1 minLength: 1
application:
title: Application
type: string
authorization_flow: authorization_flow:
title: Authorization flow title: Authorization flow
description: Flow used when authorizing this provider. description: Flow used when authorizing this provider.
@ -16892,6 +16876,14 @@ definitions:
type: string type: string
format: uuid format: uuid
x-nullable: true x-nullable: true
sp_binding:
title: Service Provider Binding
description: This determines how authentik sends the response back to the
Service Provider.
type: string
enum:
- redirect
- post
SAMLMetadata: SAMLMetadata:
type: object type: object
properties: properties:

View File

@ -4,10 +4,6 @@ export class AdminURLManager {
return `/administration/policies/${rest}`; return `/administration/policies/${rest}`;
} }
static providers(rest: string): string {
return `/administration/providers/${rest}`;
}
static stages(rest: string): string { static stages(rest: string): string {
return `/administration/stages/${rest}`; return `/administration/stages/${rest}`;
} }

View File

@ -72,12 +72,14 @@ export class GroupForm extends Form<Group> {
?required=${true} ?required=${true}
name="users"> name="users">
<select class="pf-c-form-control" multiple> <select class="pf-c-form-control" multiple>
${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({}).then(users => { ${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: "username",
}).then(users => {
return users.results.map(user => { return users.results.map(user => {
const selected = Array.from(this.group?.users || []).some(su => { const selected = Array.from(this.group?.users || []).some(su => {
return su == user.pk; return su == user.pk;
}); });
return html`<option value=${ifDefined(user.pk)} ?selected=${selected}>${user.username}</option>`; return html`<option value=${ifDefined(user.pk)} ?selected=${selected}>${user.username} (${user.name})</option>`;
}); });
}))} }))}
</select> </select>

View File

@ -77,10 +77,7 @@ export class ServiceConnectionDockerForm extends Form<DockerServiceConnection> {
ordering: "pk" ordering: "pk"
}).then(certs => { }).then(certs => {
return certs.results.map(cert => { return certs.results.map(cert => {
const selected = Array.from(this.sc?.tlsVerification || []).some(sp => { return html`<option value=${ifDefined(cert.pk)} ?selected=${this.sc?.tlsVerification === cert.pk}>${cert.name}</option>`;
return sp == cert.pk;
});
return html`<option value=${ifDefined(cert.pk)} ?selected=${selected}>${cert.name}</option>`;
}); });
}))} }))}
</select> </select>
@ -96,10 +93,7 @@ export class ServiceConnectionDockerForm extends Form<DockerServiceConnection> {
ordering: "pk" ordering: "pk"
}).then(certs => { }).then(certs => {
return certs.results.map(cert => { return certs.results.map(cert => {
const selected = Array.from(this.sc?.tlsAuthentication || []).some(sp => { return html`<option value=${ifDefined(cert.pk)} ?selected=${this.sc?.tlsAuthentication === cert.pk}>${cert.name}</option>`;
return sp == cert.pk;
});
return html`<option value=${ifDefined(cert.pk)} ?selected=${selected}>${cert.name}</option>`;
}); });
}))} }))}
</select> </select>

View File

@ -6,6 +6,7 @@ import { DEFAULT_CONFIG } from "../../api/Config";
import { Form } from "../../elements/forms/Form"; import { Form } from "../../elements/forms/Form";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/forms/HorizontalFormElement"; import "../../elements/forms/HorizontalFormElement";
import "../../elements/CodeMirror";
@customElement("ak-property-mapping-ldap-form") @customElement("ak-property-mapping-ldap-form")
export class PropertyMappingLDAPForm extends Form<LDAPPropertyMapping> { export class PropertyMappingLDAPForm extends Form<LDAPPropertyMapping> {
@ -60,7 +61,7 @@ export class PropertyMappingLDAPForm extends Form<LDAPPropertyMapping> {
<ak-form-element-horizontal <ak-form-element-horizontal
label=${gettext("Expression")} label=${gettext("Expression")}
name="expression"> name="expression">
<ak-codemirror mode="python" value="${this.mapping?.expression}"> <ak-codemirror mode="python" value="${ifDefined(this.mapping?.expression)}">
</ak-codemirror> </ak-codemirror>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables. Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables.

View File

@ -6,6 +6,7 @@ import { DEFAULT_CONFIG } from "../../api/Config";
import { Form } from "../../elements/forms/Form"; import { Form } from "../../elements/forms/Form";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/forms/HorizontalFormElement"; import "../../elements/forms/HorizontalFormElement";
import "../../elements/CodeMirror";
@customElement("ak-property-mapping-saml-form") @customElement("ak-property-mapping-saml-form")
export class PropertyMappingLDAPForm extends Form<SAMLPropertyMapping> { export class PropertyMappingLDAPForm extends Form<SAMLPropertyMapping> {
@ -62,7 +63,7 @@ export class PropertyMappingLDAPForm extends Form<SAMLPropertyMapping> {
<ak-form-element-horizontal <ak-form-element-horizontal
label=${gettext("Friendly Name")} label=${gettext("Friendly Name")}
name="friendlyName"> name="friendlyName">
<input type="text" value="${ifDefined(this.mapping?.friendlyName)}" class="pf-c-form-control"> <input type="text" value="${ifDefined(this.mapping?.friendlyName || "")}" class="pf-c-form-control">
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${gettext("Optionally set the `FriendlyName` value of the Assertion attribute.")} ${gettext("Optionally set the `FriendlyName` value of the Assertion attribute.")}
</p> </p>
@ -70,7 +71,7 @@ export class PropertyMappingLDAPForm extends Form<SAMLPropertyMapping> {
<ak-form-element-horizontal <ak-form-element-horizontal
label=${gettext("Expression")} label=${gettext("Expression")}
name="expression"> name="expression">
<ak-codemirror mode="python" value="${this.mapping?.expression}"> <ak-codemirror mode="python" value="${ifDefined(this.mapping?.expression)}">
</ak-codemirror> </ak-codemirror>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables. Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables.

View File

@ -6,6 +6,7 @@ import { DEFAULT_CONFIG } from "../../api/Config";
import { Form } from "../../elements/forms/Form"; import { Form } from "../../elements/forms/Form";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/forms/HorizontalFormElement"; import "../../elements/forms/HorizontalFormElement";
import "../../elements/CodeMirror";
@customElement("ak-property-mapping-scope-form") @customElement("ak-property-mapping-scope-form")
export class PropertyMappingScopeForm extends Form<ScopeMapping> { export class PropertyMappingScopeForm extends Form<ScopeMapping> {
@ -67,7 +68,7 @@ export class PropertyMappingScopeForm extends Form<ScopeMapping> {
<ak-form-element-horizontal <ak-form-element-horizontal
label=${gettext("Expression")} label=${gettext("Expression")}
name="expression"> name="expression">
<ak-codemirror mode="python" value="${this.mapping?.expression}"> <ak-codemirror mode="python" value="${ifDefined(this.mapping?.expression)}">
</ak-codemirror> </ak-codemirror>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables. Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables.

View File

@ -3,16 +3,20 @@ import { customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../api/Client"; import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage"; import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown"; import "../../elements/buttons/Dropdown";
import "../../elements/forms/DeleteForm"; import "../../elements/forms/DeleteForm";
import "../../elements/forms/ModalForm";
import "../../elements/forms/ProxyForm";
import "./oauth2/OAuth2ProviderForm";
import "./proxy/ProxyProviderForm";
import "./saml/SAMLProviderForm";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants"; import { PAGE_SIZE } from "../../constants";
import { Provider, ProvidersApi } from "authentik-api"; import { Provider, ProvidersApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy"; import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-provider-list") @customElement("ak-provider-list")
export class ProviderListPage extends TablePage<Provider> { export class ProviderListPage extends TablePage<Provider> {
@ -63,12 +67,29 @@ export class ProviderListPage extends TablePage<Provider> {
${gettext("Warning: Provider not assigned to any application.")}`, ${gettext("Warning: Provider not assigned to any application.")}`,
html`${item.verboseName}`, html`${item.verboseName}`,
html` html`
<ak-modal-button href="${AdminURLManager.providers(`${item.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-secondary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext(`Update ${item.verboseName}`)}
</span>
<ak-proxy-form
slot="form"
.args=${{
"providerUUID": item.pk
}}
type=${ifDefined(item.objectType)}
.typeMap=${{
"oauth2": "ak-provider-oauth2-form",
"saml": "ak-provider-saml-form",
"proxy": "ak-provider-proxy-form",
}}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${gettext("Source")} objectLabel=${gettext("Source")}
@ -95,12 +116,22 @@ export class ProviderListPage extends TablePage<Provider> {
${until(new ProvidersApi(DEFAULT_CONFIG).providersAllTypes({}).then((types) => { ${until(new ProvidersApi(DEFAULT_CONFIG).providersAllTypes({}).then((types) => {
return types.map((type) => { return types.map((type) => {
return html`<li> return html`<li>
<ak-modal-button href="${type.link}"> <ak-forms-modal>
<button slot="trigger" class="pf-c-dropdown__menu-item">${type.name}<br> <span slot="submit">
${gettext("Create")}
</span>
<span slot="header">
${gettext(`Create ${type.name}`)}
</span>
<ak-proxy-form
slot="form"
type=${type.link}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small> <small>${type.description}</small>
</button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</li>`; </li>`;
}); });
}), html`<ak-spinner></ak-spinner>`)} }), html`<ak-spinner></ak-spinner>`)}

View File

@ -4,9 +4,9 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/EmptyState"; import "../../elements/EmptyState";
import "./SAMLProviderViewPage"; import "./saml/SAMLProviderViewPage";
import "./OAuth2ProviderViewPage"; import "./oauth2/OAuth2ProviderViewPage";
import "./ProxyProviderViewPage"; import "./proxy/ProxyProviderViewPage";
import { Provider, ProvidersApi } from "authentik-api"; import { Provider, ProvidersApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";

View File

@ -0,0 +1,206 @@
import { CryptoApi, FlowDesignationEnum, FlowsApi, OAuth2Provider, OAuth2ProviderClientTypeEnum, OAuth2ProviderIssuerModeEnum, OAuth2ProviderJwtAlgEnum, OAuth2ProviderSubModeEnum, PropertymappingsApi, ProvidersApi } from "authentik-api";
import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { Form } from "../../../elements/forms/Form";
import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../../elements/forms/HorizontalFormElement";
@customElement("ak-provider-oauth2-form")
export class OAuth2ProviderFormPage extends Form<OAuth2Provider> {
set providerUUID(value: number) {
new ProvidersApi(DEFAULT_CONFIG).providersOauth2Read({
id: value,
}).then(provider => {
this.provider = provider;
});
}
@property({attribute: false})
provider?: OAuth2Provider;
getSuccessMessage(): string {
if (this.provider) {
return gettext("Successfully updated provider.");
} else {
return gettext("Successfully created provider.");
}
}
send = (data: OAuth2Provider): Promise<OAuth2Provider> => {
if (this.provider) {
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({
id: this.provider.pk || 0,
data: data
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
data: data
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${gettext("Name")}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.provider?.name)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Authorization flow")}
?required=${true}
name="authorizationFlow">
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowDesignationEnum.Authorization,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${ifDefined(flow.pk)} ?selected=${this.provider?.authorizationFlow === flow.pk}>${flow.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Flow used when authorizing this provider.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Client type")}
?required=${true}
name="clientType">
<select class="pf-c-form-control">
<option value=${OAuth2ProviderClientTypeEnum.Confidential} ?selected=${this.provider?.clientType === OAuth2ProviderClientTypeEnum.Confidential}>
${gettext("Confidential")}
</option>
<option value=${OAuth2ProviderClientTypeEnum.Public} ?selected=${this.provider?.clientType === OAuth2ProviderClientTypeEnum.Public}>
${gettext("Public")}
</option>
</select>
<p class="pf-c-form__helper-text">${gettext("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=${gettext("Client ID")}
?required=${true}
name="clientId">
<input type="text" value="${ifDefined(this.provider?.clientId)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Client Secret")}
name="clientSecret">
<input type="text" value="${ifDefined(this.provider?.clientSecret /* TODO: Generate secret */)}" class="pf-c-form-control">
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Token validity")}
?required=${true}
name="tokenValidity">
<input type="text" value="${this.provider?.tokenValidity || "minutes=10"}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("JWT Algorithm")}
?required=${true}
name="jwtAlg">
<select class="pf-c-form-control">
<option value=${OAuth2ProviderJwtAlgEnum.Rs256} ?selected=${this.provider?.jwtAlg === OAuth2ProviderJwtAlgEnum.Rs256}>
${gettext("RS256 (Asymmetric Encryption)")}
</option>
<option value=${OAuth2ProviderJwtAlgEnum.Hs256} ?selected=${this.provider?.jwtAlg === OAuth2ProviderJwtAlgEnum.Hs256}>
${gettext("HS256 (Symmetric Encryption)")}
</option>
</select>
<p class="pf-c-form__helper-text">${gettext("Algorithm used to sign the JWT Tokens.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Scopes")}
?required=${true}
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 => {
const selected = Array.from(this.provider?.propertyMappings || []).some(su => {
return su == scope.pk;
});
return html`<option value=${ifDefined(scope.pk)} ?selected=${selected}>${scope.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Select which scopes can be used by the client. The client stil has to specify the scope to access the data.")}</p>
<p class="pf-c-form__helper-text">${gettext("Hold control/command to select multiple items.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("RSA Key")}
?required=${true}
name="rsaKey">
<select class="pf-c-form-control">
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: "true",
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.provider?.rsaKey === key.pk}>${key.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Redirect URIs")}
name="redirectUris">
<textarea class="pf-c-form-control">${this.provider?.redirectUris}</textarea>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Subject mode")}
?required=${true}
name="subMode">
<select class="pf-c-form-control">
<option value="${OAuth2ProviderSubModeEnum.HashedUserId}" ?selected=${this.provider?.subMode === OAuth2ProviderSubModeEnum.HashedUserId}>
${gettext("Based on the Hashed User ID")}
</option>
<option value="${OAuth2ProviderSubModeEnum.UserUsername}" ?selected=${this.provider?.subMode === OAuth2ProviderSubModeEnum.UserUsername}>
${gettext("Based on the username")}
</option>
<option value="${OAuth2ProviderSubModeEnum.UserEmail}" ?selected=${this.provider?.subMode === OAuth2ProviderSubModeEnum.UserEmail}>
${gettext("Based on the User's Email. This is recommended over the UPN method.")}
</option>
<option value="${OAuth2ProviderSubModeEnum.UserUpn}" ?selected=${this.provider?.subMode === OAuth2ProviderSubModeEnum.UserUpn}>
${gettext("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">
${gettext("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=${this.provider?.includeClaimsInIdToken || false}>
<label class="pf-c-check__label">
${gettext("Include claims in id_token")}
</label>
</div>
<p class="pf-c-form__helper-text">${gettext("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=${gettext("Issuer mode")}
?required=${true}
name="issuerMode">
<select class="pf-c-form-control">
<option value="${OAuth2ProviderIssuerModeEnum.PerProvider}" ?selected=${this.provider?.issuerMode === OAuth2ProviderIssuerModeEnum.PerProvider}>
${gettext("Each provider has a different issuer, based on the application slug.")}
</option>
<option value="${OAuth2ProviderIssuerModeEnum.Global}" ?selected=${this.provider?.issuerMode === OAuth2ProviderIssuerModeEnum.Global}>
${gettext("Same identifier is used for all providers")}
</option>
</select>
<p class="pf-c-form__helper-text">
${gettext("Configure how the issuer field of the ID Token should be filled.")}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -9,23 +9,23 @@ import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList
import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css"; import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";
import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import AKGlobal from "../../authentik.css"; import AKGlobal from "../../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import "../../elements/buttons/ModalButton"; import "../../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../../elements/buttons/SpinnerButton";
import "../../elements/CodeMirror"; import "../../../elements/CodeMirror";
import "../../elements/Tabs"; import "../../../elements/Tabs";
import "../../elements/events/ObjectChangelog"; import "../../../elements/events/ObjectChangelog";
import "./RelatedApplicationButton"; import "../RelatedApplicationButton";
import { Page } from "../../elements/Page"; import "./OAuth2ProviderForm";
import { convertToTitle } from "../../utils"; import { Page } from "../../../elements/Page";
import { convertToTitle } from "../../../utils";
import { OAuth2Provider, OAuth2ProviderSetupURLs, ProvidersApi } from "authentik-api"; import { OAuth2Provider, OAuth2ProviderSetupURLs, ProvidersApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminURLManager } from "../../api/legacy"; import { EVENT_REFRESH } from "../../../constants";
import { EVENT_REFRESH } from "../../constants";
@customElement("ak-provider-oauth2-view") @customElement("ak-provider-oauth2-view")
export class OAuth2ProviderViewPage extends Page { export class OAuth2ProviderViewPage extends Page {
@ -128,12 +128,21 @@ export class OAuth2ProviderViewPage extends Page {
</dl> </dl>
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.providers(`${this.provider.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-primary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update OAuth2 Provider")}
</span>
<ak-provider-oauth2-form
slot="form"
.providerUUID=${this.provider.pk || 0}>
</ak-provider-oauth2-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,139 @@
import { CryptoApi, FlowDesignationEnum, FlowsApi, ProvidersApi, ProxyProvider } from "authentik-api";
import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { Form } from "../../../elements/forms/Form";
import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../../elements/forms/HorizontalFormElement";
@customElement("ak-provider-proxy-form")
export class ProxyProviderFormPage extends Form<ProxyProvider> {
set providerUUID(value: number) {
new ProvidersApi(DEFAULT_CONFIG).providersProxyRead({
id: value,
}).then(provider => {
this.provider = provider;
});
}
@property({attribute: false})
provider?: ProxyProvider;
getSuccessMessage(): string {
if (this.provider) {
return gettext("Successfully updated provider.");
} else {
return gettext("Successfully created provider.");
}
}
send = (data: ProxyProvider): Promise<ProxyProvider> => {
if (this.provider) {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({
id: this.provider.pk || 0,
data: data
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyCreate({
data: data
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${gettext("Name")}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.provider?.name)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Authorization flow")}
?required=${true}
name="authorizationFlow">
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowDesignationEnum.Authorization,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${ifDefined(flow.pk)} ?selected=${this.provider?.authorizationFlow === flow.pk}>${flow.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Flow used when authorizing this provider.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Internal host")}
?required=${true}
name="internalHost">
<input type="text" value="${ifDefined(this.provider?.internalHost)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="internalHostSslValidation">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${this.provider?.internalHostSslValidation || false}>
<label class="pf-c-check__label">
${gettext("Internal host SSL Validation")}
</label>
</div>
<p class="pf-c-form__helper-text">${gettext("Validate SSL Certificates of upstream servers.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("External host")}
?required=${true}
name="externalHost">
<input type="text" value="${ifDefined(this.provider?.externalHost)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Certificate")}
name="certificate">
<select class="pf-c-form-control">
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: "true",
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.provider?.certificate === key.pk}>${key.name}</option>`;
});
}))}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Skip path regex")}
name="skipPathRegex">
<textarea class="pf-c-form-control">${this.provider?.skipPathRegex}</textarea>
<p class="pf-c-form__helper-text">${gettext("Regular expressions for which authentication is not required. Each new line is interpreted as a new Regular Expression.")}</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=${this.provider?.basicAuthEnabled || false}>
<label class="pf-c-check__label">
${gettext("Set HTTP-Basic Authentication")}
</label>
</div>
<p class="pf-c-form__helper-text">${gettext("Set a custom HTTP-Basic Authentication header based on values from authentik.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("HTTP-Basic Username Key")}
name="basicAuthUserAttribute">
<input type="text" value="${ifDefined(this.provider?.basicAuthUserAttribute)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${gettext("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=${gettext("HTTP-Basic Password Key")}
name="basicAuthPasswordAttribute">
<input type="text" value="${ifDefined(this.provider?.basicAuthPasswordAttribute)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${gettext("User/Group Attribute used for the password part of the HTTP-Basic Header.")}</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -9,20 +9,20 @@ import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList
import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css"; import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";
import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import AKGlobal from "../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../../authentik.css";
import "../../elements/buttons/ModalButton"; import "../../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../../elements/buttons/SpinnerButton";
import "../../elements/CodeMirror"; import "../../../elements/CodeMirror";
import "../../elements/Tabs"; import "../../../elements/Tabs";
import "../../elements/events/ObjectChangelog"; import "../../../elements/events/ObjectChangelog";
import "./RelatedApplicationButton"; import "../RelatedApplicationButton";
import { Page } from "../../elements/Page"; import "./ProxyProviderForm";
import { Page } from "../../../elements/Page";
import { ProvidersApi, ProxyProvider } from "authentik-api"; import { ProvidersApi, ProxyProvider } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminURLManager } from "../../api/legacy"; import { EVENT_REFRESH } from "../../../constants";
import { EVENT_REFRESH } from "../../constants";
@customElement("ak-provider-proxy-view") @customElement("ak-provider-proxy-view")
export class ProxyProviderViewPage extends Page { export class ProxyProviderViewPage extends Page {
@ -128,12 +128,21 @@ export class ProxyProviderViewPage extends Page {
</dl> </dl>
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.providers(`${this.provider.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-primary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update Proxy Provider")}
</span>
<ak-provider-proxy-form
slot="form"
.providerUUID=${this.provider.pk || 0}>
</ak-provider-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,237 @@
import { CryptoApi, FlowDesignationEnum, FlowsApi, SAMLProvider, ProvidersApi, PropertymappingsApi, SAMLProviderSpBindingEnum, SAMLProviderDigestAlgorithmEnum, SAMLProviderSignatureAlgorithmEnum } from "authentik-api";
import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { Form } from "../../../elements/forms/Form";
import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../../elements/forms/HorizontalFormElement";
@customElement("ak-provider-saml-form")
export class SAMLProviderFormPage extends Form<SAMLProvider> {
set providerUUID(value: number) {
new ProvidersApi(DEFAULT_CONFIG).providersSamlRead({
id: value,
}).then(provider => {
this.provider = provider;
});
}
@property({attribute: false})
provider?: SAMLProvider;
getSuccessMessage(): string {
if (this.provider) {
return gettext("Successfully updated provider.");
} else {
return gettext("Successfully created provider.");
}
}
send = (data: SAMLProvider): Promise<SAMLProvider> => {
if (this.provider) {
return new ProvidersApi(DEFAULT_CONFIG).providersSamlUpdate({
id: this.provider.pk || 0,
data: data
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersSamlCreate({
data: data
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${gettext("Name")}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.provider?.name)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Authorization flow")}
?required=${true}
name="authorizationFlow">
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowDesignationEnum.Authorization,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${ifDefined(flow.pk)} ?selected=${this.provider?.authorizationFlow === flow.pk}>${flow.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Flow used when authorizing this provider.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("ACS URL")}
?required=${true}
name="acsUrl">
<input type="text" value="${ifDefined(this.provider?.acsUrl)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Issuer")}
?required=${true}
name="issuer">
<input type="text" value="${this.provider?.issuer || "authentik"}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Service Provider Binding")}
?required=${true}
name="spBinding">
<select class="pf-c-form-control">
<option value=${SAMLProviderSpBindingEnum.Redirect} ?selected=${this.provider?.spBinding === SAMLProviderSpBindingEnum.Redirect}>
${gettext("Redirect")}
</option>
<option value=${SAMLProviderSpBindingEnum.Post} ?selected=${this.provider?.spBinding === SAMLProviderSpBindingEnum.Post}>
${gettext("Post")}
</option>
</select>
<p class="pf-c-form__helper-text">${gettext("Determines how authentik sends the response back to the Service Provider.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Audience")}
name="audience">
<input type="text" value="${ifDefined(this.provider?.audience)}" class="pf-c-form-control">
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Signing Keypair")}
name="signingKp">
<select class="pf-c-form-control">
<option value="" ?selected=${this.provider?.signingKp === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: "true",
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.provider?.signingKp === key.pk}>${key.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Keypair used to sign outgoing Responses going to the Service Provider.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Verification Certificate")}
?required=${true}
name="verificationKp">
<select class="pf-c-form-control">
<option value="" ?selected=${this.provider?.verificationKp === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.provider?.verificationKp === key.pk}>${key.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("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=${gettext("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 => {
const selected = Array.from(this.provider?.propertyMappings || []).some(su => {
return su == mapping.pk;
});
return html`<option value=${ifDefined(mapping.pk)} ?selected=${selected}>${mapping.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Hold control/command to select multiple items.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("NameID Property Mapping")}
name="nameIdMapping">
<select class="pf-c-form-control">
<option value="" ?selected=${this.provider?.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.provider?.nameIdMapping === mapping.pk}>${mapping.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("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=${gettext("Assertion valid not before")}
?required=${true}
name="assertionValidNotBefore">
<input type="text" value="${this.provider?.assertionValidNotBefore || "minutes=-5"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Assertion valid not on or after")}
?required=${true}
name="assertionValidNotOnOrAfter">
<input type="text" value="${this.provider?.assertionValidNotOnOrAfter || "minutes=5"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Session valid not on or after")}
?required=${true}
name="sessionValidNotOnOrAfter">
<input type="text" value="${this.provider?.sessionValidNotOnOrAfter || "minutes=86400"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Digest algorithm")}
?required=${true}
name="digestAlgorithm">
<select class="pf-c-form-control">
<option value=${SAMLProviderDigestAlgorithmEnum._200009Xmldsigsha1} ?selected=${this.provider?.digestAlgorithm === SAMLProviderDigestAlgorithmEnum._200009Xmldsigsha1}>
${gettext("SHA1")}
</option>
<option value=${SAMLProviderDigestAlgorithmEnum._200104Xmlencsha256} ?selected=${this.provider?.digestAlgorithm === SAMLProviderDigestAlgorithmEnum._200104Xmlencsha256 || this.provider?.digestAlgorithm === undefined}>
${gettext("SHA256")}
</option>
<option value=${SAMLProviderDigestAlgorithmEnum._200104XmldsigMoresha384} ?selected=${this.provider?.digestAlgorithm === SAMLProviderDigestAlgorithmEnum._200104XmldsigMoresha384}>
${gettext("SHA384")}
</option>
<option value=${SAMLProviderDigestAlgorithmEnum._200104Xmlencsha512} ?selected=${this.provider?.digestAlgorithm === SAMLProviderDigestAlgorithmEnum._200104Xmlencsha512}>
${gettext("SHA512")}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Signature algorithm")}
?required=${true}
name="signatureAlgorithm">
<select class="pf-c-form-control">
<option value=${SAMLProviderSignatureAlgorithmEnum._200009XmldsigrsaSha1} ?selected=${this.provider?.signatureAlgorithm === SAMLProviderSignatureAlgorithmEnum._200009XmldsigrsaSha1}>
${gettext("RSA-SHA1")}
</option>
<option value=${SAMLProviderSignatureAlgorithmEnum._200104XmldsigMorersaSha256} ?selected=${this.provider?.signatureAlgorithm === SAMLProviderSignatureAlgorithmEnum._200104XmldsigMorersaSha256 || this.provider?.signatureAlgorithm === undefined}>
${gettext("RSA-SHA256")}
</option>
<option value=${SAMLProviderSignatureAlgorithmEnum._200104XmldsigMorersaSha384} ?selected=${this.provider?.signatureAlgorithm === SAMLProviderSignatureAlgorithmEnum._200104XmldsigMorersaSha384}>
${gettext("RSA-SHA384")}
</option>
<option value=${SAMLProviderSignatureAlgorithmEnum._200104XmldsigMorersaSha512} ?selected=${this.provider?.signatureAlgorithm === SAMLProviderSignatureAlgorithmEnum._200104XmldsigMorersaSha512}>
${gettext("RSA-SHA512")}
</option>
<option value=${SAMLProviderSignatureAlgorithmEnum._200009XmldsigdsaSha1} ?selected=${this.provider?.signatureAlgorithm === SAMLProviderSignatureAlgorithmEnum._200009XmldsigdsaSha1}>
${gettext("DSA-SHA1")}
</option>
</select>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -10,20 +10,21 @@ import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList
import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css"; import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";
import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import AKGlobal from "../../authentik.css"; import AKGlobal from "../../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import "../../elements/buttons/ModalButton"; import "../../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../../elements/buttons/SpinnerButton";
import "../../elements/CodeMirror"; import "../../../elements/CodeMirror";
import "../../elements/Tabs"; import "../../../elements/Tabs";
import "../../elements/events/ObjectChangelog"; import "../../../elements/events/ObjectChangelog";
import "./RelatedApplicationButton"; import "../RelatedApplicationButton";
import { Page } from "../../elements/Page"; import "./SAMLProviderForm";
import { Page } from "../../../elements/Page";
import { ProvidersApi, SAMLProvider } from "authentik-api"; import { ProvidersApi, SAMLProvider } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminURLManager, AppURLManager } from "../../api/legacy"; import { AppURLManager } from "../../../api/legacy";
import { EVENT_REFRESH } from "../../constants"; import { EVENT_REFRESH } from "../../../constants";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-provider-saml-view") @customElement("ak-provider-saml-view")
@ -121,12 +122,21 @@ export class SAMLProviderViewPage extends Page {
</dl> </dl>
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.providers(`${this.provider.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-primary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update SAML Provider")}
</span>
<ak-provider-saml-form
slot="form"
.providerUUID=${this.provider.pk || 0}>
</ak-provider-saml-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</div> </div>
</div> </div>
</div> </div>