Merge branch 'main' into za/1676-require-investigator-da

This commit is contained in:
zandercymatics 2024-02-26 08:53:51 -07:00
commit 6680d7fe17
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
5 changed files with 252 additions and 18 deletions

View file

@ -759,6 +759,14 @@ class DomainInformationAdmin(ListHeaderAdmin):
# to activate the edit/delete/view buttons # to activate the edit/delete/view buttons
filter_horizontal = ("other_contacts",) filter_horizontal = ("other_contacts",)
autocomplete_fields = [
"creator",
"domain_application",
"authorizing_official",
"domain",
"submitter",
]
# Table ordering # Table ordering
ordering = ["domain__name"] ordering = ["domain__name"]
@ -1162,6 +1170,14 @@ class DomainInformationInline(admin.StackedInline):
# to activate the edit/delete/view buttons # to activate the edit/delete/view buttons
filter_horizontal = ("other_contacts",) filter_horizontal = ("other_contacts",)
autocomplete_fields = [
"creator",
"domain_application",
"authorizing_official",
"domain",
"submitter",
]
def formfield_for_manytomany(self, db_field, request, **kwargs): def formfield_for_manytomany(self, db_field, request, **kwargs):
"""customize the behavior of formfields with manytomany relationships. the customized """customize the behavior of formfields with manytomany relationships. the customized
behavior includes sorting of objects in lists as well as customizing helper text""" behavior includes sorting of objects in lists as well as customizing helper text"""

View file

@ -438,7 +438,6 @@ class Domain(TimeStampedModel, DomainHelper):
raise NameserverError(code=nsErrorCodes.INVALID_HOST, nameserver=nameserver) raise NameserverError(code=nsErrorCodes.INVALID_HOST, nameserver=nameserver)
elif cls.isSubdomain(name, nameserver) and (ip is None or ip == []): elif cls.isSubdomain(name, nameserver) and (ip is None or ip == []):
raise NameserverError(code=nsErrorCodes.MISSING_IP, nameserver=nameserver) raise NameserverError(code=nsErrorCodes.MISSING_IP, nameserver=nameserver)
elif not cls.isSubdomain(name, nameserver) and (ip is not None and ip != []): elif not cls.isSubdomain(name, nameserver) and (ip is not None and ip != []):
raise NameserverError(code=nsErrorCodes.GLUE_RECORD_NOT_ALLOWED, nameserver=nameserver, ip=ip) raise NameserverError(code=nsErrorCodes.GLUE_RECORD_NOT_ALLOWED, nameserver=nameserver, ip=ip)
elif ip is not None and ip != []: elif ip is not None and ip != []:
@ -1789,6 +1788,10 @@ class Domain(TimeStampedModel, DomainHelper):
for cleaned_host in cleaned_hosts: for cleaned_host in cleaned_hosts:
# Check if the cleaned_host already exists # Check if the cleaned_host already exists
host_in_db, host_created = Host.objects.get_or_create(domain=self, name=cleaned_host["name"]) host_in_db, host_created = Host.objects.get_or_create(domain=self, name=cleaned_host["name"])
# Check if the nameserver is a subdomain of the current domain
# If it is NOT a subdomain, we remove the IP address
if not Domain.isSubdomain(self.name, cleaned_host["name"]):
cleaned_host["addrs"] = []
# Get cleaned list of ips for update # Get cleaned list of ips for update
cleaned_ips = cleaned_host["addrs"] cleaned_ips = cleaned_host["addrs"]
if not host_created: if not host_created:

View file

