authentik.sources.scim.tests.test_groups

Test SCIM Group

  1"""Test SCIM Group"""
  2
  3from json import dumps
  4from uuid import uuid4
  5
  6from django.urls import reverse
  7from rest_framework.test import APITestCase
  8
  9from authentik.core.models import Group
 10from authentik.core.tests.utils import create_test_user
 11from authentik.events.models import Event, EventAction
 12from authentik.lib.generators import generate_id
 13from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
 14from authentik.sources.scim.models import (
 15    SCIMSource,
 16    SCIMSourceGroup,
 17)
 18from authentik.sources.scim.views.v2.base import SCIM_CONTENT_TYPE
 19
 20
 21class TestSCIMGroups(APITestCase):
 22    """Test SCIM Group view"""
 23
 24    def setUp(self) -> None:
 25        self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
 26
 27    def test_group_list(self):
 28        """Test full group list"""
 29        response = self.client.get(
 30            reverse(
 31                "authentik_sources_scim:v2-groups",
 32                kwargs={
 33                    "source_slug": self.source.slug,
 34                },
 35            ),
 36            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
 37        )
 38        self.assertEqual(response.status_code, 200)
 39
 40    def test_group_list_single(self):
 41        """Test full group list (single group)"""
 42        group = Group.objects.create(name=generate_id())
 43        user = create_test_user()
 44        group.users.add(user)
 45        SCIMSourceGroup.objects.create(
 46            source=self.source,
 47            group=group,
 48            id=str(uuid4()),
 49        )
 50        response = self.client.get(
 51            reverse(
 52                "authentik_sources_scim:v2-groups",
 53                kwargs={
 54                    "source_slug": self.source.slug,
 55                    "group_id": str(group.pk),
 56                },
 57            ),
 58            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
 59        )
 60        self.assertEqual(response.status_code, second=200)
 61        SCIMGroupSchema.model_validate_json(response.content, strict=True)
 62
 63    def test_group_create(self):
 64        """Test group create"""
 65        ext_id = generate_id()
 66        response = self.client.post(
 67            reverse(
 68                "authentik_sources_scim:v2-groups",
 69                kwargs={
 70                    "source_slug": self.source.slug,
 71                },
 72            ),
 73            data=dumps({"displayName": generate_id(), "externalId": ext_id}),
 74            content_type=SCIM_CONTENT_TYPE,
 75            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
 76        )
 77        self.assertEqual(response.status_code, 201)
 78        self.assertTrue(
 79            SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
 80        )
 81        self.assertTrue(
 82            Event.objects.filter(
 83                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
 84            ).exists()
 85        )
 86
 87    def test_group_create_members(self):
 88        """Test group create"""
 89        user = create_test_user()
 90        ext_id = generate_id()
 91        name = generate_id()
 92        response = self.client.post(
 93            reverse(
 94                "authentik_sources_scim:v2-groups",
 95                kwargs={
 96                    "source_slug": self.source.slug,
 97                },
 98            ),
 99            data=dumps(
100                {
101                    "displayName": name,
102                    "externalId": ext_id,
103                    "members": [{"value": str(user.uuid)}],
104                }
105            ),
106            content_type=SCIM_CONTENT_TYPE,
107            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
108        )
109        self.assertEqual(response.status_code, 201)
110        connection = SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).first()
111        self.assertIsNotNone(connection)
112        self.assertTrue(
113            Event.objects.filter(
114                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
115            ).exists()
116        )
117        connection.refresh_from_db()
118        self.assertEqual(
119            connection.attributes,
120            {
121                "displayName": name,
122                "externalId": ext_id,
123                "members": [{"value": str(user.uuid)}],
124            },
125        )
126
127    def test_group_create_members_empty(self):
128        """Test group create"""
129        ext_id = generate_id()
130        response = self.client.post(
131            reverse(
132                "authentik_sources_scim:v2-groups",
133                kwargs={
134                    "source_slug": self.source.slug,
135                },
136            ),
137            data=dumps({"displayName": generate_id(), "externalId": ext_id, "members": []}),
138            content_type=SCIM_CONTENT_TYPE,
139            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
140        )
141        self.assertEqual(response.status_code, 201)
142        self.assertTrue(
143            SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
144        )
145        self.assertTrue(
146            Event.objects.filter(
147                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
148            ).exists()
149        )
150
151    def test_group_create_duplicate(self):
152        """Test group create (duplicate)"""
153        group = Group.objects.create(name=generate_id())
154        existing = SCIMSourceGroup.objects.create(
155            source=self.source, group=group, external_id=uuid4()
156        )
157        ext_id = generate_id()
158        response = self.client.post(
159            reverse(
160                "authentik_sources_scim:v2-groups",
161                kwargs={
162                    "source_slug": self.source.slug,
163                },
164            ),
165            data=dumps(
166                {"displayName": generate_id(), "externalId": ext_id, "id": str(existing.group.pk)}
167            ),
168            content_type=SCIM_CONTENT_TYPE,
169            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
170        )
171        self.assertEqual(response.status_code, 409)
172        self.assertJSONEqual(
173            response.content,
174            {
175                "detail": "Group with ID exists already.",
176                "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
177                "scimType": "uniqueness",
178                "status": 409,
179            },
180        )
181
182    def test_group_update(self):
183        """Test group update"""
184        group = Group.objects.create(name=generate_id())
185        existing = SCIMSourceGroup.objects.create(
186            source=self.source, group=group, external_id=uuid4()
187        )
188        ext_id = generate_id()
189        response = self.client.put(
190            reverse(
191                "authentik_sources_scim:v2-groups",
192                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
193            ),
194            data=dumps(
195                {"displayName": generate_id(), "externalId": ext_id, "id": str(existing.pk)}
196            ),
197            content_type=SCIM_CONTENT_TYPE,
198            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
199        )
200        self.assertEqual(response.status_code, second=200)
201
202    def test_group_update_non_existent(self):
203        """Test group update"""
204        ext_id = generate_id()
205        response = self.client.put(
206            reverse(
207                "authentik_sources_scim:v2-groups",
208                kwargs={
209                    "source_slug": self.source.slug,
210                    "group_id": str(uuid4()),
211                },
212            ),
213            data=dumps({"displayName": generate_id(), "externalId": ext_id, "id": ""}),
214            content_type=SCIM_CONTENT_TYPE,
215            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
216        )
217        self.assertEqual(response.status_code, second=404)
218        self.assertJSONEqual(
219            response.content,
220            {
221                "detail": "Group not found.",
222                "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
223                "status": 404,
224            },
225        )
226
227    def test_group_patch_modify(self):
228        """Test group patch"""
229        group = Group.objects.create(name=generate_id())
230        connection = SCIMSourceGroup.objects.create(
231            source=self.source,
232            group=group,
233            external_id=uuid4(),
234            attributes={"displayName": group.name, "members": []},
235        )
236        response = self.client.patch(
237            reverse(
238                "authentik_sources_scim:v2-groups",
239                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
240            ),
241            data=dumps(
242                {
243                    "Operations": [
244                        {
245                            "op": "Add",
246                            "value": {"externalId": "d85051cb-0557-4aa1-98ca-51eabcee4d40"},
247                        }
248                    ]
249                }
250            ),
251            content_type=SCIM_CONTENT_TYPE,
252            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
253        )
254        self.assertEqual(response.status_code, 200, response.content)
255        connection = SCIMSourceGroup.objects.filter(id="d85051cb-0557-4aa1-98ca-51eabcee4d40")
256        self.assertIsNotNone(connection)
257
258    def test_group_patch_member_add(self):
259        """Test group patch"""
260        user = create_test_user()
261        other_user = create_test_user()
262        group = Group.objects.create(name=generate_id())
263        group.users.add(other_user)
264        connection = SCIMSourceGroup.objects.create(
265            source=self.source,
266            group=group,
267            external_id=uuid4(),
268            attributes={"displayName": group.name, "members": [{"value": str(other_user.uuid)}]},
269        )
270        response = self.client.patch(
271            reverse(
272                "authentik_sources_scim:v2-groups",
273                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
274            ),
275            data=dumps(
276                {
277                    "Operations": [
278                        {
279                            "op": "Add",
280                            "path": "members",
281                            "value": [{"value": str(user.uuid)}],
282                        }
283                    ]
284                }
285            ),
286            content_type=SCIM_CONTENT_TYPE,
287            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
288        )
289        self.assertEqual(response.status_code, 200, response.content)
290        self.assertTrue(group.users.filter(pk=user.pk).exists())
291        self.assertTrue(group.users.filter(pk=other_user.pk).exists())
292        connection.refresh_from_db()
293        self.assertEqual(
294            connection.attributes,
295            {
296                "displayName": group.name,
297                "members": sorted(
298                    [{"value": str(other_user.uuid)}, {"value": str(user.uuid)}],
299                    key=lambda u: u["value"],
300                ),
301            },
302        )
303
304    def test_group_patch_member_remove(self):
305        """Test group patch"""
306        user = create_test_user()
307
308        group = Group.objects.create(name=generate_id())
309        group.users.add(user)
310        connection = SCIMSourceGroup.objects.create(
311            source=self.source,
312            group=group,
313            external_id=uuid4(),
314            attributes={"displayName": group.name, "members": []},
315        )
316        response = self.client.patch(
317            reverse(
318                "authentik_sources_scim:v2-groups",
319                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
320            ),
321            data=dumps(
322                {
323                    "Operations": [
324                        {
325                            "op": "remove",
326                            "path": "members",
327                            "value": [{"value": str(user.uuid)}],
328                        }
329                    ]
330                }
331            ),
332            content_type=SCIM_CONTENT_TYPE,
333            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
334        )
335        self.assertEqual(response.status_code, 200, response.content)
336        self.assertFalse(group.users.filter(pk=user.pk).exists())
337        connection.refresh_from_db()
338        self.assertEqual(
339            connection.attributes,
340            {
341                "displayName": group.name,
342                "members": [],
343            },
344        )
345
346    def test_group_delete(self):
347        """Test group delete"""
348        group = Group.objects.create(name=generate_id())
349        SCIMSourceGroup.objects.create(source=self.source, group=group, external_id=uuid4())
350        response = self.client.delete(
351            reverse(
352                "authentik_sources_scim:v2-groups",
353                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
354            ),
355            content_type=SCIM_CONTENT_TYPE,
356            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
357        )
358        self.assertEqual(response.status_code, second=204)
class TestSCIMGroups(rest_framework.test.APITestCase):
 22class TestSCIMGroups(APITestCase):
 23    """Test SCIM Group view"""
 24
 25    def setUp(self) -> None:
 26        self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
 27
 28    def test_group_list(self):
 29        """Test full group list"""
 30        response = self.client.get(
 31            reverse(
 32                "authentik_sources_scim:v2-groups",
 33                kwargs={
 34                    "source_slug": self.source.slug,
 35                },
 36            ),
 37            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
 38        )
 39        self.assertEqual(response.status_code, 200)
 40
 41    def test_group_list_single(self):
 42        """Test full group list (single group)"""
 43        group = Group.objects.create(name=generate_id())
 44        user = create_test_user()
 45        group.users.add(user)
 46        SCIMSourceGroup.objects.create(
 47            source=self.source,
 48            group=group,
 49            id=str(uuid4()),
 50        )
 51        response = self.client.get(
 52            reverse(
 53                "authentik_sources_scim:v2-groups",
 54                kwargs={
 55                    "source_slug": self.source.slug,
 56                    "group_id": str(group.pk),
 57                },
 58            ),
 59            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
 60        )
 61        self.assertEqual(response.status_code, second=200)
 62        SCIMGroupSchema.model_validate_json(response.content, strict=True)
 63
 64    def test_group_create(self):
 65        """Test group create"""
 66        ext_id = generate_id()
 67        response = self.client.post(
 68            reverse(
 69                "authentik_sources_scim:v2-groups",
 70                kwargs={
 71                    "source_slug": self.source.slug,
 72                },
 73            ),
 74            data=dumps({"displayName": generate_id(), "externalId": ext_id}),
 75            content_type=SCIM_CONTENT_TYPE,
 76            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
 77        )
 78        self.assertEqual(response.status_code, 201)
 79        self.assertTrue(
 80            SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
 81        )
 82        self.assertTrue(
 83            Event.objects.filter(
 84                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
 85            ).exists()
 86        )
 87
 88    def test_group_create_members(self):
 89        """Test group create"""
 90        user = create_test_user()
 91        ext_id = generate_id()
 92        name = generate_id()
 93        response = self.client.post(
 94            reverse(
 95                "authentik_sources_scim:v2-groups",
 96                kwargs={
 97                    "source_slug": self.source.slug,
 98                },
 99            ),
100            data=dumps(
101                {
102                    "displayName": name,
103                    "externalId": ext_id,
104                    "members": [{"value": str(user.uuid)}],
105                }
106            ),
107            content_type=SCIM_CONTENT_TYPE,
108            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
109        )
110        self.assertEqual(response.status_code, 201)
111        connection = SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).first()
112        self.assertIsNotNone(connection)
113        self.assertTrue(
114            Event.objects.filter(
115                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
116            ).exists()
117        )
118        connection.refresh_from_db()
119        self.assertEqual(
120            connection.attributes,
121            {
122                "displayName": name,
123                "externalId": ext_id,
124                "members": [{"value": str(user.uuid)}],
125            },
126        )
127
128    def test_group_create_members_empty(self):
129        """Test group create"""
130        ext_id = generate_id()
131        response = self.client.post(
132            reverse(
133                "authentik_sources_scim:v2-groups",
134                kwargs={
135                    "source_slug": self.source.slug,
136                },
137            ),
138            data=dumps({"displayName": generate_id(), "externalId": ext_id, "members": []}),
139            content_type=SCIM_CONTENT_TYPE,
140            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
141        )
142        self.assertEqual(response.status_code, 201)
143        self.assertTrue(
144            SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
145        )
146        self.assertTrue(
147            Event.objects.filter(
148                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
149            ).exists()
150        )
151
152    def test_group_create_duplicate(self):
153        """Test group create (duplicate)"""
154        group = Group.objects.create(name=generate_id())
155        existing = SCIMSourceGroup.objects.create(
156            source=self.source, group=group, external_id=uuid4()
157        )
158        ext_id = generate_id()
159        response = self.client.post(
160            reverse(
161                "authentik_sources_scim:v2-groups",
162                kwargs={
163                    "source_slug": self.source.slug,
164                },
165            ),
166            data=dumps(
167                {"displayName": generate_id(), "externalId": ext_id, "id": str(existing.group.pk)}
168            ),
169            content_type=SCIM_CONTENT_TYPE,
170            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
171        )
172        self.assertEqual(response.status_code, 409)
173        self.assertJSONEqual(
174            response.content,
175            {
176                "detail": "Group with ID exists already.",
177                "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
178                "scimType": "uniqueness",
179                "status": 409,
180            },
181        )
182
183    def test_group_update(self):
184        """Test group update"""
185        group = Group.objects.create(name=generate_id())
186        existing = SCIMSourceGroup.objects.create(
187            source=self.source, group=group, external_id=uuid4()
188        )
189        ext_id = generate_id()
190        response = self.client.put(
191            reverse(
192                "authentik_sources_scim:v2-groups",
193                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
194            ),
195            data=dumps(
196                {"displayName": generate_id(), "externalId": ext_id, "id": str(existing.pk)}
197            ),
198            content_type=SCIM_CONTENT_TYPE,
199            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
200        )
201        self.assertEqual(response.status_code, second=200)
202
203    def test_group_update_non_existent(self):
204        """Test group update"""
205        ext_id = generate_id()
206        response = self.client.put(
207            reverse(
208                "authentik_sources_scim:v2-groups",
209                kwargs={
210                    "source_slug": self.source.slug,
211                    "group_id": str(uuid4()),
212                },
213            ),
214            data=dumps({"displayName": generate_id(), "externalId": ext_id, "id": ""}),
215            content_type=SCIM_CONTENT_TYPE,
216            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
217        )
218        self.assertEqual(response.status_code, second=404)
219        self.assertJSONEqual(
220            response.content,
221            {
222                "detail": "Group not found.",
223                "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
224                "status": 404,
225            },
226        )
227
228    def test_group_patch_modify(self):
229        """Test group patch"""
230        group = Group.objects.create(name=generate_id())
231        connection = SCIMSourceGroup.objects.create(
232            source=self.source,
233            group=group,
234            external_id=uuid4(),
235            attributes={"displayName": group.name, "members": []},
236        )
237        response = self.client.patch(
238            reverse(
239                "authentik_sources_scim:v2-groups",
240                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
241            ),
242            data=dumps(
243                {
244                    "Operations": [
245                        {
246                            "op": "Add",
247                            "value": {"externalId": "d85051cb-0557-4aa1-98ca-51eabcee4d40"},
248                        }
249                    ]
250                }
251            ),
252            content_type=SCIM_CONTENT_TYPE,
253            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
254        )
255        self.assertEqual(response.status_code, 200, response.content)
256        connection = SCIMSourceGroup.objects.filter(id="d85051cb-0557-4aa1-98ca-51eabcee4d40")
257        self.assertIsNotNone(connection)
258
259    def test_group_patch_member_add(self):
260        """Test group patch"""
261        user = create_test_user()
262        other_user = create_test_user()
263        group = Group.objects.create(name=generate_id())
264        group.users.add(other_user)
265        connection = SCIMSourceGroup.objects.create(
266            source=self.source,
267            group=group,
268            external_id=uuid4(),
269            attributes={"displayName": group.name, "members": [{"value": str(other_user.uuid)}]},
270        )
271        response = self.client.patch(
272            reverse(
273                "authentik_sources_scim:v2-groups",
274                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
275            ),
276            data=dumps(
277                {
278                    "Operations": [
279                        {
280                            "op": "Add",
281                            "path": "members",
282                            "value": [{"value": str(user.uuid)}],
283                        }
284                    ]
285                }
286            ),
287            content_type=SCIM_CONTENT_TYPE,
288            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
289        )
290        self.assertEqual(response.status_code, 200, response.content)
291        self.assertTrue(group.users.filter(pk=user.pk).exists())
292        self.assertTrue(group.users.filter(pk=other_user.pk).exists())
293        connection.refresh_from_db()
294        self.assertEqual(
295            connection.attributes,
296            {
297                "displayName": group.name,
298                "members": sorted(
299                    [{"value": str(other_user.uuid)}, {"value": str(user.uuid)}],
300                    key=lambda u: u["value"],
301                ),
302            },
303        )
304
305    def test_group_patch_member_remove(self):
306        """Test group patch"""
307        user = create_test_user()
308
309        group = Group.objects.create(name=generate_id())
310        group.users.add(user)
311        connection = SCIMSourceGroup.objects.create(
312            source=self.source,
313            group=group,
314            external_id=uuid4(),
315            attributes={"displayName": group.name, "members": []},
316        )
317        response = self.client.patch(
318            reverse(
319                "authentik_sources_scim:v2-groups",
320                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
321            ),
322            data=dumps(
323                {
324                    "Operations": [
325                        {
326                            "op": "remove",
327                            "path": "members",
328                            "value": [{"value": str(user.uuid)}],
329                        }
330                    ]
331                }
332            ),
333            content_type=SCIM_CONTENT_TYPE,
334            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
335        )
336        self.assertEqual(response.status_code, 200, response.content)
337        self.assertFalse(group.users.filter(pk=user.pk).exists())
338        connection.refresh_from_db()
339        self.assertEqual(
340            connection.attributes,
341            {
342                "displayName": group.name,
343                "members": [],
344            },
345        )
346
347    def test_group_delete(self):
348        """Test group delete"""
349        group = Group.objects.create(name=generate_id())
350        SCIMSourceGroup.objects.create(source=self.source, group=group, external_id=uuid4())
351        response = self.client.delete(
352            reverse(
353                "authentik_sources_scim:v2-groups",
354                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
355            ),
356            content_type=SCIM_CONTENT_TYPE,
357            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
358        )
359        self.assertEqual(response.status_code, second=204)

