diff --git a/web/src/admin/sources/oauth/utils.ts b/web/src/admin/sources/oauth/utils.ts
index fab271f19790..c6825455edf8 100644
--- a/web/src/admin/sources/oauth/utils.ts
+++ b/web/src/admin/sources/oauth/utils.ts
@@ -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");
+ }
+}
diff --git a/web/src/admin/sources/saml/SAMLSourceForm.ts b/web/src/admin/sources/saml/SAMLSourceForm.ts
index 0800c97678a2..18d11dde72eb 100644
--- a/web/src/admin/sources/saml/SAMLSourceForm.ts
+++ b/web/src/admin/sources/saml/SAMLSourceForm.ts
@@ -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
) => false;
+}
+
@customElement("ak-source-saml-form")
export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm) {
@state()
@@ -151,6 +181,35 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm
+
+
+
${this.can(CapabilitiesEnum.CanSaveMedia)
? html`
@@ -451,6 +510,43 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm