mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-06-30 00:03:30 +02:00
Merge pull request #2027 from cisagov/rjm/1777-clearing-out-nameservers
Issue #1777: Allow blanking of first and/or second nameserver if enough entries are present (rjm)
This commit is contained in:
commit
fa01015e9e
4 changed files with 191 additions and 14 deletions
|
@ -530,7 +530,7 @@ function hideDeletedForms() {
|
||||||
let isDotgovDomain = document.querySelector(".dotgov-domain-form");
|
let isDotgovDomain = document.querySelector(".dotgov-domain-form");
|
||||||
// The Nameservers formset features 2 required and 11 optionals
|
// The Nameservers formset features 2 required and 11 optionals
|
||||||
if (isNameserversForm) {
|
if (isNameserversForm) {
|
||||||
cloneIndex = 2;
|
// cloneIndex = 2;
|
||||||
formLabel = "Name server";
|
formLabel = "Name server";
|
||||||
// DNSSEC: DS Data
|
// DNSSEC: DS Data
|
||||||
} else if (isDsDataForm) {
|
} else if (isDsDataForm) {
|
||||||
|
@ -766,3 +766,21 @@ function toggleTwoDomElements(ele1, ele2, index) {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An IIFE that disables the delete buttons on nameserver forms on page load if < 3 forms
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
(function nameserversFormListener() {
|
||||||
|
let isNameserversForm = document.querySelector(".nameservers-form");
|
||||||
|
if (isNameserversForm) {
|
||||||
|
let forms = document.querySelectorAll(".repeatable-form");
|
||||||
|
if (forms.length < 3) {
|
||||||
|
// Hide the delete buttons on the 2 nameservers
|
||||||
|
forms.forEach((form) => {
|
||||||
|
Array.from(form.querySelectorAll('.delete-record')).forEach((deleteButton) => {
|
||||||
|
deleteButton.setAttribute("disabled", "true");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
|
@ -83,25 +83,34 @@ class DomainNameserverForm(forms.Form):
|
||||||
# after clean_fields. it is used to determine form level errors.
|
# after clean_fields. it is used to determine form level errors.
|
||||||
# is_valid is typically called from view during a post
|
# is_valid is typically called from view during a post
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
self.clean_empty_strings(cleaned_data)
|
self.clean_empty_strings(cleaned_data)
|
||||||
|
|
||||||
server = cleaned_data.get("server", "")
|
server = cleaned_data.get("server", "")
|
||||||
# remove ANY spaces in the server field
|
server = server.replace(" ", "").lower()
|
||||||
server = server.replace(" ", "")
|
|
||||||
# lowercase the server
|
|
||||||
server = server.lower()
|
|
||||||
cleaned_data["server"] = server
|
cleaned_data["server"] = server
|
||||||
ip = cleaned_data.get("ip", None)
|
|
||||||
# remove ANY spaces in the ip field
|
ip = cleaned_data.get("ip", "")
|
||||||
ip = ip.replace(" ", "")
|
ip = ip.replace(" ", "")
|
||||||
cleaned_data["ip"] = ip
|
cleaned_data["ip"] = ip
|
||||||
|
|
||||||
domain = cleaned_data.get("domain", "")
|
domain = cleaned_data.get("domain", "")
|
||||||
|
|
||||||
ip_list = self.extract_ip_list(ip)
|
ip_list = self.extract_ip_list(ip)
|
||||||
|
|
||||||
# validate if the form has a server or an ip
|
# Capture the server_value
|
||||||
|
server_value = self.cleaned_data.get("server")
|
||||||
|
|
||||||
|
# Validate if the form has a server or an ip
|
||||||
if (ip and ip_list) or server:
|
if (ip and ip_list) or server:
|
||||||
self.validate_nameserver_ip_combo(domain, server, ip_list)
|
self.validate_nameserver_ip_combo(domain, server, ip_list)
|
||||||
|
|
||||||
|
# Re-set the server value:
|
||||||
|
# add_error which is called on validate_nameserver_ip_combo will clean-up (delete) any invalid data.
|
||||||
|
# We need that data because we need to know the total server entries (even if invalid) in the formset
|
||||||
|
# clean method where we determine whether a blank first and/or second entry should throw a required error.
|
||||||
|
self.cleaned_data["server"] = server_value
|
||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
def clean_empty_strings(self, cleaned_data):
|
def clean_empty_strings(self, cleaned_data):
|
||||||
|
@ -149,6 +158,19 @@ class BaseNameserverFormset(forms.BaseFormSet):
|
||||||
"""
|
"""
|
||||||
Check for duplicate entries in the formset.
|
Check for duplicate entries in the formset.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Check if there are at least two valid servers
|
||||||
|
valid_servers_count = sum(
|
||||||
|
1 for form in self.forms if form.cleaned_data.get("server") and form.cleaned_data.get("server").strip()
|
||||||
|
)
|
||||||
|
if valid_servers_count >= 2:
|
||||||
|
# If there are, remove the "At least two name servers are required" error from each form
|
||||||
|
# This will allow for successful submissions when the first or second entries are blanked
|
||||||
|
# but there are enough entries total
|
||||||
|
for form in self.forms:
|
||||||
|
if form.errors.get("server") == ["At least two name servers are required."]:
|
||||||
|
form.errors.pop("server")
|
||||||
|
|
||||||
if any(self.errors):
|
if any(self.errors):
|
||||||
# Don't bother validating the formset unless each form is valid on its own
|
# Don't bother validating the formset unless each form is valid on its own
|
||||||
return
|
return
|
||||||
|
@ -156,10 +178,13 @@ class BaseNameserverFormset(forms.BaseFormSet):
|
||||||
data = []
|
data = []
|
||||||
duplicates = []
|
duplicates = []
|
||||||
|
|
||||||
for form in self.forms:
|
for index, form in enumerate(self.forms):
|
||||||
if form.cleaned_data:
|
if form.cleaned_data:
|
||||||
value = form.cleaned_data["server"]
|
value = form.cleaned_data["server"]
|
||||||
if value in data:
|
# We need to make sure not to trigger the duplicate error in case the first and second nameservers
|
||||||
|
# are empty. If there are enough records in the formset, that error is an unecessary blocker.
|
||||||
|
# If there aren't, the required error will block the submit.
|
||||||
|
if value in data and not (form.cleaned_data.get("server", "").strip() == "" and index == 1):
|
||||||
form.add_error(
|
form.add_error(
|
||||||
"server",
|
"server",
|
||||||
NameserverError(code=nsErrorCodes.DUPLICATE_HOST, nameserver=value),
|
NameserverError(code=nsErrorCodes.DUPLICATE_HOST, nameserver=value),
|
||||||
|
|
|
@ -1152,6 +1152,18 @@ class MockEppLib(TestCase):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
infoDomainFourHosts = fakedEppObject(
|
||||||
|
"fournameserversDomain.gov",
|
||||||
|
cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)),
|
||||||
|
contacts=[],
|
||||||
|
hosts=[
|
||||||
|
"ns1.my-nameserver-1.com",
|
||||||
|
"ns1.my-nameserver-2.com",
|
||||||
|
"ns1.cats-are-superior3.com",
|
||||||
|
"ns1.explosive-chicken-nuggets.com",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
infoDomainNoHost = fakedEppObject(
|
infoDomainNoHost = fakedEppObject(
|
||||||
"my-nameserver.gov",
|
"my-nameserver.gov",
|
||||||
cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)),
|
cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)),
|
||||||
|
@ -1452,7 +1464,9 @@ class MockEppLib(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def mockInfoDomainCommands(self, _request, cleaned):
|
def mockInfoDomainCommands(self, _request, cleaned):
|
||||||
request_name = getattr(_request, "name", None)
|
request_name = getattr(_request, "name", None).lower()
|
||||||
|
|
||||||
|
print(request_name)
|
||||||
|
|
||||||
# Define a dictionary to map request names to data and extension values
|
# Define a dictionary to map request names to data and extension values
|
||||||
request_mappings = {
|
request_mappings = {
|
||||||
|
@ -1474,7 +1488,8 @@ class MockEppLib(TestCase):
|
||||||
"nameserverwithip.gov": (self.infoDomainHasIP, None),
|
"nameserverwithip.gov": (self.infoDomainHasIP, None),
|
||||||
"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),
|
||||||
|
"fournameserversdomain.gov": (self.infoDomainFourHosts, None),
|
||||||
"defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None),
|
"defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None),
|
||||||
"adomain2.gov": (self.InfoDomainWithVerisignSecurityContact, None),
|
"adomain2.gov": (self.InfoDomainWithVerisignSecurityContact, None),
|
||||||
"defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None),
|
"defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None),
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from .common import MockSESClient, create_user # type: ignore
|
from .common import MockEppLib, MockSESClient, create_user # type: ignore
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
|
||||||
|
@ -71,11 +71,14 @@ class TestWithDomainPermissions(TestWithUser):
|
||||||
# that inherit this setUp
|
# that inherit this setUp
|
||||||
self.domain_dnssec_none, _ = Domain.objects.get_or_create(name="dnssec-none.gov")
|
self.domain_dnssec_none, _ = Domain.objects.get_or_create(name="dnssec-none.gov")
|
||||||
|
|
||||||
|
self.domain_with_four_nameservers, _ = Domain.objects.get_or_create(name="fournameserversDomain.gov")
|
||||||
|
|
||||||
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
||||||
|
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dsdata)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dsdata)
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_multdsdata)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_multdsdata)
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dnssec_none)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dnssec_none)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_four_nameservers)
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_ip)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_ip)
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_just_nameserver)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_just_nameserver)
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_on_hold)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_on_hold)
|
||||||
|
@ -98,6 +101,11 @@ class TestWithDomainPermissions(TestWithUser):
|
||||||
domain=self.domain_dnssec_none,
|
domain=self.domain_dnssec_none,
|
||||||
role=UserDomainRole.Roles.MANAGER,
|
role=UserDomainRole.Roles.MANAGER,
|
||||||
)
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
domain=self.domain_with_four_nameservers,
|
||||||
|
role=UserDomainRole.Roles.MANAGER,
|
||||||
|
)
|
||||||
UserDomainRole.objects.get_or_create(
|
UserDomainRole.objects.get_or_create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
domain=self.domain_with_ip,
|
domain=self.domain_with_ip,
|
||||||
|
@ -727,7 +735,7 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
self.assertContains(home_page, self.domain.name)
|
self.assertContains(home_page, self.domain.name)
|
||||||
|
|
||||||
|
|
||||||
class TestDomainNameservers(TestDomainOverview):
|
class TestDomainNameservers(TestDomainOverview, MockEppLib):
|
||||||
def test_domain_nameservers(self):
|
def test_domain_nameservers(self):
|
||||||
"""Can load domain's nameservers page."""
|
"""Can load domain's nameservers page."""
|
||||||
page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
|
page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
|
||||||
|
@ -974,6 +982,117 @@ class TestDomainNameservers(TestDomainOverview):
|
||||||
page = result.follow()
|
page = result.follow()
|
||||||
self.assertContains(page, "The name servers for this domain have been updated")
|
self.assertContains(page, "The name servers for this domain have been updated")
|
||||||
|
|
||||||
|
def test_domain_nameservers_can_blank_out_first_or_second_one_if_enough_entries(self):
|
||||||
|
"""Nameserver form submits successfully with 2 valid inputs, even if the first or
|
||||||
|
second entries are blanked out.
|
||||||
|
|
||||||
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
|
"""
|
||||||
|
|
||||||
|
nameserver1 = ""
|
||||||
|
nameserver2 = "ns2.igorville.gov"
|
||||||
|
nameserver3 = "ns3.igorville.gov"
|
||||||
|
valid_ip = ""
|
||||||
|
valid_ip_2 = "128.0.0.2"
|
||||||
|
valid_ip_3 = "128.0.0.3"
|
||||||
|
nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
nameservers_page.form["form-0-server"] = nameserver1
|
||||||
|
nameservers_page.form["form-0-ip"] = valid_ip
|
||||||
|
nameservers_page.form["form-1-server"] = nameserver2
|
||||||
|
nameservers_page.form["form-1-ip"] = valid_ip_2
|
||||||
|
nameservers_page.form["form-2-server"] = nameserver3
|
||||||
|
nameservers_page.form["form-2-ip"] = valid_ip_3
|
||||||
|
with less_console_noise(): # swallow log warning message
|
||||||
|
result = nameservers_page.form.submit()
|
||||||
|
|
||||||
|
# form submission was a successful post, response should be a 302
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(
|
||||||
|
result["Location"],
|
||||||
|
reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}),
|
||||||
|
)
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
nameservers_page = result.follow()
|
||||||
|
self.assertContains(nameservers_page, "The name servers for this domain have been updated")
|
||||||
|
|
||||||
|
nameserver1 = "ns1.igorville.gov"
|
||||||
|
nameserver2 = ""
|
||||||
|
nameserver3 = "ns3.igorville.gov"
|
||||||
|
valid_ip = "128.0.0.1"
|
||||||
|
valid_ip_2 = ""
|
||||||
|
valid_ip_3 = "128.0.0.3"
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
nameservers_page.form["form-0-server"] = nameserver1
|
||||||
|
nameservers_page.form["form-0-ip"] = valid_ip
|
||||||
|
nameservers_page.form["form-1-server"] = nameserver2
|
||||||
|
nameservers_page.form["form-1-ip"] = valid_ip_2
|
||||||
|
nameservers_page.form["form-2-server"] = nameserver3
|
||||||
|
nameservers_page.form["form-2-ip"] = valid_ip_3
|
||||||
|
with less_console_noise(): # swallow log warning message
|
||||||
|
result = nameservers_page.form.submit()
|
||||||
|
|
||||||
|
# form submission was a successful post, response should be a 302
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(
|
||||||
|
result["Location"],
|
||||||
|
reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}),
|
||||||
|
)
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
nameservers_page = result.follow()
|
||||||
|
self.assertContains(nameservers_page, "The name servers for this domain have been updated")
|
||||||
|
|
||||||
|
def test_domain_nameservers_can_blank_out_first_and_second_one_if_enough_entries(self):
|
||||||
|
"""Nameserver form submits successfully with 2 valid inputs, even if the first and
|
||||||
|
second entries are blanked out.
|
||||||
|
|
||||||
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We need to start with a domain with 4 nameservers otherwise the formset in the test environment
|
||||||
|
# will only have 3 forms
|
||||||
|
nameserver1 = ""
|
||||||
|
nameserver2 = ""
|
||||||
|
nameserver3 = "ns3.igorville.gov"
|
||||||
|
nameserver4 = "ns4.igorville.gov"
|
||||||
|
valid_ip = ""
|
||||||
|
valid_ip_2 = ""
|
||||||
|
valid_ip_3 = ""
|
||||||
|
valid_ip_4 = ""
|
||||||
|
nameservers_page = self.app.get(
|
||||||
|
reverse("domain-dns-nameservers", kwargs={"pk": self.domain_with_four_nameservers.id})
|
||||||
|
)
|
||||||
|
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
# Minimal check to ensure the form is loaded correctly
|
||||||
|
self.assertEqual(nameservers_page.form["form-0-server"].value, "ns1.my-nameserver-1.com")
|
||||||
|
self.assertEqual(nameservers_page.form["form-3-server"].value, "ns1.explosive-chicken-nuggets.com")
|
||||||
|
|
||||||
|
nameservers_page.form["form-0-server"] = nameserver1
|
||||||
|
nameservers_page.form["form-0-ip"] = valid_ip
|
||||||
|
nameservers_page.form["form-1-server"] = nameserver2
|
||||||
|
nameservers_page.form["form-1-ip"] = valid_ip_2
|
||||||
|
nameservers_page.form["form-2-server"] = nameserver3
|
||||||
|
nameservers_page.form["form-2-ip"] = valid_ip_3
|
||||||
|
nameservers_page.form["form-3-server"] = nameserver4
|
||||||
|
nameservers_page.form["form-3-ip"] = valid_ip_4
|
||||||
|
with less_console_noise(): # swallow log warning message
|
||||||
|
result = nameservers_page.form.submit()
|
||||||
|
|
||||||
|
# form submission was a successful post, response should be a 302
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(
|
||||||
|
result["Location"],
|
||||||
|
reverse("domain-dns-nameservers", kwargs={"pk": self.domain_with_four_nameservers.id}),
|
||||||
|
)
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
nameservers_page = result.follow()
|
||||||
|
self.assertContains(nameservers_page, "The name servers for this domain have been updated")
|
||||||
|
|
||||||
def test_domain_nameservers_form_invalid(self):
|
def test_domain_nameservers_form_invalid(self):
|
||||||
"""Nameserver form does not submit with invalid data.
|
"""Nameserver form does not submit with invalid data.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue