Merge remote-tracking branch 'origin/main' into ms/2307-send-notification-emails

This commit is contained in:
matthewswspence 2024-10-18 14:18:28 -05:00
commit 39ee21ec7c
No known key found for this signature in database
GPG key ID: FB458202A7852BA4
8 changed files with 173 additions and 89 deletions

View file

@ -1,4 +1,5 @@
import logging
import random
from faker import Faker
from django.db import transaction
@ -51,23 +52,24 @@ class UserPortfolioPermissionFixture:
user_portfolio_permissions_to_create = []
for user in users:
for portfolio in portfolios:
try:
if not UserPortfolioPermission.objects.filter(user=user, portfolio=portfolio).exists():
user_portfolio_permission = UserPortfolioPermission(
user=user,
portfolio=portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
additional_permissions=[UserPortfolioPermissionChoices.EDIT_MEMBERS],
)
user_portfolio_permissions_to_create.append(user_portfolio_permission)
else:
logger.info(
f"Permission exists for user '{user.username}' "
f"on portfolio '{portfolio.organization_name}'."
)
except Exception as e:
logger.warning(e)
# Assign a random portfolio to a user
portfolio = random.choice(portfolios) # nosec
try:
if not UserPortfolioPermission.objects.filter(user=user, portfolio=portfolio).exists():
user_portfolio_permission = UserPortfolioPermission(
user=user,
portfolio=portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
additional_permissions=[UserPortfolioPermissionChoices.EDIT_MEMBERS],
)
user_portfolio_permissions_to_create.append(user_portfolio_permission)
else:
logger.info(
f"Permission exists for user '{user.username}' "
f"on portfolio '{portfolio.organization_name}'."
)
except Exception as e:
logger.warning(e)
# Bulk create permissions
cls._bulk_create_permissions(user_portfolio_permissions_to_create)

View file

@ -137,6 +137,20 @@ class UserFixture:
"email": "annagingle@truss.works",
"title": "Sweetwater sailor",
},
{
"username": "cce058bc-9e52-456b-99ff-f5775c481c8f",
"first_name": "Elizabeth",
"last_name": "Liao",
"email": "elizabeth.liao@cisa.dhs.gov",
"title": "Software Engineer",
},
{
"username": "c9c64cd5-bc76-45ef-85cd-4f6eefa9e998",
"first_name": "Samiyah",
"last_name": "Key",
"email": "skey@truss.works",
"title": "Designer",
},
]
STAFF = [
@ -231,6 +245,18 @@ class UserFixture:
"last_name": "Gingle-Analyst",
"email": "annagingle+analyst@truss.works",
},
{
"username": "373f7073-f90b-49d8-8da2-459246fa33bd",
"first_name": "Elizabeth-Analyst",
"last_name": "Liao-Analyst",
"email": "elizabeth.liao@gwe.cisa.dhs.gov",
},
{
"username": "ee1e68da-41a5-47f7949b-d8a4e9e2b9d2",
"first_name": "Samiyah-Analyst",
"last_name": "Key-Analyst",
"email": "skey+1@truss.works",
},
]
# Additional emails to add to the AllowedEmail whitelist.

View file

@ -4,11 +4,31 @@
{% block title %}Add a domain manager | {% endblock %}
{% block domain_content %}
{% block breadcrumb %}
{% url 'domain-users' pk=domain.id as url %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain manager breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Domain managers</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>Add a domain manager</span>
</li>
</ol>
</nav>
{% endblock breadcrumb %}
<h1>Add a domain manager</h1>
<p>You can add another user to help manage your domain. They will need to sign
in to the .gov registrar with their Login.gov account.
{% if has_organization_feature_flag %}
<p>
You can add another user to help manage your domain. Users can only be a member of one .gov organization,
and they'll need to sign in with their Login.gov account.
</p>
{% else %}
<p>
You can add another user to help manage your domain. They will need to sign in to the .gov registrar with
their Login.gov account.
</p>
{% endif %}
<form class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}

View file

@ -23,6 +23,15 @@ class InvalidDomainError(ValueError):
pass
class OutsideOrgMemberError(ValueError):
"""
Error raised when an org member tries adding a user from a different .gov org.
To be deleted when users can be members of multiple orgs.
"""
pass
class ActionNotAllowed(Exception):
"""User accessed an action that is not
allowed by the current state"""

