sources: add property mappings for all oauth and saml sources (#8771)

Co-authored-by: Jens L. <jens@goauthentik.io>
This commit is contained in:
Marc 'risson' Schmitt
2024-08-07 19:14:22 +02:00
committed by GitHub
parent 78bae556d0
commit 83b02a17d5
64 changed files with 3631 additions and 314 deletions

View File

@ -57,7 +57,9 @@ export class PropertyMappingLDAPSourceForm extends BasePropertyMappingForm<LDAPS
<a
target="_blank"
rel="noopener noreferrer"
href="${docLink("/docs/property-mappings/expression?utm_source=authentik")}"
href="${docLink(
"/docs/sources/property-mappings/expression?utm_source=authentik",
)}"
>
${msg("See documentation for a list of all variables.")}
</a>

View File

@ -2,9 +2,11 @@ import "@goauthentik/admin/property-mappings/PropertyMappingGoogleWorkspaceForm"
import "@goauthentik/admin/property-mappings/PropertyMappingLDAPSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingMicrosoftEntraForm";
import "@goauthentik/admin/property-mappings/PropertyMappingNotification";
import "@goauthentik/admin/property-mappings/PropertyMappingOAuthSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingRACForm";
import "@goauthentik/admin/property-mappings/PropertyMappingRadiusForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSAMLSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSCIMForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSCIMSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm";

View File

@ -0,0 +1,75 @@
import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { docLink } from "@goauthentik/common/global";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { OAuthSourcePropertyMapping, PropertymappingsApi } from "@goauthentik/api";
@customElement("ak-property-mapping-oauth-source-form")
export class PropertyMappingOAuthSourceForm extends BasePropertyMappingForm<OAuthSourcePropertyMapping> {
loadInstance(pk: string): Promise<OAuthSourcePropertyMapping> {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceOauthRetrieve({
pmUuid: pk,
});
}
async send(data: OAuthSourcePropertyMapping): Promise<OAuthSourcePropertyMapping> {
if (this.instance) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceOauthUpdate({
pmUuid: this.instance.pk,
oAuthSourcePropertyMappingRequest: data,
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceOauthCreate({
oAuthSourcePropertyMappingRequest: data,
});
}
}
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("Expression")}
?required=${true}
name="expression"
>
<ak-codemirror
mode=${CodeMirrorMode.Python}
value="${ifDefined(this.instance?.expression)}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${msg("Expression using Python.")}
<a
target="_blank"
rel="noopener noreferrer"
href="${docLink(
"/docs/sources/property-mappings/expression?utm_source=authentik",
)}"
>
${msg("See documentation for a list of all variables.")}
</a>
</p>
</ak-form-element-horizontal>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-property-mapping-oauth-source-form": PropertyMappingOAuthSourceForm;
}
}

View File

@ -0,0 +1,75 @@
import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { docLink } from "@goauthentik/common/global";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { PropertymappingsApi, SAMLSourcePropertyMapping } from "@goauthentik/api";
@customElement("ak-property-mapping-saml-source-form")
export class PropertyMappingSAMLSourceForm extends BasePropertyMappingForm<SAMLSourcePropertyMapping> {
loadInstance(pk: string): Promise<SAMLSourcePropertyMapping> {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceSamlRetrieve({
pmUuid: pk,
});
}
async send(data: SAMLSourcePropertyMapping): Promise<SAMLSourcePropertyMapping> {
if (this.instance) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceSamlUpdate({
pmUuid: this.instance.pk,
sAMLSourcePropertyMappingRequest: data,
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSourceSamlCreate({
sAMLSourcePropertyMappingRequest: data,
});
}
}
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("Expression")}
?required=${true}
name="expression"
>
<ak-codemirror
mode=${CodeMirrorMode.Python}
value="${ifDefined(this.instance?.expression)}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${msg("Expression using Python.")}
<a
target="_blank"
rel="noopener noreferrer"
href="${docLink(
"/docs/sources/property-mappings/expression?utm_source=authentik",
)}"
>
${msg("See documentation for a list of all variables.")}
</a>
</p>
</ak-form-element-horizontal>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-property-mapping-saml-source-form": PropertyMappingSAMLSourceForm;
}
}

View File

@ -1,7 +1,9 @@
import "@goauthentik/admin/property-mappings/PropertyMappingLDAPSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingNotification";
import "@goauthentik/admin/property-mappings/PropertyMappingOAuthSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingRACForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSAMLSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSCIMSourceForm";
import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm";
import "@goauthentik/admin/property-mappings/PropertyMappingTestForm";

View File

