Merge remote-tracking branch 'origin/main' into rjm/787-org-short-names

This commit is contained in:
Rachid Mrad 2023-10-20 17:08:48 -04:00
commit 03b7ff64f8
No known key found for this signature in database
GPG key ID: EF38E4CEC4A8F3CF
19 changed files with 438 additions and 110 deletions

View file

@ -6,13 +6,13 @@ body:
id: title-help id: title-help
attributes: attributes:
value: | value: |
> Titles should be short, descriptive, and compelling. > Titles should be short, descriptive, and compelling. Use sentence case.
- type: textarea - type: textarea
id: issue-description id: issue-description
attributes: attributes:
label: Issue description and context label: Issue description
description: | description: |
Describe the issue so that someone who wasn't present for its discovery can understand the problem and why it matters. Use full sentences, plain language, and good [formatting](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). Share desired outcomes or potential next steps. Images or links to other content/context (like documents or Slack discussions) are welcome. Describe the issue so that someone who wasn't present for its discovery can understand why it matters. Use full sentences, plain language, and good [formatting](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax).
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -20,16 +20,22 @@ body:
attributes: attributes:
label: Acceptance criteria label: Acceptance criteria
description: "If known, share 1-3 statements that would need to be true for this issue to be considered resolved. Use a [task list](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/about-task-lists#creating-task-lists) if appropriate." description: "If known, share 1-3 statements that would need to be true for this issue to be considered resolved. Use a [task list](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/about-task-lists#creating-task-lists) if appropriate."
placeholder: "- [ ] The button does the thing." placeholder: "- [ ]"
- type: textarea
id: additional-context
attributes:
label: Additional context
description: "Share any other thoughts, like how this might be implemented or fixed. Screenshots and links to documents/discussions are welcome."
- type: textarea - type: textarea
id: links-to-other-issues id: links-to-other-issues
attributes: attributes:
label: Links to other issues label: Links to other issues
description: | description: |
Add the issue #number of other issues this relates to and how (e.g., 🚧 Blocks, ⛔️ Is blocked by, 🔄 Relates to). "Add issue #numbers this relates to and how (e.g., 🚧 :construction: Blocks, ⛔️ :no_entry: Is blocked by, 🔄 :repeat: Relates to)."
placeholder: 🔄 Relates to... placeholder: 🔄 Relates to...
- type: markdown - type: markdown
id: note id: note
attributes: attributes:
value: | value: |
> We may edit this issue's text to document our understanding and clarify the product work. > We may edit the text in this issue to document our understanding and clarify the product work.

View file

@ -42,7 +42,7 @@ as health checks used by our platform).
## Adding roles ## Adding roles
The current MVP design uses only a single role called The current MVP design uses only a single role called
`UserDomainRole.Roles.ADMIN` that has all access on a domain. As such, the `UserDomainRole.Roles.MANAGER` that has all access on a domain. As such, the
permission mixin doesn't need to examine the `role` field carefully. In the permission mixin doesn't need to examine the `role` field carefully. In the
future, as we add additional roles that our product vision calls for future, as we add additional roles that our product vision calls for
(read-only? editing only some information?), we need to add conditional (read-only? editing only some information?), we need to add conditional

View file

