From 24ec18c439f2cb0d66b84fcbfbcd1736d68c07af Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 8 May 2024 15:13:03 -0600 Subject: [PATCH 1/8] Added logic to prevent creation of DomainInvitation if invite fails to send --- src/registrar/views/domain.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 9134080a1..2d67fc477 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -776,15 +776,23 @@ class DomainAddUserView(DomainFormBaseView): def _make_invitation(self, email_address: str, requestor: User): """Make a Domain invitation for this email and redirect with a message.""" - invitation, created = DomainInvitation.objects.get_or_create(email=email_address, domain=self.object) - if not created: + # Check to see if an invite has already been sent (NOTE: we do not want to create an invite just yet.) + invite_exists = DomainInvitation.objects.get(email=email_address, domain=self.object) + if invite_exists: # that invitation already existed messages.warning( self.request, f"{email_address} has already been invited to this domain.", ) else: - self._send_domain_invitation_email(email=email_address, requestor=requestor) + #Try to send the invitation. If it succeeds, add it to the DomainInvitation table. + try: + self._send_domain_invitation_email(email=email_address, requestor=requestor) + except Exception as exc: + raise EmailSendingError("Could not send SES email.") from exc + else: + #(NOTE: if the invitation fails to send, no invitation should be added to the DomainInvitation table) + DomainInvitation.objects.get_or_create(email=email_address, domain=self.object) return redirect(self.get_success_url()) def form_valid(self, form): From 5b8cf13eb183aeebbf474d91fe1458edc00446f5 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 8 May 2024 16:04:16 -0600 Subject: [PATCH 2/8] Fixed exception chain --- src/registrar/views/domain.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 2d67fc477..eb6f7d2c5 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -762,14 +762,14 @@ class DomainAddUserView(DomainFormBaseView): "requestor_email": requestor_email, }, ) - except EmailSendingError: - messages.warning(self.request, "Could not send email invitation.") + except EmailSendingError as exc: logger.warn( "Could not sent email invitation to %s for domain %s", email, self.object, exc_info=True, ) + raise EmailSendingError("Could not send email invitation.") from exc else: if add_success: messages.success(self.request, f"{email} has been invited to this domain.") @@ -777,21 +777,21 @@ class DomainAddUserView(DomainFormBaseView): def _make_invitation(self, email_address: str, requestor: User): """Make a Domain invitation for this email and redirect with a message.""" # Check to see if an invite has already been sent (NOTE: we do not want to create an invite just yet.) - invite_exists = DomainInvitation.objects.get(email=email_address, domain=self.object) - if invite_exists: + try: + invite = DomainInvitation.objects.get(email=email_address, domain=self.object) # that invitation already existed messages.warning( self.request, f"{email_address} has already been invited to this domain.", ) - else: + except DomainInvitation.DoesNotExist: #Try to send the invitation. If it succeeds, add it to the DomainInvitation table. try: self._send_domain_invitation_email(email=email_address, requestor=requestor) - except Exception as exc: - raise EmailSendingError("Could not send SES email.") from exc + except EmailSendingError as exc: + messages.warning(self.request, "Could not send email invitation.") else: - #(NOTE: if the invitation fails to send, no invitation should be added to the DomainInvitation table) + #(NOTE: only create a domainInvitation if the e-mail sends correctly) DomainInvitation.objects.get_or_create(email=email_address, domain=self.object) return redirect(self.get_success_url()) @@ -807,7 +807,10 @@ class DomainAddUserView(DomainFormBaseView): return self._make_invitation(requested_email, requestor) else: # if user already exists then just send an email - self._send_domain_invitation_email(requested_email, requestor, add_success=False) + try: + self._send_domain_invitation_email(requested_email, requestor, add_success=False) + except Exception as exc: + messages.warning(self.request, "Could not send email invitation.") try: UserDomainRole.objects.create( From 10ae65c0b09b7062c10ab21907e81a4ce2f8f098 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 15 May 2024 16:06:49 -0600 Subject: [PATCH 3/8] linted --- src/registrar/views/domain.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index eb6f7d2c5..b9ade278b 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -778,20 +778,19 @@ class DomainAddUserView(DomainFormBaseView): """Make a Domain invitation for this email and redirect with a message.""" # Check to see if an invite has already been sent (NOTE: we do not want to create an invite just yet.) try: - invite = DomainInvitation.objects.get(email=email_address, domain=self.object) # that invitation already existed messages.warning( self.request, f"{email_address} has already been invited to this domain.", ) except DomainInvitation.DoesNotExist: - #Try to send the invitation. If it succeeds, add it to the DomainInvitation table. + # Try to send the invitation. If it succeeds, add it to the DomainInvitation table. try: self._send_domain_invitation_email(email=email_address, requestor=requestor) - except EmailSendingError as exc: + except EmailSendingError: messages.warning(self.request, "Could not send email invitation.") else: - #(NOTE: only create a domainInvitation if the e-mail sends correctly) + # (NOTE: only create a domainInvitation if the e-mail sends correctly) DomainInvitation.objects.get_or_create(email=email_address, domain=self.object) return redirect(self.get_success_url()) @@ -809,7 +808,7 @@ class DomainAddUserView(DomainFormBaseView): # if user already exists then just send an email try: self._send_domain_invitation_email(requested_email, requestor, add_success=False) - except Exception as exc: + except Exception: messages.warning(self.request, "Could not send email invitation.") try: From 54623354165b6f5d7cc82225a495fa82ed5a42e5 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 22 May 2024 12:37:25 -0600 Subject: [PATCH 4/8] Updated exception handling and function comments --- src/registrar/views/domain.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index b9ade278b..cf1b24e0c 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -795,7 +795,8 @@ class DomainAddUserView(DomainFormBaseView): return redirect(self.get_success_url()) def form_valid(self, form): - """Add the specified user on this domain.""" + """Add the specified user on this domain. + Throws EmailSendingError.""" requested_email = form.cleaned_data["email"] requestor = self.request.user # look up a user with that email @@ -808,7 +809,19 @@ class DomainAddUserView(DomainFormBaseView): # if user already exists then just send an email try: self._send_domain_invitation_email(requested_email, requestor, add_success=False) + except EmailSendingError: + logger.warn( + "Could not send email invitation (EmailSendingError)", + self.object, + exc_info=True, + ) + messages.warning(self.request, "Could not send email invitation.") except Exception: + logger.warn( + "Could not send email invitation (Other Exception)", + self.object, + exc_info=True, + ) messages.warning(self.request, "Could not send email invitation.") try: From 2733a9045cf79f4ab8046ea2dc0409b2201cd658 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 22 May 2024 15:10:40 -0600 Subject: [PATCH 5/8] restored if statement (not sure why it dissappeared) --- src/registrar/utility/email.py | 5 ++++- src/registrar/views/domain.py | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/registrar/utility/email.py b/src/registrar/utility/email.py index 5f61181c7..10a4acc52 100644 --- a/src/registrar/utility/email.py +++ b/src/registrar/utility/email.py @@ -32,8 +32,9 @@ def send_templated_email( template_name and subject_template_name are relative to the same template context as Django's HTML templates. context gives additional information that the template may use. + + Raises EmailSendingError if SES client could not be accessed """ - logger.info(f"An email was sent! Template name: {template_name} to {to_address}") template = get_template(template_name) email_body = template.render(context=context) @@ -48,7 +49,9 @@ def send_templated_email( aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, config=settings.BOTO_CONFIG, ) + logger.info(f"An email was sent! Template name: {template_name} to {to_address}") except Exception as exc: + logger.debug(f"E-mail unable to send! Could not access the SES client.") raise EmailSendingError("Could not access the SES client.") from exc destination = {"ToAddresses": [to_address]} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index cf1b24e0c..03a0849eb 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -778,11 +778,13 @@ class DomainAddUserView(DomainFormBaseView): """Make a Domain invitation for this email and redirect with a message.""" # Check to see if an invite has already been sent (NOTE: we do not want to create an invite just yet.) try: + invite = DomainInvitation.objects.get(email=email_address, domain=self.object) # that invitation already existed - messages.warning( - self.request, - f"{email_address} has already been invited to this domain.", - ) + if invite is not None: + messages.warning( + self.request, + f"{email_address} has already been invited to this domain.", + ) except DomainInvitation.DoesNotExist: # Try to send the invitation. If it succeeds, add it to the DomainInvitation table. try: From 2d39561d2b8e8c299a9772aef54418182cc20904 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Thu, 23 May 2024 10:53:46 -0600 Subject: [PATCH 6/8] linted --- src/registrar/utility/email.py | 2 +- src/registrar/views/domain.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/utility/email.py b/src/registrar/utility/email.py index 10a4acc52..51d61355a 100644 --- a/src/registrar/utility/email.py +++ b/src/registrar/utility/email.py @@ -51,7 +51,7 @@ def send_templated_email( ) logger.info(f"An email was sent! Template name: {template_name} to {to_address}") except Exception as exc: - logger.debug(f"E-mail unable to send! Could not access the SES client.") + logger.debug("E-mail unable to send! Could not access the SES client.") raise EmailSendingError("Could not access the SES client.") from exc destination = {"ToAddresses": [to_address]} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 03a0849eb..66fd148f0 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -797,7 +797,7 @@ class DomainAddUserView(DomainFormBaseView): return redirect(self.get_success_url()) def form_valid(self, form): - """Add the specified user on this domain. + """Add the specified user on this domain. Throws EmailSendingError.""" requested_email = form.cleaned_data["email"] requestor = self.request.user From b72037a073d6d42257965572400a6aa04efd3a72 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 24 May 2024 13:13:24 -0600 Subject: [PATCH 7/8] Added emailSendingError exception to comment --- src/registrar/views/domain.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 66fd148f0..08affba6e 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -734,7 +734,10 @@ class DomainAddUserView(DomainFormBaseView): does not make a domain information object email: string- email to send to add_success: bool- default True indicates: - adding a success message to the view if the email sending succeeds""" + adding a success message to the view if the email sending succeeds + + raises EmailSendingError + """ # Set a default email address to send to for staff requestor_email = settings.DEFAULT_FROM_EMAIL From b2891fe2d27c2079234dac48ff3e50bd3116b70e Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 24 May 2024 13:16:42 -0600 Subject: [PATCH 8/8] linted --- src/registrar/views/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 08affba6e..be484f06a 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -735,7 +735,7 @@ class DomainAddUserView(DomainFormBaseView): email: string- email to send to add_success: bool- default True indicates: adding a success message to the view if the email sending succeeds - + raises EmailSendingError """