Merge main to nl/923-transition-domain-data-parse

Signed-off-by: CocoByte <nicolle.leclair@gmail.com>
This commit is contained in:
CocoByte 2023-10-11 22:03:15 -06:00
commit d02f1acf34
No known key found for this signature in database
GPG key ID: BBFAA2526384C97F
13 changed files with 183 additions and 55 deletions

View file

@ -138,24 +138,12 @@ class MyUserAdmin(BaseUserAdmin):
"email", "email",
"first_name", "first_name",
"last_name", "last_name",
# Group is a custom property defined within this file,
# rather than in a model like the other properties
"group", "group",
"status", "status",
) )
list_filter = (
"is_active",
"groups",
)
# Let's define First group
# (which should in theory be the ONLY group)
def group(self, obj):
if obj.groups.filter(name="full_access_group").exists():
return "Full access"
elif obj.groups.filter(name="cisa_analysts_group").exists():
return "Analyst"
return ""
fieldsets = ( fieldsets = (
( (
None, None,
@ -222,6 +210,20 @@ class MyUserAdmin(BaseUserAdmin):
"date_joined", "date_joined",
] ]
list_filter = (
"is_active",
"groups",
)
# Let's define First group
# (which should in theory be the ONLY group)
def group(self, obj):
if obj.groups.filter(name="full_access_group").exists():
return "Full access"
elif obj.groups.filter(name="cisa_analysts_group").exists():
return "Analyst"
return ""
def get_list_display(self, request): def get_list_display(self, request):
# The full_access_permission perm will load onto the full_access_group # The full_access_permission perm will load onto the full_access_group
# which is equivalent to superuser. The other group we use to manage # which is equivalent to superuser. The other group we use to manage
@ -811,7 +813,8 @@ class DomainAdmin(ListHeaderAdmin):
else: else:
self.message_user( self.message_user(
request, request,
("Domain statuses are %s" ". Thanks!") % statuses, f"The registry statuses are {statuses}. "
"These statuses are from the provider of the .gov registry.",
) )
return HttpResponseRedirect(".") return HttpResponseRedirect(".")

View file

@ -64,7 +64,7 @@ class DomainSecurityEmailForm(forms.Form):
"""Form for adding or editing a security email to a domain.""" """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): class DomainOrgNameAddressForm(forms.ModelForm):

View file

@ -15,7 +15,7 @@ from epplibwrapper import (
RegistryError, RegistryError,
ErrorCode, 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_field import DomainField
from .utility.domain_helper import DomainHelper from .utility.domain_helper import DomainHelper
@ -625,7 +625,10 @@ class Domain(TimeStampedModel, DomainHelper):
def get_security_email(self): def get_security_email(self):
logger.info("get_security_email-> getting the contact ") logger.info("get_security_email-> getting the contact ")
secContact = self.security_contact secContact = self.security_contact
return secContact.email if secContact is not None:
return secContact.email
else:
return None
def clientHoldStatus(self): def clientHoldStatus(self):
return epp.Status(state=self.Status.CLIENT_HOLD, description="", lang="en") return epp.Status(state=self.Status.CLIENT_HOLD, description="", lang="en")
@ -698,10 +701,10 @@ class Domain(TimeStampedModel, DomainHelper):
return None return None
if contact_type is None: if contact_type is None:
raise ContactError("contact_type is None") raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE)
if contact_id is 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, # Since contact_id is registry_id,
# check that its the right length # check that its the right length
@ -710,14 +713,10 @@ class Domain(TimeStampedModel, DomainHelper):
contact_id_length > PublicContact.get_max_id_length() contact_id_length > PublicContact.get_max_id_length()
or contact_id_length < 1 or contact_id_length < 1
): ):
raise ContactError( raise ContactError(code=ContactErrorCodes.CONTACT_ID_INVALID_LENGTH)
"contact_id is of invalid length. "
"Cannot exceed 16 characters, "
f"got {contact_id} with a length of {contact_id_length}"
)
if not isinstance(contact, eppInfo.InfoContactResultData): 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 auth_info = contact.auth_info
postal_info = contact.postal_info postal_info = contact.postal_info
@ -881,8 +880,8 @@ class Domain(TimeStampedModel, DomainHelper):
return self._handle_registrant_contact(desired_contact) return self._handle_registrant_contact(desired_contact)
_registry_id: str _registry_id: str = ""
if contact_type in contacts: if contacts is not None and contact_type in contacts:
_registry_id = contacts.get(contact_type) _registry_id = contacts.get(contact_type)
desired = PublicContact.objects.filter( desired = PublicContact.objects.filter(

View file

@ -1,2 +1,51 @@
from enum import IntEnum
class ContactErrorCodes(IntEnum):
"""Used in the ContactError class for
error mapping.
Overview of contact error codes:
- 2000 CONTACT_TYPE_NONE
- 2001 CONTACT_ID_NONE
- 2002 CONTACT_ID_INVALID_LENGTH
- 2003 CONTACT_INVALID_TYPE
"""
CONTACT_TYPE_NONE = 2000
CONTACT_ID_NONE = 2001
CONTACT_ID_INVALID_LENGTH = 2002
CONTACT_INVALID_TYPE = 2003
CONTACT_NOT_FOUND = 2004
class ContactError(Exception): 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}"

View file

@ -13,10 +13,10 @@
{% elif original.state == original.State.ON_HOLD %} {% elif original.state == original.State.ON_HOLD %}
<input type="submit" value="Remove hold" name="_remove_client_hold"> <input type="submit" value="Remove hold" name="_remove_client_hold">
{% endif %} {% endif %}
<input id="manageDomainSubmitButton" type="submit" value="Manage Domain" name="_edit_domain"> <input id="manageDomainSubmitButton" type="submit" value="Manage domain" name="_edit_domain">
<input type="submit" value="get status" name="_get_status"> <input type="submit" value="Get registry status" name="_get_status">
{% if original.state != original.State.DELETED %} {% if original.state != original.State.DELETED %}
<input type="submit" value="Delete Domain in Registry" name="_delete_domain"> <input type="submit" value="Delete domain in registry" name="_delete_domain">
{% endif %} {% endif %}
</div> </div>
{{ block.super }} {{ block.super }}

View file

@ -46,8 +46,11 @@
{% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %} {% 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 %} {% 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 != 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 %}
{% endif %}
{% url 'domain-users' pk=domain.id as url %} {% 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 %} {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %}

View file

@ -1,7 +1,7 @@
{% extends "domain_base.html" %} {% extends "domain_base.html" %}
{% load static field_helpers%} {% load static field_helpers%}
{% block title %}Domain name servers | {{ domain.name }} | {% endblock %} {% block title %}DNS name servers | {{ domain.name }} | {% endblock %}
{% block domain_content %} {% block domain_content %}
{# this is right after the messages block in the parent template #} {# this is right after the messages block in the parent template #}
@ -9,7 +9,7 @@
{% include "includes/form_errors.html" with form=form %} {% include "includes/form_errors.html" with form=form %}
{% endfor %} {% endfor %}
<h1>Domain name servers</h1> <h1>DNS name servers</h1>
<p>Before your domain can be used we'll need information about your domain <p>Before your domain can be used we'll need information about your domain
name servers.</p> name servers.</p>

View file

@ -1,18 +1,16 @@
{% extends "domain_base.html" %} {% extends "domain_base.html" %}
{% load static field_helpers url_helpers %} {% load static field_helpers url_helpers %}
{% block title %}Domain security email | {{ domain.name }} | {% endblock %} {% block title %}Security email | {{ domain.name }} | {% endblock %}
{% block domain_content %} {% block domain_content %}
<h1>Domain security email</h1> <h1>Security email</h1>
<p>We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. Security emails are made public and included in the <a href="{% public_site_url 'about/data/' %}">.gov domain data</a> we provide.</p> <p>We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. Security emails are made public and included in the <a href="{% public_site_url 'about/data/' %}">.gov domain data</a> we provide.</p>
<p>A security contact should be capable of evaluating or triaging security reports for your entire domain. Use a team email address, not an individuals email. We recommend using an alias, like security@domain.gov.</p> <p>A security contact should be capable of evaluating or triaging security reports for your entire domain. Use a team email address, not an individuals email. We recommend using an alias, like security@domain.gov.</p>
{% include "includes/required_fields.html" %}
<form class="usa-form usa-form--large" method="post" novalidate> <form class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %} {% csrf_token %}

View file

@ -1,11 +1,11 @@
{% extends "domain_base.html" %} {% extends "domain_base.html" %}
{% load static field_helpers %} {% load static field_helpers %}
{% block title %}Domain contact information | {{ domain.name }} | {% endblock %} {% block title %}Your contact information | {{ domain.name }} | {% endblock %}
{% block domain_content %} {% block domain_content %}
<h1>Domain contact information</h1> <h1>Your contact information</h1>
<p>If youd like us to use a different name, email, or phone number you can make those changes below. <strong>Updating your contact information here will update the contact information for all domains in your account.</strong> However, it wont affect your Login.gov account information. <p>If youd like us to use a different name, email, or phone number you can make those changes below. <strong>Updating your contact information here will update the contact information for all domains in your account.</strong> However, it wont affect your Login.gov account information.
</p> </p>

View file

@ -109,12 +109,12 @@ class TestDomainAdmin(MockEppLib):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name) self.assertContains(response, domain.name)
self.assertContains(response, "Delete Domain in Registry") self.assertContains(response, "Delete domain in registry")
# Test the info dialog # Test the info dialog
request = self.factory.post( request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk), "/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Delete Domain in Registry", "name": domain.name}, {"_delete_domain": "Delete domain in registry", "name": domain.name},
follow=True, follow=True,
) )
request.user = self.client request.user = self.client
@ -149,12 +149,12 @@ class TestDomainAdmin(MockEppLib):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name) self.assertContains(response, domain.name)
self.assertContains(response, "Delete Domain in Registry") self.assertContains(response, "Delete domain in registry")
# Test the error # Test the error
request = self.factory.post( request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk), "/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Delete Domain in Registry", "name": domain.name}, {"_delete_domain": "Delete domain in registry", "name": domain.name},
follow=True, follow=True,
) )
request.user = self.client request.user = self.client
@ -194,12 +194,12 @@ class TestDomainAdmin(MockEppLib):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name) self.assertContains(response, domain.name)
self.assertContains(response, "Delete Domain in Registry") self.assertContains(response, "Delete domain in registry")
# Test the info dialog # Test the info dialog
request = self.factory.post( request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk), "/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Delete Domain in Registry", "name": domain.name}, {"_delete_domain": "Delete domain in registry", "name": domain.name},
follow=True, follow=True,
) )
request.user = self.client request.user = self.client
@ -221,7 +221,7 @@ class TestDomainAdmin(MockEppLib):
# Test the info dialog # Test the info dialog
request = self.factory.post( request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk), "/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Delete Domain in Registry", "name": domain.name}, {"_delete_domain": "Delete domain in registry", "name": domain.name},
follow=True, follow=True,
) )
request.user = self.client request.user = self.client

View file

@ -16,6 +16,7 @@ from registrar.models.domain_information import DomainInformation
from registrar.models.draft_domain import DraftDomain from registrar.models.draft_domain import DraftDomain
from registrar.models.public_contact import PublicContact from registrar.models.public_contact import PublicContact
from registrar.models.user import User from registrar.models.user import User
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
from .common import MockEppLib from .common import MockEppLib
from django_fsm import TransitionNotAllowed # type: ignore from django_fsm import TransitionNotAllowed # type: ignore
from epplibwrapper import ( from epplibwrapper import (
@ -193,6 +194,56 @@ class TestDomainCache(MockEppLib):
self.assertEqual(cached_contact, in_db.registry_id) self.assertEqual(cached_contact, in_db.registry_id)
self.assertEqual(domain.security_contact.email, "123test@mail.gov") 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): class TestDomainCreation(MockEppLib):
"""Rule: An approved domain application must result in a domain""" """Rule: An approved domain application must result in a domain"""

View file

@ -1309,7 +1309,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
page = self.client.get( page = self.client.get(
reverse("domain-nameservers", kwargs={"pk": self.domain.id}) reverse("domain-nameservers", kwargs={"pk": self.domain.id})
) )
self.assertContains(page, "Domain name servers") self.assertContains(page, "DNS name servers")
@skip("Broken by adding registry connection fix in ticket 848") @skip("Broken by adding registry connection fix in ticket 848")
def test_domain_nameservers_form(self): def test_domain_nameservers_form(self):
@ -1414,7 +1414,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
page = self.client.get( page = self.client.get(
reverse("domain-your-contact-information", kwargs={"pk": self.domain.id}) reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})
) )
self.assertContains(page, "Domain contact information") self.assertContains(page, "Your contact information")
def test_domain_your_contact_information_content(self): def test_domain_your_contact_information_content(self):
"""Logged-in user's contact information appears on the page.""" """Logged-in user's contact information appears on the page."""
@ -1439,7 +1439,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
) )
# Loads correctly # Loads correctly
self.assertContains(page, "Domain security email") self.assertContains(page, "Security email")
self.assertContains(page, "security@mail.gov") self.assertContains(page, "security@mail.gov")
self.mockSendPatch.stop() self.mockSendPatch.stop()
@ -1455,7 +1455,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
) )
# Loads correctly # Loads correctly
self.assertContains(page, "Domain security email") self.assertContains(page, "Security email")
self.assertNotContains(page, "dotgov@cisa.dhs.gov") self.assertNotContains(page, "dotgov@cisa.dhs.gov")
self.mockSendPatch.stop() self.mockSendPatch.stop()
@ -1464,7 +1464,7 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
page = self.client.get( page = self.client.get(
reverse("domain-security-email", kwargs={"pk": self.domain.id}) reverse("domain-security-email", kwargs={"pk": self.domain.id})
) )
self.assertContains(page, "Domain security email") self.assertContains(page, "Security email")
def test_domain_security_email_form(self): def test_domain_security_email_form(self):
"""Adding a security email works. """Adding a security email works.

View file

@ -21,6 +21,7 @@ from registrar.models import (
User, User,
UserDomainRole, UserDomainRole,
) )
from registrar.models.public_contact import PublicContact
from ..forms import ( from ..forms import (
ContactForm, ContactForm,
@ -42,6 +43,19 @@ class DomainView(DomainPermissionView):
template_name = "domain_detail.html" template_name = "domain_detail.html"
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 == default_email:
context["security_email"] = None
return context
context["security_email"] = security_email
return context
class DomainOrgNameAddressView(DomainPermissionView, FormMixin): class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
"""Organization name and mailing address view""" """Organization name and mailing address view"""
@ -284,10 +298,21 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
"""The form is valid, call setter in model.""" """The form is valid, call setter in model."""
# Set the security email from the form # 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() domain = self.get_object()
contact = domain.security_contact contact = domain.security_contact
# If no default is created for security_contact,
# then we cannot connect to the registry.
if contact is None:
messages.error(self.request, "Update failed. Cannot contact the registry.")
return redirect(self.get_success_url())
contact.email = new_email contact.email = new_email
contact.save() contact.save()