@ -3,19 +3,27 @@
import json import json
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.test import TestCase, RequestFactory from django.test import RequestFactory
from ..views import available, _domains, in_domains from ..views import available, in_domains
from .common import less_console_noise from .common import less_console_noise
from registrar.tests.common import MockEppLib
from unittest.mock import call
from epplibwrapper import (
commands,
RegistryError,
)
API_BASE_PATH = "/api/v1/available/" API_BASE_PATH = "/api/v1/available/"
class AvailableViewTest(TestCase): class AvailableViewTest(MockEppLib):
"""Test that the view function works as expected.""" """Test that the view function works as expected."""
def setUp(self): def setUp(self):
super().setUp()
self.user = get_user_model().objects.create(username="username") self.user = get_user_model().objects.create(username="username")
self.factory = RequestFactory() self.factory = RequestFactory()
@ -29,26 +37,37 @@ class AvailableViewTest(TestCase):
response_object = json.loads(response.content) response_object = json.loads(response.content)
self.assertIn("available", response_object) self.assertIn("available", response_object)
def test_domain_list(self): def test_in_domains_makes_calls_(self):
"""Test the domain list that is returned from Github. """Domain searches successfully make correct mock EPP calls"""
gsa_available = in_domains("gsa.gov")
igorville_available = in_domains("igorvilleremixed.gov")
This does not mock out the external file, it is actually fetched from """Domain searches successfully make mock EPP calls"""
the internet. self.mockedSendFunction.assert_has_calls(
""" [
domains = _domains() call(
self.assertIn("gsa.gov", domains) commands.CheckDomain(
# entries are all lowercase so GSA.GOV is not in the set ["gsa.gov"],
self.assertNotIn("GSA.GOV", domains) ),
self.assertNotIn("igorvilleremixed.gov", domains) cleaned=True,
# all the entries have dots ),
self.assertNotIn("gsa", domains) call(
commands.CheckDomain(
["igorvilleremixed.gov"],
),
cleaned=True,
),
]
)
"""Domain searches return correct availability results"""
self.assertTrue(gsa_available)
self.assertFalse(igorville_available)
def test_in_domains(self): def test_in_domains_capitalized(self):
"""Domain searches work without case sensitivity"""
self.assertTrue(in_domains("gsa.gov")) self.assertTrue(in_domains("gsa.gov"))
# input is lowercased so GSA.GOV should be found # input is lowercased so GSA.GOV should be found
self.assertTrue(in_domains("GSA.GOV")) self.assertTrue(in_domains("GSA.gov"))
# This domain should not have been registered
self.assertFalse(in_domains("igorvilleremixed.gov"))
def test_in_domains_dotgov(self): def test_in_domains_dotgov(self):
"""Domain searches work without trailing .gov""" """Domain searches work without trailing .gov"""
@ -86,13 +105,18 @@ class AvailableViewTest(TestCase):
request.user = self.user request.user = self.user
response = available(request, domain=bad_string) response = available(request, domain=bad_string)
self.assertFalse(json.loads(response.content)["available"]) self.assertFalse(json.loads(response.content)["available"])
# domain set to raise error successfully raises error
with self.assertRaises(RegistryError):
error_domain_available = available(request, "errordomain.gov")
self.assertFalse(json.loads(error_domain_available.content)["available"])
class AvailableAPITest(TestCase): class AvailableAPITest(MockEppLib):
"""Test that the API can be called as expected.""" """Test that the API can be called as expected."""
def setUp(self): def setUp(self):
super().setUp()
self.user = get_user_model().objects.create(username="username") self.user = get_user_model().objects.create(username="username")
def test_available_get(self): def test_available_get(self):

View file

@ -3,8 +3,6 @@ from django.apps import apps
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.http import JsonResponse from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
import requests import requests
from cachetools.func import ttl_cache from cachetools.func import ttl_cache
@ -59,16 +57,15 @@ def in_domains(domain):
given domain doesn't end with .gov, ".gov" is added when looking for given domain doesn't end with .gov, ".gov" is added when looking for
a match. a match.
""" """
domain = domain.lower() Domain = apps.get_model("registrar.Domain")
if domain.endswith(".gov"): if domain.endswith(".gov"):
return domain.lower() in _domains() return Domain.available(domain)
else: else:
# domain search string doesn't end with .gov, add it on here # domain search string doesn't end with .gov, add it on here
return (domain + ".gov") in _domains() return Domain.available(domain + ".gov")
@require_http_methods(["GET"]) @require_http_methods(["GET"])
@login_required
def available(request, domain=""): def available(request, domain=""):
"""Is a given domain available or not. """Is a given domain available or not.

