From 62fde87631312c709a3915f2c7ed6dee764b514f Mon Sep 17 00:00:00 2001 From: raftmsohani <97037188+raftmsohani@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:39:12 -0500 Subject: [PATCH] 1337 email owner as system admin group (un)assigned (#3253) * Send email using signals * revised email template * corrected removing the user message * linting * fixed signal receiver * Added admin button for System Owner account * Changed format of email * moved imports to top * added super_user and is_staff to email * corrected the test and linting * fixed first time user created * linting * Update tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html Co-authored-by: Alex P. <63075587+ADPennington@users.noreply.github.com> * Update tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html Co-authored-by: Alex P. <63075587+ADPennington@users.noreply.github.com> * Update tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html Co-authored-by: Alex P. <63075587+ADPennington@users.noreply.github.com> * Update tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html Co-authored-by: Alex P. <63075587+ADPennington@users.noreply.github.com> * Update tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html Co-authored-by: Alex P. <63075587+ADPennington@users.noreply.github.com> * Update tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html Co-authored-by: Alex P. <63075587+ADPennington@users.noreply.github.com> --------- Co-authored-by: Alex P. <63075587+ADPennington@users.noreply.github.com> --- .gitignore | 5 +- tdrs-backend/tdpservice/email/email_enums.py | 1 + .../email/helpers/admin_notifications.py | 47 +++++++++++++- tdrs-backend/tdpservice/email/tasks.py | 10 +++ .../templates/system-admin-role-changed.html | 36 +++++++++++ tdrs-backend/tdpservice/users/apps.py | 4 ++ tdrs-backend/tdpservice/users/signals.py | 62 +++++++++++++++++++ .../tdpservice/users/test/test_signals.py | 41 ++++++++++++ tdrs-frontend/src/selectors/auth.js | 1 + 9 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html create mode 100644 tdrs-backend/tdpservice/users/signals.py create mode 100644 tdrs-backend/tdpservice/users/test/test_signals.py diff --git a/.gitignore b/.gitignore index 7d693b2c7..b627bccc4 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ compliance/opencontrols/ compliance/exports/ tdrs-backend/tdpservice/static/* *gunicorn.log +*.log # don't ignore requirements.txt !requirements.txt @@ -115,6 +116,4 @@ cypress.env.json # DB seeds tdrs-backend/*.pg - -# Log files -*.log +tdrs-backend/django.log diff --git a/tdrs-backend/tdpservice/email/email_enums.py b/tdrs-backend/tdpservice/email/email_enums.py index 82e15e66d..de8f46f1b 100644 --- a/tdrs-backend/tdpservice/email/email_enums.py +++ b/tdrs-backend/tdpservice/email/email_enums.py @@ -16,3 +16,4 @@ class EmailType(Enum): ACCOUNT_DEACTIVATED_ADMIN = 'account-deactivated-admin.html' UPCOMING_SUBMISSION_DEADLINE = 'upcoming-submission-deadline.html' STUCK_FILE_LIST = 'stuck-file-list.html' + SYSTEM_ADMIN_ROLE_CHANGED = 'system-admin-role-changed.html' diff --git a/tdrs-backend/tdpservice/email/helpers/admin_notifications.py b/tdrs-backend/tdpservice/email/helpers/admin_notifications.py index 594e48710..a56e41b6d 100644 --- a/tdrs-backend/tdpservice/email/helpers/admin_notifications.py +++ b/tdrs-backend/tdpservice/email/helpers/admin_notifications.py @@ -1,10 +1,10 @@ """helper functions to administer user accounts.""" +from tdpservice.users.models import User +from tdpservice.email.email_enums import EmailType +from tdpservice.email.email import automated_email, log def email_admin_deactivated_user(user): """Send an email to OFA Admins when a user is deactivated.""" - from tdpservice.users.models import User - from tdpservice.email.email_enums import EmailType - from tdpservice.email.email import automated_email, log from tdpservice.email.tasks import get_ofa_admin_user_emails recipient_emails = get_ofa_admin_user_emails() @@ -33,3 +33,44 @@ def email_admin_deactivated_user(user): text_message=text_message, logger_context=logger_context ) + +def email_system_owner_system_admin_role_change(user, action): + """Send an email to the System Owner when a user is assigned or removed from the System Admin role.""" + from tdpservice.email.tasks import get_system_owner_email + recipient_email = get_system_owner_email() + logger_context = { + 'user_id': user.id, + 'object_id': user.id, + 'object_repr': user.username, + 'content_type': User, + } + + template_path = EmailType.SYSTEM_ADMIN_ROLE_CHANGED.value + + if action == 'added': + text_message = 'A user has been assigned to OFA System Admin role.' + elif action == 'is_staff_assigned': + text_message = 'A user has been assigned to staff role.' + elif action == 'is_superuser_assigned': + text_message = 'A user has been assigned to superuser role.' + elif action == 'is_staff_removed': + text_message = 'A user has been removed from staff role.' + else: + text_message = 'A user has been removed from OFA System Admin role.' + subject = 'TDP User Role Change: OFA System Admin' + context = { + 'user': user, + 'action': action, + } + + log(f"Preparing email to System Owner for System Admin role change for user {user.username}", + logger_context=logger_context) + + automated_email( + email_path=template_path, + recipient_email=recipient_email, + subject=subject, + email_context=context, + text_message=text_message, + logger_context=logger_context + ) diff --git a/tdrs-backend/tdpservice/email/tasks.py b/tdrs-backend/tdpservice/email/tasks.py index 179eeed86..1a63ef033 100644 --- a/tdrs-backend/tdpservice/email/tasks.py +++ b/tdrs-backend/tdpservice/email/tasks.py @@ -75,6 +75,16 @@ def get_ofa_admin_user_emails(): groups__in=Group.objects.filter(name__in=('OFA Admin', 'OFA System Admin')) ).values_list('email', flat=True).distinct() +def get_system_owner_email(): + """Return the email of the System Owner.""" + try: + user_email = User.objects.filter(groups__name='System Owner').values_list('email', flat=True).distinct() + except User.DoesNotExist: + user_email = [None] + except User.MultipleObjectsReturned: + user_email = user_email[0] + return user_email + def get_num_access_requests(): """Return the number of users requesting access.""" return User.objects.filter( diff --git a/tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html b/tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html new file mode 100644 index 000000000..25f9b860d --- /dev/null +++ b/tdrs-backend/tdpservice/email/templates/system-admin-role-changed.html @@ -0,0 +1,36 @@ +{% extends 'base.html' %} +{% block content %} + +

+ +{% if action == "added" %} +

The following user account for the TANF Data Portal (TDP) has been assigned to OFA System Admin group:

+ +{% elif action == "removed" %} +

The following user account for the TANF Data Portal (TDP) has been removed from OFA System Admin group:

+ +{% elif action == "is_staff_assigned" %} +

The following user account for the TANF Data Portal (TDP) has been assigned to OFA Staff group:

+ +{% elif action == "is_staff_removed" %} +

The following user account for the TANF Data Portal (TDP) has been removed from OFA Staff group:

+ +{% elif action == "is_superuser_assigned" %} +

The following user account for the TANF Data Portal (TDP) has been assigned to OFA Superuser group:

+ +{% elif action == "is_superuser_removed" %} +

The following user account for the TANF Data Portal (TDP) has been removed from OFA Superuser group:

+ +{% endif %} + +

+

Account Information:

+ + +

Thank you,

+ TDP Team +{% endblock %} diff --git a/tdrs-backend/tdpservice/users/apps.py b/tdrs-backend/tdpservice/users/apps.py index 5cb2627fd..48edb5b6d 100644 --- a/tdrs-backend/tdpservice/users/apps.py +++ b/tdrs-backend/tdpservice/users/apps.py @@ -8,3 +8,7 @@ class UsersConfig(AppConfig): name = "tdpservice.users" verbose_name = "Users" + + def ready(self): + """Import signals.""" + import tdpservice.users.signals # noqa diff --git a/tdrs-backend/tdpservice/users/signals.py b/tdrs-backend/tdpservice/users/signals.py new file mode 100644 index 000000000..e22ab4561 --- /dev/null +++ b/tdrs-backend/tdpservice/users/signals.py @@ -0,0 +1,62 @@ +"""Signals for the users app.""" +from django.db.models.signals import m2m_changed, pre_save, post_save +from django.dispatch import receiver +from tdpservice.users.models import User +from django.contrib.auth.models import Group +from tdpservice.email.helpers.admin_notifications import email_system_owner_system_admin_role_change + +import logging +logger = logging.getLogger() + +@receiver(m2m_changed, sender=User.groups.through) +def user_group_changed(sender, instance, action, pk_set, **kwargs): + """Send an email to the System Owner when a user is assigned or removed from the System Admin role.""" + ACTIONS = { + 'PRE_REMOVE': 'pre_remove', + 'PRE_ADD': 'pre_add', + 'PRE_CLEAR': 'pre_clear' + } + if pk_set: + ADMIN_GROUP_PK = Group.objects.get(name="OFA System Admin").pk + group_change_list = [pk for pk in pk_set] + if ADMIN_GROUP_PK in group_change_list and action == ACTIONS['PRE_ADD']: + # EMAIL ADMIN GROUP ADDED to OFA ADMIN + email_system_owner_system_admin_role_change(instance, "added") + elif ADMIN_GROUP_PK in group_change_list and action == ACTIONS['PRE_REMOVE']: + # EMAIL ADMIN GROUP REMOVED from OFA ADMIN + email_system_owner_system_admin_role_change(instance, "removed") + elif pk_set is None and action == ACTIONS['PRE_CLEAR']: + # EMAIL ADMIN GROUP REMOVED from OFA ADMIN + email_system_owner_system_admin_role_change(instance, "removed") + +@receiver(pre_save, sender=User) +def user_is_staff_superuser_changed(sender, instance, **kwargs): + """Send an email to the System Owner when a user is assigned or removed from the System Admin role.""" + # first get instance from db for existing state + try: + current_user_state = User.objects.get(pk=instance.pk) + except User.DoesNotExist: + return + + # check if is_staff is assigned + if instance.is_staff and not current_user_state.is_staff: + email_system_owner_system_admin_role_change(instance, "is_staff_assigned") + # check if is_staff is removed + elif not instance.is_staff and current_user_state.is_staff: + email_system_owner_system_admin_role_change(instance, "is_staff_removed") + # check if is_superuser is assigned + if instance.is_superuser and not current_user_state.is_superuser: + email_system_owner_system_admin_role_change(instance, "is_superuser_assigned") + # check if is_superuser is removed + elif not instance.is_superuser and current_user_state.is_superuser: + email_system_owner_system_admin_role_change(instance, "is_superuser_removed") + + +@receiver(post_save, sender=User) +def user_is_staff_superuser_created(sender, instance, created, **kwargs): + """Send an email to the System Owner when a user is assigned or removed from the System Admin role.""" + if created: + if instance.is_staff: + email_system_owner_system_admin_role_change(instance, "is_staff_assigned") + if instance.is_superuser: + email_system_owner_system_admin_role_change(instance, "is_superuser_assigned") diff --git a/tdrs-backend/tdpservice/users/test/test_signals.py b/tdrs-backend/tdpservice/users/test/test_signals.py new file mode 100644 index 000000000..218e71113 --- /dev/null +++ b/tdrs-backend/tdpservice/users/test/test_signals.py @@ -0,0 +1,41 @@ +"""Test signals.""" +import pytest +from unittest.mock import patch, call +from tdpservice.users.models import User +from tdpservice.users.test.factories import AdminUserFactory +from django.contrib.auth.models import Group +import logging +import django + + +logger = logging.getLogger(__name__) + + +@pytest.mark.django_db +def test_my_signal_receiver(mocker): + """Test my_signal_receiver.""" + with patch("django.db.models.signals.m2m_changed.send") as mock_receiver: + instance = AdminUserFactory.create() + instance.groups.add(Group.objects.get(name="OFA System Admin")) + + mock_receiver.assert_called_with( + sender=User.groups.through, + instance=instance, + action="post_add", + pk_set={Group.objects.get(name="OFA System Admin").pk}, + reverse=False, + using="default", + model=django.contrib.auth.models.Group, + ) + mock_receiver.call_count = 2 # pre_save and post_save + + with patch( + "tdpservice.users.signals.email_system_owner_system_admin_role_change" + ) as mock_email_system_owner_system_admin_role_change: + instance = AdminUserFactory.create() + instance.groups.add(Group.objects.get(name="OFA System Admin")) + mock_email_system_owner_system_admin_role_change.assert_has_calls([ + call(instance, 'is_staff_assigned'), + call(instance, 'is_superuser_assigned'), + call(instance, "added") + ]) diff --git a/tdrs-frontend/src/selectors/auth.js b/tdrs-frontend/src/selectors/auth.js index 081196160..35d915c43 100644 --- a/tdrs-frontend/src/selectors/auth.js +++ b/tdrs-frontend/src/selectors/auth.js @@ -62,6 +62,7 @@ export const accountCanViewAdmin = (state) => 'ACF OCIO', 'OFA Admin', 'DIGIT Team', + 'System Owner', ].includes(selectPrimaryUserRole(state)?.name) export const accountCanViewKibana = (state) =>