From 096b395dea03880f2bc99c12ba302d61b450205f Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Mon, 17 May 2021 14:23:35 +0200 Subject: [PATCH] journal_event_types: add ldap user deactivation (#52671) --- src/authentic2/backends/ldap_backend.py | 6 ++++ src/authentic2/manager/journal_event_types.py | 24 ++++++++++--- tests/test_ldap.py | 20 ++++++++--- tests/test_manager_journal.py | 34 +++++++++++++++++++ tests/utils.py | 4 ++- 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py index 400a93ce..f31369eb 100644 --- a/src/authentic2/backends/ldap_backend.py +++ b/src/authentic2/backends/ldap_backend.py @@ -55,6 +55,7 @@ from authentic2.a2_rbac.utils import get_default_ou from authentic2.backends import is_user_authenticable from authentic2.compat_lasso import lasso from authentic2.ldap_utils import FilterFormatter +from authentic2.manager.journal_event_types import ManagerUserDeactivation from authentic2.middleware import StoreRequestMiddleware from authentic2.models import UserExternalId from authentic2.user_login_failure import user_login_failure, user_login_success @@ -1538,6 +1539,7 @@ class LDAPBackend(object): conn = cls.get_connection(block) if conn is None: continue + ldap_uri = conn.get_option(ldap.OPT_URI) eids = list( UserExternalId.objects.filter(user__is_active=True, source=block['realm']).values_list( 'external_id', flat=True @@ -1566,10 +1568,14 @@ class LDAPBackend(object): external_id__in=eids, user__is_active=True, source=block['realm'] ): eid.user.mark_as_inactive() + ManagerUserDeactivation.record( + target_user=eid.user, reason='ldap-not-present', origin=ldap_uri + ) # Handle users of old sources uei_qs = UserExternalId.objects.exclude(source__in=[block['realm'] for block in cls.get_config()]) for user in User.objects.filter(userexternalid__in=uei_qs): user.mark_as_inactive() + ManagerUserDeactivation.record(target_user=user, reason='ldap-old-source', origin=ldap_uri) @classmethod def ad_encoding(cls, s): diff --git a/src/authentic2/manager/journal_event_types.py b/src/authentic2/manager/journal_event_types.py index 5fc5c9d6..75a507dd 100644 --- a/src/authentic2/manager/journal_event_types.py +++ b/src/authentic2/manager/journal_event_types.py @@ -201,16 +201,32 @@ class ManagerUserDeactivation(EventTypeDefinition): label = _('user deactivation') @classmethod - def record(cls, user, session, target_user): - super().record(user=user, session=session, references=[target_user]) + def record(cls, target_user, user=None, session=None, origin='backoffice', reason=None): + data = {'reason': reason, 'origin': origin} + super().record(user=user, session=session, references=[target_user], data=data) @classmethod def get_message(cls, event, context): (user,) = event.get_typed_references(User) + reason = event.get_data('reason') if context and context == user: - return _('deactivation by administrator') + if reason == 'ldap-not-present': + return _('automatic deactivation because the associated LDAP account does not exist anymore') + elif reason == 'ldap-old-source': + return _('automatic deactivation because the associated LDAP source has been deleted') + else: + return _('deactivation by administrator') elif user: - return _('deactivation of user "%s"') % user.get_full_name() + if reason == 'ldap-not-present': + return _( + 'automatic deactivation of user "%s" because the associated LDAP account does not exist anymore' + ) + elif reason == 'ldap-old-source': + return _( + 'automatic deactivation of user "%s" because the associated LDAP source has been deleted' + ) + else: + return _('deactivation of user "%s"') % user.get_full_name() return super().get_message(event, context) diff --git a/tests/test_ldap.py b/tests/test_ldap.py index a7ccc7e2..f5110a32 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -254,8 +254,12 @@ def test_deactivate_orphaned_users(slapd, settings, client, db): ldap_backend.LDAPBackend.deactivate_orphaned_users() - assert ( - ldap_backend.UserExternalId.objects.filter(user__is_active=False, source=block['realm']).count() == 1 + deactivated_user = ldap_backend.UserExternalId.objects.get(user__is_active=False, source=block['realm']) + utils.assert_event( + 'manager.user.deactivation', + target_user=deactivated_user.user, + reason='ldap-not-present', + origin=slapd.ldap_url, ) # rename source realm @@ -264,9 +268,17 @@ def test_deactivate_orphaned_users(slapd, settings, client, db): ] ldap_backend.LDAPBackend.deactivate_orphaned_users() - assert ( - ldap_backend.UserExternalId.objects.filter(user__is_active=False, source=block['realm']).count() == 6 + deactivated_users = ldap_backend.UserExternalId.objects.filter( + user__is_active=False, source=block['realm'] ) + assert deactivated_users.count() == 6 + for ldap_user in deactivated_users.exclude(pk=deactivated_user.pk): + utils.assert_event( + 'manager.user.deactivation', + target_user=ldap_user.user, + reason='ldap-old-source', + origin=slapd.ldap_url, + ) @pytest.mark.django_db diff --git a/tests/test_manager_journal.py b/tests/test_manager_journal.py index eb99f6c7..be714bfc 100644 --- a/tests/test_manager_journal.py +++ b/tests/test_manager_journal.py @@ -251,6 +251,16 @@ def events(db, freezer): old_email='old@example.com', new_email='new@example.com', ) + make( + 'manager.user.deactivation', + target_user=user, + reason='ldap-not-present', + ) + make( + 'manager.user.deactivation', + target_user=user, + reason='ldap-old-source', + ) # verify we created at least one event for each type assert set(Event.objects.values_list("type__name", flat=True)) == set(_registry) @@ -542,6 +552,18 @@ def test_global_journal(app, superuser, events): 'type': 'user.email.change', 'user': 'Johnny doe', }, + { + 'timestamp': 'Jan. 2, 2020, 5 p.m.', + 'type': 'manager.user.deactivation', + 'user': '-', + 'message': 'automatic deactivation of user "%s" because the associated LDAP account does not exist anymore', + }, + { + 'timestamp': 'Jan. 2, 2020, 6 p.m.', + 'type': 'manager.user.deactivation', + 'user': '-', + 'message': 'automatic deactivation of user "%s" because the associated LDAP source has been deleted', + }, ] @@ -727,6 +749,18 @@ def test_user_journal(app, superuser, events): 'type': 'user.email.change', 'user': 'Johnny doe', }, + { + 'timestamp': 'Jan. 2, 2020, 5 p.m.', + 'type': 'manager.user.deactivation', + 'user': '-', + 'message': 'automatic deactivation because the associated LDAP account does not exist anymore', + }, + { + 'timestamp': 'Jan. 2, 2020, 6 p.m.', + 'type': 'manager.user.deactivation', + 'user': '-', + 'message': 'automatic deactivation because the associated LDAP source has been deleted', + }, ] diff --git a/tests/utils.py b/tests/utils.py index 43d18684..70224fed 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -264,7 +264,7 @@ def text_content(node): return ''.join(node.itertext()) if node is not None else '' -def assert_event(event_type_name, user=None, session=None, service=None, **data): +def assert_event(event_type_name, user=None, session=None, service=None, target_user=None, **data): qs = Event.objects.filter(type__name=event_type_name) if user: qs = qs.filter(user=user) @@ -278,6 +278,8 @@ def assert_event(event_type_name, user=None, session=None, service=None, **data) qs = qs.which_references(service) else: qs = qs.exclude(qs._which_references_query(models.Service)) + if target_user: + qs = qs.which_references(target_user) assert qs.count() == 1 -- 2.20.1