authentik.providers.scim.tests.test_membership

SCIM Membership tests

  1"""SCIM Membership tests"""
  2
  3from django.test import TestCase
  4from requests_mock import Mocker
  5
  6from authentik.blueprints.tests import apply_blueprint
  7from authentik.core.models import Application, Group, User
  8from authentik.lib.generators import generate_id
  9from authentik.providers.scim.clients.schema import ServiceProviderConfiguration
 10from authentik.providers.scim.models import SCIMCompatibilityMode, SCIMMapping, SCIMProvider
 11from authentik.providers.scim.tasks import scim_sync
 12from authentik.tenants.models import Tenant
 13
 14
 15class SCIMMembershipTests(TestCase):
 16    """SCIM Membership tests"""
 17
 18    provider: SCIMProvider
 19    app: Application
 20
 21    def setUp(self) -> None:
 22        # Delete all users and groups as the mocked HTTP responses only return one ID
 23        # which will cause errors with multiple users
 24        User.objects.all().exclude_anonymous().delete()
 25        Group.objects.all().delete()
 26        Tenant.objects.update(avatars="none")
 27
 28    @apply_blueprint("system/providers-scim.yaml")
 29    def configure(self, **kwargs) -> None:
 30        """Configure provider"""
 31        self.provider: SCIMProvider = SCIMProvider.objects.create(
 32            name=generate_id(),
 33            url="https://localhost",
 34            token=generate_id(),
 35            **kwargs,
 36        )
 37        self.app: Application = Application.objects.create(
 38            name=generate_id(),
 39            slug=generate_id(),
 40        )
 41        self.app.backchannel_providers.add(self.provider)
 42        self.provider.save()
 43        self.provider.property_mappings.set(
 44            [SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/user")]
 45        )
 46        self.provider.property_mappings_group.set(
 47            [SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/group")]
 48        )
 49
 50    def test_member_add(self):
 51        """Test member add"""
 52        config = ServiceProviderConfiguration.default()
 53
 54        config.patch.supported = True
 55        user_scim_id = generate_id()
 56        group_scim_id = generate_id()
 57        uid = generate_id()
 58        group = Group.objects.create(
 59            name=uid,
 60        )
 61
 62        user = User.objects.create(username=generate_id())
 63
 64        with Mocker() as mocker:
 65            mocker.get(
 66                "https://localhost/ServiceProviderConfig",
 67                json=config.model_dump(),
 68            )
 69            mocker.post(
 70                "https://localhost/Users",
 71                json={
 72                    "id": user_scim_id,
 73                },
 74            )
 75            mocker.post(
 76                "https://localhost/Groups",
 77                json={
 78                    "id": group_scim_id,
 79                },
 80            )
 81
 82            self.configure()
 83            scim_sync.send(self.provider.pk)
 84
 85            self.assertEqual(mocker.call_count, 3)
 86            self.assertEqual(mocker.request_history[0].method, "GET")
 87            self.assertEqual(mocker.request_history[1].method, "POST")
 88            self.assertEqual(mocker.request_history[2].method, "POST")
 89            self.assertJSONEqual(
 90                mocker.request_history[1].body,
 91                {
 92                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
 93                    "emails": [],
 94                    "active": True,
 95                    "externalId": user.uid,
 96                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
 97                    "displayName": "",
 98                    "userName": user.username,
 99                },
100            )
101            self.assertJSONEqual(
102                mocker.request_history[2].body,
103                {
104                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
105                    "externalId": str(group.pk),
106                    "displayName": group.name,
107                },
108            )
109
110        with Mocker() as mocker:
111            mocker.get(
112                "https://localhost/ServiceProviderConfig",
113                json=config.model_dump(),
114            )
115            mocker.patch(
116                f"https://localhost/Groups/{group_scim_id}",
117                json={},
118            )
119            group.users.add(user)
120            self.assertEqual(mocker.call_count, 1)
121            self.assertEqual(mocker.request_history[0].method, "PATCH")
122            self.assertJSONEqual(
123                mocker.request_history[0].body,
124                {
125                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
126                    "Operations": [
127                        {
128                            "op": "add",
129                            "path": "members",
130                            "value": [{"value": user_scim_id}],
131                        }
132                    ],
133                },
134            )
135
136    def test_member_remove(self):
137        """Test member remove"""
138        config = ServiceProviderConfiguration.default()
139
140        config.patch.supported = True
141        user_scim_id = generate_id()
142        group_scim_id = generate_id()
143        uid = generate_id()
144        group = Group.objects.create(
145            name=uid,
146        )
147
148        user = User.objects.create(username=generate_id())
149
150        with Mocker() as mocker:
151            mocker.get(
152                "https://localhost/ServiceProviderConfig",
153                json=config.model_dump(),
154            )
155            mocker.post(
156                "https://localhost/Users",
157                json={
158                    "id": user_scim_id,
159                },
160            )
161            mocker.post(
162                "https://localhost/Groups",
163                json={
164                    "id": group_scim_id,
165                },
166            )
167
168            self.configure()
169            scim_sync.send(self.provider.pk)
170
171            self.assertEqual(mocker.call_count, 3)
172            self.assertEqual(mocker.request_history[0].method, "GET")
173            self.assertEqual(mocker.request_history[1].method, "POST")
174            self.assertEqual(mocker.request_history[2].method, "POST")
175            self.assertJSONEqual(
176                mocker.request_history[1].body,
177                {
178                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
179                    "active": True,
180                    "displayName": "",
181                    "emails": [],
182                    "externalId": user.uid,
183                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
184                    "userName": user.username,
185                },
186            )
187            self.assertJSONEqual(
188                mocker.request_history[2].body,
189                {
190                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
191                    "externalId": str(group.pk),
192                    "displayName": group.name,
193                },
194            )
195
196        with Mocker() as mocker:
197            mocker.get(
198                "https://localhost/ServiceProviderConfig",
199                json=config.model_dump(),
200            )
201            mocker.patch(
202                f"https://localhost/Groups/{group_scim_id}",
203                json={},
204            )
205            group.users.add(user)
206            self.assertEqual(mocker.call_count, 1)
207            self.assertEqual(mocker.request_history[0].method, "PATCH")
208            self.assertJSONEqual(
209                mocker.request_history[0].body,
210                {
211                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
212                    "Operations": [
213                        {
214                            "op": "add",
215                            "path": "members",
216                            "value": [{"value": user_scim_id}],
217                        }
218                    ],
219                },
220            )
221
222        with Mocker() as mocker:
223            mocker.get(
224                "https://localhost/ServiceProviderConfig",
225                json=config.model_dump(),
226            )
227            mocker.patch(
228                f"https://localhost/Groups/{group_scim_id}",
229                json={},
230            )
231            group.users.remove(user)
232            self.assertEqual(mocker.call_count, 1)
233            self.assertEqual(mocker.request_history[0].method, "PATCH")
234            self.assertJSONEqual(
235                mocker.request_history[0].body,
236                {
237                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
238                    "Operations": [
239                        {
240                            "op": "remove",
241                            "path": "members",
242                            "value": [{"value": user_scim_id}],
243                        }
244                    ],
245                },
246            )
247
248    def test_member_add_save(self):
249        """Test member add + save"""
250        config = ServiceProviderConfiguration.default()
251
252        config.patch.supported = True
253        user_scim_id = generate_id()
254        group_scim_id = generate_id()
255        uid = generate_id()
256        group = Group.objects.create(
257            name=uid,
258        )
259
260        user = User.objects.create(username=generate_id())
261
262        # Test initial sync of group creation
263        with Mocker() as mocker:
264            mocker.get(
265                "https://localhost/ServiceProviderConfig",
266                json=config.model_dump(),
267            )
268            mocker.post(
269                "https://localhost/Users",
270                json={
271                    "id": user_scim_id,
272                },
273            )
274            mocker.post(
275                "https://localhost/Groups",
276                json={
277                    "id": group_scim_id,
278                },
279            )
280
281            self.configure()
282            scim_sync.send(self.provider.pk)
283
284            self.assertEqual(mocker.call_count, 3)
285            self.assertEqual(mocker.request_history[0].method, "GET")
286            self.assertEqual(mocker.request_history[1].method, "POST")
287            self.assertEqual(mocker.request_history[2].method, "POST")
288            self.assertJSONEqual(
289                mocker.request_history[1].body,
290                {
291                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
292                    "emails": [],
293                    "active": True,
294                    "externalId": user.uid,
295                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
296                    "displayName": "",
297                    "userName": user.username,
298                },
299            )
300            self.assertJSONEqual(
301                mocker.request_history[2].body,
302                {
303                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
304                    "externalId": str(group.pk),
305                    "displayName": group.name,
306                },
307            )
308
309        with Mocker() as mocker:
310            mocker.get(
311                "https://localhost/ServiceProviderConfig",
312                json=config.model_dump(),
313            )
314            mocker.get(
315                f"https://localhost/Groups/{group_scim_id}",
316                json={},
317            )
318            mocker.patch(
319                f"https://localhost/Groups/{group_scim_id}",
320                json={},
321            )
322            group.users.add(user)
323            group.save()
324            self.assertEqual(mocker.call_count, 3)
325            self.assertEqual(mocker.request_history[0].method, "PATCH")
326            self.assertEqual(mocker.request_history[1].method, "PATCH")
327            self.assertEqual(mocker.request_history[2].method, "GET")
328            self.assertJSONEqual(
329                mocker.request_history[0].body,
330                {
331                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
332                    "Operations": [
333                        {
334                            "op": "add",
335                            "path": "members",
336                            "value": [{"value": user_scim_id}],
337                        }
338                    ],
339                },
340            )
341            self.assertJSONEqual(
342                mocker.request_history[1].body,
343                {
344                    "Operations": [
345                        {
346                            "op": "replace",
347                            "value": {
348                                "id": group_scim_id,
349                                "displayName": group.name,
350                                "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
351                                "externalId": str(group.pk),
352                            },
353                        }
354                    ]
355                },
356            )
357
358    def test_member_add_save_compat_webex(self):
359        """Test member add + save"""
360        config = ServiceProviderConfiguration.default()
361
362        config.patch.supported = True
363        user_scim_id = generate_id()
364        group_scim_id = generate_id()
365        uid = generate_id()
366        group = Group.objects.create(
367            name=uid,
368        )
369
370        user = User.objects.create(username=generate_id())
371
372        # Test initial sync of group creation
373        with Mocker() as mocker:
374            mocker.get(
375                "https://localhost/ServiceProviderConfig",
376                json=config.model_dump(),
377            )
378            mocker.post(
379                "https://localhost/Users",
380                json={
381                    "id": user_scim_id,
382                },
383            )
384            mocker.post(
385                "https://localhost/Groups",
386                json={
387                    "id": group_scim_id,
388                },
389            )
390
391            self.configure(compatibility_mode=SCIMCompatibilityMode.WEBEX)
392            scim_sync.send(self.provider.pk)
393
394            self.assertEqual(mocker.call_count, 3)
395            self.assertEqual(mocker.request_history[0].method, "GET")
396            self.assertEqual(mocker.request_history[1].method, "POST")
397            self.assertEqual(mocker.request_history[2].method, "POST")
398            self.assertJSONEqual(
399                mocker.request_history[1].body,
400                {
401                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
402                    "emails": [],
403                    "active": True,
404                    "externalId": user.uid,
405                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
406                    "displayName": "",
407                    "userName": user.username,
408                },
409            )
410            self.assertJSONEqual(
411                mocker.request_history[2].body,
412                {
413                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
414                    "externalId": str(group.pk),
415                    "displayName": group.name,
416                },
417            )
418
419        with Mocker() as mocker:
420            mocker.get(
421                "https://localhost/ServiceProviderConfig",
422                json=config.model_dump(),
423            )
424            mocker.get(
425                f"https://localhost/Groups/{group_scim_id}",
426                json={},
427            )
428            mocker.patch(
429                f"https://localhost/Groups/{group_scim_id}",
430                json={},
431            )
432            group.users.add(user)
433            group.save()
434            self.assertEqual(mocker.call_count, 3)
435            self.assertEqual(mocker.request_history[0].method, "PATCH")
436            self.assertEqual(mocker.request_history[1].method, "PATCH")
437            self.assertEqual(mocker.request_history[2].method, "GET")
438            self.assertJSONEqual(
439                mocker.request_history[0].body,
440                {
441                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
442                    "Operations": [
443                        {
444                            "op": "add",
445                            "path": "members",
446                            "value": [{"value": user_scim_id, "type": "user"}],
447                        }
448                    ],
449                },
450            )
451            self.assertJSONEqual(
452                mocker.request_history[1].body,
453                {
454                    "Operations": [
455                        {
456                            "op": "replace",
457                            "value": {
458                                "id": group_scim_id,
459                                "displayName": group.name,
460                                "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
461                                "externalId": str(group.pk),
462                            },
463                        }
464                    ]
465                },
466            )
class SCIMMembershipTests(django.test.testcases.TestCase):
 16class SCIMMembershipTests(TestCase):
 17    """SCIM Membership tests"""
 18
 19    provider: SCIMProvider
 20    app: Application
 21
 22    def setUp(self) -> None:
 23        # Delete all users and groups as the mocked HTTP responses only return one ID
 24        # which will cause errors with multiple users
 25        User.objects.all().exclude_anonymous().delete()
 26        Group.objects.all().delete()
 27        Tenant.objects.update(avatars="none")
 28
 29    @apply_blueprint("system/providers-scim.yaml")
 30    def configure(self, **kwargs) -> None:
 31        """Configure provider"""
 32        self.provider: SCIMProvider = SCIMProvider.objects.create(
 33            name=generate_id(),
 34            url="https://localhost",
 35            token=generate_id(),
 36            **kwargs,
 37        )
 38        self.app: Application = Application.objects.create(
 39            name=generate_id(),
 40            slug=generate_id(),
 41        )
 42        self.app.backchannel_providers.add(self.provider)
 43        self.provider.save()
 44        self.provider.property_mappings.set(
 45            [SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/user")]
 46        )
 47        self.provider.property_mappings_group.set(
 48            [SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/group")]
 49        )
 50
 51    def test_member_add(self):
 52        """Test member add"""
 53        config = ServiceProviderConfiguration.default()
 54
 55        config.patch.supported = True
 56        user_scim_id = generate_id()
 57        group_scim_id = generate_id()
 58        uid = generate_id()
 59        group = Group.objects.create(
 60            name=uid,
 61        )
 62
 63        user = User.objects.create(username=generate_id())
 64
 65        with Mocker() as mocker:
 66            mocker.get(
 67                "https://localhost/ServiceProviderConfig",
 68                json=config.model_dump(),
 69            )
 70            mocker.post(
 71                "https://localhost/Users",
 72                json={
 73                    "id": user_scim_id,
 74                },
 75            )
 76            mocker.post(
 77                "https://localhost/Groups",
 78                json={
 79                    "id": group_scim_id,
 80                },
 81            )
 82
 83            self.configure()
 84            scim_sync.send(self.provider.pk)
 85
 86            self.assertEqual(mocker.call_count, 3)
 87            self.assertEqual(mocker.request_history[0].method, "GET")
 88            self.assertEqual(mocker.request_history[1].method, "POST")
 89            self.assertEqual(mocker.request_history[2].method, "POST")
 90            self.assertJSONEqual(
 91                mocker.request_history[1].body,
 92                {
 93                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
 94                    "emails": [],
 95                    "active": True,
 96                    "externalId": user.uid,
 97                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
 98                    "displayName": "",
 99                    "userName": user.username,
100                },
101            )
102            self.assertJSONEqual(
103                mocker.request_history[2].body,
104                {
105                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
106                    "externalId": str(group.pk),
107                    "displayName": group.name,
108                },
109            )
110
111        with Mocker() as mocker:
112            mocker.get(
113                "https://localhost/ServiceProviderConfig",
114                json=config.model_dump(),
115            )
116            mocker.patch(
117                f"https://localhost/Groups/{group_scim_id}",
118                json={},
119            )
120            group.users.add(user)
121            self.assertEqual(mocker.call_count, 1)
122            self.assertEqual(mocker.request_history[0].method, "PATCH")
123            self.assertJSONEqual(
124                mocker.request_history[0].body,
125                {
126                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
127                    "Operations": [
128                        {
129                            "op": "add",
130                            "path": "members",
131                            "value": [{"value": user_scim_id}],
132                        }
133                    ],
134                },
135            )
136
137    def test_member_remove(self):
138        """Test member remove"""
139        config = ServiceProviderConfiguration.default()
140
141        config.patch.supported = True
142        user_scim_id = generate_id()
143        group_scim_id = generate_id()
144        uid = generate_id()
145        group = Group.objects.create(
146            name=uid,
147        )
148
149        user = User.objects.create(username=generate_id())
150
151        with Mocker() as mocker:
152            mocker.get(
153                "https://localhost/ServiceProviderConfig",
154                json=config.model_dump(),
155            )
156            mocker.post(
157                "https://localhost/Users",
158                json={
159                    "id": user_scim_id,
160                },
161            )
162            mocker.post(
163                "https://localhost/Groups",
164                json={
165                    "id": group_scim_id,
166                },
167            )
168
169            self.configure()
170            scim_sync.send(self.provider.pk)
171
172            self.assertEqual(mocker.call_count, 3)
173            self.assertEqual(mocker.request_history[0].method, "GET")
174            self.assertEqual(mocker.request_history[1].method, "POST")
175            self.assertEqual(mocker.request_history[2].method, "POST")
176            self.assertJSONEqual(
177                mocker.request_history[1].body,
178                {
179                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
180                    "active": True,
181                    "displayName": "",
182                    "emails": [],
183                    "externalId": user.uid,
184                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
185                    "userName": user.username,
186                },
187            )
188            self.assertJSONEqual(
189                mocker.request_history[2].body,
190                {
191                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
192                    "externalId": str(group.pk),
193                    "displayName": group.name,
194                },
195            )
196
197        with Mocker() as mocker:
198            mocker.get(
199                "https://localhost/ServiceProviderConfig",
200                json=config.model_dump(),
201            )
202            mocker.patch(
203                f"https://localhost/Groups/{group_scim_id}",
204                json={},
205            )
206            group.users.add(user)
207            self.assertEqual(mocker.call_count, 1)
208            self.assertEqual(mocker.request_history[0].method, "PATCH")
209            self.assertJSONEqual(
210                mocker.request_history[0].body,
211                {
212                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
213                    "Operations": [
214                        {
215                            "op": "add",
216                            "path": "members",
217                            "value": [{"value": user_scim_id}],
218                        }
219                    ],
220                },
221            )
222
223        with Mocker() as mocker:
224            mocker.get(
225                "https://localhost/ServiceProviderConfig",
226                json=config.model_dump(),
227            )
228            mocker.patch(
229                f"https://localhost/Groups/{group_scim_id}",
230                json={},
231            )
232            group.users.remove(user)
233            self.assertEqual(mocker.call_count, 1)
234            self.assertEqual(mocker.request_history[0].method, "PATCH")
235            self.assertJSONEqual(
236                mocker.request_history[0].body,
237                {
238                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
239                    "Operations": [
240                        {
241                            "op": "remove",
242                            "path": "members",
243                            "value": [{"value": user_scim_id}],
244                        }
245                    ],
246                },
247            )
248
249    def test_member_add_save(self):
250        """Test member add + save"""
251        config = ServiceProviderConfiguration.default()
252
253        config.patch.supported = True
254        user_scim_id = generate_id()
255        group_scim_id = generate_id()
256        uid = generate_id()
257        group = Group.objects.create(
258            name=uid,
259        )
260
261        user = User.objects.create(username=generate_id())
262
263        # Test initial sync of group creation
264        with Mocker() as mocker:
265            mocker.get(
266                "https://localhost/ServiceProviderConfig",
267                json=config.model_dump(),
268            )
269            mocker.post(
270                "https://localhost/Users",
271                json={
272                    "id": user_scim_id,
273                },
274            )
275            mocker.post(
276                "https://localhost/Groups",
277                json={
278                    "id": group_scim_id,
279                },
280            )
281
282            self.configure()
283            scim_sync.send(self.provider.pk)
284
285            self.assertEqual(mocker.call_count, 3)
286            self.assertEqual(mocker.request_history[0].method, "GET")
287            self.assertEqual(mocker.request_history[1].method, "POST")
288            self.assertEqual(mocker.request_history[2].method, "POST")
289            self.assertJSONEqual(
290                mocker.request_history[1].body,
291                {
292                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
293                    "emails": [],
294                    "active": True,
295                    "externalId": user.uid,
296                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
297                    "displayName": "",
298                    "userName": user.username,
299                },
300            )
301            self.assertJSONEqual(
302                mocker.request_history[2].body,
303                {
304                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
305                    "externalId": str(group.pk),
306                    "displayName": group.name,
307                },
308            )
309
310        with Mocker() as mocker:
311            mocker.get(
312                "https://localhost/ServiceProviderConfig",
313                json=config.model_dump(),
314            )
315            mocker.get(
316                f"https://localhost/Groups/{group_scim_id}",
317                json={},
318            )
319            mocker.patch(
320                f"https://localhost/Groups/{group_scim_id}",
321                json={},
322            )
323            group.users.add(user)
324            group.save()
325            self.assertEqual(mocker.call_count, 3)
326            self.assertEqual(mocker.request_history[0].method, "PATCH")
327            self.assertEqual(mocker.request_history[1].method, "PATCH")
328            self.assertEqual(mocker.request_history[2].method, "GET")
329            self.assertJSONEqual(
330                mocker.request_history[0].body,
331                {
332                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
333                    "Operations": [
334                        {
335                            "op": "add",
336                            "path": "members",
337                            "value": [{"value": user_scim_id}],
338                        }
339                    ],
340                },
341            )
342            self.assertJSONEqual(
343                mocker.request_history[1].body,
344                {
345                    "Operations": [
346                        {
347                            "op": "replace",
348                            "value": {
349                                "id": group_scim_id,
350                                "displayName": group.name,
351                                "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
352                                "externalId": str(group.pk),
353                            },
354                        }
355                    ]
356                },
357            )
358
359    def test_member_add_save_compat_webex(self):
360        """Test member add + save"""
361        config = ServiceProviderConfiguration.default()
362
363        config.patch.supported = True
364        user_scim_id = generate_id()
365        group_scim_id = generate_id()
366        uid = generate_id()
367        group = Group.objects.create(
368            name=uid,
369        )
370
371        user = User.objects.create(username=generate_id())
372
373        # Test initial sync of group creation
374        with Mocker() as mocker:
375            mocker.get(
376                "https://localhost/ServiceProviderConfig",
377                json=config.model_dump(),
378            )
379            mocker.post(
380                "https://localhost/Users",
381                json={
382                    "id": user_scim_id,
383                },
384            )
385            mocker.post(
386                "https://localhost/Groups",
387                json={
388                    "id": group_scim_id,
389                },
390            )
391
392            self.configure(compatibility_mode=SCIMCompatibilityMode.WEBEX)
393            scim_sync.send(self.provider.pk)
394
395            self.assertEqual(mocker.call_count, 3)
396            self.assertEqual(mocker.request_history[0].method, "GET")
397            self.assertEqual(mocker.request_history[1].method, "POST")
398            self.assertEqual(mocker.request_history[2].method, "POST")
399            self.assertJSONEqual(
400                mocker.request_history[1].body,
401                {
402                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
403                    "emails": [],
404                    "active": True,
405                    "externalId": user.uid,
406                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
407                    "displayName": "",
408                    "userName": user.username,
409                },
410            )
411            self.assertJSONEqual(
412                mocker.request_history[2].body,
413                {
414                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
415                    "externalId": str(group.pk),
416                    "displayName": group.name,
417                },
418            )
419
420        with Mocker() as mocker:
421            mocker.get(
422                "https://localhost/ServiceProviderConfig",
423                json=config.model_dump(),
424            )
425            mocker.get(
426                f"https://localhost/Groups/{group_scim_id}",
427                json={},
428            )
429            mocker.patch(
430                f"https://localhost/Groups/{group_scim_id}",
431                json={},
432            )
433            group.users.add(user)
434            group.save()
435            self.assertEqual(mocker.call_count, 3)
436            self.assertEqual(mocker.request_history[0].method, "PATCH")
437            self.assertEqual(mocker.request_history[1].method, "PATCH")
438            self.assertEqual(mocker.request_history[2].method, "GET")
439            self.assertJSONEqual(
440                mocker.request_history[0].body,
441                {
442                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
443                    "Operations": [
444                        {
445                            "op": "add",
446                            "path": "members",
447                            "value": [{"value": user_scim_id, "type": "user"}],
448                        }
449                    ],
450                },
451            )
452            self.assertJSONEqual(
453                mocker.request_history[1].body,
454                {
455                    "Operations": [
456                        {
457                            "op": "replace",
458                            "value": {
459                                "id": group_scim_id,
460                                "displayName": group.name,
461                                "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
462                                "externalId": str(group.pk),
463                            },
464                        }
465                    ]
466                },
467            )

SCIM Membership tests

def setUp(self) -> None:
22    def setUp(self) -> None:
23        # Delete all users and groups as the mocked HTTP responses only return one ID
24        # which will cause errors with multiple users
25        User.objects.all().exclude_anonymous().delete()
26        Group.objects.all().delete()
27        Tenant.objects.update(avatars="none")

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

@apply_blueprint('system/providers-scim.yaml')
def configure(self, **kwargs) -> None:
29    @apply_blueprint("system/providers-scim.yaml")
30    def configure(self, **kwargs) -> None:
31        """Configure provider"""
32        self.provider: SCIMProvider = SCIMProvider.objects.create(
33            name=generate_id(),
34            url="https://localhost",
35            token=generate_id(),
36            **kwargs,
37        )
38        self.app: Application = Application.objects.create(
39            name=generate_id(),
40            slug=generate_id(),
41        )
42        self.app.backchannel_providers.add(self.provider)
43        self.provider.save()
44        self.provider.property_mappings.set(
45            [SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/user")]
46        )
47        self.provider.property_mappings_group.set(
48            [SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/group")]
49        )

Configure provider

def test_member_add(self):
 51    def test_member_add(self):
 52        """Test member add"""
 53        config = ServiceProviderConfiguration.default()
 54
 55        config.patch.supported = True
 56        user_scim_id = generate_id()
 57        group_scim_id = generate_id()
 58        uid = generate_id()
 59        group = Group.objects.create(
 60            name=uid,
 61        )
 62
 63        user = User.objects.create(username=generate_id())
 64
 65        with Mocker() as mocker:
 66            mocker.get(
 67                "https://localhost/ServiceProviderConfig",
 68                json=config.model_dump(),
 69            )
 70            mocker.post(
 71                "https://localhost/Users",
 72                json={
 73                    "id": user_scim_id,
 74                },
 75            )
 76            mocker.post(
 77                "https://localhost/Groups",
 78                json={
 79                    "id": group_scim_id,
 80                },
 81            )
 82
 83            self.configure()
 84            scim_sync.send(self.provider.pk)
 85
 86            self.assertEqual(mocker.call_count, 3)
 87            self.assertEqual(mocker.request_history[0].method, "GET")
 88            self.assertEqual(mocker.request_history[1].method, "POST")
 89            self.assertEqual(mocker.request_history[2].method, "POST")
 90            self.assertJSONEqual(
 91                mocker.request_history[1].body,
 92                {
 93                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
 94                    "emails": [],
 95                    "active": True,
 96                    "externalId": user.uid,
 97                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
 98                    "displayName": "",
 99                    "userName": user.username,
100                },
101            )
102            self.assertJSONEqual(
103                mocker.request_history[2].body,
104                {
105                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
106                    "externalId": str(group.pk),
107                    "displayName": group.name,
108                },
109            )
110
111        with Mocker() as mocker:
112            mocker.get(
113                "https://localhost/ServiceProviderConfig",
114                json=config.model_dump(),
115            )
116            mocker.patch(
117                f"https://localhost/Groups/{group_scim_id}",
118                json={},
119            )
120            group.users.add(user)
121            self.assertEqual(mocker.call_count, 1)
122            self.assertEqual(mocker.request_history[0].method, "PATCH")
123            self.assertJSONEqual(
124                mocker.request_history[0].body,
125                {
126                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
127                    "Operations": [
128                        {
129                            "op": "add",
130                            "path": "members",
131                            "value": [{"value": user_scim_id}],
132                        }
133                    ],
134                },
135            )

Test member add

def test_member_remove(self):
137    def test_member_remove(self):
138        """Test member remove"""
139        config = ServiceProviderConfiguration.default()
140
141        config.patch.supported = True
142        user_scim_id = generate_id()
143        group_scim_id = generate_id()
144        uid = generate_id()
145        group = Group.objects.create(
146            name=uid,
147        )
148
149        user = User.objects.create(username=generate_id())
150
151        with Mocker() as mocker:
152            mocker.get(
153                "https://localhost/ServiceProviderConfig",
154                json=config.model_dump(),
155            )
156            mocker.post(
157                "https://localhost/Users",
158                json={
159                    "id": user_scim_id,
160                },
161            )
162            mocker.post(
163                "https://localhost/Groups",
164                json={
165                    "id": group_scim_id,
166                },
167            )
168
169            self.configure()
170            scim_sync.send(self.provider.pk)
171
172            self.assertEqual(mocker.call_count, 3)
173            self.assertEqual(mocker.request_history[0].method, "GET")
174            self.assertEqual(mocker.request_history[1].method, "POST")
175            self.assertEqual(mocker.request_history[2].method, "POST")
176            self.assertJSONEqual(
177                mocker.request_history[1].body,
178                {
179                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
180                    "active": True,
181                    "displayName": "",
182                    "emails": [],
183                    "externalId": user.uid,
184                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
185                    "userName": user.username,
186                },
187            )
188            self.assertJSONEqual(
189                mocker.request_history[2].body,
190                {
191                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
192                    "externalId": str(group.pk),
193                    "displayName": group.name,
194                },
195            )
196
197        with Mocker() as mocker:
198            mocker.get(
199                "https://localhost/ServiceProviderConfig",
200                json=config.model_dump(),
201            )
202            mocker.patch(
203                f"https://localhost/Groups/{group_scim_id}",
204                json={},
205            )
206            group.users.add(user)
207            self.assertEqual(mocker.call_count, 1)
208            self.assertEqual(mocker.request_history[0].method, "PATCH")
209            self.assertJSONEqual(
210                mocker.request_history[0].body,
211                {
212                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
213                    "Operations": [
214                        {
215                            "op": "add",
216                            "path": "members",
217                            "value": [{"value": user_scim_id}],
218                        }
219                    ],
220                },
221            )
222
223        with Mocker() as mocker:
224            mocker.get(
225                "https://localhost/ServiceProviderConfig",
226                json=config.model_dump(),
227            )
228            mocker.patch(
229                f"https://localhost/Groups/{group_scim_id}",
230                json={},
231            )
232            group.users.remove(user)
233            self.assertEqual(mocker.call_count, 1)
234            self.assertEqual(mocker.request_history[0].method, "PATCH")
235            self.assertJSONEqual(
236                mocker.request_history[0].body,
237                {
238                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
239                    "Operations": [
240                        {
241                            "op": "remove",
242                            "path": "members",
243                            "value": [{"value": user_scim_id}],
244                        }
245                    ],
246                },
247            )

Test member remove

def test_member_add_save(self):
249    def test_member_add_save(self):
250        """Test member add + save"""
251        config = ServiceProviderConfiguration.default()
252
253        config.patch.supported = True
254        user_scim_id = generate_id()
255        group_scim_id = generate_id()
256        uid = generate_id()
257        group = Group.objects.create(
258            name=uid,
259        )
260
261        user = User.objects.create(username=generate_id())
262
263        # Test initial sync of group creation
264        with Mocker() as mocker:
265            mocker.get(
266                "https://localhost/ServiceProviderConfig",
267                json=config.model_dump(),
268            )
269            mocker.post(
270                "https://localhost/Users",
271                json={
272                    "id": user_scim_id,
273                },
274            )
275            mocker.post(
276                "https://localhost/Groups",
277                json={
278                    "id": group_scim_id,
279                },
280            )
281
282            self.configure()
283            scim_sync.send(self.provider.pk)
284
285            self.assertEqual(mocker.call_count, 3)
286            self.assertEqual(mocker.request_history[0].method, "GET")
287            self.assertEqual(mocker.request_history[1].method, "POST")
288            self.assertEqual(mocker.request_history[2].method, "POST")
289            self.assertJSONEqual(
290                mocker.request_history[1].body,
291                {
292                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
293                    "emails": [],
294                    "active": True,
295                    "externalId": user.uid,
296                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
297                    "displayName": "",
298                    "userName": user.username,
299                },
300            )
301            self.assertJSONEqual(
302                mocker.request_history[2].body,
303                {
304                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
305                    "externalId": str(group.pk),
306                    "displayName": group.name,
307                },
308            )
309
310        with Mocker() as mocker:
311            mocker.get(
312                "https://localhost/ServiceProviderConfig",
313                json=config.model_dump(),
314            )
315            mocker.get(
316                f"https://localhost/Groups/{group_scim_id}",
317                json={},
318            )
319            mocker.patch(
320                f"https://localhost/Groups/{group_scim_id}",
321                json={},
322            )
323            group.users.add(user)
324            group.save()
325            self.assertEqual(mocker.call_count, 3)
326            self.assertEqual(mocker.request_history[0].method, "PATCH")
327            self.assertEqual(mocker.request_history[1].method, "PATCH")
328            self.assertEqual(mocker.request_history[2].method, "GET")
329            self.assertJSONEqual(
330                mocker.request_history[0].body,
331                {
332                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
333                    "Operations": [
334                        {
335                            "op": "add",
336                            "path": "members",
337                            "value": [{"value": user_scim_id}],
338                        }
339                    ],
340                },
341            )
342            self.assertJSONEqual(
343                mocker.request_history[1].body,
344                {
345                    "Operations": [
346                        {
347                            "op": "replace",
348                            "value": {
349                                "id": group_scim_id,
350                                "displayName": group.name,
351                                "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
352                                "externalId": str(group.pk),
353                            },
354                        }
355                    ]
356                },
357            )

Test member add + save

def test_member_add_save_compat_webex(self):
359    def test_member_add_save_compat_webex(self):
360        """Test member add + save"""
361        config = ServiceProviderConfiguration.default()
362
363        config.patch.supported = True
364        user_scim_id = generate_id()
365        group_scim_id = generate_id()
366        uid = generate_id()
367        group = Group.objects.create(
368            name=uid,
369        )
370
371        user = User.objects.create(username=generate_id())
372
373        # Test initial sync of group creation
374        with Mocker() as mocker:
375            mocker.get(
376                "https://localhost/ServiceProviderConfig",
377                json=config.model_dump(),
378            )
379            mocker.post(
380                "https://localhost/Users",
381                json={
382                    "id": user_scim_id,
383                },
384            )
385            mocker.post(
386                "https://localhost/Groups",
387                json={
388                    "id": group_scim_id,
389                },
390            )
391
392            self.configure(compatibility_mode=SCIMCompatibilityMode.WEBEX)
393            scim_sync.send(self.provider.pk)
394
395            self.assertEqual(mocker.call_count, 3)
396            self.assertEqual(mocker.request_history[0].method, "GET")
397            self.assertEqual(mocker.request_history[1].method, "POST")
398            self.assertEqual(mocker.request_history[2].method, "POST")
399            self.assertJSONEqual(
400                mocker.request_history[1].body,
401                {
402                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
403                    "emails": [],
404                    "active": True,
405                    "externalId": user.uid,
406                    "name": {"familyName": " ", "formatted": " ", "givenName": ""},
407                    "displayName": "",
408                    "userName": user.username,
409                },
410            )
411            self.assertJSONEqual(
412                mocker.request_history[2].body,
413                {
414                    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
415                    "externalId": str(group.pk),
416                    "displayName": group.name,
417                },
418            )
419
420        with Mocker() as mocker:
421            mocker.get(
422                "https://localhost/ServiceProviderConfig",
423                json=config.model_dump(),
424            )
425            mocker.get(
426                f"https://localhost/Groups/{group_scim_id}",
427                json={},
428            )
429            mocker.patch(
430                f"https://localhost/Groups/{group_scim_id}",
431                json={},
432            )
433            group.users.add(user)
434            group.save()
435            self.assertEqual(mocker.call_count, 3)
436            self.assertEqual(mocker.request_history[0].method, "PATCH")
437            self.assertEqual(mocker.request_history[1].method, "PATCH")
438            self.assertEqual(mocker.request_history[2].method, "GET")
439            self.assertJSONEqual(
440                mocker.request_history[0].body,
441                {
442                    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
443                    "Operations": [
444                        {
445                            "op": "add",
446                            "path": "members",
447                            "value": [{"value": user_scim_id, "type": "user"}],
448                        }
449                    ],
450                },
451            )
452            self.assertJSONEqual(
453                mocker.request_history[1].body,
454                {
455                    "Operations": [
456                        {
457                            "op": "replace",
458                            "value": {
459                                "id": group_scim_id,
460                                "displayName": group.name,
461                                "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
462                                "externalId": str(group.pk),
463                            },
464                        }
465                    ]
466                },
467            )

Test member add + save