@ -1,7 +1,10 @@
import "@goauthentik/admin/common/ak-flow-search/ak-source-flow-search";
import { iconHelperText, placeholderHelperText } from "@goauthentik/admin/helperText";
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
import {
GroupMatchingModeToLabel,
UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
@ -10,6 +13,8 @@ import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
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/SearchSelect";
@ -21,14 +26,39 @@ import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum,
OAuthSource,
OAuthSourcePropertyMapping,
OAuthSourceRequest,
PropertymappingsApi,
ProviderTypeEnum,
SourceType,
SourcesApi,
UserMatchingModeEnum,
} from "@goauthentik/api";
async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsSourceOauthList({
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
function makePropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, _]: DualSelectPair<OAuthSourcePropertyMapping>) => false;
}
@customElement("ak-source-oauth-form")
export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuthSource>) {
async loadInstance(pk: string): Promise<OAuthSource> {
@ -40,6 +70,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
return source;
}
_modelName?: string;
@property()
modelName?: string;
@ -299,6 +331,35 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group matching mode")}
?required=${true}
name="groupMatchingMode"
>
<select class="pf-c-form-control">
<option
value=${GroupMatchingModeEnum.Identifier}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.Identifier}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.Identifier)}
</option>
<option
value=${GroupMatchingModeEnum.NameLink}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.NameLink}
>
${GroupMatchingModeToLabel(GroupMatchingModeEnum.NameLink)}
</option>
<option
value=${GroupMatchingModeEnum.NameDeny}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.NameDeny}
>
${GroupMatchingModeToLabel(GroupMatchingModeEnum.NameDeny)}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("User path")} name="userPathTemplate">
<input
type="text"
@ -397,6 +458,43 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
</div>
</ak-form-group>
${this.renderUrlOptions()}
<ak-form-group ?expanded=${true}>
<span slot="header"> ${msg("OAuth Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
>
<ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider}
.selector=${makePropertyMappingsSelector(
this.instance?.userPropertyMappings,
)}
available-label="${msg("Available User Property Mappings")}"
selected-label="${msg("Selected User Property Mappings")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg("Property mappings for user creation.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group Property Mappings")}
name="groupPropertyMappings"
>
<ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider}
.selector=${makePropertyMappingsSelector(
this.instance?.groupPropertyMappings,
)}
available-label="${msg("Available Group Property Mappings")}"
selected-label="${msg("Selected Group Property Mappings")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg("Property mappings for group creation.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">

View File

@ -1,6 +1,6 @@
import { msg } from "@lit/localize";
import { UserMatchingModeEnum } from "@goauthentik/api";
import { GroupMatchingModeEnum, UserMatchingModeEnum } from "@goauthentik/api";
export function UserMatchingModeToLabel(mode?: UserMatchingModeEnum): string {
if (!mode) return "";
@ -27,3 +27,19 @@ export function UserMatchingModeToLabel(mode?: UserMatchingModeEnum): string {
return msg("Unknown user matching mode");
}
}
export function GroupMatchingModeToLabel(mode?: GroupMatchingModeEnum): string {
if (!mode) return "";
switch (mode) {
case GroupMatchingModeEnum.Identifier:
return msg("Link users on unique identifier");
case GroupMatchingModeEnum.NameLink:
return msg(
"Link to a group with identical name. Can have security implications when a group is used with another source",
);
case GroupMatchingModeEnum.NameDeny:
return msg("Use the group's name, but deny enrollment when the name already exists");
case UserMatchingModeEnum.UnknownDefaultOpenApi:
return msg("Unknown user matching mode");
}
}

View File

@ -2,13 +2,18 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-source-flow-search";
import { iconHelperText, placeholderHelperText } from "@goauthentik/admin/helperText";
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
import {
GroupMatchingModeToLabel,
UserMatchingModeToLabel,
} from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import {
CapabilitiesEnum,
WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider";
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";
@ -23,13 +28,38 @@ import {
BindingTypeEnum,
DigestAlgorithmEnum,
FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum,
NameIdPolicyEnum,
PropertymappingsApi,
SAMLSource,
SAMLSourcePropertyMapping,
SignatureAlgorithmEnum,
SourcesApi,
UserMatchingModeEnum,
} from "@goauthentik/api";
async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsSourceSamlList({
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
function makePropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, _]: DualSelectPair<SAMLSourcePropertyMapping>) => false;
}
@customElement("ak-source-saml-form")
export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSource>) {
@state()
@ -151,6 +181,35 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group matching mode")}
?required=${true}
name="groupMatchingMode"
>
<select class="pf-c-form-control">
<option
value=${GroupMatchingModeEnum.Identifier}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.Identifier}
>
${UserMatchingModeToLabel(UserMatchingModeEnum.Identifier)}
</option>
<option
value=${GroupMatchingModeEnum.NameLink}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.NameLink}
>
${GroupMatchingModeToLabel(GroupMatchingModeEnum.NameLink)}
</option>
<option
value=${GroupMatchingModeEnum.NameDeny}
?selected=${this.instance?.groupMatchingMode ===
GroupMatchingModeEnum.NameDeny}
>
${GroupMatchingModeToLabel(GroupMatchingModeEnum.NameDeny)}
</option>
</select>
</ak-form-element-horizontal>
${this.can(CapabilitiesEnum.CanSaveMedia)
? html`<ak-form-element-horizontal label=${msg("Icon")} name="icon">
<input type="file" value="" class="pf-c-form-control" />
@ -451,6 +510,43 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group ?expanded=${true}>
<span slot="header"> ${msg("SAML Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
>
<ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider}
.selector=${makePropertyMappingsSelector(
this.instance?.userPropertyMappings,
)}
available-label="${msg("Available User Property Mappings")}"
selected-label="${msg("Selected User Property Mappings")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg("Property mappings for user creation.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group Property Mappings")}
name="groupPropertyMappings"
>
<ak-dual-select-dynamic-selected
.provider=${propertyMappingsProvider}
.selector=${makePropertyMappingsSelector(
this.instance?.groupPropertyMappings,
)}
available-label="${msg("Available Group Property Mappings")}"
selected-label="${msg("Selected Group Property Mappings")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text">
${msg("Property mappings for group creation.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">