Test SCIM Group view

def setUp(self) -> None:
25    def setUp(self) -> None:
26        self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())

Hook method for setting up the test fixture before exercising it.

def test_group_list(self):
28    def test_group_list(self):
29        """Test full group list"""
30        response = self.client.get(
31            reverse(
32                "authentik_sources_scim:v2-groups",
33                kwargs={
34                    "source_slug": self.source.slug,
35                },
36            ),
37            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
38        )
39        self.assertEqual(response.status_code, 200)

Test full group list

def test_group_list_single(self):
41    def test_group_list_single(self):
42        """Test full group list (single group)"""
43        group = Group.objects.create(name=generate_id())
44        user = create_test_user()
45        group.users.add(user)
46        SCIMSourceGroup.objects.create(
47            source=self.source,
48            group=group,
49            id=str(uuid4()),
50        )
51        response = self.client.get(
52            reverse(
53                "authentik_sources_scim:v2-groups",
54                kwargs={
55                    "source_slug": self.source.slug,
56                    "group_id": str(group.pk),
57                },
58            ),
59            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
60        )
61        self.assertEqual(response.status_code, second=200)
62        SCIMGroupSchema.model_validate_json(response.content, strict=True)

Test full group list (single group)

def test_group_create(self):
64    def test_group_create(self):
65        """Test group create"""
66        ext_id = generate_id()
67        response = self.client.post(
68            reverse(
69                "authentik_sources_scim:v2-groups",
70                kwargs={
71                    "source_slug": self.source.slug,
72                },
73            ),
74            data=dumps({"displayName": generate_id(), "externalId": ext_id}),
75            content_type=SCIM_CONTENT_TYPE,
76            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
77        )
78        self.assertEqual(response.status_code, 201)
79        self.assertTrue(
80            SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
81        )
82        self.assertTrue(
83            Event.objects.filter(
84                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
85            ).exists()
86        )

