mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-06 01:35:22 +02:00
Merge branch 'main' into rjm/1148-ds-data
This commit is contained in:
commit
bdbff9260e
35 changed files with 1105 additions and 305 deletions
|
@ -3,19 +3,27 @@
|
|||
import json
|
||||
|
||||
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 registrar.tests.common import MockEppLib
|
||||
from unittest.mock import call
|
||||
|
||||
from epplibwrapper import (
|
||||
commands,
|
||||
RegistryError,
|
||||
)
|
||||
|
||||
API_BASE_PATH = "/api/v1/available/"
|
||||
|
||||
|
||||
class AvailableViewTest(TestCase):
|
||||
class AvailableViewTest(MockEppLib):
|
||||
|
||||
"""Test that the view function works as expected."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = get_user_model().objects.create(username="username")
|
||||
self.factory = RequestFactory()
|
||||
|
||||
|
@ -29,26 +37,37 @@ class AvailableViewTest(TestCase):
|
|||
response_object = json.loads(response.content)
|
||||
self.assertIn("available", response_object)
|
||||
|
||||
def test_domain_list(self):
|
||||
"""Test the domain list that is returned from Github.
|
||||
def test_in_domains_makes_calls_(self):
|
||||
"""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
|
||||
the internet.
|
||||
"""
|
||||
domains = _domains()
|
||||
self.assertIn("gsa.gov", domains)
|
||||
# entries are all lowercase so GSA.GOV is not in the set
|
||||
self.assertNotIn("GSA.GOV", domains)
|
||||
self.assertNotIn("igorvilleremixed.gov", domains)
|
||||
# all the entries have dots
|
||||
self.assertNotIn("gsa", domains)
|
||||
"""Domain searches successfully make mock EPP calls"""
|
||||
self.mockedSendFunction.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
commands.CheckDomain(
|
||||
["gsa.gov"],
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
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"))
|
||||
# input is lowercased so GSA.GOV should be found
|
||||
self.assertTrue(in_domains("GSA.GOV"))
|
||||
# This domain should not have been registered
|
||||
self.assertFalse(in_domains("igorvilleremixed.gov"))
|
||||
self.assertTrue(in_domains("GSA.gov"))
|
||||
|
||||
def test_in_domains_dotgov(self):
|
||||
"""Domain searches work without trailing .gov"""
|
||||
|
@ -86,13 +105,18 @@ class AvailableViewTest(TestCase):
|
|||
request.user = self.user
|
||||
response = available(request, domain=bad_string)
|
||||
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."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = get_user_model().objects.create(username="username")
|
||||
|
||||
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.http import JsonResponse
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
import requests
|
||||
|
||||
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
|
||||
a match.
|
||||
"""
|
||||
domain = domain.lower()
|
||||
Domain = apps.get_model("registrar.Domain")
|
||||
if domain.endswith(".gov"):
|
||||
return domain.lower() in _domains()
|
||||
return Domain.available(domain)
|
||||
else:
|
||||
# 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"])
|
||||
@login_required
|
||||
def available(request, domain=""):
|
||||
"""Is a given domain available or not.
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ except NameError:
|
|||
# Attn: these imports should NOT be at the top of the file
|
||||
try:
|
||||
from .client import CLIENT, commands
|
||||
from .errors import RegistryError, ErrorCode
|
||||
from .errors import RegistryError, ErrorCode, CANNOT_CONTACT_REGISTRY, GENERIC_ERROR
|
||||
from epplib.models import common, info
|
||||
from epplib.responses import extensions
|
||||
from epplib import responses
|
||||
|
@ -61,4 +61,6 @@ __all__ = [
|
|||
"info",
|
||||
"ErrorCode",
|
||||
"RegistryError",
|
||||
"CANNOT_CONTACT_REGISTRY",
|
||||
"GENERIC_ERROR",
|
||||
]
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
from enum import IntEnum
|
||||
|
||||
CANNOT_CONTACT_REGISTRY = "Update failed. Cannot contact the registry."
|
||||
GENERIC_ERROR = "Value entered was wrong."
|
||||
|
||||
|
||||
class ErrorCode(IntEnum):
|
||||
"""
|
||||
|
|
|
@ -219,9 +219,9 @@ class MyUserAdmin(BaseUserAdmin):
|
|||
# (which should in theory be the ONLY group)
|
||||
def group(self, obj):
|
||||
if obj.groups.filter(name="full_access_group").exists():
|
||||
return "Full access"
|
||||
return "full_access_group"
|
||||
elif obj.groups.filter(name="cisa_analysts_group").exists():
|
||||
return "Analyst"
|
||||
return "cisa_analysts_group"
|
||||
return ""
|
||||
|
||||
def get_list_display(self, request):
|
||||
|
@ -294,6 +294,26 @@ class ContactAdmin(ListHeaderAdmin):
|
|||
|
||||
contact.admin_order_field = "first_name" # type: ignore
|
||||
|
||||
# Read only that we'll leverage for CISA Analysts
|
||||
analyst_readonly_fields = [
|
||||
"user",
|
||||
]
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
"""Set the read-only state on form elements.
|
||||
We have 1 conditions that determine which fields are read-only:
|
||||
admin user permissions.
|
||||
"""
|
||||
|
||||
readonly_fields = list(self.readonly_fields)
|
||||
|
||||
if request.user.has_perm("registrar.full_access_permission"):
|
||||
return readonly_fields
|
||||
# Return restrictive Read-only fields for analysts and
|
||||
# users who might not belong to groups
|
||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||
return readonly_fields # Read-only fields for analysts
|
||||
|
||||
|
||||
class WebsiteAdmin(ListHeaderAdmin):
|
||||
"""Custom website admin class."""
|
||||
|
@ -420,9 +440,6 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
|||
"creator",
|
||||
"type_of_work",
|
||||
"more_organization_information",
|
||||
"address_line1",
|
||||
"address_line2",
|
||||
"zipcode",
|
||||
"domain",
|
||||
"submitter",
|
||||
"no_other_contacts_rationale",
|
||||
|
@ -557,9 +574,6 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
analyst_readonly_fields = [
|
||||
"creator",
|
||||
"about_your_organization",
|
||||
"address_line1",
|
||||
"address_line2",
|
||||
"zipcode",
|
||||
"requested_domain",
|
||||
"alternative_domains",
|
||||
"purpose",
|
||||
|
@ -721,7 +735,7 @@ class DomainAdmin(ListHeaderAdmin):
|
|||
]
|
||||
|
||||
def organization_type(self, obj):
|
||||
return obj.domain_info.organization_type
|
||||
return obj.domain_info.get_organization_type_display()
|
||||
|
||||
organization_type.admin_order_field = ( # type: ignore
|
||||
"domain_info__organization_type"
|
||||
|
|
|
@ -22,15 +22,15 @@ a.breadcrumb__back {
|
|||
}
|
||||
}
|
||||
|
||||
a.usa-button {
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
|
||||
text-decoration: none;
|
||||
color: color('white');
|
||||
}
|
||||
|
||||
a.usa-button:visited,
|
||||
a.usa-button:hover,
|
||||
a.usa-button:focus,
|
||||
a.usa-button:active {
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline):visited,
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline):hover,
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline):focus,
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline):active {
|
||||
color: color('white');
|
||||
}
|
||||
|
||||
|
|
|
@ -581,7 +581,7 @@ ALLOWED_HOSTS = [
|
|||
"getgov-bl.app.cloud.gov",
|
||||
"getgov-rjm.app.cloud.gov",
|
||||
"getgov-dk.app.cloud.gov",
|
||||
"get.gov",
|
||||
"manage.get.gov",
|
||||
]
|
||||
|
||||
# Extend ALLOWED_HOSTS.
|
||||
|
@ -652,6 +652,9 @@ SESSION_COOKIE_SAMESITE = "Lax"
|
|||
# instruct browser to only send cookie via HTTPS
|
||||
SESSION_COOKIE_SECURE = True
|
||||
|
||||
# session engine to cache session information
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
|
||||
# ~ Set by django.middleware.clickjacking.XFrameOptionsMiddleware
|
||||
# prevent clickjacking by instructing the browser not to load
|
||||
# our site within an iframe
|
||||
|
|
|
@ -153,7 +153,8 @@ class RegistrarFormSet(forms.BaseFormSet):
|
|||
|
||||
class OrganizationTypeForm(RegistrarForm):
|
||||
organization_type = forms.ChoiceField(
|
||||
choices=DomainApplication.OrganizationChoices.choices,
|
||||
# use the long names in the application form
|
||||
choices=DomainApplication.OrganizationChoicesVerbose.choices,
|
||||
widget=forms.RadioSelect,
|
||||
error_messages={"required": "Select the type of organization you represent."},
|
||||
)
|
||||
|
|
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")]),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,52 @@
|
|||
# Generated by Django 4.2.1 on 2023-10-20 21:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registrar", "0040_alter_userdomainrole_role"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="domainapplication",
|
||||
name="organization_type",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("federal", "Federal"),
|
||||
("interstate", "Interstate"),
|
||||
("state_or_territory", "State or territory"),
|
||||
("tribal", "Tribal"),
|
||||
("county", "County"),
|
||||
("city", "City"),
|
||||
("special_district", "Special district"),
|
||||
("school_district", "School district"),
|
||||
],
|
||||
help_text="Type of organization",
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="domaininformation",
|
||||
name="organization_type",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("federal", "Federal"),
|
||||
("interstate", "Interstate"),
|
||||
("state_or_territory", "State or territory"),
|
||||
("tribal", "Tribal"),
|
||||
("county", "County"),
|
||||
("city", "City"),
|
||||
("special_district", "Special district"),
|
||||
("school_district", "School district"),
|
||||
],
|
||||
help_text="Type of Organization",
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
37
src/registrar/migrations/0042_create_groups_v03.py
Normal file
37
src/registrar/migrations/0042_create_groups_v03.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
||||
# It is dependent on 0035 (which populates ContentType and Permissions)
|
||||
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
||||
# in the user_group model then:
|
||||
# [NOT RECOMMENDED]
|
||||
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
||||
# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
|
||||
# step 3: fake run the latest migration in the migrations list
|
||||
# [RECOMMENDED]
|
||||
# Alternatively:
|
||||
# step 1: duplicate the migration that loads data
|
||||
# step 2: docker-compose exec app ./manage.py migrate
|
||||
|
||||
from django.db import migrations
|
||||
from registrar.models import UserGroup
|
||||
from typing import Any
|
||||
|
||||
|
||||
# For linting: RunPython expects a function reference,
|
||||
# so let's give it one
|
||||
def create_groups(apps, schema_editor) -> Any:
|
||||
UserGroup.create_cisa_analyst_group(apps, schema_editor)
|
||||
UserGroup.create_full_access_group(apps, schema_editor)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registrar", "0041_alter_domainapplication_organization_type_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
create_groups,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
atomic=True,
|
||||
),
|
||||
]
|
|
@ -260,7 +260,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
"""Creates the host object in the registry
|
||||
doesn't add the created host to the domain
|
||||
returns ErrorCode (int)"""
|
||||
logger.info("Creating host")
|
||||
if addrs is not None:
|
||||
addresses = [epp.Ip(addr=addr) for addr in addrs]
|
||||
request = commands.CreateHost(name=host, addrs=addresses)
|
||||
|
@ -782,7 +781,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
and errorCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
||||
):
|
||||
# TODO- ticket #433 look here for error handling
|
||||
raise Exception("Unable to add contact to registry")
|
||||
raise RegistryError(code=errorCode)
|
||||
|
||||
# contact doesn't exist on the domain yet
|
||||
logger.info("_set_singleton_contact()-> contact has been added to the registry")
|
||||
|
@ -1209,7 +1208,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
count = 0
|
||||
while not exitEarly and count < 3:
|
||||
try:
|
||||
logger.info("Getting domain info from epp")
|
||||
req = commands.InfoDomain(name=self.name)
|
||||
domainInfoResponse = registry.send(req, cleaned=True)
|
||||
exitEarly = True
|
||||
|
@ -1376,18 +1374,16 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
"""creates a disclose object that can be added to a contact Create using
|
||||
.disclose= <this function> on the command before sending.
|
||||
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
|
||||
fields = {DF.FAX, DF.VOICE, DF.ADDR}
|
||||
|
||||
if not isSecurity or (
|
||||
isSecurity and contact.email == PublicContact.get_default_security().email
|
||||
):
|
||||
fields.add(DF.EMAIL)
|
||||
fields = {DF.EMAIL}
|
||||
disclose = (
|
||||
is_security and contact.email != PublicContact.get_default_security().email
|
||||
)
|
||||
# Will only disclose DF.EMAIL if its not the default
|
||||
return epp.Disclose(
|
||||
flag=False,
|
||||
flag=disclose,
|
||||
fields=fields,
|
||||
types={DF.ADDR: "loc"},
|
||||
)
|
||||
|
||||
def _make_epp_contact_postal_info(self, contact: PublicContact): # type: ignore
|
||||
|
@ -1648,74 +1644,84 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
"""Contact registry for info about a domain."""
|
||||
try:
|
||||
# get info from registry
|
||||
dataResponse = self._get_or_create_domain()
|
||||
data = dataResponse.res_data[0]
|
||||
# extract properties from response
|
||||
# (Ellipsis is used to mean "null")
|
||||
cache = {
|
||||
"auth_info": getattr(data, "auth_info", ...),
|
||||
"_contacts": getattr(data, "contacts", ...),
|
||||
"cr_date": getattr(data, "cr_date", ...),
|
||||
"ex_date": getattr(data, "ex_date", ...),
|
||||
"_hosts": getattr(data, "hosts", ...),
|
||||
"name": getattr(data, "name", ...),
|
||||
"registrant": getattr(data, "registrant", ...),
|
||||
"statuses": getattr(data, "statuses", ...),
|
||||
"tr_date": getattr(data, "tr_date", ...),
|
||||
"up_date": getattr(data, "up_date", ...),
|
||||
}
|
||||
# remove null properties (to distinguish between "a value of None" and null)
|
||||
cleaned = {k: v for k, v in cache.items() if v is not ...}
|
||||
data_response = self._get_or_create_domain()
|
||||
cache = self._extract_data_from_response(data_response)
|
||||
|
||||
# remove null properties (to distinguish between "a value of None" and null)
|
||||
cleaned = self._remove_null_properties(cache)
|
||||
|
||||
# statuses can just be a list no need to keep the epp object
|
||||
if "statuses" in cleaned:
|
||||
cleaned["statuses"] = [status.state for status in cleaned["statuses"]]
|
||||
|
||||
# get extensions info, if there is any
|
||||
# DNSSECExtension is one possible extension, make sure to handle
|
||||
# only DNSSECExtension and not other type extensions
|
||||
returned_extensions = dataResponse.extensions
|
||||
cleaned["dnssecdata"] = None
|
||||
for extension in returned_extensions:
|
||||
if isinstance(extension, extensions.DNSSECExtension):
|
||||
cleaned["dnssecdata"] = extension
|
||||
cleaned["dnssecdata"] = self._get_dnssec_data(data_response.extensions)
|
||||
|
||||
# Capture and store old hosts and contacts from cache if they exist
|
||||
old_cache_hosts = self._cache.get("hosts")
|
||||
old_cache_contacts = self._cache.get("contacts")
|
||||
|
||||
# get contact info, if there are any
|
||||
if (
|
||||
fetch_contacts
|
||||
and "_contacts" in cleaned
|
||||
and isinstance(cleaned["_contacts"], list)
|
||||
and len(cleaned["_contacts"]) > 0
|
||||
):
|
||||
cleaned["contacts"] = self._fetch_contacts(cleaned["_contacts"])
|
||||
# We're only getting contacts, so retain the old
|
||||
# hosts that existed in cache (if they existed)
|
||||
# and pass them along.
|
||||
if fetch_contacts:
|
||||
cleaned["contacts"] = self._get_contacts(cleaned.get("_contacts", []))
|
||||
if old_cache_hosts is not None:
|
||||
logger.debug("resetting cleaned['hosts'] to old_cache_hosts")
|
||||
cleaned["hosts"] = old_cache_hosts
|
||||
|
||||
# get nameserver info, if there are any
|
||||
if (
|
||||
fetch_hosts
|
||||
and "_hosts" in cleaned
|
||||
and isinstance(cleaned["_hosts"], list)
|
||||
and len(cleaned["_hosts"])
|
||||
):
|
||||
cleaned["hosts"] = self._fetch_hosts(cleaned["_hosts"])
|
||||
# We're only getting hosts, so retain the old
|
||||
# contacts that existed in cache (if they existed)
|
||||
# and pass them along.
|
||||
if fetch_hosts:
|
||||
cleaned["hosts"] = self._get_hosts(cleaned.get("_hosts", []))
|
||||
if old_cache_contacts is not None:
|
||||
cleaned["contacts"] = old_cache_contacts
|
||||
# replace the prior cache with new data
|
||||
|
||||
self._cache = cleaned
|
||||
|
||||
except RegistryError as e:
|
||||
logger.error(e)
|
||||
|
||||
def _extract_data_from_response(self, data_response):
|
||||
data = data_response.res_data[0]
|
||||
return {
|
||||
"auth_info": getattr(data, "auth_info", ...),
|
||||
"_contacts": getattr(data, "contacts", ...),
|
||||
"cr_date": getattr(data, "cr_date", ...),
|
||||
"ex_date": getattr(data, "ex_date", ...),
|
||||
"_hosts": getattr(data, "hosts", ...),
|
||||
"name": getattr(data, "name", ...),
|
||||
"registrant": getattr(data, "registrant", ...),
|
||||
"statuses": getattr(data, "statuses", ...),
|
||||
"tr_date": getattr(data, "tr_date", ...),
|
||||
"up_date": getattr(data, "up_date", ...),
|
||||
}
|
||||
|
||||
def _remove_null_properties(self, cache):
|
||||
return {k: v for k, v in cache.items() if v is not ...}
|
||||
|
||||
def _get_dnssec_data(self, response_extensions):
|
||||
# get extensions info, if there is any
|
||||
# DNSSECExtension is one possible extension, make sure to handle
|
||||
# only DNSSECExtension and not other type extensions
|
||||
dnssec_data = None
|
||||
for extension in response_extensions:
|
||||
if isinstance(extension, extensions.DNSSECExtension):
|
||||
dnssec_data = extension
|
||||
return dnssec_data
|
||||
|
||||
def _get_contacts(self, contacts):
|
||||
choices = PublicContact.ContactTypeChoices
|
||||
# We expect that all these fields get populated,
|
||||
# so we can create these early, rather than waiting.
|
||||
cleaned_contacts = {
|
||||
choices.ADMINISTRATIVE: None,
|
||||
choices.SECURITY: None,
|
||||
choices.TECHNICAL: None,
|
||||
}
|
||||
if contacts and isinstance(contacts, list) and len(contacts) > 0:
|
||||
cleaned_contacts = self._fetch_contacts(contacts)
|
||||
return cleaned_contacts
|
||||
|
||||
def _get_hosts(self, hosts):
|
||||
cleaned_hosts = []
|
||||
if hosts and isinstance(hosts, list):
|
||||
cleaned_hosts = self._fetch_hosts(hosts)
|
||||
return cleaned_hosts
|
||||
|
||||
def _get_or_create_public_contact(self, public_contact: PublicContact):
|
||||
"""Tries to find a PublicContact object in our DB.
|
||||
If it can't, it'll create it. Returns PublicContact"""
|
||||
|
|
|
@ -105,28 +105,57 @@ class DomainApplication(TimeStampedModel):
|
|||
ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)"
|
||||
|
||||
class OrganizationChoices(models.TextChoices):
|
||||
|
||||
"""
|
||||
Primary organization choices:
|
||||
For use in django admin
|
||||
Keys need to match OrganizationChoicesVerbose
|
||||
"""
|
||||
|
||||
FEDERAL = "federal", "Federal"
|
||||
INTERSTATE = "interstate", "Interstate"
|
||||
STATE_OR_TERRITORY = "state_or_territory", "State or territory"
|
||||
TRIBAL = "tribal", "Tribal"
|
||||
COUNTY = "county", "County"
|
||||
CITY = "city", "City"
|
||||
SPECIAL_DISTRICT = "special_district", "Special district"
|
||||
SCHOOL_DISTRICT = "school_district", "School district"
|
||||
|
||||
class OrganizationChoicesVerbose(models.TextChoices):
|
||||
|
||||
"""
|
||||
Secondary organization choices
|
||||
For use in the application form and on the templates
|
||||
Keys need to match OrganizationChoices
|
||||
"""
|
||||
|
||||
FEDERAL = (
|
||||
"federal",
|
||||
"Federal: an agency of the U.S. government's executive, legislative, "
|
||||
"or judicial branches",
|
||||
"Federal: an agency of the U.S. government's executive, "
|
||||
"legislative, or judicial branches",
|
||||
)
|
||||
INTERSTATE = "interstate", "Interstate: an organization of two or more states"
|
||||
STATE_OR_TERRITORY = "state_or_territory", (
|
||||
"State or territory: one of the 50 U.S. states, the District of "
|
||||
"Columbia, American Samoa, Guam, Northern Mariana Islands, "
|
||||
"Puerto Rico, or the U.S. Virgin Islands"
|
||||
STATE_OR_TERRITORY = (
|
||||
"state_or_territory",
|
||||
"State or territory: one of the 50 U.S. states, the District of Columbia, "
|
||||
"American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. "
|
||||
"Virgin Islands",
|
||||
)
|
||||
TRIBAL = "tribal", (
|
||||
"Tribal: a tribal government recognized by the federal or "
|
||||
"a state government"
|
||||
TRIBAL = (
|
||||
"tribal",
|
||||
"Tribal: a tribal government recognized by the federal or a state "
|
||||
"government",
|
||||
)
|
||||
COUNTY = "county", "County: a county, parish, or borough"
|
||||
CITY = "city", "City: a city, town, township, village, etc."
|
||||
SPECIAL_DISTRICT = "special_district", (
|
||||
"Special district: an independent organization within a single state"
|
||||
SPECIAL_DISTRICT = (
|
||||
"special_district",
|
||||
"Special district: an independent organization within a single state",
|
||||
)
|
||||
SCHOOL_DISTRICT = "school_district", (
|
||||
"School district: a school district that is not part of a local government"
|
||||
SCHOOL_DISTRICT = (
|
||||
"school_district",
|
||||
"School district: a school district that is not part of a local "
|
||||
"government",
|
||||
)
|
||||
|
||||
class BranchChoices(models.TextChoices):
|
||||
|
@ -297,6 +326,7 @@ class DomainApplication(TimeStampedModel):
|
|||
# ##### data fields from the initial form #####
|
||||
organization_type = models.CharField(
|
||||
max_length=255,
|
||||
# use the short names in Django admin
|
||||
choices=OrganizationChoices.choices,
|
||||
null=True,
|
||||
blank=True,
|
||||
|
@ -582,7 +612,7 @@ class DomainApplication(TimeStampedModel):
|
|||
# create the permission for the user
|
||||
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
||||
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(
|
||||
|
|
|
@ -21,6 +21,7 @@ class DomainInformation(TimeStampedModel):
|
|||
|
||||
StateTerritoryChoices = DomainApplication.StateTerritoryChoices
|
||||
|
||||
# use the short names in Django admin
|
||||
OrganizationChoices = DomainApplication.OrganizationChoices
|
||||
|
||||
BranchChoices = DomainApplication.BranchChoices
|
||||
|
|
|
@ -63,7 +63,7 @@ class DomainInvitation(TimeStampedModel):
|
|||
|
||||
# and create a role for that user on this domain
|
||||
_, 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:
|
||||
# something strange happened and this role already existed when
|
||||
|
|
|
@ -15,7 +15,7 @@ class UserDomainRole(TimeStampedModel):
|
|||
elsewhere.
|
||||
"""
|
||||
|
||||
ADMIN = "manager"
|
||||
MANAGER = "manager"
|
||||
|
||||
user = models.ForeignKey(
|
||||
"registrar.User",
|
||||
|
|
|
@ -24,7 +24,7 @@ class UserGroup(Group):
|
|||
{
|
||||
"app_label": "registrar",
|
||||
"model": "contact",
|
||||
"permissions": ["view_contact"],
|
||||
"permissions": ["change_contact"],
|
||||
},
|
||||
{
|
||||
"app_label": "registrar",
|
||||
|
@ -56,6 +56,11 @@ class UserGroup(Group):
|
|||
"model": "domaininvitation",
|
||||
"permissions": ["add_domaininvitation", "view_domaininvitation"],
|
||||
},
|
||||
{
|
||||
"app_label": "registrar",
|
||||
"model": "website",
|
||||
"permissions": ["change_website"],
|
||||
},
|
||||
]
|
||||
|
||||
# Avoid error: You can't execute queries until the end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends 'application_form.html' %}
|
||||
{% load static url_helpers %}
|
||||
{% load custom_filters %}
|
||||
|
||||
{% block form_required_fields_help_text %}
|
||||
{# there are no required fields on this page so don't show this #}
|
||||
|
@ -26,7 +27,13 @@
|
|||
<div class="review__step__name">{{ form_titles|get_item:step }}</div>
|
||||
<div>
|
||||
{% if step == Step.ORGANIZATION_TYPE %}
|
||||
{{ application.get_organization_type_display|default:"Incomplete" }}
|
||||
{% if application.organization_type is not None %}
|
||||
{% with long_org_type=application.organization_type|get_organization_long_name %}
|
||||
{{ long_org_type }}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
Incomplete
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if step == Step.TRIBAL_GOVERNMENT %}
|
||||
{{ application.tribe_name|default:"Incomplete" }}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load custom_filters %}
|
||||
|
||||
{% block title %}Domain request status | {{ domainapplication.requested_domain.name }} | {% endblock %}
|
||||
{% load static url_helpers %}
|
||||
|
||||
|
@ -50,7 +52,9 @@
|
|||
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
|
||||
<h2 class="text-primary-darker"> Summary of your domain request </h2>
|
||||
{% with heading_level='h3' %}
|
||||
{% include "includes/summary_item.html" with title='Type of organization' value=domainapplication.get_organization_type_display heading_level=heading_level %}
|
||||
{% with long_org_type=domainapplication.organization_type|get_organization_long_name %}
|
||||
{% include "includes/summary_item.html" with title='Type of organization' value=long_org_type heading_level=heading_level %}
|
||||
{% endwith %}
|
||||
|
||||
{% if domainapplication.tribe_name %}
|
||||
{% include "includes/summary_item.html" with title='Tribal government' value=domainapplication.tribe_name heading_level=heading_level %}
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %}
|
||||
{% endif %}
|
||||
{% url 'domain-users' pk=domain.id as url %}
|
||||
{% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %}
|
||||
{% include "includes/summary_item.html" with title='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url %}
|
||||
|
||||
</div>
|
||||
{% endblock %} {# domain_content #}
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
<a href="{{ url }}"
|
||||
{% if request.path|startswith:url %}class="usa-current"{% endif %}
|
||||
>
|
||||
User management
|
||||
Domain managers
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
{% 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 %}
|
||||
<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 %}
|
||||
<section class="section--outlined">
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import logging
|
||||
from django import template
|
||||
import re
|
||||
from registrar.models.domain_application import DomainApplication
|
||||
|
||||
register = template.Library()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register.filter(name="extract_value")
|
||||
|
@ -48,3 +51,16 @@ def contains_checkbox(html_list):
|
|||
if re.search(r'<input[^>]*type="checkbox"', html_string):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_organization_long_name(organization_type):
|
||||
organization_choices_dict = dict(
|
||||
DomainApplication.OrganizationChoicesVerbose.choices
|
||||
)
|
||||
long_form_type = organization_choices_dict[organization_type]
|
||||
if long_form_type is None:
|
||||
logger.error("Organization type error, triggered by a template's custom filter")
|
||||
return "Error"
|
||||
|
||||
return long_form_type
|
||||
|
|
|
@ -31,8 +31,11 @@ from epplibwrapper import (
|
|||
info,
|
||||
RegistryError,
|
||||
ErrorCode,
|
||||
responses,
|
||||
)
|
||||
|
||||
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -667,6 +670,44 @@ class MockEppLib(TestCase):
|
|||
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(
|
||||
"securityContact", "security@mail.gov"
|
||||
)
|
||||
|
@ -771,51 +812,63 @@ 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):
|
||||
"""Mocks the registry.send function used inside of domain.py
|
||||
registry is imported from epplibwrapper
|
||||
returns objects that simulate what would be in a epp response
|
||||
but only relevant pieces for tests"""
|
||||
if isinstance(_request, commands.InfoDomain):
|
||||
return self.mockInfoDomainCommands(_request, cleaned)
|
||||
elif isinstance(_request, commands.InfoContact):
|
||||
return self.mockInfoContactCommands(_request, cleaned)
|
||||
elif isinstance(_request, commands.UpdateDomain):
|
||||
return self.mockUpdateDomainCommands(_request, cleaned)
|
||||
elif (
|
||||
isinstance(_request, commands.CreateContact)
|
||||
and getattr(_request, "id", None) == "fail"
|
||||
and self.mockedSendFunction.call_count == 3
|
||||
):
|
||||
# use this for when a contact is being updated
|
||||
# sets the second send() to fail
|
||||
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
|
||||
elif isinstance(_request, commands.CreateHost):
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
elif isinstance(_request, commands.UpdateHost):
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
elif isinstance(_request, commands.DeleteHost):
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
elif (
|
||||
isinstance(_request, commands.DeleteDomain)
|
||||
and 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
|
||||
|
||||
match type(_request):
|
||||
case commands.InfoDomain:
|
||||
return self.mockInfoDomainCommands(_request, cleaned)
|
||||
case commands.InfoContact:
|
||||
return self.mockInfoContactCommands(_request, cleaned)
|
||||
case commands.CreateContact:
|
||||
return self.mockCreateContactCommands(_request, cleaned)
|
||||
case commands.UpdateDomain:
|
||||
return self.mockUpdateDomainCommands(_request, cleaned)
|
||||
case commands.CreateHost:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||
case commands.UpdateHost:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
case commands.DeleteHost:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
case commands.CheckDomain:
|
||||
return self.mockCheckDomainCommand(_request, cleaned)
|
||||
case commands.DeleteDomain:
|
||||
return self.mockDeleteDomainCommands(_request, cleaned)
|
||||
case _:
|
||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||
|
||||
def mockUpdateDomainCommands(self, _request, cleaned):
|
||||
if getattr(_request, "name", None) == "dnssec-invalid.gov":
|
||||
|
@ -826,6 +879,16 @@ class MockEppLib(TestCase):
|
|||
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):
|
||||
request_name = getattr(_request, "name", None)
|
||||
|
||||
|
@ -851,6 +914,8 @@ class MockEppLib(TestCase):
|
|||
"namerserversubdomain.gov": (self.infoDomainCheckHostIPCombo, None),
|
||||
"freeman.gov": (self.InfoDomainWithContacts, None),
|
||||
"threenameserversDomain.gov": (self.infoDomainThreeHosts, None),
|
||||
"defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None),
|
||||
"defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None),
|
||||
}
|
||||
|
||||
# Retrieve the corresponding values from the dictionary
|
||||
|
@ -876,12 +941,34 @@ class MockEppLib(TestCase):
|
|||
mocked_result = self.mockAdministrativeContact
|
||||
case "regContact":
|
||||
mocked_result = self.mockRegistrantContact
|
||||
case "defaultSec":
|
||||
mocked_result = self.mockDefaultSecurityContact
|
||||
case "defaultTech":
|
||||
mocked_result = self.mockDefaultTechnicalContact
|
||||
case _:
|
||||
# Default contact return
|
||||
mocked_result = self.mockDataInfoContact
|
||||
|
||||
return MagicMock(res_data=[mocked_result])
|
||||
|
||||
def mockCreateContactCommands(self, _request, cleaned):
|
||||
if (
|
||||
getattr(_request, "id", None) == "fail"
|
||||
and self.mockedSendFunction.call_count == 3
|
||||
):
|
||||
# use this for when a contact is being updated
|
||||
# sets the second send() to fail
|
||||
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
|
||||
elif getattr(_request, "email", None) == "test@failCreate.gov":
|
||||
# use this for when a contact is being updated
|
||||
# mocks a registry error on creation
|
||||
raise RegistryError(code=None)
|
||||
elif getattr(_request, "email", None) == "test@contactError.gov":
|
||||
# use this for when a contact is being updated
|
||||
# mocks a contact error on creation
|
||||
raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE)
|
||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||
|
||||
def setUp(self):
|
||||
"""mock epp send function as this will fail locally"""
|
||||
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
||||
|
@ -892,15 +979,11 @@ class MockEppLib(TestCase):
|
|||
self, contact: PublicContact, disclose_email=False, createContact=True
|
||||
):
|
||||
DF = common.DiscloseField
|
||||
fields = {DF.FAX, DF.VOICE, DF.ADDR}
|
||||
|
||||
if not disclose_email:
|
||||
fields.add(DF.EMAIL)
|
||||
fields = {DF.EMAIL}
|
||||
|
||||
di = common.Disclose(
|
||||
flag=False,
|
||||
flag=disclose_email,
|
||||
fields=fields,
|
||||
types={DF.ADDR: "loc"},
|
||||
)
|
||||
|
||||
# check docs here looks like we may have more than one address field but
|
||||
|
|
|
@ -11,6 +11,7 @@ from registrar.admin import (
|
|||
ListHeaderAdmin,
|
||||
MyUserAdmin,
|
||||
AuditedAdmin,
|
||||
ContactAdmin,
|
||||
)
|
||||
from registrar.models import (
|
||||
Domain,
|
||||
|
@ -52,6 +53,26 @@ class TestDomainAdmin(MockEppLib):
|
|||
self.factory = RequestFactory()
|
||||
super().setUp()
|
||||
|
||||
def test_short_org_name_in_domains_list(self):
|
||||
"""
|
||||
Make sure the short name is displaying in admin on the list page
|
||||
"""
|
||||
self.client.force_login(self.superuser)
|
||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||
application.approve()
|
||||
|
||||
response = self.client.get("/admin/registrar/domain/")
|
||||
|
||||
# There are 3 template references to Federal (3) plus one reference in the table
|
||||
# for our actual application
|
||||
self.assertContains(response, "Federal", count=4)
|
||||
# This may be a bit more robust
|
||||
self.assertContains(
|
||||
response, '<td class="field-organization_type">Federal</td>', count=1
|
||||
)
|
||||
# Now let's make sure the long description does not exist
|
||||
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
||||
|
||||
@skip("Why did this test stop working, and is is a good test")
|
||||
def test_place_and_remove_hold(self):
|
||||
domain = create_ready_domain()
|
||||
|
@ -243,8 +264,11 @@ class TestDomainAdmin(MockEppLib):
|
|||
raise
|
||||
|
||||
def tearDown(self):
|
||||
User.objects.all().delete()
|
||||
super().tearDown()
|
||||
Domain.objects.all().delete()
|
||||
DomainInformation.objects.all().delete()
|
||||
DomainApplication.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
|
||||
|
||||
class TestDomainApplicationAdminForm(TestCase):
|
||||
|
@ -300,6 +324,23 @@ class TestDomainApplicationAdmin(MockEppLib):
|
|||
self.superuser = create_superuser()
|
||||
self.staffuser = create_user()
|
||||
|
||||
def test_short_org_name_in_applications_list(self):
|
||||
"""
|
||||
Make sure the short name is displaying in admin on the list page
|
||||
"""
|
||||
self.client.force_login(self.superuser)
|
||||
completed_application()
|
||||
response = self.client.get("/admin/registrar/domainapplication/")
|
||||
# There are 3 template references to Federal (3) plus one reference in the table
|
||||
# for our actual application
|
||||
self.assertContains(response, "Federal", count=4)
|
||||
# This may be a bit more robust
|
||||
self.assertContains(
|
||||
response, '<td class="field-organization_type">Federal</td>', count=1
|
||||
)
|
||||
# Now let's make sure the long description does not exist
|
||||
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_save_model_sends_submitted_email(self):
|
||||
# make sure there is no user with this email
|
||||
|
@ -620,9 +661,6 @@ class TestDomainApplicationAdmin(MockEppLib):
|
|||
expected_fields = [
|
||||
"creator",
|
||||
"about_your_organization",
|
||||
"address_line1",
|
||||
"address_line2",
|
||||
"zipcode",
|
||||
"requested_domain",
|
||||
"alternative_domains",
|
||||
"purpose",
|
||||
|
@ -1313,3 +1351,38 @@ class DomainSessionVariableTest(TestCase):
|
|||
{"_edit_domain": "true"},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
|
||||
class ContactAdminTest(TestCase):
|
||||
def setUp(self):
|
||||
self.site = AdminSite()
|
||||
self.factory = RequestFactory()
|
||||
self.client = Client(HTTP_HOST="localhost:8080")
|
||||
self.admin = ContactAdmin(model=get_user_model(), admin_site=None)
|
||||
self.superuser = create_superuser()
|
||||
self.staffuser = create_user()
|
||||
|
||||
def test_readonly_when_restricted_staffuser(self):
|
||||
request = self.factory.get("/")
|
||||
request.user = self.staffuser
|
||||
|
||||
readonly_fields = self.admin.get_readonly_fields(request)
|
||||
|
||||
expected_fields = [
|
||||
"user",
|
||||
]
|
||||
|
||||
self.assertEqual(readonly_fields, expected_fields)
|
||||
|
||||
def test_readonly_when_restricted_superuser(self):
|
||||
request = self.factory.get("/")
|
||||
request.user = self.superuser
|
||||
|
||||
readonly_fields = self.admin.get_readonly_fields(request)
|
||||
|
||||
expected_fields = []
|
||||
|
||||
self.assertEqual(readonly_fields, expected_fields)
|
||||
|
||||
def tearDown(self):
|
||||
User.objects.all().delete()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Test form validation requirements."""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, RequestFactory
|
||||
|
||||
from registrar.forms.application_wizard import (
|
||||
CurrentSitesForm,
|
||||
|
@ -16,9 +16,16 @@ from registrar.forms.application_wizard import (
|
|||
AboutYourOrganizationForm,
|
||||
)
|
||||
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):
|
||||
form = OrganizationContactForm(data={"zipcode": "nah"})
|
||||
self.assertEqual(
|
||||
|
|
|
@ -36,7 +36,7 @@ class TestGroups(TestCase):
|
|||
# Define the expected permission codenames
|
||||
expected_permissions = [
|
||||
"view_logentry",
|
||||
"view_contact",
|
||||
"change_contact",
|
||||
"view_domain",
|
||||
"change_domainapplication",
|
||||
"change_domaininformation",
|
||||
|
@ -45,6 +45,7 @@ class TestGroups(TestCase):
|
|||
"change_draftdomain",
|
||||
"analyst_access_permission",
|
||||
"change_user",
|
||||
"change_website",
|
||||
]
|
||||
|
||||
# Get the codenames of actual permissions associated with the group
|
||||
|
|
|
@ -601,7 +601,7 @@ class TestInvitations(TestCase):
|
|||
def test_retrieve_existing_role_no_error(self):
|
||||
# make the overlapping 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
|
||||
)
|
||||
# this is not an error but does produce a console warning
|
||||
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 .common import MockEppLib
|
||||
|
||||
from django_fsm import TransitionNotAllowed # type: ignore
|
||||
from epplibwrapper import (
|
||||
commands,
|
||||
|
@ -29,6 +29,7 @@ from epplibwrapper import (
|
|||
RegistryError,
|
||||
ErrorCode,
|
||||
)
|
||||
from .common import MockEppLib
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -760,6 +761,198 @@ class TestRegistrantContacts(MockEppLib):
|
|||
self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True)
|
||||
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")
|
||||
def test_update_is_unsuccessful(self):
|
||||
"""
|
||||
|
|
|
@ -89,7 +89,7 @@ class LoggedInTests(TestWithUser):
|
|||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
self.assertNotContains(response, "igorville.gov")
|
||||
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("/")
|
||||
# count = 2 because it is also in screenreader content
|
||||
|
@ -142,9 +142,12 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
|
||||
@boto3_mocking.patching
|
||||
def test_application_form_submission(self):
|
||||
"""Can fill out the entire form and submit.
|
||||
"""
|
||||
Can fill out the entire form and submit.
|
||||
As we add additional form pages, we need to include them here to make
|
||||
this test work.
|
||||
|
||||
This test also looks for the long organization name on the summary page.
|
||||
"""
|
||||
num_pages_tested = 0
|
||||
# elections, type_of_work, tribal_government, no_other_contacts
|
||||
|
@ -428,7 +431,8 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
review_form = review_page.form
|
||||
|
||||
# Review page contains all the previously entered data
|
||||
self.assertContains(review_page, "Federal")
|
||||
# Let's make sure the long org name is displayed
|
||||
self.assertContains(review_page, "Federal: an agency of the U.S. government")
|
||||
self.assertContains(review_page, "Executive")
|
||||
self.assertContains(review_page, "Testorg")
|
||||
self.assertContains(review_page, "address 1")
|
||||
|
@ -1066,6 +1070,26 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# page = self.app.get(url)
|
||||
# self.assertNotContains(page, "VALUE")
|
||||
|
||||
def test_long_org_name_in_application(self):
|
||||
"""
|
||||
Make sure the long name is displaying in the application form,
|
||||
org step
|
||||
"""
|
||||
request = self.app.get(reverse("application:")).follow()
|
||||
self.assertContains(request, "Federal: an agency of the U.S. government")
|
||||
|
||||
def test_long_org_name_in_application_manage(self):
|
||||
"""
|
||||
Make sure the long name is displaying in the application summary
|
||||
page (manage your application)
|
||||
"""
|
||||
completed_application(status=DomainApplication.SUBMITTED, user=self.user)
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "city.gov")
|
||||
# click the "Edit" link
|
||||
detail_page = home_page.click("Manage")
|
||||
self.assertContains(detail_page, "Federal: an agency of the U.S. government")
|
||||
|
||||
|
||||
class TestWithDomainPermissions(TestWithUser):
|
||||
def setUp(self):
|
||||
|
@ -1093,10 +1117,10 @@ class TestWithDomainPermissions(TestWithUser):
|
|||
creator=self.user, domain=self.domain_dnssec_none
|
||||
)
|
||||
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(
|
||||
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(
|
||||
user=self.user,
|
||||
|
@ -1106,7 +1130,7 @@ class TestWithDomainPermissions(TestWithUser):
|
|||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user,
|
||||
domain=self.domain_dnssec_none,
|
||||
role=UserDomainRole.Roles.ADMIN,
|
||||
role=UserDomainRole.Roles.MANAGER,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -1192,14 +1216,14 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
class TestDomainUserManagement(TestDomainOverview):
|
||||
def test_domain_user_management(self):
|
||||
class TestDomainManagers(TestDomainOverview):
|
||||
def test_domain_managers(self):
|
||||
response = self.client.get(
|
||||
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."""
|
||||
management_page = self.app.get(
|
||||
reverse("domain-users", kwargs={"pk": self.domain.id})
|
||||
|
@ -1547,6 +1571,78 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
|||
success_page, "The security email for this domain has been updated"
|
||||
)
|
||||
|
||||
def test_security_email_form_messages(self):
|
||||
"""
|
||||
Test against the success and error messages that are defined in the view
|
||||
"""
|
||||
p = "adminpass"
|
||||
self.client.login(username="superuser", password=p)
|
||||
|
||||
form_data_registry_error = {
|
||||
"security_email": "test@failCreate.gov",
|
||||
}
|
||||
|
||||
form_data_contact_error = {
|
||||
"security_email": "test@contactError.gov",
|
||||
}
|
||||
|
||||
form_data_success = {
|
||||
"security_email": "test@something.gov",
|
||||
}
|
||||
|
||||
test_cases = [
|
||||
(
|
||||
"RegistryError",
|
||||
form_data_registry_error,
|
||||
"Update failed. Cannot contact the registry.",
|
||||
),
|
||||
("ContactError", form_data_contact_error, "Value entered was wrong."),
|
||||
(
|
||||
"RegistrySuccess",
|
||||
form_data_success,
|
||||
"The security email for this domain has been updated.",
|
||||
),
|
||||
# Add more test cases with different scenarios here
|
||||
]
|
||||
|
||||
for test_name, data, expected_message in test_cases:
|
||||
response = self.client.post(
|
||||
reverse("domain-security-email", kwargs={"pk": self.domain.id}),
|
||||
data=data,
|
||||
follow=True,
|
||||
)
|
||||
|
||||
# Check the response status code, content, or any other relevant assertions
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Check if the expected message tag is set
|
||||
if test_name == "RegistryError" or test_name == "ContactError":
|
||||
message_tag = "error"
|
||||
elif test_name == "RegistrySuccess":
|
||||
message_tag = "success"
|
||||
else:
|
||||
# Handle other cases if needed
|
||||
message_tag = "info" # Change to the appropriate default
|
||||
|
||||
# Check the message tag
|
||||
messages = list(response.context["messages"])
|
||||
self.assertEqual(len(messages), 1)
|
||||
message = messages[0]
|
||||
self.assertEqual(message.tags, message_tag)
|
||||
self.assertEqual(message.message, expected_message)
|
||||
|
||||
def test_domain_overview_blocked_for_ineligible_user(self):
|
||||
"""We could easily duplicate this test for all domain management
|
||||
views, but a single url test should be solid enough since all domain
|
||||
management pages share the same permissions class"""
|
||||
self.user.status = User.RESTRICTED
|
||||
self.user.save()
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "igorville.gov")
|
||||
with less_console_noise():
|
||||
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
class TestDomainDNSSEC(TestDomainOverview):
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ from registrar.models import (
|
|||
UserDomainRole,
|
||||
)
|
||||
from registrar.models.public_contact import PublicContact
|
||||
from registrar.models.utility.contact_error import ContactError
|
||||
|
||||
from ..forms import (
|
||||
ContactForm,
|
||||
|
@ -41,6 +42,8 @@ from epplibwrapper import (
|
|||
common,
|
||||
extensions,
|
||||
RegistryError,
|
||||
CANNOT_CONTACT_REGISTRY,
|
||||
GENERIC_ERROR,
|
||||
)
|
||||
|
||||
from ..utility.email import send_templated_email, EmailSendingError
|
||||
|
@ -50,7 +53,81 @@ from .utility import DomainPermissionView, DomainInvitationPermissionDeleteView
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DomainView(DomainPermissionView):
|
||||
class DomainBaseView(DomainPermissionView):
|
||||
"""
|
||||
Base View for the Domain. Handles getting and setting the domain
|
||||
in session cache on GETs. Also provides methods for getting
|
||||
and setting the domain in cache
|
||||
"""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self._get_domain(request)
|
||||
context = self.get_context_data(object=self.object)
|
||||
return self.render_to_response(context)
|
||||
|
||||
def _get_domain(self, request):
|
||||
"""
|
||||
get domain from session cache or from db and set
|
||||
to self.object
|
||||
set session to self for downstream functions to
|
||||
update session cache
|
||||
"""
|
||||
self.session = request.session
|
||||
# domain:private_key is the session key to use for
|
||||
# caching the domain in the session
|
||||
domain_pk = "domain:" + str(self.kwargs.get("pk"))
|
||||
cached_domain = self.session.get(domain_pk)
|
||||
|
||||
if cached_domain:
|
||||
self.object = cached_domain
|
||||
else:
|
||||
self.object = self.get_object()
|
||||
self._update_session_with_domain()
|
||||
|
||||
def _update_session_with_domain(self):
|
||||
"""
|
||||
update domain in the session cache
|
||||
"""
|
||||
domain_pk = "domain:" + str(self.kwargs.get("pk"))
|
||||
self.session[domain_pk] = self.object
|
||||
|
||||
|
||||
class DomainFormBaseView(DomainBaseView, FormMixin):
|
||||
"""
|
||||
Form Base View for the Domain. Handles getting and setting
|
||||
domain in cache when dealing with domain forms. Provides
|
||||
implementations of post, form_valid and form_invalid.
|
||||
"""
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Form submission posts to this view.
|
||||
|
||||
This post method harmonizes using DomainBaseView and FormMixin
|
||||
"""
|
||||
self._get_domain(request)
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
# updates session cache with domain
|
||||
self._update_session_with_domain()
|
||||
|
||||
# superclass has the redirect
|
||||
return super().form_valid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
# updates session cache with domain
|
||||
self._update_session_with_domain()
|
||||
|
||||
# superclass has the redirect
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class DomainView(DomainBaseView):
|
||||
|
||||
"""Domain detail overview page."""
|
||||
|
||||
template_name = "domain_detail.html"
|
||||
|
@ -58,10 +135,10 @@ class DomainView(DomainPermissionView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
default_email = Domain().get_default_security_contact().email
|
||||
default_email = self.object.get_default_security_contact().email
|
||||
context["default_security_email"] = default_email
|
||||
|
||||
security_email = self.get_object().get_security_email()
|
||||
security_email = self.object.get_security_email()
|
||||
if security_email is None or security_email == default_email:
|
||||
context["security_email"] = None
|
||||
return context
|
||||
|
@ -69,7 +146,7 @@ class DomainView(DomainPermissionView):
|
|||
return context
|
||||
|
||||
|
||||
class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
|
||||
class DomainOrgNameAddressView(DomainFormBaseView):
|
||||
"""Organization name and mailing address view"""
|
||||
|
||||
model = Domain
|
||||
|
@ -80,25 +157,13 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
|
|||
def get_form_kwargs(self, *args, **kwargs):
|
||||
"""Add domain_info.organization_name instance to make a bound form."""
|
||||
form_kwargs = super().get_form_kwargs(*args, **kwargs)
|
||||
form_kwargs["instance"] = self.get_object().domain_info
|
||||
form_kwargs["instance"] = self.object.domain_info
|
||||
return form_kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
"""Redirect to the overview page for the domain."""
|
||||
return reverse("domain-org-name-address", kwargs={"pk": self.object.pk})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Form submission posts to this view.
|
||||
|
||||
This post method harmonizes using DetailView and FormMixin together.
|
||||
"""
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
"""The form is valid, save the organization name and mailing address."""
|
||||
form.save()
|
||||
|
@ -111,7 +176,7 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
|
|||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
|
||||
class DomainAuthorizingOfficialView(DomainFormBaseView):
|
||||
"""Domain authorizing official editing view."""
|
||||
|
||||
model = Domain
|
||||
|
@ -122,25 +187,13 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
|
|||
def get_form_kwargs(self, *args, **kwargs):
|
||||
"""Add domain_info.authorizing_official instance to make a bound form."""
|
||||
form_kwargs = super().get_form_kwargs(*args, **kwargs)
|
||||
form_kwargs["instance"] = self.get_object().domain_info.authorizing_official
|
||||
form_kwargs["instance"] = self.object.domain_info.authorizing_official
|
||||
return form_kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
"""Redirect to the overview page for the domain."""
|
||||
return reverse("domain-authorizing-official", kwargs={"pk": self.object.pk})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Form submission posts to this view.
|
||||
|
||||
This post method harmonizes using DetailView and FormMixin together.
|
||||
"""
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
"""The form is valid, save the authorizing official."""
|
||||
form.save()
|
||||
|
@ -153,13 +206,13 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
|
|||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DomainDNSView(DomainPermissionView):
|
||||
class DomainDNSView(DomainBaseView):
|
||||
"""DNS Information View."""
|
||||
|
||||
template_name = "domain_dns.html"
|
||||
|
||||
|
||||
class DomainNameserversView(DomainPermissionView, FormMixin):
|
||||
class DomainNameserversView(DomainFormBaseView):
|
||||
"""Domain nameserver editing view."""
|
||||
|
||||
template_name = "domain_nameservers.html"
|
||||
|
@ -167,8 +220,7 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
|||
|
||||
def get_initial(self):
|
||||
"""The initial value for the form (which is a formset here)."""
|
||||
domain = self.get_object()
|
||||
nameservers = domain.nameservers
|
||||
nameservers = self.object.nameservers
|
||||
initial_data = []
|
||||
|
||||
if nameservers is not None:
|
||||
|
@ -204,16 +256,6 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
|||
form.fields["server"].required = False
|
||||
return formset
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Formset submission posts to this view."""
|
||||
self.object = self.get_object()
|
||||
formset = self.get_form()
|
||||
|
||||
if formset.is_valid():
|
||||
return self.form_valid(formset)
|
||||
else:
|
||||
return self.form_invalid(formset)
|
||||
|
||||
def form_valid(self, formset):
|
||||
"""The formset is valid, perform something with it."""
|
||||
|
||||
|
@ -226,8 +268,7 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
|||
except KeyError:
|
||||
# no server information in this field, skip it
|
||||
pass
|
||||
domain = self.get_object()
|
||||
domain.nameservers = nameservers
|
||||
self.object.nameservers = nameservers
|
||||
|
||||
messages.success(
|
||||
self.request, "The name servers for this domain have been updated."
|
||||
|
@ -237,7 +278,7 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
|||
return super().form_valid(formset)
|
||||
|
||||
|
||||
class DomainDNSSECView(DomainPermissionView, FormMixin):
|
||||
class DomainDNSSECView(DomainFormBaseView):
|
||||
"""Domain DNSSEC editing view."""
|
||||
|
||||
template_name = "domain_dnssec.html"
|
||||
|
@ -247,9 +288,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
|
|||
"""The initial value for the form (which is a formset here)."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
self.domain = self.get_object()
|
||||
|
||||
has_dnssec_records = self.domain.dnssecdata is not None
|
||||
has_dnssec_records = self.object.dnssecdata is not None
|
||||
|
||||
# Create HTML for the modal button
|
||||
modal_button = (
|
||||
|
@ -266,16 +305,16 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
|
|||
|
||||
def get_success_url(self):
|
||||
"""Redirect to the DNSSEC page for the domain."""
|
||||
return reverse("domain-dns-dnssec", kwargs={"pk": self.domain.pk})
|
||||
return reverse("domain-dns-dnssec", kwargs={"pk": self.object.pk})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Form submission posts to this view."""
|
||||
self.domain = self.get_object()
|
||||
self._get_domain(request)
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
if "disable_dnssec" in request.POST:
|
||||
try:
|
||||
self.domain.dnssecdata = {}
|
||||
self.object.dnssecdata = {}
|
||||
except RegistryError as err:
|
||||
errmsg = "Error removing existing DNSSEC record(s)."
|
||||
logger.error(errmsg + ": " + err)
|
||||
|
@ -284,7 +323,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
|
|||
return self.form_valid(form)
|
||||
|
||||
|
||||
class DomainDsDataView(DomainPermissionView, FormMixin):
|
||||
class DomainDsDataView(DomainFormBaseView):
|
||||
"""Domain DNSSEC ds data editing view."""
|
||||
|
||||
template_name = "domain_dsdata.html"
|
||||
|
@ -293,8 +332,7 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
|||
|
||||
def get_initial(self):
|
||||
"""The initial value for the form (which is a formset here)."""
|
||||
domain = self.get_object()
|
||||
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
|
||||
dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
|
||||
initial_data = []
|
||||
|
||||
if dnssecdata is not None and dnssecdata.dsData is not None:
|
||||
|
@ -329,7 +367,7 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
|||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Formset submission posts to this view."""
|
||||
self.object = self.get_object()
|
||||
self._get_domain(request)
|
||||
formset = self.get_form()
|
||||
override = False
|
||||
|
||||
|
@ -390,9 +428,8 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
|||
# as valid; this can happen if form has been added but
|
||||
# not been interacted with; in that case, want to ignore
|
||||
pass
|
||||
domain = self.get_object()
|
||||
try:
|
||||
domain.dnssecdata = dnssecdata
|
||||
self.object.dnssecdata = dnssecdata
|
||||
except RegistryError as err:
|
||||
errmsg = "Error updating DNSSEC data in the registry."
|
||||
logger.error(errmsg)
|
||||
|
@ -407,7 +444,7 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
|||
return super().form_valid(formset)
|
||||
|
||||
|
||||
class DomainYourContactInformationView(DomainPermissionView, FormMixin):
|
||||
class DomainYourContactInformationView(DomainFormBaseView):
|
||||
"""Domain your contact information editing view."""
|
||||
|
||||
template_name = "domain_your_contact_information.html"
|
||||
|
@ -423,16 +460,6 @@ class DomainYourContactInformationView(DomainPermissionView, FormMixin):
|
|||
"""Redirect to the your contact information for the domain."""
|
||||
return reverse("domain-your-contact-information", kwargs={"pk": self.object.pk})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Form submission posts to this view."""
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
# there is a valid email address in the form
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
"""The form is valid, call setter in model."""
|
||||
|
||||
|
@ -447,7 +474,7 @@ class DomainYourContactInformationView(DomainPermissionView, FormMixin):
|
|||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
||||
class DomainSecurityEmailView(DomainFormBaseView):
|
||||
"""Domain security email editing view."""
|
||||
|
||||
template_name = "domain_security_email.html"
|
||||
|
@ -455,9 +482,8 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
|||
|
||||
def get_initial(self):
|
||||
"""The initial value for the form."""
|
||||
domain = self.get_object()
|
||||
initial = super().get_initial()
|
||||
security_contact = domain.security_contact
|
||||
security_contact = self.object.security_contact
|
||||
if security_contact is None or security_contact.email == "dotgov@cisa.dhs.gov":
|
||||
initial["security_email"] = None
|
||||
return initial
|
||||
|
@ -468,16 +494,6 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
|||
"""Redirect to the security email page for the domain."""
|
||||
return reverse("domain-security-email", kwargs={"pk": self.object.pk})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Form submission posts to this view."""
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
# there is a valid email address in the form
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
"""The form is valid, call setter in model."""
|
||||
|
||||
|
@ -488,33 +504,44 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
|||
if new_email is None or new_email.strip() == "":
|
||||
new_email = PublicContact.get_default_security().email
|
||||
|
||||
domain = self.get_object()
|
||||
contact = domain.security_contact
|
||||
contact = self.object.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.")
|
||||
messages.error(self.request, CANNOT_CONTACT_REGISTRY)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
contact.email = new_email
|
||||
contact.save()
|
||||
|
||||
messages.success(
|
||||
self.request, "The security email for this domain has been updated."
|
||||
)
|
||||
try:
|
||||
contact.save()
|
||||
except RegistryError as Err:
|
||||
if Err.is_connection_error():
|
||||
messages.error(self.request, CANNOT_CONTACT_REGISTRY)
|
||||
logger.error(f"Registry connection error: {Err}")
|
||||
else:
|
||||
messages.error(self.request, GENERIC_ERROR)
|
||||
logger.error(f"Registry error: {Err}")
|
||||
except ContactError as Err:
|
||||
messages.error(self.request, GENERIC_ERROR)
|
||||
logger.error(f"Generic registry error: {Err}")
|
||||
else:
|
||||
messages.success(
|
||||
self.request, "The security email for this domain has been updated."
|
||||
)
|
||||
|
||||
# superclass has the redirect
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
|
||||
class DomainUsersView(DomainPermissionView):
|
||||
"""User management page in the domain details."""
|
||||
class DomainUsersView(DomainBaseView):
|
||||
"""Domain managers page in the domain details."""
|
||||
|
||||
template_name = "domain_users.html"
|
||||
|
||||
|
||||
class DomainAddUserView(DomainPermissionView, FormMixin):
|
||||
class DomainAddUserView(DomainFormBaseView):
|
||||
"""Inside of a domain's user management, a form for adding users.
|
||||
|
||||
Multiple inheritance is used here for permissions, form handling, and
|
||||
|
@ -527,15 +554,6 @@ class DomainAddUserView(DomainPermissionView, FormMixin):
|
|||
def get_success_url(self):
|
||||
return reverse("domain-users", kwargs={"pk": self.object.pk})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
# there is a valid email address in the form
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def _domain_abs_url(self):
|
||||
"""Get an absolute URL for this domain."""
|
||||
return self.request.build_absolute_uri(
|
||||
|
@ -598,7 +616,9 @@ class DomainAddUserView(DomainPermissionView, FormMixin):
|
|||
|
||||
try:
|
||||
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:
|
||||
# User already has the desired role! Do nothing??
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue