diff --git a/src/paperless/signals.py b/src/paperless/signals.py index a173ccc2e..cfad29dbd 100644 --- a/src/paperless/signals.py +++ b/src/paperless/signals.py @@ -38,10 +38,19 @@ def handle_social_account_updated(sender, request, sociallogin, **kwargs): """ from django.contrib.auth.models import Group - social_account_groups = sociallogin.account.extra_data.get( + extra_data = sociallogin.account.extra_data or {} + social_account_groups = extra_data.get( "groups", [], - ) # None if not found + ) # pre-allauth 65.11.0 structure + + if not social_account_groups: + # allauth 65.11.0+ nests claims under `userinfo`/`id_token` + social_account_groups = ( + extra_data.get("userinfo", {}).get("groups") + or extra_data.get("id_token", {}).get("groups") + or [] + ) if settings.SOCIAL_ACCOUNT_SYNC_GROUPS and social_account_groups is not None: groups = Group.objects.filter(name__in=social_account_groups) logger.debug( diff --git a/src/paperless/tests/test_signals.py b/src/paperless/tests/test_signals.py index a77580b7b..a21f3b660 100644 --- a/src/paperless/tests/test_signals.py +++ b/src/paperless/tests/test_signals.py @@ -192,6 +192,68 @@ class TestSyncSocialLoginGroups(TestCase): ) self.assertEqual(list(user.groups.all()), []) + @override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True) + def test_userinfo_groups(self): + """ + GIVEN: + - Enabled group syncing, and `groups` nested under `userinfo` + WHEN: + - The social login is updated via signal after login + THEN: + - The user's groups are updated using `userinfo.groups` + """ + group = Group.objects.create(name="group1") + user = User.objects.create_user(username="testuser") + sociallogin = Mock( + user=user, + account=Mock( + extra_data={ + "userinfo": { + "groups": ["group1"], + }, + }, + ), + ) + + handle_social_account_updated( + sender=None, + request=HttpRequest(), + sociallogin=sociallogin, + ) + + self.assertEqual(list(user.groups.all()), [group]) + + @override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True) + def test_id_token_groups_fallback(self): + """ + GIVEN: + - Enabled group syncing, and `groups` only under `id_token` + WHEN: + - The social login is updated via signal after login + THEN: + - The user's groups are updated using `id_token.groups` + """ + group = Group.objects.create(name="group1") + user = User.objects.create_user(username="testuser") + sociallogin = Mock( + user=user, + account=Mock( + extra_data={ + "id_token": { + "groups": ["group1"], + }, + }, + ), + ) + + handle_social_account_updated( + sender=None, + request=HttpRequest(), + sociallogin=sociallogin, + ) + + self.assertEqual(list(user.groups.all()), [group]) + class TestUserGroupDeletionCleanup(TestCase): """