Test group create

def test_group_create_members(self):
 88    def test_group_create_members(self):
 89        """Test group create"""
 90        user = create_test_user()
 91        ext_id = generate_id()
 92        name = generate_id()
 93        response = self.client.post(
 94            reverse(
 95                "authentik_sources_scim:v2-groups",
 96                kwargs={
 97                    "source_slug": self.source.slug,
 98                },
 99            ),
100            data=dumps(
101                {
102                    "displayName": name,
103                    "externalId": ext_id,
104                    "members": [{"value": str(user.uuid)}],
105                }
106            ),
107            content_type=SCIM_CONTENT_TYPE,
108            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
109        )
110        self.assertEqual(response.status_code, 201)
111        connection = SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).first()
112        self.assertIsNotNone(connection)
113        self.assertTrue(
114            Event.objects.filter(
115                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
116            ).exists()
117        )
118        connection.refresh_from_db()
119        self.assertEqual(
120            connection.attributes,
121            {
122                "displayName": name,
123                "externalId": ext_id,
124                "members": [{"value": str(user.uuid)}],
125            },
126        )

Test group create

def test_group_create_members_empty(self):
128    def test_group_create_members_empty(self):
129        """Test group create"""
130        ext_id = generate_id()
131        response = self.client.post(
132            reverse(
133                "authentik_sources_scim:v2-groups",
134                kwargs={
135                    "source_slug": self.source.slug,
136                },
137            ),
138            data=dumps({"displayName": generate_id(), "externalId": ext_id, "members": []}),
139            content_type=SCIM_CONTENT_TYPE,
140            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
141        )
142        self.assertEqual(response.status_code, 201)
143        self.assertTrue(
144            SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
145        )
146        self.assertTrue(
147            Event.objects.filter(
148                action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
149            ).exists()
150        )

