From 1e917ca9e731c0a905369a0db62273b14b74d1fc Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 9 Mar 2021 23:03:00 +0100 Subject: [PATCH 3/3] manager: show deleted users informations in journal (#51808) --- .../journal/templates/journal/event_list.html | 2 +- src/authentic2/custom_user/models.py | 8 +- src/authentic2/manager/journal_event_types.py | 79 ++++++++++--------- src/authentic2/manager/journal_views.py | 6 ++ tests/test_manager_journal.py | 10 +++ 5 files changed, 64 insertions(+), 41 deletions(-) diff --git a/src/authentic2/apps/journal/templates/journal/event_list.html b/src/authentic2/apps/journal/templates/journal/event_list.html index 8e6639f4..049a8ff2 100644 --- a/src/authentic2/apps/journal/templates/journal/event_list.html +++ b/src/authentic2/apps/journal/templates/journal/event_list.html @@ -15,7 +15,7 @@ {% endif %} {% block event-timestamp %}{{ event.timestamp }}{% endblock %} - {% block event-user %}{{ event.user.get_full_name|default:"-" }}{% endblock %} + {% block event-user %}{% firstof event.user.get_full_name event.user "-" %}{% endblock %} {% block event-session %}{{ event.session_id_shortened|default:"-" }}{% endblock %} {% block event-message %}{{ event.message|default:"-" }}{% endblock %} diff --git a/src/authentic2/custom_user/models.py b/src/authentic2/custom_user/models.py index 6936f731..f06fa68e 100644 --- a/src/authentic2/custom_user/models.py +++ b/src/authentic2/custom_user/models.py @@ -414,13 +414,19 @@ class DeletedUser(models.Model): ) cls.objects.filter(deleted__lt=threshold).delete() - def __str__(self): + def __repr__(self): return 'DeletedUser(old_id=%s, old_uuid=%s…, old_email=%s)' % ( self.old_user_id or '-', (self.old_uuid or '')[:6], self.old_email or '-', ) + def __str__(self): + data = ['#%d' % self.old_user_id] + if self.old_email: + data.append(self.old_email) + return _('deleted user (%s)') % ', '.join(data) + class Meta: verbose_name = _('deleted user') verbose_name_plural = _('deleted users') diff --git a/src/authentic2/manager/journal_event_types.py b/src/authentic2/manager/journal_event_types.py index 6f137c1d..094c2843 100644 --- a/src/authentic2/manager/journal_event_types.py +++ b/src/authentic2/manager/journal_event_types.py @@ -23,6 +23,7 @@ from authentic2.backends.ldap_backend import ( LDAP_DEACTIVATION_REASON_NOT_PRESENT, LDAP_DEACTIVATION_REASON_OLD_SOURCE, ) +from authentic2.custom_user.models import DeletedUser from authentic2.journal_event_types import EventTypeWithService, get_attributes_label from django_rbac.utils import get_role_model @@ -30,6 +31,12 @@ User = get_user_model() Role = get_role_model() +def user_to_str(user): + if hasattr(user, 'get_full_name'): + return user.get_full_name() + return str(user) + + class ManagerUserCreation(EventTypeDefinition): name = 'manager.user.creation' label = _('user creation') @@ -40,13 +47,13 @@ class ManagerUserCreation(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) # user journal page if context and context == user: return _('creation by administrator') elif user: # manager gloabal journal page - return _('creation of user "%s"') % user.get_full_name() + return _('creation of user "%s"') % user_to_str(user) return super().get_message(event, context) @@ -60,13 +67,13 @@ class ManagerUserProfileEdit(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) new = event.get_data('new') or {} edited_attributes = ', '.join(get_attributes_label(new)) or '' if context and context == user: return _('edit by administrator (%s)') % edited_attributes elif user: - user_full_name = user.get_full_name() + user_full_name = user_to_str(user) return _('edit of user "{0}" ({1})').format(user_full_name, edited_attributes) return super().get_message(event, context) @@ -85,12 +92,12 @@ class ManagerUserEmailChangeRequest(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) new_email = event.get_data('email') if context and context == user: return _('email change for email address "%s" requested by administrator') % new_email elif user: - user_full_name = user.get_full_name() + user_full_name = user_to_str(user) return _('email change of user "{0}" for email address "{1}"').format(user_full_name, new_email) return super().get_message(event, context) @@ -109,7 +116,7 @@ class ManagerUserPasswordChange(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) send_mail = event.get_data('send_mail') if context and context == user: if send_mail: @@ -117,7 +124,7 @@ class ManagerUserPasswordChange(EventTypeDefinition): else: return _('password change by administrator') elif user: - user_full_name = user.get_full_name() + user_full_name = user_to_str(user) if send_mail: return _('password change of user "%s" and notification by mail') % user_full_name else: @@ -137,12 +144,12 @@ class ManagerUserPasswordResetRequest(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) email = event.get_data('email') if context and context == user: return _('password reset request by administrator sent to "%s"') % email elif user: - return _('password reset request of "{0}" sent to "{1}"').format(user.get_full_name(), email) + return _('password reset request of "{0}" sent to "{1}"').format(user_to_str(user), email) return super().get_message(event, context) @@ -156,11 +163,11 @@ class ManagerUserPasswordChangeForce(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) if context and context == user: return _('mandatory password change at next login set by administrator') elif user: - return _('mandatory password change at next login set for user "%s"') % user.get_full_name() + return _('mandatory password change at next login set for user "%s"') % user_to_str(user) return super().get_message(event, context) @@ -174,11 +181,11 @@ class ManagerUserPasswordChangeUnforce(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) if context and context == user: return _('mandatory password change at next login unset by administrator') elif user: - return _('mandatory password change at next login unset for user "%s"') % user.get_full_name() + return _('mandatory password change at next login unset for user "%s"') % user_to_str(user) return super().get_message(event, context) @@ -192,11 +199,11 @@ class ManagerUserActivation(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) if context and context == user: return _('activation by administrator') elif user: - return _('activation of user "%s"') % user.get_full_name() + return _('activation of user "%s"') % user_to_str(user) return super().get_message(event, context) @@ -211,7 +218,7 @@ class ManagerUserDeactivation(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) reason = event.get_data('reason') if context and context == user: if reason == LDAP_DEACTIVATION_REASON_NOT_PRESENT: @@ -222,21 +229,15 @@ class ManagerUserDeactivation(EventTypeDefinition): return _('deactivation by administrator') elif user: if reason == LDAP_DEACTIVATION_REASON_NOT_PRESENT: - return ( - _( - 'automatic deactivation of user "%s" because the associated LDAP account does not exist anymore' - ) - % user.get_full_name() - ) + return _( + 'automatic deactivation of user "%s" because the associated LDAP account does not exist anymore' + ) % user_to_str(user) elif reason == LDAP_DEACTIVATION_REASON_OLD_SOURCE: - return ( - _( - 'automatic deactivation of user "%s" because the associated LDAP source has been deleted' - ) - % user.get_full_name() - ) + return _( + 'automatic deactivation of user "%s" because the associated LDAP source has been deleted' + ) % user_to_str(user) else: - return _('deactivation of user "%s"') % user.get_full_name() + return _('deactivation of user "%s"') % user_to_str(user) return super().get_message(event, context) @@ -250,11 +251,11 @@ class ManagerUserDeletion(EventTypeDefinition): @classmethod def get_message(cls, event, context): - (user,) = event.get_typed_references(User) + (user,) = event.get_typed_references((DeletedUser, User)) if context and context == user: return _('deletion by administrator') elif user: - return _('deletion of user "%s"') % user.get_full_name() + return _('deletion of user "%s"') % user_to_str(user) return super().get_message(event, context) @@ -269,7 +270,7 @@ class ManagerUserSSOAuthorizationDeletion(EventTypeWithService): @classmethod def get_message(cls, event, context): # first reference is to the service - __, user = event.get_typed_references(None, User) + __, user = event.get_typed_references(None, (DeletedUser, User)) service_name = cls.get_service_name(event) if context and context == user: return _('deletion of authorization of single sign on with "{service}" by administrator').format( @@ -278,7 +279,7 @@ class ManagerUserSSOAuthorizationDeletion(EventTypeWithService): elif user: return _('deletion of authorization of single sign on with "{service}" of user "{user}"').format( service=service_name, - user=user.get_full_name(), + user=user_to_str(user), ) return super().get_message(event, context) @@ -357,7 +358,7 @@ class ManagerRoleMembershipGrant(RoleEventsMixin): @classmethod def get_message(cls, event, context): - role, member = event.get_typed_references(Role, User) + role, member = event.get_typed_references(Role, (DeletedUser, User)) role = role or event.get_data('role_name') member = member or event.get_data('member_name') if context == member: @@ -379,7 +380,7 @@ class ManagerRoleMembershipRemoval(RoleEventsMixin): @classmethod def get_message(cls, event, context): - role, member = event.get_typed_references(Role, User) + role, member = event.get_typed_references(Role, (DeletedUser, User)) role = role or event.get_data('role_name') member = member or event.get_data('member_name') if context == member: @@ -491,14 +492,14 @@ class ManagerRoleAdministratorUserAddition(RoleEventsMixin): @classmethod def record(cls, user, session, role, admin_user): data = { - 'admin_user_name': admin_user.get_full_name(), + 'admin_user_name': user_to_str(admin_user), 'admin_user_uuid': admin_user.uuid, } super().record(user=user, session=session, role=role, references=[admin_user], data=data) @classmethod def get_message(cls, event, context): - role, admin_user = event.get_typed_references(Role, User) + role, admin_user = event.get_typed_references(Role, (DeletedUser, User)) role = role or event.get_data('role_name') admin_user = admin_user or event.get_data('admin_user_name') if context == role: @@ -517,7 +518,7 @@ class ManagerRoleAdministratorUserRemoval(ManagerRoleAdministratorUserAddition): @classmethod def get_message(cls, event, context): - role, admin_user = event.get_typed_references(Role, User) + role, admin_user = event.get_typed_references(Role, (DeletedUser, User)) role = role or event.get_data('role_name') admin_user = admin_user or event.get_data('admin_user_name') if context == role: diff --git a/src/authentic2/manager/journal_views.py b/src/authentic2/manager/journal_views.py index 66fe9dc3..fc1b5a53 100644 --- a/src/authentic2/manager/journal_views.py +++ b/src/authentic2/manager/journal_views.py @@ -124,6 +124,12 @@ class JournalForm(JournalForm): return qs + def prefetcher(self, model, pks): + if not issubclass(model, User): + return + for deleted_user in DeletedUser.objects.filter(old_user_id__in=pks): + yield deleted_user.old_user_id, deleted_user + class BaseJournalView(views.TitleMixin, views.MediaMixin, views.MultipleOUMixin, JournalView): template_name = 'authentic2/manager/journal.html' diff --git a/tests/test_manager_journal.py b/tests/test_manager_journal.py index 08317922..855f77c3 100644 --- a/tests/test_manager_journal.py +++ b/tests/test_manager_journal.py @@ -1041,3 +1041,13 @@ def test_search(app, superuser, events): 'addition of user "user (111111)" as administrator of role "role1"', 'removal of role "role2" as administrator of role "role1"', ] + + +def test_delete_user(app, superuser, events): + old_user_id = events['user'].id + events['user'].delete() + response = login(app, user=superuser, path="/manage/journal/") + response.form.set('search', events['user'].email) + response = response.form.submit() + assert len(response.pyquery('tbody tr')) == 20 + assert 'deletion of user "deleted user (#%s, user@example.com)"' % old_user_id in str(response) -- 2.32.0.rc0