View file

@ -22,15 +22,15 @@ a.breadcrumb__back {
} }
} }
a.usa-button { a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
text-decoration: none; text-decoration: none;
color: color('white'); color: color('white');
} }
a.usa-button:visited, a.usa-button:not(.usa-button--unstyled, .usa-button--outline):visited,
a.usa-button:hover, a.usa-button:not(.usa-button--unstyled, .usa-button--outline):hover,
a.usa-button:focus, a.usa-button:not(.usa-button--unstyled, .usa-button--outline):focus,
a.usa-button:active { a.usa-button:not(.usa-button--unstyled, .usa-button--outline):active {
color: color('white'); color: color('white');
} }

View file

@ -0,0 +1,17 @@
# Generated by Django 4.2.1 on 2023-10-20 15:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0039_alter_transitiondomain_status"),
]
operations = [
migrations.AlterField(
model_name="userdomainrole",
name="role",
field=models.TextField(choices=[("manager", "Manager")]),
),
]

View file

@ -1410,18 +1410,16 @@ class Domain(TimeStampedModel, DomainHelper):
"""creates a disclose object that can be added to a contact Create using """creates a disclose object that can be added to a contact Create using
.disclose= <this function> on the command before sending. .disclose= <this function> on the command before sending.
if item is security email then make sure email is visable""" if item is security email then make sure email is visable"""
isSecurity = contact.contact_type == contact.ContactTypeChoices.SECURITY is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY
DF = epp.DiscloseField DF = epp.DiscloseField
fields = {DF.FAX, DF.VOICE, DF.ADDR} fields = {DF.EMAIL}
disclose = (
if not isSecurity or ( is_security and contact.email != PublicContact.get_default_security().email
isSecurity and contact.email == PublicContact.get_default_security().email )
): # Will only disclose DF.EMAIL if its not the default
fields.add(DF.EMAIL)
return epp.Disclose( return epp.Disclose(
flag=False, flag=disclose,
fields=fields, fields=fields,
types={DF.ADDR: "loc"},
) )
def _make_epp_contact_postal_info(self, contact: PublicContact): # type: ignore def _make_epp_contact_postal_info(self, contact: PublicContact): # type: ignore

View file