Test group create

def test_group_create_duplicate(self):
152    def test_group_create_duplicate(self):
153        """Test group create (duplicate)"""
154        group = Group.objects.create(name=generate_id())
155        existing = SCIMSourceGroup.objects.create(
156            source=self.source, group=group, external_id=uuid4()
157        )
158        ext_id = generate_id()
159        response = self.client.post(
160            reverse(
161                "authentik_sources_scim:v2-groups",
162                kwargs={
163                    "source_slug": self.source.slug,
164                },
165            ),
166            data=dumps(
167                {"displayName": generate_id(), "externalId": ext_id, "id": str(existing.group.pk)}
168            ),
169            content_type=SCIM_CONTENT_TYPE,
170            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
171        )
172        self.assertEqual(response.status_code, 409)
173        self.assertJSONEqual(
174            response.content,
175            {
176                "detail": "Group with ID exists already.",
177                "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
178                "scimType": "uniqueness",
179                "status": 409,
180            },
181        )

Test group create (duplicate)

def test_group_update(self):
183    def test_group_update(self):
184        """Test group update"""
185        group = Group.objects.create(name=generate_id())
186        existing = SCIMSourceGroup.objects.create(
187            source=self.source, group=group, external_id=uuid4()
188        )
189        ext_id = generate_id()
190        response = self.client.put(
191            reverse(
192                "authentik_sources_scim:v2-groups",
193                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
194            ),
195            data=dumps(
196                {"displayName": generate_id(), "externalId": ext_id, "id": str(existing.pk)}
197            ),
198            content_type=SCIM_CONTENT_TYPE,
199            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
200        )
201        self.assertEqual(response.status_code, second=200)