@ -694,6 +694,56 @@ class MockEppLib(TestCase):
], ],
ex_date=datetime.date(2023, 5, 25), ex_date=datetime.date(2023, 5, 25),
) )
mockDataInfoDomainSubdomain = fakedEppObject(
"fakePw",
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
hosts=["fake.meoward.gov"],
statuses=[
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
ex_date=datetime.date(2023, 5, 25),
)
mockDataInfoDomainSubdomainAndIPAddress = fakedEppObject(
"fakePw",
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
hosts=["fake.meow.gov"],
statuses=[
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
ex_date=datetime.date(2023, 5, 25),
addrs=[common.Ip(addr="2.0.0.8")],
)
mockDataInfoDomainNotSubdomainNoIP = fakedEppObject(
"fakePw",
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
hosts=["fake.meow.com"],
statuses=[
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
ex_date=datetime.date(2023, 5, 25),
)
mockDataInfoDomainSubdomainNoIP = fakedEppObject(
"fakePw",
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
hosts=["fake.subdomainwoip.gov"],
statuses=[
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
ex_date=datetime.date(2023, 5, 25),
)
mockDataExtensionDomain = fakedEppObject( mockDataExtensionDomain = fakedEppObject(
"fakePw", "fakePw",
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
@ -831,6 +881,24 @@ class MockEppLib(TestCase):
addrs=[common.Ip(addr="1.2.3.4"), common.Ip(addr="2.3.4.5")], addrs=[common.Ip(addr="1.2.3.4"), common.Ip(addr="2.3.4.5")],
) )
mockDataInfoHosts1IP = fakedEppObject(
"lastPw",
cr_date=make_aware(datetime.datetime(2023, 8, 25, 19, 45, 35)),
addrs=[common.Ip(addr="2.0.0.8")],
)
mockDataInfoHostsNotSubdomainNoIP = fakedEppObject(
"lastPw",
cr_date=make_aware(datetime.datetime(2023, 8, 26, 19, 45, 35)),
addrs=[],
)
mockDataInfoHostsSubdomainNoIP = fakedEppObject(
"lastPw",
cr_date=make_aware(datetime.datetime(2023, 8, 27, 19, 45, 35)),
addrs=[],
)
mockDataHostChange = fakedEppObject("lastPw", cr_date=make_aware(datetime.datetime(2023, 8, 25, 19, 45, 35))) mockDataHostChange = fakedEppObject("lastPw", cr_date=make_aware(datetime.datetime(2023, 8, 25, 19, 45, 35)))
addDsData1 = { addDsData1 = {
"keyTag": 1234, "keyTag": 1234,
@ -997,6 +1065,8 @@ class MockEppLib(TestCase):
return self.mockDeleteDomainCommands(_request, cleaned) return self.mockDeleteDomainCommands(_request, cleaned)
case commands.RenewDomain: case commands.RenewDomain:
return self.mockRenewDomainCommand(_request, cleaned) return self.mockRenewDomainCommand(_request, cleaned)
case commands.InfoHost:
return self.mockInfoHostCommmands(_request, cleaned)
case _: case _:
return MagicMock(res_data=[self.mockDataInfoHosts]) return MagicMock(res_data=[self.mockDataInfoHosts])
@ -1011,6 +1081,25 @@ class MockEppLib(TestCase):
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
) )
def mockInfoHostCommmands(self, _request, cleaned):
request_name = getattr(_request, "name", None)
# Define a dictionary to map request names to data and extension values
request_mappings = {
"fake.meow.gov": (self.mockDataInfoHosts1IP, None), # is subdomain and has ip
"fake.meow.com": (self.mockDataInfoHostsNotSubdomainNoIP, None), # not subdomain w no ip
"fake.subdomainwoip.gov": (self.mockDataInfoHostsSubdomainNoIP, None), # subdomain w no ip
}
# Retrieve the corresponding values from the dictionary
default_mapping = (self.mockDataInfoHosts, None)
res_data, extensions = request_mappings.get(request_name, default_mapping)
return MagicMock(
res_data=[res_data],
extensions=[extensions] if extensions is not None else [],
)
def mockUpdateHostCommands(self, _request, cleaned): def mockUpdateHostCommands(self, _request, cleaned):
test_ws_ip = common.Ip(addr="1.1. 1.1") test_ws_ip = common.Ip(addr="1.1. 1.1")
addrs_submitted = getattr(_request, "addrs", []) addrs_submitted = getattr(_request, "addrs", [])
@ -1099,6 +1188,10 @@ class MockEppLib(TestCase):
"adomain2.gov": (self.InfoDomainWithVerisignSecurityContact, None), "adomain2.gov": (self.InfoDomainWithVerisignSecurityContact, None),
"defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None), "defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None),
"justnameserver.com": (self.justNameserver, None), "justnameserver.com": (self.justNameserver, None),
"meoward.gov": (self.mockDataInfoDomainSubdomain, None),
"meow.gov": (self.mockDataInfoDomainSubdomainAndIPAddress, None),
"fakemeow.gov": (self.mockDataInfoDomainNotSubdomainNoIP, None),
"subdomainwoip.gov": (self.mockDataInfoDomainSubdomainNoIP, None),
} }
# Retrieve the corresponding values from the dictionary # Retrieve the corresponding values from the dictionary

View file

@ -96,7 +96,7 @@ class TestDomainCache(MockEppLib):
self.mockedSendFunction.assert_has_calls(expectedCalls) self.mockedSendFunction.assert_has_calls(expectedCalls)
def test_cache_nested_elements(self): def test_cache_nested_elements_not_subdomain(self):
"""Cache works correctly with the nested objects cache and hosts""" """Cache works correctly with the nested objects cache and hosts"""
with less_console_noise(): with less_console_noise():
domain, _ = Domain.objects.get_or_create(name="igorville.gov") domain, _ = Domain.objects.get_or_create(name="igorville.gov")
@ -113,7 +113,7 @@ class TestDomainCache(MockEppLib):
} }
expectedHostsDict = { expectedHostsDict = {
"name": self.mockDataInfoDomain.hosts[0], "name": self.mockDataInfoDomain.hosts[0],
"addrs": [item.addr for item in self.mockDataInfoHosts.addrs], "addrs": [], # should return empty bc fake.host.com is not a subdomain of igorville.gov
"cr_date": self.mockDataInfoHosts.cr_date, "cr_date": self.mockDataInfoHosts.cr_date,
} }
@ -138,6 +138,59 @@ class TestDomainCache(MockEppLib):
# invalidate cache # invalidate cache
domain._cache = {} domain._cache = {}
# get host
domain._get_property("hosts")
# Should return empty bc fake.host.com is not a subdomain of igorville.gov
self.assertEqual(domain._cache["hosts"], [expectedHostsDict])
# get contacts
domain._get_property("contacts")
self.assertEqual(domain._cache["hosts"], [expectedHostsDict])
self.assertEqual(domain._cache["contacts"], expectedContactsDict)
def test_cache_nested_elements_is_subdomain(self):
"""Cache works correctly with the nested objects cache and hosts"""
with less_console_noise():
domain, _ = Domain.objects.get_or_create(name="meoward.gov")
# The contact list will initially contain objects of type 'DomainContact'
# this is then transformed into PublicContact, and cache should NOT
# hold onto the DomainContact object
expectedUnfurledContactsList = [
common.DomainContact(contact="123", type="security"),
]
expectedContactsDict = {
PublicContact.ContactTypeChoices.ADMINISTRATIVE: None,
PublicContact.ContactTypeChoices.SECURITY: "123",
PublicContact.ContactTypeChoices.TECHNICAL: None,
}
expectedHostsDict = {
"name": self.mockDataInfoDomainSubdomain.hosts[0],
"addrs": [item.addr for item in self.mockDataInfoHosts.addrs],
"cr_date": self.mockDataInfoHosts.cr_date,
}
# this can be changed when the getter for contacts is implemented
domain._get_property("contacts")
# check domain info is still correct and not overridden
self.assertEqual(domain._cache["auth_info"], self.mockDataInfoDomainSubdomain.auth_info)
self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomainSubdomain.cr_date)
# check contacts
self.assertEqual(domain._cache["_contacts"], self.mockDataInfoDomainSubdomain.contacts)
# The contact list should not contain what is sent by the registry by default,
# as _fetch_cache will transform the type to PublicContact
self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList)
self.assertEqual(domain._cache["contacts"], expectedContactsDict)
# get and check hosts is set correctly
domain._get_property("hosts")
self.assertEqual(domain._cache["hosts"], [expectedHostsDict])
self.assertEqual(domain._cache["contacts"], expectedContactsDict)
# invalidate cache
domain._cache = {}
# get host # get host
domain._get_property("hosts") domain._get_property("hosts")
self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) self.assertEqual(domain._cache["hosts"], [expectedHostsDict])
@ -1585,31 +1638,100 @@ class TestRegistrantNameservers(MockEppLib):
self.assertEqual(nameservers[0][1], ["1.1.1.1"]) self.assertEqual(nameservers[0][1], ["1.1.1.1"])
patcher.stop() patcher.stop()
def test_nameservers_stored_on_fetch_cache(self): def test_nameservers_stored_on_fetch_cache_a_subdomain_with_ip(self):
"""
#1: Nameserver is a subdomain, and has an IP address
referenced by mockDataInfoDomainSubdomainAndIPAddress
"""
with less_console_noise():
# make the domain
domain, _ = Domain.objects.get_or_create(name="meow.gov", state=Domain.State.READY)
# mock the get_or_create methods for Host and HostIP
with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object(
HostIP.objects, "get_or_create"
) as mock_host_ip_get_or_create:
mock_host_get_or_create.return_value = (Host(domain=domain), True)
mock_host_ip_get_or_create.return_value = (HostIP(), True)
# force fetch_cache to be called, which will return above documented mocked hosts
domain.nameservers
mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.meow.gov")
# Retrieve the mocked_host from the return value of the mock
actual_mocked_host, _ = mock_host_get_or_create.return_value
mock_host_ip_get_or_create.assert_called_with(address="2.0.0.8", host=actual_mocked_host)
self.assertEqual(mock_host_ip_get_or_create.call_count, 1)
def test_nameservers_stored_on_fetch_cache_a_subdomain_without_ip(self):
"""
#2: Nameserver is a subdomain, but doesn't have an IP address associated
referenced by mockDataInfoDomainSubdomainNoIP
"""
with less_console_noise():
# make the domain
domain, _ = Domain.objects.get_or_create(name="subdomainwoip.gov", state=Domain.State.READY)
# mock the get_or_create methods for Host and HostIP
with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object(
HostIP.objects, "get_or_create"
) as mock_host_ip_get_or_create:
mock_host_get_or_create.return_value = (Host(domain=domain), True)
mock_host_ip_get_or_create.return_value = (HostIP(), True)
# force fetch_cache to be called, which will return above documented mocked hosts
domain.nameservers
mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.subdomainwoip.gov")
mock_host_ip_get_or_create.assert_not_called()
self.assertEqual(mock_host_ip_get_or_create.call_count, 0)
def test_nameservers_stored_on_fetch_cache_not_subdomain_with_ip(self):
""" """
Scenario: Nameservers are stored in db when they are retrieved from fetch_cache. Scenario: Nameservers are stored in db when they are retrieved from fetch_cache.
Verify the success of this by asserting get_or_create calls to db. Verify the success of this by asserting get_or_create calls to db.
The mocked data for the EPP calls returns a host name The mocked data for the EPP calls returns a host name
of 'fake.host.com' from InfoDomain and an array of 2 IPs: 1.2.3.4 and 2.3.4.5 of 'fake.host.com' from InfoDomain and an array of 2 IPs: 1.2.3.4 and 2.3.4.5
from InfoHost from InfoHost
#3: Nameserver is not a subdomain, but it does have an IP address returned
due to how we set up our defaults
""" """
with less_console_noise(): with less_console_noise():
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
# mock the get_or_create methods for Host and HostIP
with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object( with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object(
HostIP.objects, "get_or_create" HostIP.objects, "get_or_create"
) as mock_host_ip_get_or_create: ) as mock_host_ip_get_or_create:
# Set the return value for the mocks mock_host_get_or_create.return_value = (Host(domain=domain), True)
mock_host_get_or_create.return_value = (Host(), True)
mock_host_ip_get_or_create.return_value = (HostIP(), True) mock_host_ip_get_or_create.return_value = (HostIP(), True)
# force fetch_cache to be called, which will return above documented mocked hosts # force fetch_cache to be called, which will return above documented mocked hosts
domain.nameservers domain.nameservers
# assert that the mocks are called
mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.host.com") mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.host.com")
# Retrieve the mocked_host from the return value of the mock mock_host_ip_get_or_create.assert_not_called()
actual_mocked_host, _ = mock_host_get_or_create.return_value self.assertEqual(mock_host_ip_get_or_create.call_count, 0)
mock_host_ip_get_or_create.assert_called_with(address="2.3.4.5", host=actual_mocked_host)
self.assertEqual(mock_host_ip_get_or_create.call_count, 2) def test_nameservers_stored_on_fetch_cache_not_subdomain_without_ip(self):
"""
#4: Nameserver is not a subdomain and doesn't have an associated IP address
referenced by self.mockDataInfoDomainNotSubdomainNoIP
"""
with less_console_noise():
domain, _ = Domain.objects.get_or_create(name="fakemeow.gov", state=Domain.State.READY)
with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object(
HostIP.objects, "get_or_create"
) as mock_host_ip_get_or_create:
mock_host_get_or_create.return_value = (Host(domain=domain), True)
mock_host_ip_get_or_create.return_value = (HostIP(), True)
# force fetch_cache to be called, which will return above documented mocked hosts
domain.nameservers
mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.meow.com")
mock_host_ip_get_or_create.assert_not_called()
self.assertEqual(mock_host_ip_get_or_create.call_count, 0)
@skip("not implemented yet") @skip("not implemented yet")
def test_update_is_unsuccessful(self): def test_update_is_unsuccessful(self):