View file

@ -22,8 +22,10 @@ from registrar.models import (
DomainRequest,
DomainInformation,
DomainInvitation,
PortfolioInvitation,
User,
UserDomainRole,
UserPortfolioPermission,
PublicContact,
)
from registrar.utility.enums import DefaultEmail
@ -36,9 +38,11 @@ from registrar.utility.errors import (
DsDataErrorCodes,
SecurityEmailError,
SecurityEmailErrorCodes,
OutsideOrgMemberError,
)
from registrar.models.utility.contact_error import ContactError
from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView
from registrar.utility.waffle import flag_is_active_for_user
from ..forms import (
SeniorOfficialContactForm,
@ -894,7 +898,18 @@ class DomainAddUserView(DomainFormBaseView):
"""Get an absolute URL for this domain."""
return self.request.build_absolute_uri(reverse("domain", kwargs={"pk": self.object.id}))
def _send_domain_invitation_email(self, email: str, requestor: User, add_success=True):
def _is_member_of_different_org(self, email, requestor, requested_user):
"""Verifies if an email belongs to a different organization as a member or invited member."""
# Check if user is a already member of a different organization than the requestor's org
requestor_org = UserPortfolioPermission.objects.filter(user=requestor).first().portfolio
existing_org_permission = UserPortfolioPermission.objects.filter(user=requested_user).first()
existing_org_invitation = PortfolioInvitation.objects.filter(email=email).first()
return (existing_org_permission and existing_org_permission.portfolio != requestor_org) or (
existing_org_invitation and existing_org_invitation.portfolio != requestor_org
)
def _send_domain_invitation_email(self, email: str, requestor: User, requested_user=None, add_success=True):
"""Performs the sending of the domain invitation email,
does not make a domain information object
email: string- email to send to
@ -919,6 +934,13 @@ class DomainAddUserView(DomainFormBaseView):
)
return None
# Check is user is a member or invited member of a different org from this domain's org
if flag_is_active_for_user(requestor, "organization_feature") and self._is_member_of_different_org(
email, requestor, requested_user
):
add_success = False
raise OutsideOrgMemberError
# Check to see if an invite has already been sent
try:
invite = DomainInvitation.objects.get(email=email, domain=self.object)
@ -975,16 +997,21 @@ class DomainAddUserView(DomainFormBaseView):
Throws EmailSendingError."""
requested_email = form.cleaned_data["email"]
requestor = self.request.user
email_success = False
# look up a user with that email
try:
requested_user = User.objects.get(email=requested_email)
except User.DoesNotExist:
# no matching user, go make an invitation
email_success = True
return self._make_invitation(requested_email, requestor)
else:
# if user already exists then just send an email
try:
self._send_domain_invitation_email(requested_email, requestor, add_success=False)
self._send_domain_invitation_email(
requested_email, requestor, requested_user=requested_user, add_success=False
)
email_success = True
except EmailSendingError:
logger.warn(
"Could not send email invitation (EmailSendingError)",
@ -992,6 +1019,17 @@ class DomainAddUserView(DomainFormBaseView):
exc_info=True,
)
messages.warning(self.request, "Could not send email invitation.")
email_success = True
except OutsideOrgMemberError:
logger.warn(
"Could not send email. Can not invite member of a .gov organization to a different organization.",
self.object,
exc_info=True,
)
messages.error(
self.request,
f"{requested_email} is already a member of another .gov organization.",
)
except Exception:
logger.warn(
"Could not send email invitation (Other Exception)",
@ -999,17 +1037,17 @@ class DomainAddUserView(DomainFormBaseView):
exc_info=True,
)
messages.warning(self.request, "Could not send email invitation.")
if email_success:
try:
UserDomainRole.objects.create(
user=requested_user,
domain=self.object,
role=UserDomainRole.Roles.MANAGER,
)
messages.success(self.request, f"Added user {requested_email}.")
except IntegrityError:
messages.warning(self.request, f"{requested_email} is already a manager for this domain")
try:
UserDomainRole.objects.create(
user=requested_user,
domain=self.object,
role=UserDomainRole.Roles.MANAGER,
)
except IntegrityError:
messages.warning(self.request, f"{requested_email} is already a manager for this domain")
else:
messages.success(self.request, f"Added user {requested_email}.")
return redirect(self.get_success_url())