Test group update

def test_group_update_non_existent(self):
203    def test_group_update_non_existent(self):
204        """Test group update"""
205        ext_id = generate_id()
206        response = self.client.put(
207            reverse(
208                "authentik_sources_scim:v2-groups",
209                kwargs={
210                    "source_slug": self.source.slug,
211                    "group_id": str(uuid4()),
212                },
213            ),
214            data=dumps({"displayName": generate_id(), "externalId": ext_id, "id": ""}),
215            content_type=SCIM_CONTENT_TYPE,
216            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
217        )
218        self.assertEqual(response.status_code, second=404)
219        self.assertJSONEqual(
220            response.content,
221            {
222                "detail": "Group not found.",
223                "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
224                "status": 404,
225            },
226        )

Test group update

def test_group_patch_modify(self):
228    def test_group_patch_modify(self):
229        """Test group patch"""
230        group = Group.objects.create(name=generate_id())
231        connection = SCIMSourceGroup.objects.create(
232            source=self.source,
233            group=group,
234            external_id=uuid4(),
235            attributes={"displayName": group.name, "members": []},
236        )
237        response = self.client.patch(
238            reverse(
239                "authentik_sources_scim:v2-groups",
240                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
241            ),
242            data=dumps(
243                {
244                    "Operations": [
245                        {
246                            "op": "Add",
247                            "value": {"externalId": "d85051cb-0557-4aa1-98ca-51eabcee4d40"},
248                        }
249                    ]
250                }
251            ),
252            content_type=SCIM_CONTENT_TYPE,
253            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
254        )
255        self.assertEqual(response.status_code, 200, response.content)
256        connection = SCIMSourceGroup.objects.filter(id="d85051cb-0557-4aa1-98ca-51eabcee4d40")
257        self.assertIsNotNone(connection)

