mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
Support syncing groups on login
This commit is contained in:
parent
5020afc7ec
commit
2dbca9e9ce
@ -586,6 +586,18 @@ system. See the corresponding
|
|||||||
|
|
||||||
Defaults to False
|
Defaults to False
|
||||||
|
|
||||||
|
#### [`PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS=<bool>`](#PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS) {#PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS}
|
||||||
|
|
||||||
|
: Sync groups from the third party authentication system (e.g. OIDC) to Paperless-ngx. When enabled, users will be added or removed from groups based on their group membership in the third party authentication system. Groups must already exist in Paperless-ngx and have the same name as in the third party authentication system. Groups are updated upon logging in via the third party authentication system, see the corresponding [django-allauth documentation](https://docs.allauth.org/en/dev/socialaccount/signals.html).
|
||||||
|
|
||||||
|
In order to pass groups from the authentication system you will need to update your [PAPERLESS_SOCIALACCOUNT_PROVIDERS](#PAPERLESS_SOCIALACCOUNT_PROVIDERS) setting by adding a top-level "SCOPES" setting which includes "groups", e.g.:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"openid_connect":{"SCOPE": ["openid","profile","email","groups"]...
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults to False
|
||||||
|
|
||||||
#### [`PAPERLESS_ACCOUNT_DEFAULT_GROUPS=<comma-separated-list>`](#PAPERLESS_ACCOUNT_DEFAULT_GROUPS) {#PAPERLESS_ACCOUNT_DEFAULT_GROUPS}
|
#### [`PAPERLESS_ACCOUNT_DEFAULT_GROUPS=<comma-separated-list>`](#PAPERLESS_ACCOUNT_DEFAULT_GROUPS) {#PAPERLESS_ACCOUNT_DEFAULT_GROUPS}
|
||||||
|
|
||||||
: A list of group names that users will be added to when they sign up for a new account. Groups listed here must already exist.
|
: A list of group names that users will be added to when they sign up for a new account. Groups listed here must already exist.
|
||||||
|
@ -2,6 +2,7 @@ from django.apps import AppConfig
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from paperless.signals import handle_failed_login
|
from paperless.signals import handle_failed_login
|
||||||
|
from paperless.signals import handle_social_account_updated
|
||||||
|
|
||||||
|
|
||||||
class PaperlessConfig(AppConfig):
|
class PaperlessConfig(AppConfig):
|
||||||
@ -13,4 +14,9 @@ class PaperlessConfig(AppConfig):
|
|||||||
from django.contrib.auth.signals import user_login_failed
|
from django.contrib.auth.signals import user_login_failed
|
||||||
|
|
||||||
user_login_failed.connect(handle_failed_login)
|
user_login_failed.connect(handle_failed_login)
|
||||||
|
|
||||||
|
from allauth.socialaccount.signals import social_account_updated
|
||||||
|
|
||||||
|
social_account_updated.connect(handle_social_account_updated)
|
||||||
|
|
||||||
AppConfig.ready(self)
|
AppConfig.ready(self)
|
||||||
|
@ -492,6 +492,7 @@ SOCIALACCOUNT_PROVIDERS = json.loads(
|
|||||||
os.getenv("PAPERLESS_SOCIALACCOUNT_PROVIDERS", "{}"),
|
os.getenv("PAPERLESS_SOCIALACCOUNT_PROVIDERS", "{}"),
|
||||||
)
|
)
|
||||||
SOCIAL_ACCOUNT_DEFAULT_GROUPS = __get_list("PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS")
|
SOCIAL_ACCOUNT_DEFAULT_GROUPS = __get_list("PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS")
|
||||||
|
SOCIAL_ACCOUNT_SYNC_GROUPS = __get_boolean("PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS")
|
||||||
|
|
||||||
MFA_TOTP_ISSUER = "Paperless-ngx"
|
MFA_TOTP_ISSUER = "Paperless-ngx"
|
||||||
|
|
||||||
|
@ -30,3 +30,21 @@ def handle_failed_login(sender, credentials, request, **kwargs):
|
|||||||
log_output += f" from private IP `{client_ip}`."
|
log_output += f" from private IP `{client_ip}`."
|
||||||
|
|
||||||
logger.info(log_output)
|
logger.info(log_output)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_social_account_updated(sender, request, sociallogin, **kwargs):
|
||||||
|
"""
|
||||||
|
Handle the social account update signal.
|
||||||
|
"""
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
|
social_account_groups = sociallogin.account.extra_data.get(
|
||||||
|
"groups",
|
||||||
|
[],
|
||||||
|
) # None if not found
|
||||||
|
if settings.SOCIAL_ACCOUNT_SYNC_GROUPS and social_account_groups is not None:
|
||||||
|
groups = Group.objects.filter(name__in=social_account_groups)
|
||||||
|
logger.debug(
|
||||||
|
f"Syncing groups for user `{sociallogin.user}`: {social_account_groups}",
|
||||||
|
)
|
||||||
|
sociallogin.user.groups.set(groups, clear=True)
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test import override_settings
|
||||||
|
|
||||||
from paperless.signals import handle_failed_login
|
from paperless.signals import handle_failed_login
|
||||||
|
from paperless.signals import handle_social_account_updated
|
||||||
|
|
||||||
|
|
||||||
class TestFailedLoginLogging(TestCase):
|
class TestFailedLoginLogging(TestCase):
|
||||||
@ -99,3 +105,88 @@ class TestFailedLoginLogging(TestCase):
|
|||||||
"INFO:paperless.auth:Login failed for user `john lennon` from private IP `10.0.0.1`.",
|
"INFO:paperless.auth:Login failed for user `john lennon` from private IP `10.0.0.1`.",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSyncSocialLoginGroups(TestCase):
|
||||||
|
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
||||||
|
def test_sync_enabled(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Enabled group syncing, a user, and a social login
|
||||||
|
WHEN:
|
||||||
|
- The social login is updated via signal after login
|
||||||
|
THEN:
|
||||||
|
- The user's groups are updated to match the social login's groups
|
||||||
|
"""
|
||||||
|
group = Group.objects.create(name="group1")
|
||||||
|
user = User.objects.create_user(username="testuser")
|
||||||
|
sociallogin = Mock(
|
||||||
|
user=user,
|
||||||
|
account=Mock(
|
||||||
|
extra_data={
|
||||||
|
"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=False)
|
||||||
|
def test_sync_disabled(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Disabled group syncing, a user, and a social login
|
||||||
|
WHEN:
|
||||||
|
- The social login is updated via signal after login
|
||||||
|
THEN:
|
||||||
|
- The user's groups are not updated
|
||||||
|
"""
|
||||||
|
Group.objects.create(name="group1")
|
||||||
|
user = User.objects.create_user(username="testuser")
|
||||||
|
sociallogin = Mock(
|
||||||
|
user=user,
|
||||||
|
account=Mock(
|
||||||
|
extra_data={
|
||||||
|
"groups": ["group1"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
handle_social_account_updated(
|
||||||
|
sender=None,
|
||||||
|
request=HttpRequest(),
|
||||||
|
sociallogin=sociallogin,
|
||||||
|
)
|
||||||
|
self.assertEqual(list(user.groups.all()), [])
|
||||||
|
|
||||||
|
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
||||||
|
def test_no_groups(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Enabled group syncing, a user, and a social login with no groups
|
||||||
|
WHEN:
|
||||||
|
- The social login is updated via signal after login
|
||||||
|
THEN:
|
||||||
|
- The user's groups are cleared to match the social login's groups
|
||||||
|
"""
|
||||||
|
group = Group.objects.create(name="group1")
|
||||||
|
user = User.objects.create_user(username="testuser")
|
||||||
|
user.groups.add(group)
|
||||||
|
user.save()
|
||||||
|
sociallogin = Mock(
|
||||||
|
user=user,
|
||||||
|
account=Mock(
|
||||||
|
extra_data={
|
||||||
|
"groups": [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
handle_social_account_updated(
|
||||||
|
sender=None,
|
||||||
|
request=HttpRequest(),
|
||||||
|
sociallogin=sociallogin,
|
||||||
|
)
|
||||||
|
self.assertEqual(list(user.groups.all()), [])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user