View file

@ -821,14 +821,15 @@ class TestDomainNameservers(TestDomainOverview):
nameserver1 = "ns1.igorville.gov" nameserver1 = "ns1.igorville.gov"
nameserver2 = "ns2.igorville.gov" nameserver2 = "ns2.igorville.gov"
valid_ip = "1.1. 1.1" valid_ip = "1.1. 1.1"
# initial nameservers page has one server with two ips valid_ip_2 = "2.2. 2.2"
# have to throw an error in order to test that the whitespace has been stripped from ip # have to throw an error in order to test that the whitespace has been stripped from ip
nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# attempt to submit the form without one host and an ip with whitespace # attempt to submit the form without one host and an ip with whitespace
nameservers_page.form["form-0-server"] = nameserver1 nameservers_page.form["form-0-server"] = nameserver1
nameservers_page.form["form-1-ip"] = valid_ip nameservers_page.form["form-0-ip"] = valid_ip
nameservers_page.form["form-1-ip"] = valid_ip_2
nameservers_page.form["form-1-server"] = nameserver2 nameservers_page.form["form-1-server"] = nameserver2
with less_console_noise(): # swallow log warning message with less_console_noise(): # swallow log warning message
result = nameservers_page.form.submit() result = nameservers_page.form.submit()
@ -937,15 +938,14 @@ class TestDomainNameservers(TestDomainOverview):
nameserver1 = "ns1.igorville.gov" nameserver1 = "ns1.igorville.gov"
nameserver2 = "ns2.igorville.gov" nameserver2 = "ns2.igorville.gov"
valid_ip = "127.0.0.1" valid_ip = "127.0.0.1"
# initial nameservers page has one server with two ips valid_ip_2 = "128.0.0.2"
nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# attempt to submit the form without two hosts, both subdomains,
# only one has ips
nameservers_page.form["form-0-server"] = nameserver1 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-server"] = nameserver2
nameservers_page.form["form-1-ip"] = valid_ip nameservers_page.form["form-1-ip"] = valid_ip_2
with less_console_noise(): # swallow log warning message with less_console_noise(): # swallow log warning message
result = nameservers_page.form.submit() result = nameservers_page.form.submit()
# form submission was a successful post, response should be a 302 # form submission was a successful post, response should be a 302