Test group patch

def test_group_patch_member_add(self):
259    def test_group_patch_member_add(self):
260        """Test group patch"""
261        user = create_test_user()
262        other_user = create_test_user()
263        group = Group.objects.create(name=generate_id())
264        group.users.add(other_user)
265        connection = SCIMSourceGroup.objects.create(
266            source=self.source,
267            group=group,
268            external_id=uuid4(),
269            attributes={"displayName": group.name, "members": [{"value": str(other_user.uuid)}]},
270        )
271        response = self.client.patch(
272            reverse(
273                "authentik_sources_scim:v2-groups",
274                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
275            ),
276            data=dumps(
277                {
278                    "Operations": [
279                        {
280                            "op": "Add",
281                            "path": "members",
282                            "value": [{"value": str(user.uuid)}],
283                        }
284                    ]
285                }
286            ),
287            content_type=SCIM_CONTENT_TYPE,
288            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
289        )
290        self.assertEqual(response.status_code, 200, response.content)
291        self.assertTrue(group.users.filter(pk=user.pk).exists())
292        self.assertTrue(group.users.filter(pk=other_user.pk).exists())
293        connection.refresh_from_db()
294        self.assertEqual(
295            connection.attributes,
296            {
297                "displayName": group.name,
298                "members": sorted(
299                    [{"value": str(other_user.uuid)}, {"value": str(user.uuid)}],
300                    key=lambda u: u["value"],
301                ),
302            },
303        )

