We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SCIM provider automatic sync not triggering since 2023.5.0
**Describe the bug**
SCIM provider automatic sync is not triggering (hourly & on user/group change).
Just upgraded to 2023.5.0 yesterday and (I think) that sync is broken since upgrade. I was using 2023.3.1 and 2023.4.1 previously with the SCIM provider to provision AWS SSO (IAM Identity Center) and those triggers worked (but without PATCH added in 2023.5.0 so that was the main reason for this upgrade).

**To Reproduce**
Configure SCIM provider and wait for the full sync hourly, try to add/remove a member in a group or create a new user.

**Expected behavior**
From [documentation](https://goauthentik.io/docs/providers/scim/#syncing):

```
Data is synchronized in multiple ways:

When a user/group is created/modified/deleted, that action is sent to all SCIM providers
Periodically (once an hour), all SCIM providers are fully synchronized
```

**Screenshots**
No screenshots for this case.

**Logs**
There's no sync events events in logs. Logs are configured in trace level.
The only SCIM events I see are:

```
2023-05-17 09:24:58 | {"event": "Task started", "level": "info", "logger": "authentik.root.celery", "pid": 5476, "task_id": "d2f7357b-caf3-40b9-8750-93134c36badf", "task_name": "scim_signal_direct", "timestamp": "2023-05-17T12:24:58.174683"}
2023-05-17 09:24:58 | {"event": "Task finished", "level": "info", "logger": "authentik.root.celery", "pid": 5476, "state": "SUCCESS", "task_id": "d2f7357b-caf3-40b9-8750-93134c36badf", "task_name": "scim_signal_direct", "timestamp": "2023-05-17T12:24:58.210445"}
```

**Version and Deployment (please complete the following information):**

-   authentik version:2023.5.0
-   Deployment: Helm chart

**Additional context**
If I run the sync manually it works, but the full sync adds/replace objects it doesn't remove users from a group since I think it's only incremental and the removal of a member should be done whent the group is modified.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `authentik/providers/scim/tasks.py`
Content:
```
1 """SCIM Provider tasks"""
2 from typing import Any, Optional
3 
4 from celery.result import allow_join_result
5 from django.core.paginator import Paginator
6 from django.db.models import Model, QuerySet
7 from django.utils.text import slugify
8 from django.utils.translation import gettext_lazy as _
9 from pydanticscim.responses import PatchOp
10 from structlog.stdlib import get_logger
11 
12 from authentik.core.models import Group, User
13 from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
14 from authentik.lib.utils.reflection import path_to_class
15 from authentik.providers.scim.clients import PAGE_SIZE
16 from authentik.providers.scim.clients.base import SCIMClient
17 from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync
18 from authentik.providers.scim.clients.group import SCIMGroupClient
19 from authentik.providers.scim.clients.user import SCIMUserClient
20 from authentik.providers.scim.models import SCIMProvider
21 from authentik.root.celery import CELERY_APP
22 
23 LOGGER = get_logger(__name__)
24 
25 
26 def client_for_model(provider: SCIMProvider, model: Model) -> SCIMClient:
27     """Get SCIM client for model"""
28     if isinstance(model, User):
29         return SCIMUserClient(provider)
30     if isinstance(model, Group):
31         return SCIMGroupClient(provider)
32     raise ValueError(f"Invalid model {model}")
33 
34 
35 @CELERY_APP.task()
36 def scim_sync_all():
37     """Run sync for all providers"""
38     for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):
39         scim_sync.delay(provider.pk)
40 
41 
42 @CELERY_APP.task(bind=True, base=MonitoredTask)
43 def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
44     """Run SCIM full sync for provider"""
45     provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()
46     if not provider:
47         return
48     self.set_uid(slugify(provider.name))
49     result = TaskResult(TaskResultStatus.SUCCESSFUL, [])
50     result.messages.append(_("Starting full SCIM sync"))
51     LOGGER.debug("Starting SCIM sync")
52     users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
53     groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
54     with allow_join_result():
55         try:
56             for page in users_paginator.page_range:
57                 result.messages.append(_("Syncing page %(page)d of users" % {"page": page}))
58                 for msg in scim_sync_users.delay(page, provider_pk).get():
59                     result.messages.append(msg)
60             for page in groups_paginator.page_range:
61                 result.messages.append(_("Syncing page %(page)d of groups" % {"page": page}))
62                 for msg in scim_sync_group.delay(page, provider_pk).get():
63                     result.messages.append(msg)
64         except StopSync as exc:
65             self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
66             return
67     self.set_status(result)
68 
69 
70 @CELERY_APP.task()
71 def scim_sync_users(page: int, provider_pk: int):
72     """Sync single or multiple users to SCIM"""
73     messages = []
74     provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()
75     if not provider:
76         return messages
77     try:
78         client = SCIMUserClient(provider)
79     except SCIMRequestException:
80         return messages
81     paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
82     LOGGER.debug("starting user sync for page", page=page)
83     for user in paginator.page(page).object_list:
84         try:
85             client.write(user)
86         except SCIMRequestException as exc:
87             LOGGER.warning("failed to sync user", exc=exc, user=user)
88             messages.append(
89                 _(
90                     "Failed to sync user %(user_name)s due to remote error: %(error)s"
91                     % {
92                         "user_name": user.username,
93                         "error": exc.detail(),
94                     }
95                 )
96             )
97         except StopSync as exc:
98             LOGGER.warning("Stopping sync", exc=exc)
99             messages.append(
100                 _(
101                     "Stopping sync due to error: %(error)s"
102                     % {
103                         "error": exc.detail(),
104                     }
105                 )
106             )
107             break
108     return messages
109 
110 
111 @CELERY_APP.task()
112 def scim_sync_group(page: int, provider_pk: int):
113     """Sync single or multiple groups to SCIM"""
114     messages = []
115     provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()
116     if not provider:
117         return messages
118     try:
119         client = SCIMGroupClient(provider)
120     except SCIMRequestException:
121         return messages
122     paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
123     LOGGER.debug("starting group sync for page", page=page)
124     for group in paginator.page(page).object_list:
125         try:
126             client.write(group)
127         except SCIMRequestException as exc:
128             LOGGER.warning("failed to sync group", exc=exc, group=group)
129             messages.append(
130                 _(
131                     "Failed to sync group %(group_name)s due to remote error: %(error)s"
132                     % {
133                         "group_name": group.name,
134                         "error": exc.detail(),
135                     }
136                 )
137             )
138         except StopSync as exc:
139             LOGGER.warning("Stopping sync", exc=exc)
140             messages.append(
141                 _(
142                     "Stopping sync due to error: %(error)s"
143                     % {
144                         "error": exc.detail(),
145                     }
146                 )
147             )
148             break
149     return messages
150 
151 
152 @CELERY_APP.task()
153 def scim_signal_direct(model: str, pk: Any, raw_op: str):
154     """Handler for post_save and pre_delete signal"""
155     model_class: type[Model] = path_to_class(model)
156     instance = model_class.objects.filter(pk=pk).first()
157     if not instance:
158         return
159     operation = PatchOp(raw_op)
160     for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):
161         client = client_for_model(provider, instance)
162         # Check if the object is allowed within the provider's restrictions
163         queryset: Optional[QuerySet] = None
164         if isinstance(instance, User):
165             queryset = provider.get_user_qs()
166         if isinstance(instance, Group):
167             queryset = provider.get_group_qs()
168         if not queryset:
169             continue
170 
171         # The queryset we get from the provider must include the instance we've got given
172         # otherwise ignore this provider
173         if not queryset.filter(pk=instance.pk).exists():
174             continue
175 
176         try:
177             if operation == PatchOp.add:
178                 client.write(instance)
179             if operation == PatchOp.remove:
180                 client.delete(instance)
181         except (StopSync, SCIMRequestException) as exc:
182             LOGGER.warning(exc)
183 
184 
185 @CELERY_APP.task()
186 def scim_signal_m2m(group_pk: str, action: str, pk_set: list[int]):
187     """Update m2m (group membership)"""
188     group = Group.objects.filter(pk=group_pk).first()
189     if not group:
190         return
191     for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):
192         # Check if the object is allowed within the provider's restrictions
193         queryset: QuerySet = provider.get_group_qs()
194         # The queryset we get from the provider must include the instance we've got given
195         # otherwise ignore this provider
196         if not queryset.filter(pk=group_pk).exists():
197             continue
198 
199         client = SCIMGroupClient(provider)
200         try:
201             operation = None
202             if action == "post_add":
203                 operation = PatchOp.add
204             if action == "post_remove":
205                 operation = PatchOp.remove
206             client.update_group(group, operation, pk_set)
207         except (StopSync, SCIMRequestException) as exc:
208             LOGGER.warning(exc)
209
```

Path: `authentik/providers/scim/api/providers.py`
Content:
```
1 """SCIM Provider API Views"""
2 from django.utils.text import slugify
3 from drf_spectacular.utils import OpenApiResponse, extend_schema
4 from rest_framework.decorators import action
5 from rest_framework.request import Request
6 from rest_framework.response import Response
7 from rest_framework.viewsets import ModelViewSet
8 
9 from authentik.admin.api.tasks import TaskSerializer
10 from authentik.core.api.providers import ProviderSerializer
11 from authentik.core.api.used_by import UsedByMixin
12 from authentik.events.monitored_tasks import TaskInfo
13 from authentik.providers.scim.models import SCIMProvider
14 
15 
16 class SCIMProviderSerializer(ProviderSerializer):
17     """SCIMProvider Serializer"""
18 
19     class Meta:
20         model = SCIMProvider
21         fields = [
22             "pk",
23             "name",
24             "property_mappings",
25             "property_mappings_group",
26             "component",
27             "assigned_application_slug",
28             "assigned_application_name",
29             "verbose_name",
30             "verbose_name_plural",
31             "meta_model_name",
32             "url",
33             "token",
34             "exclude_users_service_account",
35             "filter_group",
36         ]
37         extra_kwargs = {}
38 
39 
40 class SCIMProviderViewSet(UsedByMixin, ModelViewSet):
41     """SCIMProvider Viewset"""
42 
43     queryset = SCIMProvider.objects.all()
44     serializer_class = SCIMProviderSerializer
45     filterset_fields = ["name", "exclude_users_service_account", "url", "filter_group"]
46     search_fields = ["name", "url"]
47     ordering = ["name", "url"]
48 
49     @extend_schema(
50         responses={
51             200: TaskSerializer(),
52             404: OpenApiResponse(description="Task not found"),
53         }
54     )
55     @action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
56     def sync_status(self, request: Request, pk: int) -> Response:
57         """Get provider's sync status"""
58         provider = self.get_object()
59         task = TaskInfo.by_name(f"scim_sync:{slugify(provider.name)}")
60         if not task:
61             return Response(status=404)
62         return Response(TaskSerializer(task).data)
63
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/authentik/providers/scim/api/providers.py b/authentik/providers/scim/api/providers.py
--- a/authentik/providers/scim/api/providers.py
+++ b/authentik/providers/scim/api/providers.py
@@ -24,8 +24,8 @@
             "property_mappings",
             "property_mappings_group",
             "component",
-            "assigned_application_slug",
-            "assigned_application_name",
+            "assigned_backchannel_application_slug",
+            "assigned_backchannel_application_name",
             "verbose_name",
             "verbose_name_plural",
             "meta_model_name",
diff --git a/authentik/providers/scim/tasks.py b/authentik/providers/scim/tasks.py
--- a/authentik/providers/scim/tasks.py
+++ b/authentik/providers/scim/tasks.py
@@ -42,7 +42,9 @@
 @CELERY_APP.task(bind=True, base=MonitoredTask)
 def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
     """Run SCIM full sync for provider"""
-    provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()
+    provider: SCIMProvider = SCIMProvider.objects.filter(
+        pk=provider_pk, backchannel_application__isnull=False
+    ).first()
     if not provider:
         return
     self.set_uid(slugify(provider.name))

{"golden_diff": "diff --git a/authentik/providers/scim/api/providers.py b/authentik/providers/scim/api/providers.py\n--- a/authentik/providers/scim/api/providers.py\n+++ b/authentik/providers/scim/api/providers.py\n@@ -24,8 +24,8 @@\n             \"property_mappings\",\n             \"property_mappings_group\",\n             \"component\",\n-            \"assigned_application_slug\",\n-            \"assigned_application_name\",\n+            \"assigned_backchannel_application_slug\",\n+            \"assigned_backchannel_application_name\",\n             \"verbose_name\",\n             \"verbose_name_plural\",\n             \"meta_model_name\",\ndiff --git a/authentik/providers/scim/tasks.py b/authentik/providers/scim/tasks.py\n--- a/authentik/providers/scim/tasks.py\n+++ b/authentik/providers/scim/tasks.py\n@@ -42,7 +42,9 @@\n @CELERY_APP.task(bind=True, base=MonitoredTask)\n def scim_sync(self: MonitoredTask, provider_pk: int) -> None:\n     \"\"\"Run SCIM full sync for provider\"\"\"\n-    provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()\n+    provider: SCIMProvider = SCIMProvider.objects.filter(\n+        pk=provider_pk, backchannel_application__isnull=False\n+    ).first()\n     if not provider:\n         return\n     self.set_uid(slugify(provider.name))\n", "issue": "SCIM provider automatic sync not triggering since 2023.5.0\n**Describe the bug**\r\nSCIM provider automatic sync is not triggering (hourly & on user/group change).\r\nJust upgraded to 2023.5.0 yesterday and (I think) that sync is broken since upgrade. I was using 2023.3.1 and 2023.4.1 previously with the SCIM provider to provision AWS SSO (IAM Identity Center) and those triggers worked (but without PATCH added in 2023.5.0 so that was the main reason for this upgrade).\r\n\r\n**To Reproduce**\r\nConfigure SCIM provider and wait for the full sync hourly, try to add/remove a member in a group or create a new user.\r\n\r\n**Expected behavior**\r\nFrom [documentation](https://goauthentik.io/docs/providers/scim/#syncing):\r\n\r\n```\r\nData is synchronized in multiple ways:\r\n\r\nWhen a user/group is created/modified/deleted, that action is sent to all SCIM providers\r\nPeriodically (once an hour), all SCIM providers are fully synchronized\r\n```\r\n\r\n**Screenshots**\r\nNo screenshots for this case.\r\n\r\n**Logs**\r\nThere's no sync events events in logs. Logs are configured in trace level.\r\nThe only SCIM events I see are:\r\n\r\n```\r\n2023-05-17 09:24:58 | {\"event\": \"Task started\", \"level\": \"info\", \"logger\": \"authentik.root.celery\", \"pid\": 5476, \"task_id\": \"d2f7357b-caf3-40b9-8750-93134c36badf\", \"task_name\": \"scim_signal_direct\", \"timestamp\": \"2023-05-17T12:24:58.174683\"}\r\n2023-05-17 09:24:58 | {\"event\": \"Task finished\", \"level\": \"info\", \"logger\": \"authentik.root.celery\", \"pid\": 5476, \"state\": \"SUCCESS\", \"task_id\": \"d2f7357b-caf3-40b9-8750-93134c36badf\", \"task_name\": \"scim_signal_direct\", \"timestamp\": \"2023-05-17T12:24:58.210445\"}\r\n```\r\n\r\n**Version and Deployment (please complete the following information):**\r\n\r\n-   authentik version:2023.5.0\r\n-   Deployment: Helm chart\r\n\r\n**Additional context**\r\nIf I run the sync manually it works, but the full sync adds/replace objects it doesn't remove users from a group since I think it's only incremental and the removal of a member should be done whent the group is modified.\n", "before_files": [{"content": "\"\"\"SCIM Provider tasks\"\"\"\nfrom typing import Any, Optional\n\nfrom celery.result import allow_join_result\nfrom django.core.paginator import Paginator\nfrom django.db.models import Model, QuerySet\nfrom django.utils.text import slugify\nfrom django.utils.translation import gettext_lazy as _\nfrom pydanticscim.responses import PatchOp\nfrom structlog.stdlib import get_logger\n\nfrom authentik.core.models import Group, User\nfrom authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus\nfrom authentik.lib.utils.reflection import path_to_class\nfrom authentik.providers.scim.clients import PAGE_SIZE\nfrom authentik.providers.scim.clients.base import SCIMClient\nfrom authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync\nfrom authentik.providers.scim.clients.group import SCIMGroupClient\nfrom authentik.providers.scim.clients.user import SCIMUserClient\nfrom authentik.providers.scim.models import SCIMProvider\nfrom authentik.root.celery import CELERY_APP\n\nLOGGER = get_logger(__name__)\n\n\ndef client_for_model(provider: SCIMProvider, model: Model) -> SCIMClient:\n    \"\"\"Get SCIM client for model\"\"\"\n    if isinstance(model, User):\n        return SCIMUserClient(provider)\n    if isinstance(model, Group):\n        return SCIMGroupClient(provider)\n    raise ValueError(f\"Invalid model {model}\")\n\n\n@CELERY_APP.task()\ndef scim_sync_all():\n    \"\"\"Run sync for all providers\"\"\"\n    for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):\n        scim_sync.delay(provider.pk)\n\n\n@CELERY_APP.task(bind=True, base=MonitoredTask)\ndef scim_sync(self: MonitoredTask, provider_pk: int) -> None:\n    \"\"\"Run SCIM full sync for provider\"\"\"\n    provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()\n    if not provider:\n        return\n    self.set_uid(slugify(provider.name))\n    result = TaskResult(TaskResultStatus.SUCCESSFUL, [])\n    result.messages.append(_(\"Starting full SCIM sync\"))\n    LOGGER.debug(\"Starting SCIM sync\")\n    users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)\n    groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)\n    with allow_join_result():\n        try:\n            for page in users_paginator.page_range:\n                result.messages.append(_(\"Syncing page %(page)d of users\" % {\"page\": page}))\n                for msg in scim_sync_users.delay(page, provider_pk).get():\n                    result.messages.append(msg)\n            for page in groups_paginator.page_range:\n                result.messages.append(_(\"Syncing page %(page)d of groups\" % {\"page\": page}))\n                for msg in scim_sync_group.delay(page, provider_pk).get():\n                    result.messages.append(msg)\n        except StopSync as exc:\n            self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))\n            return\n    self.set_status(result)\n\n\n@CELERY_APP.task()\ndef scim_sync_users(page: int, provider_pk: int):\n    \"\"\"Sync single or multiple users to SCIM\"\"\"\n    messages = []\n    provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()\n    if not provider:\n        return messages\n    try:\n        client = SCIMUserClient(provider)\n    except SCIMRequestException:\n        return messages\n    paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)\n    LOGGER.debug(\"starting user sync for page\", page=page)\n    for user in paginator.page(page).object_list:\n        try:\n            client.write(user)\n        except SCIMRequestException as exc:\n            LOGGER.warning(\"failed to sync user\", exc=exc, user=user)\n            messages.append(\n                _(\n                    \"Failed to sync user %(user_name)s due to remote error: %(error)s\"\n                    % {\n                        \"user_name\": user.username,\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n        except StopSync as exc:\n            LOGGER.warning(\"Stopping sync\", exc=exc)\n            messages.append(\n                _(\n                    \"Stopping sync due to error: %(error)s\"\n                    % {\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n            break\n    return messages\n\n\n@CELERY_APP.task()\ndef scim_sync_group(page: int, provider_pk: int):\n    \"\"\"Sync single or multiple groups to SCIM\"\"\"\n    messages = []\n    provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()\n    if not provider:\n        return messages\n    try:\n        client = SCIMGroupClient(provider)\n    except SCIMRequestException:\n        return messages\n    paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)\n    LOGGER.debug(\"starting group sync for page\", page=page)\n    for group in paginator.page(page).object_list:\n        try:\n            client.write(group)\n        except SCIMRequestException as exc:\n            LOGGER.warning(\"failed to sync group\", exc=exc, group=group)\n            messages.append(\n                _(\n                    \"Failed to sync group %(group_name)s due to remote error: %(error)s\"\n                    % {\n                        \"group_name\": group.name,\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n        except StopSync as exc:\n            LOGGER.warning(\"Stopping sync\", exc=exc)\n            messages.append(\n                _(\n                    \"Stopping sync due to error: %(error)s\"\n                    % {\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n            break\n    return messages\n\n\n@CELERY_APP.task()\ndef scim_signal_direct(model: str, pk: Any, raw_op: str):\n    \"\"\"Handler for post_save and pre_delete signal\"\"\"\n    model_class: type[Model] = path_to_class(model)\n    instance = model_class.objects.filter(pk=pk).first()\n    if not instance:\n        return\n    operation = PatchOp(raw_op)\n    for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):\n        client = client_for_model(provider, instance)\n        # Check if the object is allowed within the provider's restrictions\n        queryset: Optional[QuerySet] = None\n        if isinstance(instance, User):\n            queryset = provider.get_user_qs()\n        if isinstance(instance, Group):\n            queryset = provider.get_group_qs()\n        if not queryset:\n            continue\n\n        # The queryset we get from the provider must include the instance we've got given\n        # otherwise ignore this provider\n        if not queryset.filter(pk=instance.pk).exists():\n            continue\n\n        try:\n            if operation == PatchOp.add:\n                client.write(instance)\n            if operation == PatchOp.remove:\n                client.delete(instance)\n        except (StopSync, SCIMRequestException) as exc:\n            LOGGER.warning(exc)\n\n\n@CELERY_APP.task()\ndef scim_signal_m2m(group_pk: str, action: str, pk_set: list[int]):\n    \"\"\"Update m2m (group membership)\"\"\"\n    group = Group.objects.filter(pk=group_pk).first()\n    if not group:\n        return\n    for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):\n        # Check if the object is allowed within the provider's restrictions\n        queryset: QuerySet = provider.get_group_qs()\n        # The queryset we get from the provider must include the instance we've got given\n        # otherwise ignore this provider\n        if not queryset.filter(pk=group_pk).exists():\n            continue\n\n        client = SCIMGroupClient(provider)\n        try:\n            operation = None\n            if action == \"post_add\":\n                operation = PatchOp.add\n            if action == \"post_remove\":\n                operation = PatchOp.remove\n            client.update_group(group, operation, pk_set)\n        except (StopSync, SCIMRequestException) as exc:\n            LOGGER.warning(exc)\n", "path": "authentik/providers/scim/tasks.py"}, {"content": "\"\"\"SCIM Provider API Views\"\"\"\nfrom django.utils.text import slugify\nfrom drf_spectacular.utils import OpenApiResponse, extend_schema\nfrom rest_framework.decorators import action\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom authentik.admin.api.tasks import TaskSerializer\nfrom authentik.core.api.providers import ProviderSerializer\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.events.monitored_tasks import TaskInfo\nfrom authentik.providers.scim.models import SCIMProvider\n\n\nclass SCIMProviderSerializer(ProviderSerializer):\n    \"\"\"SCIMProvider Serializer\"\"\"\n\n    class Meta:\n        model = SCIMProvider\n        fields = [\n            \"pk\",\n            \"name\",\n            \"property_mappings\",\n            \"property_mappings_group\",\n            \"component\",\n            \"assigned_application_slug\",\n            \"assigned_application_name\",\n            \"verbose_name\",\n            \"verbose_name_plural\",\n            \"meta_model_name\",\n            \"url\",\n            \"token\",\n            \"exclude_users_service_account\",\n            \"filter_group\",\n        ]\n        extra_kwargs = {}\n\n\nclass SCIMProviderViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"SCIMProvider Viewset\"\"\"\n\n    queryset = SCIMProvider.objects.all()\n    serializer_class = SCIMProviderSerializer\n    filterset_fields = [\"name\", \"exclude_users_service_account\", \"url\", \"filter_group\"]\n    search_fields = [\"name\", \"url\"]\n    ordering = [\"name\", \"url\"]\n\n    @extend_schema(\n        responses={\n            200: TaskSerializer(),\n            404: OpenApiResponse(description=\"Task not found\"),\n        }\n    )\n    @action(methods=[\"GET\"], detail=True, pagination_class=None, filter_backends=[])\n    def sync_status(self, request: Request, pk: int) -> Response:\n        \"\"\"Get provider's sync status\"\"\"\n        provider = self.get_object()\n        task = TaskInfo.by_name(f\"scim_sync:{slugify(provider.name)}\")\n        if not task:\n            return Response(status=404)\n        return Response(TaskSerializer(task).data)\n", "path": "authentik/providers/scim/api/providers.py"}], "after_files": [{"content": "\"\"\"SCIM Provider tasks\"\"\"\nfrom typing import Any, Optional\n\nfrom celery.result import allow_join_result\nfrom django.core.paginator import Paginator\nfrom django.db.models import Model, QuerySet\nfrom django.utils.text import slugify\nfrom django.utils.translation import gettext_lazy as _\nfrom pydanticscim.responses import PatchOp\nfrom structlog.stdlib import get_logger\n\nfrom authentik.core.models import Group, User\nfrom authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus\nfrom authentik.lib.utils.reflection import path_to_class\nfrom authentik.providers.scim.clients import PAGE_SIZE\nfrom authentik.providers.scim.clients.base import SCIMClient\nfrom authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync\nfrom authentik.providers.scim.clients.group import SCIMGroupClient\nfrom authentik.providers.scim.clients.user import SCIMUserClient\nfrom authentik.providers.scim.models import SCIMProvider\nfrom authentik.root.celery import CELERY_APP\n\nLOGGER = get_logger(__name__)\n\n\ndef client_for_model(provider: SCIMProvider, model: Model) -> SCIMClient:\n    \"\"\"Get SCIM client for model\"\"\"\n    if isinstance(model, User):\n        return SCIMUserClient(provider)\n    if isinstance(model, Group):\n        return SCIMGroupClient(provider)\n    raise ValueError(f\"Invalid model {model}\")\n\n\n@CELERY_APP.task()\ndef scim_sync_all():\n    \"\"\"Run sync for all providers\"\"\"\n    for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):\n        scim_sync.delay(provider.pk)\n\n\n@CELERY_APP.task(bind=True, base=MonitoredTask)\ndef scim_sync(self: MonitoredTask, provider_pk: int) -> None:\n    \"\"\"Run SCIM full sync for provider\"\"\"\n    provider: SCIMProvider = SCIMProvider.objects.filter(\n        pk=provider_pk, backchannel_application__isnull=False\n    ).first()\n    if not provider:\n        return\n    self.set_uid(slugify(provider.name))\n    result = TaskResult(TaskResultStatus.SUCCESSFUL, [])\n    result.messages.append(_(\"Starting full SCIM sync\"))\n    LOGGER.debug(\"Starting SCIM sync\")\n    users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)\n    groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)\n    with allow_join_result():\n        try:\n            for page in users_paginator.page_range:\n                result.messages.append(_(\"Syncing page %(page)d of users\" % {\"page\": page}))\n                for msg in scim_sync_users.delay(page, provider_pk).get():\n                    result.messages.append(msg)\n            for page in groups_paginator.page_range:\n                result.messages.append(_(\"Syncing page %(page)d of groups\" % {\"page\": page}))\n                for msg in scim_sync_group.delay(page, provider_pk).get():\n                    result.messages.append(msg)\n        except StopSync as exc:\n            self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))\n            return\n    self.set_status(result)\n\n\n@CELERY_APP.task()\ndef scim_sync_users(page: int, provider_pk: int):\n    \"\"\"Sync single or multiple users to SCIM\"\"\"\n    messages = []\n    provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()\n    if not provider:\n        return messages\n    try:\n        client = SCIMUserClient(provider)\n    except SCIMRequestException:\n        return messages\n    paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)\n    LOGGER.debug(\"starting user sync for page\", page=page)\n    for user in paginator.page(page).object_list:\n        try:\n            client.write(user)\n        except SCIMRequestException as exc:\n            LOGGER.warning(\"failed to sync user\", exc=exc, user=user)\n            messages.append(\n                _(\n                    \"Failed to sync user %(user_name)s due to remote error: %(error)s\"\n                    % {\n                        \"user_name\": user.username,\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n        except StopSync as exc:\n            LOGGER.warning(\"Stopping sync\", exc=exc)\n            messages.append(\n                _(\n                    \"Stopping sync due to error: %(error)s\"\n                    % {\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n            break\n    return messages\n\n\n@CELERY_APP.task()\ndef scim_sync_group(page: int, provider_pk: int):\n    \"\"\"Sync single or multiple groups to SCIM\"\"\"\n    messages = []\n    provider: SCIMProvider = SCIMProvider.objects.filter(pk=provider_pk).first()\n    if not provider:\n        return messages\n    try:\n        client = SCIMGroupClient(provider)\n    except SCIMRequestException:\n        return messages\n    paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)\n    LOGGER.debug(\"starting group sync for page\", page=page)\n    for group in paginator.page(page).object_list:\n        try:\n            client.write(group)\n        except SCIMRequestException as exc:\n            LOGGER.warning(\"failed to sync group\", exc=exc, group=group)\n            messages.append(\n                _(\n                    \"Failed to sync group %(group_name)s due to remote error: %(error)s\"\n                    % {\n                        \"group_name\": group.name,\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n        except StopSync as exc:\n            LOGGER.warning(\"Stopping sync\", exc=exc)\n            messages.append(\n                _(\n                    \"Stopping sync due to error: %(error)s\"\n                    % {\n                        \"error\": exc.detail(),\n                    }\n                )\n            )\n            break\n    return messages\n\n\n@CELERY_APP.task()\ndef scim_signal_direct(model: str, pk: Any, raw_op: str):\n    \"\"\"Handler for post_save and pre_delete signal\"\"\"\n    model_class: type[Model] = path_to_class(model)\n    instance = model_class.objects.filter(pk=pk).first()\n    if not instance:\n        return\n    operation = PatchOp(raw_op)\n    for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):\n        client = client_for_model(provider, instance)\n        # Check if the object is allowed within the provider's restrictions\n        queryset: Optional[QuerySet] = None\n        if isinstance(instance, User):\n            queryset = provider.get_user_qs()\n        if isinstance(instance, Group):\n            queryset = provider.get_group_qs()\n        if not queryset:\n            continue\n\n        # The queryset we get from the provider must include the instance we've got given\n        # otherwise ignore this provider\n        if not queryset.filter(pk=instance.pk).exists():\n            continue\n\n        try:\n            if operation == PatchOp.add:\n                client.write(instance)\n            if operation == PatchOp.remove:\n                client.delete(instance)\n        except (StopSync, SCIMRequestException) as exc:\n            LOGGER.warning(exc)\n\n\n@CELERY_APP.task()\ndef scim_signal_m2m(group_pk: str, action: str, pk_set: list[int]):\n    \"\"\"Update m2m (group membership)\"\"\"\n    group = Group.objects.filter(pk=group_pk).first()\n    if not group:\n        return\n    for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):\n        # Check if the object is allowed within the provider's restrictions\n        queryset: QuerySet = provider.get_group_qs()\n        # The queryset we get from the provider must include the instance we've got given\n        # otherwise ignore this provider\n        if not queryset.filter(pk=group_pk).exists():\n            continue\n\n        client = SCIMGroupClient(provider)\n        try:\n            operation = None\n            if action == \"post_add\":\n                operation = PatchOp.add\n            if action == \"post_remove\":\n                operation = PatchOp.remove\n            client.update_group(group, operation, pk_set)\n        except (StopSync, SCIMRequestException) as exc:\n            LOGGER.warning(exc)\n", "path": "authentik/providers/scim/tasks.py"}, {"content": "\"\"\"SCIM Provider API Views\"\"\"\nfrom django.utils.text import slugify\nfrom drf_spectacular.utils import OpenApiResponse, extend_schema\nfrom rest_framework.decorators import action\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom authentik.admin.api.tasks import TaskSerializer\nfrom authentik.core.api.providers import ProviderSerializer\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.events.monitored_tasks import TaskInfo\nfrom authentik.providers.scim.models import SCIMProvider\n\n\nclass SCIMProviderSerializer(ProviderSerializer):\n    \"\"\"SCIMProvider Serializer\"\"\"\n\n    class Meta:\n        model = SCIMProvider\n        fields = [\n            \"pk\",\n            \"name\",\n            \"property_mappings\",\n            \"property_mappings_group\",\n            \"component\",\n            \"assigned_backchannel_application_slug\",\n            \"assigned_backchannel_application_name\",\n            \"verbose_name\",\n            \"verbose_name_plural\",\n            \"meta_model_name\",\n            \"url\",\n            \"token\",\n            \"exclude_users_service_account\",\n            \"filter_group\",\n        ]\n        extra_kwargs = {}\n\n\nclass SCIMProviderViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"SCIMProvider Viewset\"\"\"\n\n    queryset = SCIMProvider.objects.all()\n    serializer_class = SCIMProviderSerializer\n    filterset_fields = [\"name\", \"exclude_users_service_account\", \"url\", \"filter_group\"]\n    search_fields = [\"name\", \"url\"]\n    ordering = [\"name\", \"url\"]\n\n    @extend_schema(\n        responses={\n            200: TaskSerializer(),\n            404: OpenApiResponse(description=\"Task not found\"),\n        }\n    )\n    @action(methods=[\"GET\"], detail=True, pagination_class=None, filter_backends=[])\n    def sync_status(self, request: Request, pk: int) -> Response:\n        \"\"\"Get provider's sync status\"\"\"\n        provider = self.get_object()\n        task = TaskInfo.by_name(f\"scim_sync:{slugify(provider.name)}\")\n        if not task:\n            return Response(status=404)\n        return Response(TaskSerializer(task).data)\n", "path": "authentik/providers/scim/api/providers.py"}]}