@ -612,7 +612,7 @@ class DomainApplication(TimeStampedModel):
# create the permission for the user # create the permission for the user
UserDomainRole = apps.get_model("registrar.UserDomainRole") UserDomainRole = apps.get_model("registrar.UserDomainRole")
UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(
user=self.creator, domain=created_domain, role=UserDomainRole.Roles.ADMIN user=self.creator, domain=created_domain, role=UserDomainRole.Roles.MANAGER
) )
self._send_status_update_email( self._send_status_update_email(

View file

@ -63,7 +63,7 @@ class DomainInvitation(TimeStampedModel):
# and create a role for that user on this domain # and create a role for that user on this domain
_, created = UserDomainRole.objects.get_or_create( _, created = UserDomainRole.objects.get_or_create(
user=user, domain=self.domain, role=UserDomainRole.Roles.ADMIN user=user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
) )
if not created: if not created:
# something strange happened and this role already existed when # something strange happened and this role already existed when

View file

@ -15,7 +15,7 @@ class UserDomainRole(TimeStampedModel):
elsewhere. elsewhere.
""" """
ADMIN = "manager" MANAGER = "manager"
user = models.ForeignKey( user = models.ForeignKey(
"registrar.User", "registrar.User",

View file

@ -52,7 +52,7 @@
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %} {% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %}
{% endif %} {% 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='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url %}
</div> </div>
{% endblock %} {# domain_content #} {% endblock %} {# domain_content #}

View file

@ -100,7 +100,7 @@
<a href="{{ url }}" <a href="{{ url }}"
{% if request.path|startswith:url %}class="usa-current"{% endif %} {% if request.path|startswith:url %}class="usa-current"{% endif %}
> >
User management Domain managers
</a> </a>
</li> </li>
</ul> </ul>

View file

@ -1,10 +1,23 @@
{% extends "domain_base.html" %} {% extends "domain_base.html" %}
{% load static %} {% load static url_helpers %}
{% block title %}User management | {{ domain.name }} | {% endblock %} {% block title %}Domain managers | {{ domain.name }} | {% endblock %}
{% block domain_content %} {% block domain_content %}
<h1>User management</h1> <h1>Domain managers</h1>
<p>
Domain managers can update all information related to a domain within the
.gov registrar, including contact details, authorizing official, security
email, and DNS name servers.
</p>
<ul>
<li>There is no limit to the number of domain managers you can add.</li>
<li>After adding a domain manager, an email invitation will be sent to that user with
instructions on how to set up an account.</li>
<li>To remove a domain manager, <a href="{% public_site_url 'contact/' %}" class="usa-link">contact us</a> for assistance.
</ul>
{% if domain.permissions %} {% if domain.permissions %}
<section class="section--outlined"> <section class="section--outlined">

View file

@ -31,6 +31,7 @@ from epplibwrapper import (
info, info,
RegistryError, RegistryError,
ErrorCode, ErrorCode,
responses,
) )
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
@ -669,6 +670,44 @@ class MockEppLib(TestCase):
registrant="regContact", registrant="regContact",
) )
InfoDomainWithDefaultSecurityContact = fakedEppObject(
"fakepw",
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
contacts=[
common.DomainContact(
contact="defaultSec",
type=PublicContact.ContactTypeChoices.SECURITY,
)
],
hosts=["fake.host.com"],
statuses=[
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
)
InfoDomainWithDefaultTechnicalContact = fakedEppObject(
"fakepw",
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
contacts=[
common.DomainContact(
contact="defaultTech",
type=PublicContact.ContactTypeChoices.TECHNICAL,
)
],
hosts=["fake.host.com"],
statuses=[
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
)
mockDefaultTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData(
"defaultTech", "dotgov@cisa.dhs.gov"
)
mockDefaultSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData(
"defaultSec", "dotgov@cisa.dhs.gov"
)
mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData( mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData(
"securityContact", "security@mail.gov" "securityContact", "security@mail.gov"
) )
@ -784,44 +823,62 @@ class MockEppLib(TestCase):
], ],
) )
def _mockDomainName(self, _name, _avail=False):
return MagicMock(
res_data=[
responses.check.CheckDomainResultData(
name=_name, avail=_avail, reason=None
),
]
)
def mockCheckDomainCommand(self, _request, cleaned):
if "gsa.gov" in getattr(_request, "names", None):
return self._mockDomainName("gsa.gov", True)
elif "GSA.gov" in getattr(_request, "names", None):
return self._mockDomainName("GSA.gov", True)
elif "igorvilleremixed.gov" in getattr(_request, "names", None):
return self._mockDomainName("igorvilleremixed.gov", False)
elif "errordomain.gov" in getattr(_request, "names", None):
raise RegistryError("Registry cannot find domain availability.")
else:
return self._mockDomainName("domainnotfound.gov", False)
def mockSend(self, _request, cleaned): def mockSend(self, _request, cleaned):
"""Mocks the registry.send function used inside of domain.py """Mocks the registry.send function used inside of domain.py
registry is imported from epplibwrapper registry is imported from epplibwrapper
returns objects that simulate what would be in a epp response returns objects that simulate what would be in a epp response
but only relevant pieces for tests""" but only relevant pieces for tests"""
if isinstance(_request, commands.InfoDomain):
match type(_request):
case commands.InfoDomain:
return self.mockInfoDomainCommands(_request, cleaned) return self.mockInfoDomainCommands(_request, cleaned)
elif isinstance(_request, commands.InfoContact): case commands.InfoContact:
return self.mockInfoContactCommands(_request, cleaned) return self.mockInfoContactCommands(_request, cleaned)
elif isinstance(_request, commands.UpdateDomain): case commands.CreateContact:
return self.mockUpdateDomainCommands(_request, cleaned)
elif isinstance(_request, commands.CreateContact):
return self.mockCreateContactCommands(_request, cleaned) return self.mockCreateContactCommands(_request, cleaned)
elif isinstance(_request, commands.CreateHost): case commands.UpdateDomain:
return self.mockUpdateDomainCommands(_request, cleaned)
case commands.CreateHost:
return MagicMock( return MagicMock(
res_data=[self.mockDataHostChange], res_data=[self.mockDataHostChange],
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
) )
elif isinstance(_request, commands.UpdateHost): case commands.UpdateHost:
return MagicMock( return MagicMock(
res_data=[self.mockDataHostChange], res_data=[self.mockDataHostChange],
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
) )
elif isinstance(_request, commands.DeleteHost): case commands.DeleteHost:
return MagicMock( return MagicMock(
res_data=[self.mockDataHostChange], res_data=[self.mockDataHostChange],
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
) )
elif ( case commands.CheckDomain:
isinstance(_request, commands.DeleteDomain) return self.mockCheckDomainCommand(_request, cleaned)
and getattr(_request, "name", None) == "failDelete.gov" case commands.DeleteDomain:
): return self.mockDeleteDomainCommands(_request, cleaned)
name = getattr(_request, "name", None) case _:
fake_nameserver = "ns1.failDelete.gov"
if name in fake_nameserver:
raise RegistryError(
code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
)
return MagicMock(res_data=[self.mockDataInfoHosts]) return MagicMock(res_data=[self.mockDataInfoHosts])
def mockUpdateDomainCommands(self, _request, cleaned): def mockUpdateDomainCommands(self, _request, cleaned):
@ -833,6 +890,16 @@ class MockEppLib(TestCase):
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
) )
def mockDeleteDomainCommands(self, _request, cleaned):
if getattr(_request, "name", None) == "failDelete.gov":
name = getattr(_request, "name", None)
fake_nameserver = "ns1.failDelete.gov"
if name in fake_nameserver:
raise RegistryError(
code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
)
return None
def mockInfoDomainCommands(self, _request, cleaned): def mockInfoDomainCommands(self, _request, cleaned):
request_name = getattr(_request, "name", None) request_name = getattr(_request, "name", None)
@ -862,6 +929,8 @@ class MockEppLib(TestCase):
"namerserversubdomain.gov": (self.infoDomainCheckHostIPCombo, None), "namerserversubdomain.gov": (self.infoDomainCheckHostIPCombo, None),
"freeman.gov": (self.InfoDomainWithContacts, None), "freeman.gov": (self.InfoDomainWithContacts, None),
"threenameserversDomain.gov": (self.infoDomainThreeHosts, None), "threenameserversDomain.gov": (self.infoDomainThreeHosts, None),
"defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None),
"defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None),
} }
# Retrieve the corresponding values from the dictionary # Retrieve the corresponding values from the dictionary
@ -887,6 +956,10 @@ class MockEppLib(TestCase):
mocked_result = self.mockAdministrativeContact mocked_result = self.mockAdministrativeContact
case "regContact": case "regContact":
mocked_result = self.mockRegistrantContact mocked_result = self.mockRegistrantContact
case "defaultSec":
mocked_result = self.mockDefaultSecurityContact
case "defaultTech":
mocked_result = self.mockDefaultTechnicalContact
case _: case _:
# Default contact return # Default contact return
mocked_result = self.mockDataInfoContact mocked_result = self.mockDataInfoContact
@ -921,15 +994,11 @@ class MockEppLib(TestCase):
self, contact: PublicContact, disclose_email=False, createContact=True self, contact: PublicContact, disclose_email=False, createContact=True
): ):
DF = common.DiscloseField DF = common.DiscloseField
fields = {DF.FAX, DF.VOICE, DF.ADDR} fields = {DF.EMAIL}
if not disclose_email:
fields.add(DF.EMAIL)
di = common.Disclose( di = common.Disclose(
flag=False, flag=disclose_email,
fields=fields, fields=fields,
types={DF.ADDR: "loc"},
) )
# check docs here looks like we may have more than one address field but # check docs here looks like we may have more than one address field but

View file

@ -1,6 +1,6 @@
"""Test form validation requirements.""" """Test form validation requirements."""
from django.test import TestCase from django.test import TestCase, RequestFactory
from registrar.forms.application_wizard import ( from registrar.forms.application_wizard import (
CurrentSitesForm, CurrentSitesForm,
@ -16,9 +16,16 @@ from registrar.forms.application_wizard import (
AboutYourOrganizationForm, AboutYourOrganizationForm,
) )
from registrar.forms.domain import ContactForm from registrar.forms.domain import ContactForm
from registrar.tests.common import MockEppLib
from django.contrib.auth import get_user_model
class TestFormValidation(TestCase): class TestFormValidation(MockEppLib):
def setUp(self):
super().setUp()
self.user = get_user_model().objects.create(username="username")
self.factory = RequestFactory()
def test_org_contact_zip_invalid(self): def test_org_contact_zip_invalid(self):
form = OrganizationContactForm(data={"zipcode": "nah"}) form = OrganizationContactForm(data={"zipcode": "nah"})
self.assertEqual( self.assertEqual(

View file

@ -601,7 +601,7 @@ class TestInvitations(TestCase):
def test_retrieve_existing_role_no_error(self): def test_retrieve_existing_role_no_error(self):
# make the overlapping role # make the overlapping role
UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain, role=UserDomainRole.Roles.ADMIN user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
) )
# this is not an error but does produce a console warning # this is not an error but does produce a console warning
with less_console_noise(): with less_console_noise():

View file

@ -19,7 +19,7 @@ from registrar.utility.errors import ActionNotAllowed, NameserverError
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
from .common import MockEppLib
from django_fsm import TransitionNotAllowed # type: ignore from django_fsm import TransitionNotAllowed # type: ignore
from epplibwrapper import ( from epplibwrapper import (
commands, commands,
@ -29,6 +29,7 @@ from epplibwrapper import (
RegistryError, RegistryError,
ErrorCode, ErrorCode,
) )
from .common import MockEppLib
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -760,6 +761,198 @@ class TestRegistrantContacts(MockEppLib):
self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True)
self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1)
def test_not_disclosed_on_other_contacts(self):
"""
Scenario: Registrant creates a new domain with multiple contacts
When `domain` has registrant, admin, technical,
and security contacts
Then Domain sends `commands.CreateContact` to the registry
And the field `disclose` is set to false for DF.EMAIL
on all fields except security
"""
# Generates a domain with four existing contacts
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
# Contact setup
expected_admin = domain.get_default_administrative_contact()
expected_admin.email = self.mockAdministrativeContact.email
expected_registrant = domain.get_default_registrant_contact()
expected_registrant.email = self.mockRegistrantContact.email
expected_security = domain.get_default_security_contact()
expected_security.email = self.mockSecurityContact.email
expected_tech = domain.get_default_technical_contact()
expected_tech.email = self.mockTechnicalContact.email
domain.administrative_contact = expected_admin
domain.registrant_contact = expected_registrant
domain.security_contact = expected_security
domain.technical_contact = expected_tech
contacts = [
(expected_admin, domain.administrative_contact),
(expected_registrant, domain.registrant_contact),
(expected_security, domain.security_contact),
(expected_tech, domain.technical_contact),
]
# Test for each contact
for contact in contacts:
expected_contact = contact[0]
actual_contact = contact[1]
is_security = expected_contact.contact_type == "security"
expectedCreateCommand = self._convertPublicContactToEpp(
expected_contact, disclose_email=is_security
)
# Should only be disclosed if the type is security, as the email is valid
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
# The emails should match on both items
self.assertEqual(expected_contact.email, actual_contact.email)
def test_convert_public_contact_to_epp(self):
self.maxDiff = None
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
dummy_contact = domain.get_default_security_contact()
test_disclose = self._convertPublicContactToEpp(
dummy_contact, disclose_email=True
).__dict__
test_not_disclose = self._convertPublicContactToEpp(
dummy_contact, disclose_email=False
).__dict__
# Separated for linter
disclose_email_field = {common.DiscloseField.EMAIL}
expected_disclose = {
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
"disclose": common.Disclose(
flag=True, fields=disclose_email_field, types=None
),
"email": "dotgov@cisa.dhs.gov",
"extensions": [],
"fax": None,
"id": "ThIq2NcRIDN7PauO",
"ident": None,
"notify_email": None,
"postal_info": common.PostalInfo(
name="Registry Customer Service",
addr=common.ContactAddr(
street=["4200 Wilson Blvd.", None, None],
city="Arlington",
pc="22201",
cc="US",
sp="VA",
),
org="Cybersecurity and Infrastructure Security Agency",
type="loc",
),
"vat": None,
"voice": "+1.8882820870",
}
# Separated for linter
expected_not_disclose = {
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
"disclose": common.Disclose(
flag=False, fields=disclose_email_field, types=None
),
"email": "dotgov@cisa.dhs.gov",
"extensions": [],
"fax": None,
"id": "ThrECENCHI76PGLh",
"ident": None,
"notify_email": None,
"postal_info": common.PostalInfo(
name="Registry Customer Service",
addr=common.ContactAddr(
street=["4200 Wilson Blvd.", None, None],
city="Arlington",
pc="22201",
cc="US",
sp="VA",
),
org="Cybersecurity and Infrastructure Security Agency",
type="loc",
),
"vat": None,
"voice": "+1.8882820870",
}
# Set the ids equal, since this value changes
test_disclose["id"] = expected_disclose["id"]
test_not_disclose["id"] = expected_not_disclose["id"]
self.assertEqual(test_disclose, expected_disclose)
self.assertEqual(test_not_disclose, expected_not_disclose)
def test_not_disclosed_on_default_security_contact(self):
"""
Scenario: Registrant creates a new domain with no security email
When `domain.security_contact.email` is equal to the default
Then Domain sends `commands.CreateContact` to the registry
And the field `disclose` is set to false for DF.EMAIL
"""
domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov")
expectedSecContact = PublicContact.get_default_security()
expectedSecContact.domain = domain
expectedSecContact.registry_id = "defaultSec"
domain.security_contact = expectedSecContact
expectedCreateCommand = self._convertPublicContactToEpp(
expectedSecContact, disclose_email=False
)
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
# Confirm that we are getting a default email
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
def test_not_disclosed_on_default_technical_contact(self):
"""
Scenario: Registrant creates a new domain with no technical contact
When `domain.technical_contact.email` is equal to the default
Then Domain sends `commands.CreateContact` to the registry
And the field `disclose` is set to false for DF.EMAIL
"""
domain, _ = Domain.objects.get_or_create(name="defaulttechnical.gov")
expectedTechContact = PublicContact.get_default_technical()
expectedTechContact.domain = domain
expectedTechContact.registry_id = "defaultTech"
domain.technical_contact = expectedTechContact
expectedCreateCommand = self._convertPublicContactToEpp(
expectedTechContact, disclose_email=False
)
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
# Confirm that we are getting a default email
self.assertEqual(domain.technical_contact.email, expectedTechContact.email)
def test_is_disclosed_on_security_contact(self):
"""
Scenario: Registrant creates a new domain with a security email
When `domain.security_contact.email` is set to a valid email
and is not the default
Then Domain sends `commands.CreateContact` to the registry
And the field `disclose` is set to true for DF.EMAIL
"""
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
expectedSecContact = PublicContact.get_default_security()
expectedSecContact.domain = domain
expectedSecContact.email = "123@mail.gov"
domain.security_contact = expectedSecContact
expectedCreateCommand = self._convertPublicContactToEpp(
expectedSecContact, disclose_email=True
)
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
# Confirm that we are getting the desired email
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
@skip("not implemented yet") @skip("not implemented yet")
def test_update_is_unsuccessful(self): def test_update_is_unsuccessful(self):
""" """