Test group patch

def test_group_patch_member_remove(self):
305    def test_group_patch_member_remove(self):
306        """Test group patch"""
307        user = create_test_user()
308
309        group = Group.objects.create(name=generate_id())
310        group.users.add(user)
311        connection = SCIMSourceGroup.objects.create(
312            source=self.source,
313            group=group,
314            external_id=uuid4(),
315            attributes={"displayName": group.name, "members": []},
316        )
317        response = self.client.patch(
318            reverse(
319                "authentik_sources_scim:v2-groups",
320                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
321            ),
322            data=dumps(
323                {
324                    "Operations": [
325                        {
326                            "op": "remove",
327                            "path": "members",
328                            "value": [{"value": str(user.uuid)}],
329                        }
330                    ]
331                }
332            ),
333            content_type=SCIM_CONTENT_TYPE,
334            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
335        )
336        self.assertEqual(response.status_code, 200, response.content)
337        self.assertFalse(group.users.filter(pk=user.pk).exists())
338        connection.refresh_from_db()
339        self.assertEqual(
340            connection.attributes,
341            {
342                "displayName": group.name,
343                "members": [],
344            },
345        )

Test group patch

def test_group_delete(self):
347    def test_group_delete(self):
348        """Test group delete"""
349        group = Group.objects.create(name=generate_id())
350        SCIMSourceGroup.objects.create(source=self.source, group=group, external_id=uuid4())
351        response = self.client.delete(
352            reverse(
353                "authentik_sources_scim:v2-groups",
354                kwargs={"source_slug": self.source.slug, "group_id": group.pk},
355            ),
356            content_type=SCIM_CONTENT_TYPE,
357            HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
358        )
359        self.assertEqual(response.status_code, second=204)

Test group delete