From cb20b80d0ce0540e4006f50c37a1c2020022e97a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:28:09 -0600 Subject: [PATCH 01/18] Update contact_error.py --- src/registrar/models/utility/contact_error.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py index fad928afe..f525c358b 100644 --- a/src/registrar/models/utility/contact_error.py +++ b/src/registrar/models/utility/contact_error.py @@ -2,8 +2,7 @@ from enum import IntEnum class ContactErrorCodes(IntEnum): - """ - Used in the ContactError class for + """Used in the ContactError class for error mapping. Overview of contact error codes: From 237b120d1f95c092768b53e39ba44eab928366b9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:36:17 -0600 Subject: [PATCH 02/18] Revert changes --- src/registrar/models/domain.py | 14 ++++----- src/registrar/models/utility/contact_error.py | 30 ++++++++++++++++++- src/registrar/templates/domain_detail.html | 7 +++-- src/registrar/views/domain.py | 8 +++++ 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 59edb707a..6b7078162 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -15,7 +15,7 @@ from epplibwrapper import ( RegistryError, ErrorCode, ) -from registrar.models.utility.contact_error import ContactError +from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from .utility.domain_field import DomainField from .utility.domain_helper import DomainHelper @@ -698,10 +698,10 @@ class Domain(TimeStampedModel, DomainHelper): return None if contact_type is None: - raise ContactError("contact_type is None") + raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE) if contact_id is None: - raise ContactError("contact_id is None") + raise ContactError(code=ContactErrorCodes.CONTACT_ID_NONE) # Since contact_id is registry_id, # check that its the right length @@ -710,14 +710,10 @@ class Domain(TimeStampedModel, DomainHelper): contact_id_length > PublicContact.get_max_id_length() or contact_id_length < 1 ): - raise ContactError( - "contact_id is of invalid length. " - "Cannot exceed 16 characters, " - f"got {contact_id} with a length of {contact_id_length}" - ) + raise ContactError(code=ContactErrorCodes.CONTACT_ID_INVALID_LENGTH) if not isinstance(contact, eppInfo.InfoContactResultData): - raise ContactError("Contact must be of type InfoContactResultData") + raise ContactError(code=ContactErrorCodes.CONTACT_INVALID_TYPE) auth_info = contact.auth_info postal_info = contact.postal_info diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py index af6dca818..f525c358b 100644 --- a/src/registrar/models/utility/contact_error.py +++ b/src/registrar/models/utility/contact_error.py @@ -20,4 +20,32 @@ class ContactErrorCodes(IntEnum): class ContactError(Exception): - ... + """ + Overview of contact error codes: + - 2000 CONTACT_TYPE_NONE + - 2001 CONTACT_ID_NONE + - 2002 CONTACT_ID_INVALID_LENGTH + - 2003 CONTACT_INVALID_TYPE + - 2004 CONTACT_NOT_FOUND + """ + + # For linter + _contact_id_error = "contact_id has an invalid length. Cannot exceed 16 characters." + _contact_invalid_error = "Contact must be of type InfoContactResultData" + _contact_not_found_error = "No contact was found in cache or the registry" + _error_mapping = { + ContactErrorCodes.CONTACT_TYPE_NONE: "contact_type is None", + ContactErrorCodes.CONTACT_ID_NONE: "contact_id is None", + ContactErrorCodes.CONTACT_ID_INVALID_LENGTH: _contact_id_error, + ContactErrorCodes.CONTACT_INVALID_TYPE: _contact_invalid_error, + ContactErrorCodes.CONTACT_NOT_FOUND: _contact_not_found_error + } + + def __init__(self, *args, code=None, **kwargs): + super().__init__(*args, **kwargs) + self.code = code + if self.code in self._error_mapping: + self.message = self._error_mapping.get(self.code) + + def __str__(self): + return f"{self.message}" diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 6a700b393..ea3efd68c 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,8 +46,11 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% include "includes/summary_item.html" with title='Security email' value=domain.security_email edit_link=url %} - + {% if security_email is not None and security_email != "dotgov@cisa.dhs.gov"%} + {% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %} + {% else %} + {% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %} + {% endif %} {% url 'domain-users' pk=domain.id as url %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index d8c3c80fa..8f1f2edde 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -42,6 +42,14 @@ class DomainView(DomainPermissionView): template_name = "domain_detail.html" + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + security_email = self.get_object().get_security_email() + if security_email is None or security_email == "dotgov@cisa.dhs.gov": + context["security_email"] = None + return context + context["security_email"] = security_email + return context class DomainOrgNameAddressView(DomainPermissionView, FormMixin): """Organization name and mailing address view""" From 664d9507b28c6318e37848d6477424fa180eb2dd Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:42:28 -0600 Subject: [PATCH 03/18] Black reformatting --- src/registrar/models/utility/contact_error.py | 2 +- src/registrar/views/domain.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/utility/contact_error.py b/src/registrar/models/utility/contact_error.py index f525c358b..cf392cb6e 100644 --- a/src/registrar/models/utility/contact_error.py +++ b/src/registrar/models/utility/contact_error.py @@ -38,7 +38,7 @@ class ContactError(Exception): ContactErrorCodes.CONTACT_ID_NONE: "contact_id is None", ContactErrorCodes.CONTACT_ID_INVALID_LENGTH: _contact_id_error, ContactErrorCodes.CONTACT_INVALID_TYPE: _contact_invalid_error, - ContactErrorCodes.CONTACT_NOT_FOUND: _contact_not_found_error + ContactErrorCodes.CONTACT_NOT_FOUND: _contact_not_found_error, } def __init__(self, *args, code=None, **kwargs): diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 8f1f2edde..0292baf5a 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -51,6 +51,7 @@ class DomainView(DomainPermissionView): context["security_email"] = security_email return context + class DomainOrgNameAddressView(DomainPermissionView, FormMixin): """Organization name and mailing address view""" From 029415974a17e7b836b1c5b7c19d535acd31b6a0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 5 Oct 2023 08:25:25 -0600 Subject: [PATCH 04/18] Store default email in a variable --- src/registrar/templates/domain_detail.html | 2 +- src/registrar/views/domain.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index ea3efd68c..bcf775fe5 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -46,7 +46,7 @@ {% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% url 'domain-security-email' pk=domain.id as url %} - {% if security_email is not None and security_email != "dotgov@cisa.dhs.gov"%} + {% if security_email is not None and security_email != default_security_email%} {% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %} {% else %} {% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 0292baf5a..7ffc3fa53 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -44,8 +44,12 @@ class DomainView(DomainPermissionView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + + default_email = Domain().get_default_security_contact().email + context["default_security_email"] = default_email + security_email = self.get_object().get_security_email() - if security_email is None or security_email == "dotgov@cisa.dhs.gov": + if security_email is None or security_email == default_email: context["security_email"] = None return context context["security_email"] = security_email From c902d08aae6be4a245758e1c65b119855563b102 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:49:38 -0600 Subject: [PATCH 05/18] Sanity tests --- src/registrar/tests/test_models_domain.py | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 50456c2d5..c2b956cf6 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -16,6 +16,7 @@ from registrar.models.domain_information import DomainInformation from registrar.models.draft_domain import DraftDomain from registrar.models.public_contact import PublicContact from registrar.models.user import User +from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from .common import MockEppLib from django_fsm import TransitionNotAllowed # type: ignore from epplibwrapper import ( @@ -193,6 +194,60 @@ class TestDomainCache(MockEppLib): self.assertEqual(cached_contact, in_db.registry_id) self.assertEqual(domain.security_contact.email, "123test@mail.gov") + def test_errors_map_epp_contact_to_public_contact(self): + """ + Scenario: Registrant gets invalid data from EPPLib + When the `map_epp_contact_to_public_contact` function + gets invalid data from EPPLib + Then the function throws the expected ContactErrors + """ + domain, _ = Domain.objects.get_or_create(name="registry.gov") + fakedEpp = self.fakedEppObject() + invalid_length = fakedEpp.dummyInfoContactResultData( + "Cymaticsisasubsetofmodalvibrationalphenomena", + "lengthInvalid@mail.gov" + ) + valid_object = fakedEpp.dummyInfoContactResultData( + "valid", + "valid@mail.gov" + ) + + desired_error = ContactErrorCodes.CONTACT_ID_INVALID_LENGTH + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + invalid_length, + invalid_length.id, + PublicContact.ContactTypeChoices.SECURITY, + ) + self.assertEqual(context.exception.code, desired_error) + + desired_error = ContactErrorCodes.CONTACT_ID_NONE + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + valid_object, + None, + PublicContact.ContactTypeChoices.SECURITY, + ) + self.assertEqual(context.exception.code, desired_error) + + desired_error = ContactErrorCodes.CONTACT_INVALID_TYPE + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + "bad_object", + valid_object.id, + PublicContact.ContactTypeChoices.SECURITY, + ) + self.assertEqual(context.exception.code, desired_error) + + desired_error = ContactErrorCodes.CONTACT_TYPE_NONE + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + valid_object, + valid_object.id, + None, + ) + self.assertEqual(context.exception.code, desired_error) + class TestDomainCreation(MockEppLib): """Rule: An approved domain application must result in a domain""" From b9022b622cc7aa8068467814d2276ac0fc7ca9bd Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:53:50 -0600 Subject: [PATCH 06/18] Linting --- src/registrar/tests/test_models_domain.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index c2b956cf6..46a6de004 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -204,13 +204,9 @@ class TestDomainCache(MockEppLib): domain, _ = Domain.objects.get_or_create(name="registry.gov") fakedEpp = self.fakedEppObject() invalid_length = fakedEpp.dummyInfoContactResultData( - "Cymaticsisasubsetofmodalvibrationalphenomena", - "lengthInvalid@mail.gov" - ) - valid_object = fakedEpp.dummyInfoContactResultData( - "valid", - "valid@mail.gov" + "Cymaticsisasubsetofmodalvibrationalphenomena", "lengthInvalid@mail.gov" ) + valid_object = fakedEpp.dummyInfoContactResultData("valid", "valid@mail.gov") desired_error = ContactErrorCodes.CONTACT_ID_INVALID_LENGTH with self.assertRaises(ContactError) as context: From abbcf315eed58dd40f944a64b8f66c7cb6be53e9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:01:23 -0600 Subject: [PATCH 07/18] Update admin.py --- src/registrar/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 275f67bb3..622934382 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -141,7 +141,7 @@ class MyUserAdmin(BaseUserAdmin): "is_superuser", "status", ) - + # Temp comment for deploy to sandbox - will remove fieldsets = ( ( None, From 545a5d80a6037439ba46622ea440e281b5a48411 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:12:41 -0600 Subject: [PATCH 08/18] Remove temp comment --- src/registrar/admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 622934382..5b23a6128 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -141,7 +141,6 @@ class MyUserAdmin(BaseUserAdmin): "is_superuser", "status", ) - # Temp comment for deploy to sandbox - will remove fieldsets = ( ( None, From 8da6578b46c605634c1c39d40b14f98e0147168c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:57:59 -0600 Subject: [PATCH 09/18] Let email field be null --- src/registrar/forms/domain.py | 2 +- src/registrar/models/domain.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index f14448bcf..2f9aa1976 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -64,7 +64,7 @@ class DomainSecurityEmailForm(forms.Form): """Form for adding or editing a security email to a domain.""" - security_email = forms.EmailField(label="Security email") + security_email = forms.EmailField(label="Security email", required=False) class DomainOrgNameAddressForm(forms.ModelForm): diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 6b7078162..91f4299e5 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -625,7 +625,10 @@ class Domain(TimeStampedModel, DomainHelper): def get_security_email(self): logger.info("get_security_email-> getting the contact ") secContact = self.security_contact - return secContact.email + if secContact is not None: + return secContact.email + else: + return None def clientHoldStatus(self): return epp.Status(state=self.Status.CLIENT_HOLD, description="", lang="en") From 3625fc092759599a589db2e179ff8c2c48ce616d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:27:16 -0600 Subject: [PATCH 10/18] Add delete functionality for sec email --- src/registrar/views/domain.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 7ffc3fa53..276895799 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -21,6 +21,7 @@ from registrar.models import ( User, UserDomainRole, ) +from registrar.models.public_contact import PublicContact from ..forms import ( ContactForm, @@ -297,7 +298,11 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin): """The form is valid, call setter in model.""" # Set the security email from the form - new_email = form.cleaned_data.get("security_email", "") + new_email: str = form.cleaned_data.get("security_email", "") + + # If we pass nothing for the sec email, set to the default + if new_email is None or new_email.strip() == "": + new_email = PublicContact.get_default_security().email domain = self.get_object() contact = domain.security_contact From 7f34a389f43cac815a7129bca6243cbd65ac978b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:59:51 -0600 Subject: [PATCH 11/18] Remove required field text --- src/registrar/templates/domain_security_email.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index 8175fa394..aac8abc84 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -11,8 +11,6 @@
A security contact should be capable of evaluating or triaging security reports for your entire domain. Use a team email address, not an individual’s email. We recommend using an alias, like security@domain.gov.
- {% include "includes/required_fields.html" %} -