mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-24 19:48:36 +02:00
Merge branch 'main' into dk/1016-nameservers-ui
This commit is contained in:
commit
a15f1b392e
8 changed files with 389 additions and 172 deletions
89
docs/architecture/decisions/0023-use-geventconnpool.md
Normal file
89
docs/architecture/decisions/0023-use-geventconnpool.md
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# 22. Use geventconnpool library for Connection Pooling
|
||||||
|
|
||||||
|
Date: 2023-13-10
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
In Review
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
When sending and receiving data from the registry, we use the [EPPLib](https://github.com/cisagov/epplib) library to facilitate that process. To manage these connections within our application, we utilize a module named `epplibwrapper` which serves as a bridge between getgov and the EPPLib library. As part of this process, `epplibwrapper` will instantiate a client that handles sending/receiving data.
|
||||||
|
|
||||||
|
At present, each time we need to send a command to the registry, the client establishes a new connection to handle this task. This becomes inefficient when dealing with multiple calls in parallel or in series, as we have to initiate a handshake for each of them. To mitigate this issue, a widely adopted solution is to use a [connection pool](https://en.wikipedia.org/wiki/Connection_pool). In general, a connection pool stores a cache of active connections so that rather than restarting the handshake process when it is unnecessary, we can utilize an existing connection to avoid this problem.
|
||||||
|
|
||||||
|
In practice, the lack of a connection pool has resulted in performance issues when dealing with connections to and from the registry. Given the unique nature of our development stack, our options for prebuilt libraries are limited. Out of our available options, a library called [`geventconnpool`](https://github.com/rasky/geventconnpool) was identified that most closely matched our needs.
|
||||||
|
|
||||||
|
## Considered Options
|
||||||
|
|
||||||
|
**Option 1:** Use the existing connection pool library `geventconnpool`.
|
||||||
|
<details open>
|
||||||
|
<summary>➕ Pros</summary>
|
||||||
|
|
||||||
|
- Saves development time and effort.
|
||||||
|
- A tiny library that is easy to audit and understand.
|
||||||
|
- Built to be flexible, so every built-in function can be overridden with minimal effort.
|
||||||
|
- This library has been used for [EPP before](https://github.com/rasky/geventconnpool/issues/9).
|
||||||
|
- Uses [`gevent`](http://www.gevent.org/) for coroutines, which is reliable and well maintained.
|
||||||
|
- [`gevent`](http://www.gevent.org/) is used in our WSGI web server.
|
||||||
|
- This library is the closest match to our needs that we have found.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details open>
|
||||||
|
<summary>➖ Cons</summary>
|
||||||
|
|
||||||
|
- Not a well maintained library, could require a fork if a dependency breaks.
|
||||||
|
- Heavily reliant on `gevent`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Option 2:** Write our own connection pool logic.
|
||||||
|
<details open>
|
||||||
|
<summary>➕ Pros</summary>
|
||||||
|
|
||||||
|
- Full control over functionality, can be tailored to our specific needs.
|
||||||
|
- Highly specific to our stack, could be fine tuned for performance.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details open>
|
||||||
|
<summary>➖ Cons</summary>
|
||||||
|
|
||||||
|
- Requires significant development time and effort, needs thorough testing.
|
||||||
|
- Would require managing with and developing around concurrency.
|
||||||
|
- Introduces the potential for many unseen bugs.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Option 3:** Modify an existing library which we will then tailor to our needs.
|
||||||
|
<details open>
|
||||||
|
<summary>➕ Pros</summary>
|
||||||
|
|
||||||
|
- Savings in development time and effort, can be tailored to our specific needs.
|
||||||
|
- Good middleground between the first two options.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details open>
|
||||||
|
<summary>➖ Cons</summary>
|
||||||
|
|
||||||
|
- Could introduce complexity, potential issues with maintaining the modified library.
|
||||||
|
- May not be necessary if the given library is flexible enough.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We have decided to go with option 1, which is to use the `geventconnpool` library. It closely matches our needs and offers several advantages. Of note, it significantly saves on development time and it is inherently flexible. This allows us to easily change functionality with minimal effort. In addition, the gevent library (which this uses) offers performance benefits due to it being a) written in [cython](https://cython.org/), b) very well maintained and purpose built for tasks such as these, and c) used in our WGSI server.
|
||||||
|
|
||||||
|
In summary, this decision was driven by the library's flexibility, simplicity, and compatibility with our tech stack. We acknowledge the risk associated with its maintenance status, but believe that the benefit outweighs the risk.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
While its small size makes it easy to work around, `geventconnpool` is not actively maintained. Its last update was in 2021, and as such there is a risk that its dependencies (gevent) will outpace this library and cause it to break. If such an event occurs, it would require that we fork the library and fix those issues. See option 3 pros/cons.
|
||||||
|
|
||||||
|
## Mitigation Plan
|
||||||
|
To manage this risk, we'll:
|
||||||
|
|
||||||
|
1. Monitor the gevent library for updates.
|
||||||
|
2. Design the connection pool logic abstractly such that we can easily swap the underlying logic out without needing (or minimizing the need) to rewrite code in `epplibwrapper`.
|
||||||
|
3. Document a process for forking and maintaining the library if it becomes necessary, including testing procedures.
|
||||||
|
4. Establish a contingency plan for reverting to a previous system state or switching to a different library if significant issues arise with `gevent` or `geventconnpool`.
|
|
@ -45,7 +45,7 @@ except NameError:
|
||||||
# Attn: these imports should NOT be at the top of the file
|
# Attn: these imports should NOT be at the top of the file
|
||||||
try:
|
try:
|
||||||
from .client import CLIENT, commands
|
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.models import common, info
|
||||||
from epplib.responses import extensions
|
from epplib.responses import extensions
|
||||||
from epplib import responses
|
from epplib import responses
|
||||||
|
@ -61,4 +61,6 @@ __all__ = [
|
||||||
"info",
|
"info",
|
||||||
"ErrorCode",
|
"ErrorCode",
|
||||||
"RegistryError",
|
"RegistryError",
|
||||||
|
"CANNOT_CONTACT_REGISTRY",
|
||||||
|
"GENERIC_ERROR",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
|
CANNOT_CONTACT_REGISTRY = "Update failed. Cannot contact the registry."
|
||||||
|
GENERIC_ERROR = "Value entered was wrong."
|
||||||
|
|
||||||
|
|
||||||
class ErrorCode(IntEnum):
|
class ErrorCode(IntEnum):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -652,6 +652,9 @@ SESSION_COOKIE_SAMESITE = "Lax"
|
||||||
# instruct browser to only send cookie via HTTPS
|
# instruct browser to only send cookie via HTTPS
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
# session engine to cache session information
|
||||||
|
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||||
|
|
||||||
# ~ Set by django.middleware.clickjacking.XFrameOptionsMiddleware
|
# ~ Set by django.middleware.clickjacking.XFrameOptionsMiddleware
|
||||||
# prevent clickjacking by instructing the browser not to load
|
# prevent clickjacking by instructing the browser not to load
|
||||||
# our site within an iframe
|
# our site within an iframe
|
||||||
|
|
|
@ -260,7 +260,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
"""Creates the host object in the registry
|
"""Creates the host object in the registry
|
||||||
doesn't add the created host to the domain
|
doesn't add the created host to the domain
|
||||||
returns ErrorCode (int)"""
|
returns ErrorCode (int)"""
|
||||||
logger.info("Creating host")
|
|
||||||
if addrs is not None:
|
if addrs is not None:
|
||||||
addresses = [epp.Ip(addr=addr) for addr in addrs]
|
addresses = [epp.Ip(addr=addr) for addr in addrs]
|
||||||
request = commands.CreateHost(name=host, addrs=addresses)
|
request = commands.CreateHost(name=host, addrs=addresses)
|
||||||
|
@ -821,7 +820,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
and errorCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
and errorCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
||||||
):
|
):
|
||||||
# TODO- ticket #433 look here for error handling
|
# 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
|
# contact doesn't exist on the domain yet
|
||||||
logger.info("_set_singleton_contact()-> contact has been added to the registry")
|
logger.info("_set_singleton_contact()-> contact has been added to the registry")
|
||||||
|
@ -1248,7 +1247,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
count = 0
|
count = 0
|
||||||
while not exitEarly and count < 3:
|
while not exitEarly and count < 3:
|
||||||
try:
|
try:
|
||||||
logger.info("Getting domain info from epp")
|
|
||||||
req = commands.InfoDomain(name=self.name)
|
req = commands.InfoDomain(name=self.name)
|
||||||
domainInfoResponse = registry.send(req, cleaned=True)
|
domainInfoResponse = registry.send(req, cleaned=True)
|
||||||
exitEarly = True
|
exitEarly = True
|
||||||
|
@ -1687,74 +1685,84 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
"""Contact registry for info about a domain."""
|
"""Contact registry for info about a domain."""
|
||||||
try:
|
try:
|
||||||
# get info from registry
|
# get info from registry
|
||||||
dataResponse = self._get_or_create_domain()
|
data_response = self._get_or_create_domain()
|
||||||
data = dataResponse.res_data[0]
|
cache = self._extract_data_from_response(data_response)
|
||||||
# extract properties from response
|
|
||||||
# (Ellipsis is used to mean "null")
|
# remove null properties (to distinguish between "a value of None" and null)
|
||||||
cache = {
|
cleaned = self._remove_null_properties(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 ...}
|
|
||||||
|
|
||||||
# statuses can just be a list no need to keep the epp object
|
|
||||||
if "statuses" in cleaned:
|
if "statuses" in cleaned:
|
||||||
cleaned["statuses"] = [status.state for status in cleaned["statuses"]]
|
cleaned["statuses"] = [status.state for status in cleaned["statuses"]]
|
||||||
|
|
||||||
# get extensions info, if there is any
|
cleaned["dnssecdata"] = self._get_dnssec_data(data_response.extensions)
|
||||||
# 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
|
|
||||||
# Capture and store old hosts and contacts from cache if they exist
|
# Capture and store old hosts and contacts from cache if they exist
|
||||||
old_cache_hosts = self._cache.get("hosts")
|
old_cache_hosts = self._cache.get("hosts")
|
||||||
old_cache_contacts = self._cache.get("contacts")
|
old_cache_contacts = self._cache.get("contacts")
|
||||||
|
|
||||||
# get contact info, if there are any
|
if fetch_contacts:
|
||||||
if (
|
cleaned["contacts"] = self._get_contacts(cleaned.get("_contacts", []))
|
||||||
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 old_cache_hosts is not None:
|
if old_cache_hosts is not None:
|
||||||
|
logger.debug("resetting cleaned['hosts'] to old_cache_hosts")
|
||||||
cleaned["hosts"] = old_cache_hosts
|
cleaned["hosts"] = old_cache_hosts
|
||||||
|
|
||||||
# get nameserver info, if there are any
|
if fetch_hosts:
|
||||||
if (
|
cleaned["hosts"] = self._get_hosts(cleaned.get("_hosts", []))
|
||||||
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 old_cache_contacts is not None:
|
if old_cache_contacts is not None:
|
||||||
cleaned["contacts"] = old_cache_contacts
|
cleaned["contacts"] = old_cache_contacts
|
||||||
# replace the prior cache with new data
|
|
||||||
self._cache = cleaned
|
self._cache = cleaned
|
||||||
|
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
logger.error(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):
|
def _get_or_create_public_contact(self, public_contact: PublicContact):
|
||||||
"""Tries to find a PublicContact object in our DB.
|
"""Tries to find a PublicContact object in our DB.
|
||||||
If it can't, it'll create it. Returns PublicContact"""
|
If it can't, it'll create it. Returns PublicContact"""
|
||||||
|
|
|
@ -33,6 +33,8 @@ from epplibwrapper import (
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -793,14 +795,8 @@ class MockEppLib(TestCase):
|
||||||
return self.mockInfoContactCommands(_request, cleaned)
|
return self.mockInfoContactCommands(_request, cleaned)
|
||||||
elif isinstance(_request, commands.UpdateDomain):
|
elif isinstance(_request, commands.UpdateDomain):
|
||||||
return self.mockUpdateDomainCommands(_request, cleaned)
|
return self.mockUpdateDomainCommands(_request, cleaned)
|
||||||
elif (
|
elif isinstance(_request, commands.CreateContact):
|
||||||
isinstance(_request, commands.CreateContact)
|
return self.mockCreateContactCommands(_request, cleaned)
|
||||||
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):
|
elif isinstance(_request, commands.CreateHost):
|
||||||
return MagicMock(
|
return MagicMock(
|
||||||
res_data=[self.mockDataHostChange],
|
res_data=[self.mockDataHostChange],
|
||||||
|
@ -897,6 +893,24 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
return MagicMock(res_data=[mocked_result])
|
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):
|
def setUp(self):
|
||||||
"""mock epp send function as this will fail locally"""
|
"""mock epp send function as this will fail locally"""
|
||||||
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
||||||
|
|
|
@ -1554,6 +1554,78 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
success_page, "The security email for this domain has been updated"
|
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):
|
class TestDomainDNSSEC(TestDomainOverview):
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,11 @@ from registrar.models import (
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
)
|
)
|
||||||
from registrar.models.public_contact import PublicContact
|
from registrar.models.public_contact import PublicContact
|
||||||
|
<<<<<<< HEAD
|
||||||
from registrar.utility.errors import NameserverError
|
from registrar.utility.errors import NameserverError
|
||||||
|
=======
|
||||||
|
from registrar.models.utility.contact_error import ContactError
|
||||||
|
>>>>>>> main
|
||||||
|
|
||||||
from ..forms import (
|
from ..forms import (
|
||||||
ContactForm,
|
ContactForm,
|
||||||
|
@ -42,6 +46,8 @@ from epplibwrapper import (
|
||||||
common,
|
common,
|
||||||
extensions,
|
extensions,
|
||||||
RegistryError,
|
RegistryError,
|
||||||
|
CANNOT_CONTACT_REGISTRY,
|
||||||
|
GENERIC_ERROR,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..utility.email import send_templated_email, EmailSendingError
|
from ..utility.email import send_templated_email, EmailSendingError
|
||||||
|
@ -51,7 +57,81 @@ from .utility import DomainPermissionView, DomainInvitationPermissionDeleteView
|
||||||
logger = logging.getLogger(__name__)
|
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."""
|
"""Domain detail overview page."""
|
||||||
|
|
||||||
template_name = "domain_detail.html"
|
template_name = "domain_detail.html"
|
||||||
|
@ -59,10 +139,10 @@ class DomainView(DomainPermissionView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**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
|
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:
|
if security_email is None or security_email == default_email:
|
||||||
context["security_email"] = None
|
context["security_email"] = None
|
||||||
return context
|
return context
|
||||||
|
@ -70,7 +150,7 @@ class DomainView(DomainPermissionView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
|
class DomainOrgNameAddressView(DomainFormBaseView):
|
||||||
"""Organization name and mailing address view"""
|
"""Organization name and mailing address view"""
|
||||||
|
|
||||||
model = Domain
|
model = Domain
|
||||||
|
@ -81,25 +161,13 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
|
||||||
def get_form_kwargs(self, *args, **kwargs):
|
def get_form_kwargs(self, *args, **kwargs):
|
||||||
"""Add domain_info.organization_name instance to make a bound form."""
|
"""Add domain_info.organization_name instance to make a bound form."""
|
||||||
form_kwargs = super().get_form_kwargs(*args, **kwargs)
|
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
|
return form_kwargs
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to the overview page for the domain."""
|
"""Redirect to the overview page for the domain."""
|
||||||
return reverse("domain-org-name-address", kwargs={"pk": self.object.pk})
|
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):
|
def form_valid(self, form):
|
||||||
"""The form is valid, save the organization name and mailing address."""
|
"""The form is valid, save the organization name and mailing address."""
|
||||||
form.save()
|
form.save()
|
||||||
|
@ -112,7 +180,7 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
|
class DomainAuthorizingOfficialView(DomainFormBaseView):
|
||||||
"""Domain authorizing official editing view."""
|
"""Domain authorizing official editing view."""
|
||||||
|
|
||||||
model = Domain
|
model = Domain
|
||||||
|
@ -123,25 +191,13 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
|
||||||
def get_form_kwargs(self, *args, **kwargs):
|
def get_form_kwargs(self, *args, **kwargs):
|
||||||
"""Add domain_info.authorizing_official instance to make a bound form."""
|
"""Add domain_info.authorizing_official instance to make a bound form."""
|
||||||
form_kwargs = super().get_form_kwargs(*args, **kwargs)
|
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
|
return form_kwargs
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to the overview page for the domain."""
|
"""Redirect to the overview page for the domain."""
|
||||||
return reverse("domain-authorizing-official", kwargs={"pk": self.object.pk})
|
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):
|
def form_valid(self, form):
|
||||||
"""The form is valid, save the authorizing official."""
|
"""The form is valid, save the authorizing official."""
|
||||||
form.save()
|
form.save()
|
||||||
|
@ -154,13 +210,13 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class DomainDNSView(DomainPermissionView):
|
class DomainDNSView(DomainBaseView):
|
||||||
"""DNS Information View."""
|
"""DNS Information View."""
|
||||||
|
|
||||||
template_name = "domain_dns.html"
|
template_name = "domain_dns.html"
|
||||||
|
|
||||||
|
|
||||||
class DomainNameserversView(DomainPermissionView, FormMixin):
|
class DomainNameserversView(DomainFormBaseView):
|
||||||
"""Domain nameserver editing view."""
|
"""Domain nameserver editing view."""
|
||||||
|
|
||||||
template_name = "domain_nameservers.html"
|
template_name = "domain_nameservers.html"
|
||||||
|
@ -168,8 +224,7 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""The initial value for the form (which is a formset here)."""
|
"""The initial value for the form (which is a formset here)."""
|
||||||
domain = self.get_object()
|
nameservers = self.object.nameservers
|
||||||
nameservers = domain.nameservers
|
|
||||||
initial_data = []
|
initial_data = []
|
||||||
|
|
||||||
if nameservers is not None:
|
if nameservers is not None:
|
||||||
|
@ -206,16 +261,6 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
||||||
form.fields["server"].required = False
|
form.fields["server"].required = False
|
||||||
return formset
|
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):
|
def form_valid(self, formset):
|
||||||
"""The formset is valid, perform something with it."""
|
"""The formset is valid, perform something with it."""
|
||||||
|
|
||||||
|
@ -239,6 +284,7 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# no server information in this field, skip it
|
# no server information in this field, skip it
|
||||||
pass
|
pass
|
||||||
|
<<<<<<< HEAD
|
||||||
domain = self.get_object()
|
domain = self.get_object()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -261,12 +307,19 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request, "The name servers for this domain have been updated."
|
self.request, "The name servers for this domain have been updated."
|
||||||
)
|
)
|
||||||
|
=======
|
||||||
|
self.object.nameservers = nameservers
|
||||||
|
|
||||||
|
messages.success(
|
||||||
|
self.request, "The name servers for this domain have been updated."
|
||||||
|
)
|
||||||
|
>>>>>>> main
|
||||||
|
|
||||||
# superclass has the redirect
|
# superclass has the redirect
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
|
|
||||||
class DomainDNSSECView(DomainPermissionView, FormMixin):
|
class DomainDNSSECView(DomainFormBaseView):
|
||||||
"""Domain DNSSEC editing view."""
|
"""Domain DNSSEC editing view."""
|
||||||
|
|
||||||
template_name = "domain_dnssec.html"
|
template_name = "domain_dnssec.html"
|
||||||
|
@ -276,9 +329,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
|
||||||
"""The initial value for the form (which is a formset here)."""
|
"""The initial value for the form (which is a formset here)."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
self.domain = self.get_object()
|
has_dnssec_records = self.object.dnssecdata is not None
|
||||||
|
|
||||||
has_dnssec_records = self.domain.dnssecdata is not None
|
|
||||||
|
|
||||||
# Create HTML for the modal button
|
# Create HTML for the modal button
|
||||||
modal_button = (
|
modal_button = (
|
||||||
|
@ -295,16 +346,16 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to the DNSSEC page for the domain."""
|
"""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):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Form submission posts to this view."""
|
"""Form submission posts to this view."""
|
||||||
self.domain = self.get_object()
|
self._get_domain(request)
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
if "disable_dnssec" in request.POST:
|
if "disable_dnssec" in request.POST:
|
||||||
try:
|
try:
|
||||||
self.domain.dnssecdata = {}
|
self.object.dnssecdata = {}
|
||||||
except RegistryError as err:
|
except RegistryError as err:
|
||||||
errmsg = "Error removing existing DNSSEC record(s)."
|
errmsg = "Error removing existing DNSSEC record(s)."
|
||||||
logger.error(errmsg + ": " + err)
|
logger.error(errmsg + ": " + err)
|
||||||
|
@ -319,7 +370,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class DomainDsDataView(DomainPermissionView, FormMixin):
|
class DomainDsDataView(DomainFormBaseView):
|
||||||
"""Domain DNSSEC ds data editing view."""
|
"""Domain DNSSEC ds data editing view."""
|
||||||
|
|
||||||
template_name = "domain_dsdata.html"
|
template_name = "domain_dsdata.html"
|
||||||
|
@ -328,8 +379,7 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""The initial value for the form (which is a formset here)."""
|
"""The initial value for the form (which is a formset here)."""
|
||||||
domain = self.get_object()
|
dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
|
||||||
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
|
|
||||||
initial_data = []
|
initial_data = []
|
||||||
|
|
||||||
if dnssecdata is not None:
|
if dnssecdata is not None:
|
||||||
|
@ -370,8 +420,7 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
||||||
# set the dnssec_ds_confirmed flag in the context for this view
|
# set the dnssec_ds_confirmed flag in the context for this view
|
||||||
# based either on the existence of DS Data in the domain,
|
# based either on the existence of DS Data in the domain,
|
||||||
# or on the flag stored in the session
|
# or on the flag stored in the session
|
||||||
domain = self.get_object()
|
dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
|
||||||
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
|
|
||||||
|
|
||||||
if dnssecdata is not None and dnssecdata.dsData is not None:
|
if dnssecdata is not None and dnssecdata.dsData is not None:
|
||||||
self.request.session["dnssec_ds_confirmed"] = True
|
self.request.session["dnssec_ds_confirmed"] = True
|
||||||
|
@ -383,7 +432,7 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Formset submission posts to this view."""
|
"""Formset submission posts to this view."""
|
||||||
self.object = self.get_object()
|
self._get_domain(request)
|
||||||
formset = self.get_form()
|
formset = self.get_form()
|
||||||
|
|
||||||
if "confirm-ds" in request.POST:
|
if "confirm-ds" in request.POST:
|
||||||
|
@ -423,9 +472,8 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
||||||
# as valid; this can happen if form has been added but
|
# as valid; this can happen if form has been added but
|
||||||
# not been interacted with; in that case, want to ignore
|
# not been interacted with; in that case, want to ignore
|
||||||
pass
|
pass
|
||||||
domain = self.get_object()
|
|
||||||
try:
|
try:
|
||||||
domain.dnssecdata = dnssecdata
|
self.object.dnssecdata = dnssecdata
|
||||||
except RegistryError as err:
|
except RegistryError as err:
|
||||||
errmsg = "Error updating DNSSEC data in the registry."
|
errmsg = "Error updating DNSSEC data in the registry."
|
||||||
logger.error(errmsg)
|
logger.error(errmsg)
|
||||||
|
@ -440,7 +488,7 @@ class DomainDsDataView(DomainPermissionView, FormMixin):
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
|
|
||||||
class DomainKeyDataView(DomainPermissionView, FormMixin):
|
class DomainKeyDataView(DomainFormBaseView):
|
||||||
"""Domain DNSSEC key data editing view."""
|
"""Domain DNSSEC key data editing view."""
|
||||||
|
|
||||||
template_name = "domain_keydata.html"
|
template_name = "domain_keydata.html"
|
||||||
|
@ -449,8 +497,7 @@ class DomainKeyDataView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""The initial value for the form (which is a formset here)."""
|
"""The initial value for the form (which is a formset here)."""
|
||||||
domain = self.get_object()
|
dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
|
||||||
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
|
|
||||||
initial_data = []
|
initial_data = []
|
||||||
|
|
||||||
if dnssecdata is not None:
|
if dnssecdata is not None:
|
||||||
|
@ -491,8 +538,7 @@ class DomainKeyDataView(DomainPermissionView, FormMixin):
|
||||||
# set the dnssec_key_confirmed flag in the context for this view
|
# set the dnssec_key_confirmed flag in the context for this view
|
||||||
# based either on the existence of Key Data in the domain,
|
# based either on the existence of Key Data in the domain,
|
||||||
# or on the flag stored in the session
|
# or on the flag stored in the session
|
||||||
domain = self.get_object()
|
dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
|
||||||
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
|
|
||||||
|
|
||||||
if dnssecdata is not None and dnssecdata.keyData is not None:
|
if dnssecdata is not None and dnssecdata.keyData is not None:
|
||||||
self.request.session["dnssec_key_confirmed"] = True
|
self.request.session["dnssec_key_confirmed"] = True
|
||||||
|
@ -504,7 +550,7 @@ class DomainKeyDataView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Formset submission posts to this view."""
|
"""Formset submission posts to this view."""
|
||||||
self.object = self.get_object()
|
self._get_domain(request)
|
||||||
formset = self.get_form()
|
formset = self.get_form()
|
||||||
|
|
||||||
if "confirm-key" in request.POST:
|
if "confirm-key" in request.POST:
|
||||||
|
@ -543,9 +589,8 @@ class DomainKeyDataView(DomainPermissionView, FormMixin):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# no server information in this field, skip it
|
# no server information in this field, skip it
|
||||||
pass
|
pass
|
||||||
domain = self.get_object()
|
|
||||||
try:
|
try:
|
||||||
domain.dnssecdata = dnssecdata
|
self.object.dnssecdata = dnssecdata
|
||||||
except RegistryError as err:
|
except RegistryError as err:
|
||||||
errmsg = "Error updating DNSSEC data in the registry."
|
errmsg = "Error updating DNSSEC data in the registry."
|
||||||
logger.error(errmsg)
|
logger.error(errmsg)
|
||||||
|
@ -560,7 +605,7 @@ class DomainKeyDataView(DomainPermissionView, FormMixin):
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
|
|
||||||
class DomainYourContactInformationView(DomainPermissionView, FormMixin):
|
class DomainYourContactInformationView(DomainFormBaseView):
|
||||||
"""Domain your contact information editing view."""
|
"""Domain your contact information editing view."""
|
||||||
|
|
||||||
template_name = "domain_your_contact_information.html"
|
template_name = "domain_your_contact_information.html"
|
||||||
|
@ -576,16 +621,6 @@ class DomainYourContactInformationView(DomainPermissionView, FormMixin):
|
||||||
"""Redirect to the your contact information for the domain."""
|
"""Redirect to the your contact information for the domain."""
|
||||||
return reverse("domain-your-contact-information", kwargs={"pk": self.object.pk})
|
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):
|
def form_valid(self, form):
|
||||||
"""The form is valid, call setter in model."""
|
"""The form is valid, call setter in model."""
|
||||||
|
|
||||||
|
@ -600,7 +635,7 @@ class DomainYourContactInformationView(DomainPermissionView, FormMixin):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
class DomainSecurityEmailView(DomainFormBaseView):
|
||||||
"""Domain security email editing view."""
|
"""Domain security email editing view."""
|
||||||
|
|
||||||
template_name = "domain_security_email.html"
|
template_name = "domain_security_email.html"
|
||||||
|
@ -608,9 +643,8 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""The initial value for the form."""
|
"""The initial value for the form."""
|
||||||
domain = self.get_object()
|
|
||||||
initial = super().get_initial()
|
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":
|
if security_contact is None or security_contact.email == "dotgov@cisa.dhs.gov":
|
||||||
initial["security_email"] = None
|
initial["security_email"] = None
|
||||||
return initial
|
return initial
|
||||||
|
@ -621,16 +655,6 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
||||||
"""Redirect to the security email page for the domain."""
|
"""Redirect to the security email page for the domain."""
|
||||||
return reverse("domain-security-email", kwargs={"pk": self.object.pk})
|
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):
|
def form_valid(self, form):
|
||||||
"""The form is valid, call setter in model."""
|
"""The form is valid, call setter in model."""
|
||||||
|
|
||||||
|
@ -641,33 +665,44 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
|
||||||
if new_email is None or new_email.strip() == "":
|
if new_email is None or new_email.strip() == "":
|
||||||
new_email = PublicContact.get_default_security().email
|
new_email = PublicContact.get_default_security().email
|
||||||
|
|
||||||
domain = self.get_object()
|
contact = self.object.security_contact
|
||||||
contact = domain.security_contact
|
|
||||||
|
|
||||||
# If no default is created for security_contact,
|
# If no default is created for security_contact,
|
||||||
# then we cannot connect to the registry.
|
# then we cannot connect to the registry.
|
||||||
if contact is None:
|
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())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
contact.email = new_email
|
contact.email = new_email
|
||||||
contact.save()
|
|
||||||
|
|
||||||
messages.success(
|
try:
|
||||||
self.request, "The security email for this domain has been updated."
|
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
|
# superclass has the redirect
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
class DomainUsersView(DomainPermissionView):
|
class DomainUsersView(DomainBaseView):
|
||||||
"""User management page in the domain details."""
|
"""User management page in the domain details."""
|
||||||
|
|
||||||
template_name = "domain_users.html"
|
template_name = "domain_users.html"
|
||||||
|
|
||||||
|
|
||||||
class DomainAddUserView(DomainPermissionView, FormMixin):
|
class DomainAddUserView(DomainFormBaseView):
|
||||||
"""Inside of a domain's user management, a form for adding users.
|
"""Inside of a domain's user management, a form for adding users.
|
||||||
|
|
||||||
Multiple inheritance is used here for permissions, form handling, and
|
Multiple inheritance is used here for permissions, form handling, and
|
||||||
|
@ -680,15 +715,6 @@ class DomainAddUserView(DomainPermissionView, FormMixin):
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("domain-users", kwargs={"pk": self.object.pk})
|
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):
|
def _domain_abs_url(self):
|
||||||
"""Get an absolute URL for this domain."""
|
"""Get an absolute URL for this domain."""
|
||||||
return self.request.build_absolute_uri(
|
return self.request.build_absolute_uri(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue