mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-15 17:17:02 +02:00
Merge remote-tracking branch 'origin/main' into rjm/787-org-short-names
This commit is contained in:
commit
03b7ff64f8
19 changed files with 438 additions and 110 deletions
18
.github/ISSUE_TEMPLATE/issue-default.yml
vendored
18
.github/ISSUE_TEMPLATE/issue-default.yml
vendored
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
src/registrar/migrations/0040_alter_userdomainrole_role.py
Normal file
17
src/registrar/migrations/0040_alter_userdomainrole_role.py
Normal 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")]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -15,7 +15,7 @@ class UserDomainRole(TimeStampedModel):
|
||||||
elsewhere.
|
elsewhere.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ADMIN = "manager"
|
MANAGER = "manager"
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
"registrar.User",
|
"registrar.User",
|
||||||
|
|
|
@ -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 #}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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})
|
||||||
|
|
|
@ -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??
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue