Skip to content

Commit

Permalink
Merge branch 'develop' into 3.7.5-release-notes
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-jameson authored Dec 18, 2024
2 parents ec1e27f + 62fde87 commit 41341d4
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 6 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ compliance/opencontrols/
compliance/exports/
tdrs-backend/tdpservice/static/*
*gunicorn.log
*.log

# don't ignore requirements.txt
!requirements.txt
Expand Down Expand Up @@ -115,6 +116,4 @@ cypress.env.json

# DB seeds
tdrs-backend/*.pg

# Log files
*.log
tdrs-backend/django.log
1 change: 1 addition & 0 deletions tdrs-backend/tdpservice/email/email_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
47 changes: 44 additions & 3 deletions tdrs-backend/tdpservice/email/helpers/admin_notifications.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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
)
10 changes: 10 additions & 0 deletions tdrs-backend/tdpservice/email/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% extends 'base.html' %}
{% block content %}
<!-- Body copy -->
<p style="color: #000000;">

{% if action == "added" %}
<p>The following user account for the TANF Data Portal (TDP) has been assigned to OFA System Admin group:</p>

{% elif action == "removed" %}
<p>The following user account for the TANF Data Portal (TDP) has been removed from OFA System Admin group:</p>

{% elif action == "is_staff_assigned" %}
<p>The following user account for the TANF Data Portal (TDP) has been assigned to OFA Staff group:</p>

{% elif action == "is_staff_removed" %}
<p>The following user account for the TANF Data Portal (TDP) has been removed from OFA Staff group:</p>

{% elif action == "is_superuser_assigned" %}
<p>The following user account for the TANF Data Portal (TDP) has been assigned to OFA Superuser group:</p>

{% elif action == "is_superuser_removed" %}
<p>The following user account for the TANF Data Portal (TDP) has been removed from OFA Superuser group:</p>

{% endif %}

</p>
<p>Account Information:</p>
<ul>
<li>Name: {{ user.first_name }}</li>
<li>Last name: {{ user.last_name }}</li>
<li>Email: {{ user.email }}</li>
</ul>

<p>Thank you,</p>
TDP Team
{% endblock %}
4 changes: 4 additions & 0 deletions tdrs-backend/tdpservice/users/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ class UsersConfig(AppConfig):

name = "tdpservice.users"
verbose_name = "Users"

def ready(self):
"""Import signals."""
import tdpservice.users.signals # noqa
62 changes: 62 additions & 0 deletions tdrs-backend/tdpservice/users/signals.py
Original file line number Diff line number Diff line change
@@ -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")
41 changes: 41 additions & 0 deletions tdrs-backend/tdpservice/users/test/test_signals.py
Original file line number Diff line number Diff line change
@@ -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")
])
1 change: 1 addition & 0 deletions tdrs-frontend/src/selectors/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const accountCanViewAdmin = (state) =>
'ACF OCIO',
'OFA Admin',
'DIGIT Team',
'System Owner',
].includes(selectPrimaryUserRole(state)?.name)

export const accountCanViewKibana = (state) =>
Expand Down

0 comments on commit 41341d4

Please sign in to comment.