View file

@ -89,7 +89,7 @@ class LoggedInTests(TestWithUser):
domain, _ = Domain.objects.get_or_create(name="igorville.gov") domain, _ = Domain.objects.get_or_create(name="igorville.gov")
self.assertNotContains(response, "igorville.gov") self.assertNotContains(response, "igorville.gov")
role, _ = UserDomainRole.objects.get_or_create( role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=domain, role=UserDomainRole.Roles.ADMIN user=self.user, domain=domain, role=UserDomainRole.Roles.MANAGER
) )
response = self.client.get("/") response = self.client.get("/")
# count = 2 because it is also in screenreader content # count = 2 because it is also in screenreader content
@ -1121,23 +1121,25 @@ class TestWithDomainPermissions(TestWithUser):
creator=self.user, domain=self.domain_dnssec_none creator=self.user, domain=self.domain_dnssec_none
) )
self.role, _ = UserDomainRole.objects.get_or_create( self.role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain, role=UserDomainRole.Roles.ADMIN user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
) )
UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain_dsdata, role=UserDomainRole.Roles.ADMIN user=self.user, domain=self.domain_dsdata, role=UserDomainRole.Roles.MANAGER
) )
UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(
user=self.user, user=self.user,
domain=self.domain_multdsdata, domain=self.domain_multdsdata,
role=UserDomainRole.Roles.ADMIN, role=UserDomainRole.Roles.MANAGER,
) )
UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain_keydata, role=UserDomainRole.Roles.ADMIN user=self.user,
domain=self.domain_keydata,
role=UserDomainRole.Roles.MANAGER,
) )
UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(
user=self.user, user=self.user,
domain=self.domain_dnssec_none, domain=self.domain_dnssec_none,
role=UserDomainRole.Roles.ADMIN, role=UserDomainRole.Roles.MANAGER,
) )
def tearDown(self): def tearDown(self):
@ -1223,14 +1225,14 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
class TestDomainUserManagement(TestDomainOverview): class TestDomainManagers(TestDomainOverview):
def test_domain_user_management(self): def test_domain_managers(self):
response = self.client.get( response = self.client.get(
reverse("domain-users", kwargs={"pk": self.domain.id}) reverse("domain-users", kwargs={"pk": self.domain.id})
) )
self.assertContains(response, "User management") self.assertContains(response, "Domain managers")
def test_domain_user_management_add_link(self): def test_domain_managers_add_link(self):
"""Button to get to user add page works.""" """Button to get to user add page works."""
management_page = self.app.get( management_page = self.app.get(
reverse("domain-users", kwargs={"pk": self.domain.id}) reverse("domain-users", kwargs={"pk": self.domain.id})

View file

@ -656,7 +656,7 @@ class DomainSecurityEmailView(DomainFormBaseView):
class DomainUsersView(DomainBaseView): class DomainUsersView(DomainBaseView):
"""User management page in the domain details.""" """Domain managers page in the domain details."""
template_name = "domain_users.html" template_name = "domain_users.html"
@ -736,7 +736,9 @@ class DomainAddUserView(DomainFormBaseView):
try: try:
UserDomainRole.objects.create( UserDomainRole.objects.create(
user=requested_user, domain=self.object, role=UserDomainRole.Roles.ADMIN user=requested_user,
domain=self.object,
role=UserDomainRole.Roles.MANAGER,
) )
except IntegrityError: except IntegrityError:
# User already has the desired role! Do nothing?? # User already has the desired role! Do nothing??