From 6df226188fb1564c3d38897ac7788d25bd7be230 Mon Sep 17 00:00:00 2001 From: Dominic R Date: Mon, 15 Jun 2026 16:30:07 -0400 Subject: [PATCH] providers/scim: Add GitLab compatibility mode (#22906) * providers/scim: Add GitLab compatibility mode Add a GitLab SCIM compatibility mode that skips ServiceProviderConfig probing and document when to use it. Also wrap non-JSON SCIM responses so providers that return HTML redirects fall back through the existing ServiceProviderConfig default path. Agent-thread: https://sdko.org/internal/thr/per/019ea36a-92dd-7651-8a2d-0d838e724a7d A7k-product: product A7k-product-repo: 1 Co-authored-by: Agent * providers/scim: Fold GitLab mode into existing migration Agent-thread: https://sdko.org/internal/thr/ak/019ea7bd-ce63-77a2-90d6-5dcc25d4402d A7k-product: product A7k-product-repo: 2 Co-authored-by: Agent --------- Co-authored-by: Agent --- authentik/providers/scim/clients/base.py | 12 +++++++--- ...019_scimprovider_group_filters_and_more.py | 1 + authentik/providers/scim/models.py | 1 + authentik/providers/scim/tests/test_client.py | 12 ++++++++++ blueprints/schema.json | 1 + .../src/models/CompatibilityModeEnum.ts | 1 + schema.yml | 1 + .../add-secure-apps/providers/scim/index.md | 1 + .../integrations/development/gitlab/index.mdx | 24 +++++++++++++++++++ 9 files changed, 51 insertions(+), 3 deletions(-) diff --git a/authentik/providers/scim/clients/base.py b/authentik/providers/scim/clients/base.py index 4192906462..cf28dd9bd1 100644 --- a/authentik/providers/scim/clients/base.py +++ b/authentik/providers/scim/clients/base.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from django.core.cache import cache from django.http import HttpResponseBadRequest, HttpResponseNotFound from pydantic import ValidationError -from requests import RequestException, Session +from requests import JSONDecodeError, RequestException, Session from authentik.lib.sync.outgoing import ( HTTP_CONFLICT, @@ -84,7 +84,10 @@ class SCIMClient[TModel: "Model", TConnection: "Model", TSchema: "BaseModel"]( raise SCIMRequestException(response) if response.status_code == HTTP_NO_CONTENT: return {} - return response.json() + try: + return response.json() + except JSONDecodeError as exc: + raise SCIMRequestException(message="Failed to decode SCIM response") from exc def get_service_provider_config(self): """Get Service provider config""" @@ -97,7 +100,10 @@ class SCIMClient[TModel: "Model", TConnection: "Model", TSchema: "BaseModel"]( if cached_config is not None: return cached_config - if self.provider.compatibility_mode == SCIMCompatibilityMode.VCENTER: + if self.provider.compatibility_mode in [ + SCIMCompatibilityMode.GITLAB, + SCIMCompatibilityMode.VCENTER, + ]: return default_config # Attempt to fetch from remote diff --git a/authentik/providers/scim/migrations/0019_scimprovider_group_filters_and_more.py b/authentik/providers/scim/migrations/0019_scimprovider_group_filters_and_more.py index 6c0407edca..336ddbd350 100644 --- a/authentik/providers/scim/migrations/0019_scimprovider_group_filters_and_more.py +++ b/authentik/providers/scim/migrations/0019_scimprovider_group_filters_and_more.py @@ -93,6 +93,7 @@ class Migration(migrations.Migration): ("aws", "AWS"), ("slack", "Slack"), ("sfdc", "Salesforce"), + ("gitlab", "GitLab"), ("webex", "Webex"), ("vcenter", "vCenter"), ], diff --git a/authentik/providers/scim/models.py b/authentik/providers/scim/models.py index 3fe265f1ac..c487d27498 100644 --- a/authentik/providers/scim/models.py +++ b/authentik/providers/scim/models.py @@ -83,6 +83,7 @@ class SCIMCompatibilityMode(models.TextChoices): AWS = "aws", _("AWS") SLACK = "slack", _("Slack") SALESFORCE = "sfdc", _("Salesforce") + GITLAB = "gitlab", _("GitLab") WEBEX = "webex", _("Webex") VCENTER = "vcenter", _("vCenter") diff --git a/authentik/providers/scim/tests/test_client.py b/authentik/providers/scim/tests/test_client.py index 28ca5852f3..1c4e8b2448 100644 --- a/authentik/providers/scim/tests/test_client.py +++ b/authentik/providers/scim/tests/test_client.py @@ -88,6 +88,18 @@ class SCIMClientTests(TestCase): self.assertEqual(mock.call_count, 1) self.assertEqual(mock.request_history[0].method, "GET") + def test_config_non_json_response(self): + """Test non-JSON config response falls back to defaults""" + with Mocker() as mock: + mock.get( + "https://localhost/ServiceProviderConfig", + text="", + headers={"Content-Type": "text/html"}, + ) + SCIMClient(self.provider) + self.assertEqual(mock.call_count, 1) + self.assertEqual(mock.request_history[0].method, "GET") + def test_scim_sync(self): """test scim_sync task""" scim_sync.send(self.provider.pk).get_result() diff --git a/blueprints/schema.json b/blueprints/schema.json index 602e676233..2f5a2615e0 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -11226,6 +11226,7 @@ "aws", "slack", "sfdc", + "gitlab", "webex", "vcenter" ], diff --git a/packages/client-ts/src/models/CompatibilityModeEnum.ts b/packages/client-ts/src/models/CompatibilityModeEnum.ts index d80a395ea4..915d6a41eb 100644 --- a/packages/client-ts/src/models/CompatibilityModeEnum.ts +++ b/packages/client-ts/src/models/CompatibilityModeEnum.ts @@ -21,6 +21,7 @@ export const CompatibilityModeEnum = { Aws: "aws", Slack: "slack", Sfdc: "sfdc", + Gitlab: "gitlab", Webex: "webex", Vcenter: "vcenter", UnknownDefaultOpenApi: "11184809", diff --git a/schema.yml b/schema.yml index f21afc33db..f62cc7e9c9 100644 --- a/schema.yml +++ b/schema.yml @@ -36608,6 +36608,7 @@ components: - aws - slack - sfdc + - gitlab - webex - vcenter type: string diff --git a/website/docs/add-secure-apps/providers/scim/index.md b/website/docs/add-secure-apps/providers/scim/index.md index 12f1ef9cc2..8f011aa969 100644 --- a/website/docs/add-secure-apps/providers/scim/index.md +++ b/website/docs/add-secure-apps/providers/scim/index.md @@ -121,6 +121,7 @@ Available compatibility modes are: - **AWS**: Disables PATCH operations for AWS Identity Center compatibility - **Slack**: Enables filtering support for Slack's SCIM implementation - **Salesforce**: Uses the non-standard `/ServiceProviderConfigs` endpoint +- **GitLab**: Skips the `ServiceProviderConfig` endpoint because GitLab's SCIM implementation does not expose it - **Webex**: Uses the vendor-specific behavior required for Webex SCIM - **vCenter**: Skips the `ServiceProviderConfig` endpoint, which is not implemented in VMware vCenter diff --git a/website/integrations/development/gitlab/index.mdx b/website/integrations/development/gitlab/index.mdx index e8e243e85d..6c2ac2708f 100644 --- a/website/integrations/development/gitlab/index.mdx +++ b/website/integrations/development/gitlab/index.mdx @@ -161,3 +161,27 @@ For further GitLab provider arguments, check the [GitLab docs](https://docs.gitl + +## SCIM provisioning _(optional)_ + +GitLab self-managed instances expose SCIM endpoints for user and group provisioning when SCIM is enabled in GitLab. Use the SCIM API endpoint URL and token shown in GitLab's SCIM configuration. + +### Create a SCIM provider + +1. Log in to authentik as an administrator and open the authentik Admin interface. +2. Navigate to **Applications** > **Providers** and click **Create**. +3. Select **SCIM Provider** as the provider type and click **Next**. +4. Enter the following values: + - **Name**: Choose a descriptive name. + - **URL**: Paste the SCIM API endpoint URL from GitLab. + - **Token**: Paste the SCIM token from GitLab. + - **Compatibility Mode**: Select **GitLab**. +5. Click **Finish** to save the provider. + +### Add the SCIM provider to your application + +1. Log in to authentik as an administrator and open the authentik Admin interface. +2. Navigate to **Applications** > **Applications** and select your GitLab application. +3. Click **Edit**. +4. In the **Backchannel Providers** field, select the SCIM provider you created. +5. Click **Update** to save the application.