Committed harmony on SAML. Streamlined the tests even further.
This commit is contained in:
@ -1,363 +1,34 @@
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title";
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import AkCryptoCertificateSearch from "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-multi-select";
|
||||
import "@goauthentik/components/ak-number-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||
import { html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { html } from "lit";
|
||||
|
||||
import {
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PaginatedSAMLPropertyMappingList,
|
||||
PropertymappingsApi,
|
||||
SAMLProvider,
|
||||
} from "@goauthentik/api";
|
||||
import { SAMLProvider } from "@goauthentik/api";
|
||||
|
||||
import BaseProviderPanel from "../BaseProviderPanel";
|
||||
import {
|
||||
digestAlgorithmOptions,
|
||||
signatureAlgorithmOptions,
|
||||
spBindingOptions,
|
||||
} from "./SamlProviderOptions";
|
||||
import "./saml-property-mappings-search";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-by-saml-configuration")
|
||||
export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPanel {
|
||||
@state()
|
||||
propertyMappings?: PaginatedSAMLPropertyMappingList;
|
||||
|
||||
@state()
|
||||
hasSigningKp = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsProviderSamlList({
|
||||
ordering: "saml_name",
|
||||
})
|
||||
.then((propertyMappings: PaginatedSAMLPropertyMappingList) => {
|
||||
this.propertyMappings = propertyMappings;
|
||||
});
|
||||
}
|
||||
|
||||
propertyMappingConfiguration(provider?: SAMLProvider) {
|
||||
const propertyMappings = this.propertyMappings?.results ?? [];
|
||||
|
||||
const configuredMappings = (providerMappings: string[]) =>
|
||||
propertyMappings.map((pm) => pm.pk).filter((pmpk) => providerMappings.includes(pmpk));
|
||||
|
||||
const managedMappings = () =>
|
||||
propertyMappings
|
||||
.filter((pm) => (pm?.managed ?? "").startsWith("goauthentik.io/providers/saml"))
|
||||
.map((pm) => pm.pk);
|
||||
|
||||
const pmValues = provider?.propertyMappings
|
||||
? configuredMappings(provider?.propertyMappings ?? [])
|
||||
: managedMappings();
|
||||
|
||||
const propertyPairs = propertyMappings.map((pm) => [pm.pk, pm.name]);
|
||||
|
||||
return { pmValues, propertyPairs };
|
||||
}
|
||||
|
||||
render() {
|
||||
const provider = this.wizard.provider as SAMLProvider | undefined;
|
||||
const errors = this.wizard.errors.provider;
|
||||
|
||||
const { pmValues, propertyPairs } = this.propertyMappingConfiguration(provider);
|
||||
|
||||
return html` <ak-wizard-title>${msg("Configure SAML Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
<ak-text-input
|
||||
name="name"
|
||||
value=${ifDefined(provider?.name)}
|
||||
required
|
||||
label=${msg("Name")}
|
||||
.errorMessages=${errors?.name ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authorization flow")}
|
||||
?required=${true}
|
||||
name="authorizationFlow"
|
||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="acsUrl"
|
||||
value=${ifDefined(provider?.acsUrl)}
|
||||
required
|
||||
label=${msg("ACS URL")}
|
||||
.errorMessages=${errors?.acsUrl ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="issuer"
|
||||
value=${provider?.issuer || "authentik"}
|
||||
required
|
||||
label=${msg("Issuer")}
|
||||
help=${msg("Also known as EntityID.")}
|
||||
.errorMessages=${errors?.issuer ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-radio-input
|
||||
name="spBinding"
|
||||
label=${msg("Service Provider Binding")}
|
||||
required
|
||||
.options=${spBindingOptions}
|
||||
.value=${provider?.spBinding}
|
||||
help=${msg(
|
||||
"Determines how authentik sends the response back to the Service Provider.",
|
||||
)}
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
<ak-text-input
|
||||
name="audience"
|
||||
value=${ifDefined(provider?.audience)}
|
||||
label=${msg("Audience")}
|
||||
.errorMessages=${errors?.audience ?? []}
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<ak-form-element-horizontal
|
||||
name="authenticationFlow"
|
||||
label=${msg("Authentication flow")}
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Flow used when a user access this provider and is not authenticated.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when logging out of this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Signing Certificate")}
|
||||
name="signingKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.signingKp ?? undefined)}
|
||||
@input=${(ev: InputEvent) => {
|
||||
const setHasSigningKp = (ev: InputEvent) => {
|
||||
const target = ev.target as AkCryptoCertificateSearch;
|
||||
if (!target) return;
|
||||
this.hasSigningKp = !!target.selectedKeypair;
|
||||
}}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Certificate used to sign outgoing Responses going to the Service Provider.",
|
||||
};
|
||||
|
||||
return html` <ak-wizard-title>${msg("Configure SAML Provider")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
|
||||
${renderForm(
|
||||
(this.wizard.provider as SAMLProvider) ?? {},
|
||||
this.wizard.errors.provider,
|
||||
setHasSigningKp,
|
||||
this.hasSigningKp,
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${
|
||||
this.hasSigningKp
|
||||
? html` <ak-form-element-horizontal name="signAssertion">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(provider?.signAssertion, true)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i
|
||||
class="fas fa-check"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label"
|
||||
>${msg("Sign assertions")}</span
|
||||
>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="signResponse">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(provider?.signResponse, false)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i
|
||||
class="fas fa-check"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label"
|
||||
>${msg("Sign responses")}</span
|
||||
>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`
|
||||
: nothing
|
||||
}
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Verification Certificate")}
|
||||
name="verificationKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.verificationKp ?? undefined)}
|
||||
nokey
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"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=${msg("Encryption Certificate")}
|
||||
name="encryptionKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
certificate=${ifDefined(provider?.encryptionKp ?? undefined)}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When selected, encrypted assertions will be decrypted using this keypair.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-multi-select
|
||||
label=${msg("Property Mappings")}
|
||||
name="propertyMappings"
|
||||
.options=${propertyPairs}
|
||||
.values=${pmValues}
|
||||
.richhelp=${html` <p class="pf-c-form__helper-text">
|
||||
${msg("Property mappings used for user mapping.")}
|
||||
</p>`}
|
||||
></ak-multi-select>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("NameID Property Mapping")}
|
||||
name="nameIdMapping"
|
||||
>
|
||||
<ak-saml-property-mapping-search
|
||||
name="nameIdMapping"
|
||||
propertymapping=${ifDefined(provider?.nameIdMapping ?? undefined)}
|
||||
></ak-saml-property-mapping-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"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-text-input
|
||||
name="assertionValidNotBefore"
|
||||
value=${provider?.assertionValidNotBefore || "minutes=-5"}
|
||||
required
|
||||
label=${msg("Assertion valid not before")}
|
||||
help=${msg(
|
||||
"Configure the maximum allowed time drift for an assertion.",
|
||||
)}
|
||||
.errorMessages=${errors?.assertionValidNotBefore ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="assertionValidNotOnOrAfter"
|
||||
value=${provider?.assertionValidNotOnOrAfter || "minutes=5"}
|
||||
required
|
||||
label=${msg("Assertion valid not on or after")}
|
||||
help=${msg(
|
||||
"Assertion not valid on or after current time + this value.",
|
||||
)}
|
||||
.errorMessages=${errors?.assertionValidNotOnOrAfter ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-text-input
|
||||
name="sessionValidNotOnOrAfter"
|
||||
value=${provider?.sessionValidNotOnOrAfter || "minutes=86400"}
|
||||
required
|
||||
label=${msg("Session valid not on or after")}
|
||||
help=${msg("Session not valid on or after current time + this value.")}
|
||||
.errorMessages=${errors?.sessionValidNotOnOrAfter ?? []}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-radio-input
|
||||
name="digestAlgorithm"
|
||||
label=${msg("Digest algorithm")}
|
||||
required
|
||||
.options=${digestAlgorithmOptions}
|
||||
.value=${provider?.digestAlgorithm}
|
||||
>
|
||||
</ak-radio-input>
|
||||
|
||||
<ak-radio-input
|
||||
name="signatureAlgorithm"
|
||||
label=${msg("Signature algorithm")}
|
||||
required
|
||||
.options=${signatureAlgorithmOptions}
|
||||
.value=${provider?.signatureAlgorithm}
|
||||
>
|
||||
</ak-radio-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,58 +1,11 @@
|
||||
import {
|
||||
digestAlgorithmOptions,
|
||||
signatureAlgorithmOptions,
|
||||
} from "@goauthentik/admin/applications/wizard/methods/saml/SamlProviderOptions";
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import AkCryptoCertificateSearch from "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/forms/Radio";
|
||||
import "@goauthentik/elements/forms/SearchSelect";
|
||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import {
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PropertymappingsApi,
|
||||
PropertymappingsProviderSamlListRequest,
|
||||
ProvidersApi,
|
||||
SAMLPropertyMapping,
|
||||
SAMLProvider,
|
||||
SpBindingEnum,
|
||||
} from "@goauthentik/api";
|
||||
import { ProvidersApi, SAMLProvider } from "@goauthentik/api";
|
||||
|
||||
export async function samlPropertyMappingsProvider(page = 1, search = "") {
|
||||
const propertyMappings = await new PropertymappingsApi(
|
||||
DEFAULT_CONFIG,
|
||||
).propertymappingsProviderSamlList({
|
||||
ordering: "saml_name",
|
||||
pageSize: 20,
|
||||
search: search.trim(),
|
||||
page,
|
||||
});
|
||||
return {
|
||||
pagination: propertyMappings.pagination,
|
||||
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
|
||||
};
|
||||
}
|
||||
|
||||
export function makeSAMLPropertyMappingsSelector(instanceMappings?: string[]) {
|
||||
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
|
||||
return localMappings
|
||||
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
|
||||
: ([_0, _1, _2, mapping]: DualSelectPair<SAMLPropertyMapping>) =>
|
||||
mapping?.managed?.startsWith("goauthentik.io/providers/saml");
|
||||
}
|
||||
import { renderForm } from "./SAMLProviderFormForm.js";
|
||||
|
||||
@customElement("ak-provider-saml-form")
|
||||
export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
|
||||
@ -80,368 +33,14 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
|
||||
}
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal label=${msg("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=${msg("Authorization flow")}
|
||||
required
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${this.instance?.authorizationFlow}
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("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=${msg("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">${msg("Also known as EntityID.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Service Provider Binding")}
|
||||
?required=${true}
|
||||
name="spBinding"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: msg("Redirect"),
|
||||
value: SpBindingEnum.Redirect,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: msg("Post"),
|
||||
value: SpBindingEnum.Post,
|
||||
},
|
||||
]}
|
||||
.value=${this.instance?.spBinding}
|
||||
>
|
||||
</ak-radio>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Determines how authentik sends the response back to the Service Provider.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("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"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
?required=${false}
|
||||
name="authenticationFlow"
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${this.instance?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Flow used when a user access this provider and is not authenticated.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${this.instance?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when logging out of this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Signing Certificate")}
|
||||
name="signingKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${this.instance?.signingKp}
|
||||
@input=${(ev: InputEvent) => {
|
||||
renderForm() {
|
||||
const setHasSigningKp = (ev: InputEvent) => {
|
||||
const target = ev.target as AkCryptoCertificateSearch;
|
||||
if (!target) return;
|
||||
this.hasSigningKp = !!target.selectedKeypair;
|
||||
}}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Certificate used to sign outgoing Responses going to the Service Provider.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.hasSigningKp
|
||||
? html` <ak-form-element-horizontal name="signAssertion">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(this.instance?.signAssertion, true)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label"
|
||||
>${msg("Sign assertions")}</span
|
||||
>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="signResponse">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(this.instance?.signResponse, false)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label"
|
||||
>${msg("Sign responses")}</span
|
||||
>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`
|
||||
: nothing}
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Verification Certificate")}
|
||||
name="verificationKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${this.instance?.verificationKp}
|
||||
nokey
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"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=${msg("Encryption Certificate")}
|
||||
name="encryptionKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${this.instance?.encryptionKp}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When selected, assertions will be encrypted using this keypair.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Property mappings")}
|
||||
name="propertyMappings"
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${samlPropertyMappingsProvider}
|
||||
.selector=${makeSAMLPropertyMappingsSelector(
|
||||
this.instance?.propertyMappings,
|
||||
)}
|
||||
available-label=${msg("Available User Property Mappings")}
|
||||
selected-label=${msg("Selected User Property Mappings")}
|
||||
></ak-dual-select-dynamic-selected>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("NameID Property Mapping")}
|
||||
name="nameIdMapping"
|
||||
>
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (
|
||||
query?: string,
|
||||
): Promise<SAMLPropertyMapping[]> => {
|
||||
const args: PropertymappingsProviderSamlListRequest = {
|
||||
ordering: "saml_name",
|
||||
};
|
||||
if (query !== undefined) {
|
||||
args.search = query;
|
||||
}
|
||||
const items = await new PropertymappingsApi(
|
||||
DEFAULT_CONFIG,
|
||||
).propertymappingsProviderSamlList(args);
|
||||
return items.results;
|
||||
}}
|
||||
.renderElement=${(item: SAMLPropertyMapping): string => {
|
||||
return item.name;
|
||||
}}
|
||||
.value=${(
|
||||
item: SAMLPropertyMapping | undefined,
|
||||
): string | undefined => {
|
||||
return item?.pk;
|
||||
}}
|
||||
.selected=${(item: SAMLPropertyMapping): boolean => {
|
||||
return this.instance?.nameIdMapping === item.pk;
|
||||
}}
|
||||
?blankable=${true}
|
||||
>
|
||||
</ak-search-select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"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=${msg("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">
|
||||
${msg("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=${msg("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">
|
||||
${msg("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=${msg("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">
|
||||
${msg("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=${msg("Default relay state")}
|
||||
?required=${true}
|
||||
name="defaultRelayState"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.defaultRelayState || ""}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When using IDP-initiated logins, the relay state will be set to this value.",
|
||||
)}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Digest algorithm")}
|
||||
?required=${true}
|
||||
name="digestAlgorithm"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${digestAlgorithmOptions}
|
||||
.value=${this.instance?.digestAlgorithm}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Signature algorithm")}
|
||||
?required=${true}
|
||||
name="signatureAlgorithm"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${signatureAlgorithmOptions}
|
||||
.value=${this.instance?.signatureAlgorithm}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>`;
|
||||
return renderForm(this.instance ?? {}, [], setHasSigningKp, this.hasSigningKp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
394
web/src/admin/providers/saml/SAMLProviderFormForm.ts
Normal file
394
web/src/admin/providers/saml/SAMLProviderFormForm.ts
Normal file
@ -0,0 +1,394 @@
|
||||
import {
|
||||
digestAlgorithmOptions,
|
||||
signatureAlgorithmOptions,
|
||||
} from "@goauthentik/admin/applications/wizard/methods/saml/SamlProviderOptions";
|
||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
|
||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/forms/Radio";
|
||||
import "@goauthentik/elements/forms/SearchSelect";
|
||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import {
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PropertymappingsApi,
|
||||
PropertymappingsProviderSamlListRequest,
|
||||
SAMLPropertyMapping,
|
||||
SAMLProvider,
|
||||
SpBindingEnum,
|
||||
ValidationError,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
export async function samlPropertyMappingsProvider(page = 1, search = "") {
|
||||
const propertyMappings = await new PropertymappingsApi(
|
||||
DEFAULT_CONFIG,
|
||||
).propertymappingsProviderSamlList({
|
||||
ordering: "saml_name",
|
||||
pageSize: 20,
|
||||
search: search.trim(),
|
||||
page,
|
||||
});
|
||||
return {
|
||||
pagination: propertyMappings.pagination,
|
||||
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
|
||||
};
|
||||
}
|
||||
|
||||
export function makeSAMLPropertyMappingsSelector(instanceMappings?: string[]) {
|
||||
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
|
||||
return localMappings
|
||||
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
|
||||
: ([_0, _1, _2, mapping]: DualSelectPair<SAMLPropertyMapping>) =>
|
||||
mapping?.managed?.startsWith("goauthentik.io/providers/saml");
|
||||
}
|
||||
|
||||
export function renderForm(
|
||||
provider?: Partial<SAMLProvider>,
|
||||
errors: ValidationError,
|
||||
setHasSigningKp: (ev: InputEvent) => void,
|
||||
hasSigningKp: boolean,
|
||||
) {
|
||||
return html` <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(provider?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authorization flow")}
|
||||
required
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||
.currentFlow=${provider?.authorizationFlow}
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when authorizing this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("ACS URL")} ?required=${true} name="acsUrl">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(provider?.acsUrl)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Issuer")} ?required=${true} name="issuer">
|
||||
<input
|
||||
type="text"
|
||||
value="${provider?.issuer || "authentik"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${msg("Also known as EntityID.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Service Provider Binding")}
|
||||
?required=${true}
|
||||
name="spBinding"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: msg("Redirect"),
|
||||
value: SpBindingEnum.Redirect,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: msg("Post"),
|
||||
value: SpBindingEnum.Post,
|
||||
},
|
||||
]}
|
||||
.value=${provider?.spBinding}
|
||||
>
|
||||
</ak-radio>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Determines how authentik sends the response back to the Service Provider.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Audience")} name="audience">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(provider?.audience)}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
?required=${false}
|
||||
name="authenticationFlow"
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||
.currentFlow=${provider?.authenticationFlow}
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Flow used when a user access this provider and is not authenticated.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
name="invalidationFlow"
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||
.currentFlow=${provider?.invalidationFlow}
|
||||
defaultFlowSlug="default-provider-invalidation-flow"
|
||||
required
|
||||
></ak-flow-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Flow used when logging out of this provider.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Signing Certificate")} name="signingKp">
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider?.signingKp}
|
||||
@input=${setHasSigningKp}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"Certificate used to sign outgoing Responses going to the Service Provider.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${hasSigningKp
|
||||
? html` <ak-form-element-horizontal name="signAssertion">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(provider?.signAssertion, true)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label">${msg("Sign assertions")}</span>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="signResponse">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(provider?.signResponse, false)}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pf-c-switch__label">${msg("Sign responses")}</span>
|
||||
</label>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When enabled, the assertion element of the SAML response will be signed.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`
|
||||
: nothing}
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Verification Certificate")}
|
||||
name="verificationKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider?.verificationKp}
|
||||
nokey
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"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=${msg("Encryption Certificate")}
|
||||
name="encryptionKp"
|
||||
>
|
||||
<ak-crypto-certificate-search
|
||||
.certificate=${provider?.encryptionKp}
|
||||
></ak-crypto-certificate-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("When selected, assertions will be encrypted using this keypair.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Property mappings")}
|
||||
name="propertyMappings"
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
.provider=${samlPropertyMappingsProvider}
|
||||
.selector=${makeSAMLPropertyMappingsSelector(provider?.propertyMappings)}
|
||||
available-label=${msg("Available User Property Mappings")}
|
||||
selected-label=${msg("Selected User Property Mappings")}
|
||||
></ak-dual-select-dynamic-selected>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("NameID Property Mapping")}
|
||||
name="nameIdMapping"
|
||||
>
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<SAMLPropertyMapping[]> => {
|
||||
const args: PropertymappingsProviderSamlListRequest = {
|
||||
ordering: "saml_name",
|
||||
};
|
||||
if (query !== undefined) {
|
||||
args.search = query;
|
||||
}
|
||||
const items = await new PropertymappingsApi(
|
||||
DEFAULT_CONFIG,
|
||||
).propertymappingsProviderSamlList(args);
|
||||
return items.results;
|
||||
}}
|
||||
.renderElement=${(item: SAMLPropertyMapping): string => {
|
||||
return item.name;
|
||||
}}
|
||||
.value=${(item: SAMLPropertyMapping | undefined): string | undefined => {
|
||||
return item?.pk;
|
||||
}}
|
||||
.selected=${(item: SAMLPropertyMapping): boolean => {
|
||||
return provider?.nameIdMapping === item.pk;
|
||||
}}
|
||||
?blankable=${true}
|
||||
>
|
||||
</ak-search-select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"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=${msg("Assertion valid not before")}
|
||||
?required=${true}
|
||||
name="assertionValidNotBefore"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${provider?.assertionValidNotBefore || "minutes=-5"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("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=${msg("Assertion valid not on or after")}
|
||||
?required=${true}
|
||||
name="assertionValidNotOnOrAfter"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${provider?.assertionValidNotOnOrAfter || "minutes=5"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("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=${msg("Session valid not on or after")}
|
||||
?required=${true}
|
||||
name="sessionValidNotOnOrAfter"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${provider?.sessionValidNotOnOrAfter || "minutes=86400"}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("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=${msg("Default relay state")}
|
||||
?required=${true}
|
||||
name="defaultRelayState"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${provider?.defaultRelayState || ""}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"When using IDP-initiated logins, the relay state will be set to this value.",
|
||||
)}
|
||||
</p>
|
||||
<ak-utils-time-delta-help></ak-utils-time-delta-help>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Digest algorithm")}
|
||||
?required=${true}
|
||||
name="digestAlgorithm"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${digestAlgorithmOptions}
|
||||
.value=${provider?.digestAlgorithm}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Signature algorithm")}
|
||||
?required=${true}
|
||||
name="signatureAlgorithm"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${signatureAlgorithmOptions}
|
||||
.value=${provider?.signatureAlgorithm}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>`;
|
||||
}
|
||||
@ -1,28 +1,43 @@
|
||||
import { expect } from "@wdio/globals";
|
||||
import {
|
||||
clickButton,
|
||||
setFormGroup,
|
||||
setSearchSelect,
|
||||
setTextInput,
|
||||
setTypeCreate,
|
||||
} from "pageobjects/controls.js";
|
||||
|
||||
import ProviderWizardView from "../pageobjects/provider-wizard.page.js";
|
||||
import ProvidersListPage from "../pageobjects/providers-list.page.js";
|
||||
import { randomId } from "../utils/index.js";
|
||||
import { login } from "../utils/login.js";
|
||||
import {
|
||||
simpleLDAPProviderForm,
|
||||
simpleOAuth2ProviderForm,
|
||||
simpleRadiusProviderForm,
|
||||
} from "./shared-sequences.js";
|
||||
|
||||
async function reachTheProvider() {
|
||||
await ProvidersListPage.logout();
|
||||
await login();
|
||||
await ProvidersListPage.open();
|
||||
await expect(await ProvidersListPage.pageHeader()).toHaveText("Providers");
|
||||
await expect(await containedMessages()).not.toContain("Successfully created provider.");
|
||||
|
||||
await ProvidersListPage.startWizardButton.click();
|
||||
await ProviderWizardView.wizardTitle.waitForDisplayed();
|
||||
await expect(await ProviderWizardView.wizardTitle).toHaveText("New provider");
|
||||
}
|
||||
|
||||
const containedMessages = async () =>
|
||||
await (async () => {
|
||||
const messages = [];
|
||||
for await (const alert of $("ak-message-container").$$("ak-message")) {
|
||||
messages.push(await alert.$("p.pf-c-alert__title").getText());
|
||||
}
|
||||
return messages;
|
||||
})();
|
||||
|
||||
const hasProviderSuccessMessage = async () =>
|
||||
await browser.waitUntil(
|
||||
async () => (await containedMessages()).includes("Successfully created provider."),
|
||||
{ timeout: 1000, timeoutMsg: "Expected to see provider success message." },
|
||||
);
|
||||
|
||||
type FieldDesc = [(..._: unknown) => Promise<void>, ...unknown];
|
||||
|
||||
async function fillOutFields(fields: FieldDesc[]) {
|
||||
for (const field of fields) {
|
||||
const thefunc = field[0];
|
||||
@ -33,64 +48,44 @@ async function fillOutFields(fields: FieldDesc[]) {
|
||||
|
||||
describe("Configure Oauth2 Providers", () => {
|
||||
it("Should configure a simple OAuth2 Provider", async () => {
|
||||
const newProviderName = `New OAuth2 Provider - ${randomId()}`;
|
||||
|
||||
await reachTheProvider();
|
||||
|
||||
await $("ak-wizard-page-type-create").waitForDisplayed();
|
||||
|
||||
// prettier-ignore
|
||||
await fillOutFields([
|
||||
[setTypeCreate, "selectProviderType", "OAuth2/OpenID Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newProviderName],
|
||||
[setSearchSelect, "authorizationFlow", "default-provider-authorization-explicit-consent"],
|
||||
]);
|
||||
|
||||
await fillOutFields(simpleOAuth2ProviderForm());
|
||||
await ProviderWizardView.pause();
|
||||
await ProviderWizardView.nextButton.click();
|
||||
await hasProviderSuccessMessage();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Configure LDAP Providers", () => {
|
||||
it("Should configure a simple LDAP Provider", async () => {
|
||||
const newProviderName = `New LDAP Provider - ${randomId()}`;
|
||||
|
||||
await reachTheProvider();
|
||||
await $("ak-wizard-page-type-create").waitForDisplayed();
|
||||
|
||||
// prettier-ignore
|
||||
await fillOutFields([
|
||||
[setTypeCreate, "selectProviderType", "LDAP Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newProviderName],
|
||||
[setFormGroup, /Flow settings/, "open"],
|
||||
// This will never not weird me out.
|
||||
[setSearchSelect, "authorizationFlow", "default-authentication-flow"],
|
||||
[setSearchSelect, "invalidationFlow", "default-invalidation-flow"],
|
||||
]);
|
||||
|
||||
await fillOutFields(simpleLDAPProviderForm());
|
||||
await ProviderWizardView.pause();
|
||||
await ProviderWizardView.nextButton.click();
|
||||
await hasProviderSuccessMessage();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Configure Radius Providers", () => {
|
||||
it("Should configure a simple Radius Provider", async () => {
|
||||
const newProviderName = `New Radius Provider - ${randomId()}`;
|
||||
|
||||
await reachTheProvider();
|
||||
await $("ak-wizard-page-type-create").waitForDisplayed();
|
||||
|
||||
// prettier-ignore
|
||||
await fillOutFields([
|
||||
[setTypeCreate, "selectProviderType", "Radius Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newProviderName],
|
||||
[setSearchSelect, "authorizationFlow", "default-authentication-flow"],
|
||||
]);
|
||||
|
||||
await fillOutFields(simpleRadiusProviderForm());
|
||||
await ProviderWizardView.pause();
|
||||
await ProviderWizardView.nextButton.click();
|
||||
await hasProviderSuccessMessage();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Configure SAML Providers", () => {
|
||||
it("Should configure a simple Radius Provider", async () => {
|
||||
await reachTheProvider();
|
||||
await $("ak-wizard-page-type-create").waitForDisplayed();
|
||||
await fillOutFields(simpleRadiusProviderForm());
|
||||
await ProviderWizardView.pause();
|
||||
await ProviderWizardView.nextButton.click();
|
||||
await hasProviderSuccessMessage();
|
||||
});
|
||||
});
|
||||
|
||||
35
web/tests/specs/shared-sequences.ts
Normal file
35
web/tests/specs/shared-sequences.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import {
|
||||
clickButton,
|
||||
setFormGroup,
|
||||
setSearchSelect,
|
||||
setTextInput,
|
||||
setTypeCreate,
|
||||
} from "pageobjects/controls.js";
|
||||
|
||||
import { randomId } from "../utils/index.js";
|
||||
|
||||
const newObjectName = (prefix: string) => `${prefix} - ${randomId()}`;
|
||||
|
||||
export const simpleOAuth2ProviderForm = () => [
|
||||
[setTypeCreate, "selectProviderType", "OAuth2/OpenID Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Oauth2 Provider")],
|
||||
[setSearchSelect, "authorizationFlow", "default-provider-authorization-explicit-consent"],
|
||||
];
|
||||
|
||||
export const simpleLDAPProviderForm = () => [
|
||||
[setTypeCreate, "selectProviderType", "LDAP Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New LDAP Provider")],
|
||||
// This will never not weird me out.
|
||||
[setSearchSelect, "authorizationFlow", "default-authentication-flow"],
|
||||
[setFormGroup, /Flow settings/, "open"],
|
||||
[setSearchSelect, "invalidationFlow", "default-invalidation-flow"],
|
||||
];
|
||||
|
||||
export const simpleRadiusProviderForm = () => [
|
||||
[setTypeCreate, "selectProviderType", "Radius Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Radius Provider")],
|
||||
[setSearchSelect, "authorizationFlow", "default-authentication-flow"],
|
||||
];
|
||||
Reference in New Issue
Block a user