mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-21 10:16:13 +02:00
Merge branch 'main' into nmb/email-fix
This commit is contained in:
commit
5bb1909abf
36 changed files with 509 additions and 239 deletions
|
@ -69,8 +69,8 @@ class AvailableViewTest(MockEppLib):
|
||||||
self.assertTrue(check_domain_available("igorville.gov"))
|
self.assertTrue(check_domain_available("igorville.gov"))
|
||||||
# input is lowercased so GSA.GOV should also not be available
|
# input is lowercased so GSA.GOV should also not be available
|
||||||
self.assertFalse(check_domain_available("GSA.gov"))
|
self.assertFalse(check_domain_available("GSA.gov"))
|
||||||
# input is lowercased so IGORVILLE.GOV should also not be available
|
# input is lowercased so IGORVILLE.GOV should also be available
|
||||||
self.assertFalse(check_domain_available("IGORVILLE.gov"))
|
self.assertTrue(check_domain_available("IGORVILLE.gov"))
|
||||||
|
|
||||||
def test_domain_available_dotgov(self):
|
def test_domain_available_dotgov(self):
|
||||||
"""Domain searches work without trailing .gov"""
|
"""Domain searches work without trailing .gov"""
|
||||||
|
|
|
@ -32,7 +32,9 @@ DOMAIN_API_MESSAGES = {
|
||||||
"Read more about choosing your .gov domain.</a>".format(public_site_url("domains/choosing"))
|
"Read more about choosing your .gov domain.</a>".format(public_site_url("domains/choosing"))
|
||||||
),
|
),
|
||||||
"invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).",
|
"invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).",
|
||||||
"success": "That domain is available!",
|
"success": "That domain is available! We’ll try to give you the domain you want, \
|
||||||
|
but it's not guaranteed. After you complete this form, we’ll \
|
||||||
|
evaluate whether your request meets our requirements.",
|
||||||
"error": GenericError.get_error_message(GenericErrorCodes.CANNOT_CONTACT_REGISTRY),
|
"error": GenericError.get_error_message(GenericErrorCodes.CANNOT_CONTACT_REGISTRY),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,9 @@ class Client(oic.Client):
|
||||||
"state": session["state"],
|
"state": session["state"],
|
||||||
"nonce": session["nonce"],
|
"nonce": session["nonce"],
|
||||||
"redirect_uri": self.registration_response["redirect_uris"][0],
|
"redirect_uri": self.registration_response["redirect_uris"][0],
|
||||||
"acr_values": self.behaviour.get("acr_value"),
|
# acr_value may be passed in session if overriding, as in the case
|
||||||
|
# of step up auth, otherwise get from settings.py
|
||||||
|
"acr_values": session.get("acr_value") or self.behaviour.get("acr_value"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if extra_args is not None:
|
if extra_args is not None:
|
||||||
|
@ -162,7 +164,6 @@ class Client(oic.Client):
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
logger.error("Unable to parse response for %s" % state)
|
logger.error("Unable to parse response for %s" % state)
|
||||||
raise o_e.AuthenticationFailed(locator=state)
|
raise o_e.AuthenticationFailed(locator=state)
|
||||||
|
|
||||||
# ErrorResponse is not raised, it is passed back...
|
# ErrorResponse is not raised, it is passed back...
|
||||||
if isinstance(authn_response, ErrorResponse):
|
if isinstance(authn_response, ErrorResponse):
|
||||||
error = authn_response.get("error", "")
|
error = authn_response.get("error", "")
|
||||||
|
@ -207,7 +208,6 @@ class Client(oic.Client):
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
logger.error("Unable to request user info for %s" % state)
|
logger.error("Unable to request user info for %s" % state)
|
||||||
raise o_e.AuthenticationFailed(locator=state)
|
raise o_e.AuthenticationFailed(locator=state)
|
||||||
|
|
||||||
# ErrorResponse is not raised, it is passed back...
|
# ErrorResponse is not raised, it is passed back...
|
||||||
if isinstance(info_response, ErrorResponse):
|
if isinstance(info_response, ErrorResponse):
|
||||||
logger.error("Unable to get user info (%s) for %s" % (info_response.get("error", ""), state))
|
logger.error("Unable to get user info (%s) for %s" % (info_response.get("error", ""), state))
|
||||||
|
@ -272,6 +272,11 @@ class Client(oic.Client):
|
||||||
|
|
||||||
super(Client, self).store_response(resp, info)
|
super(Client, self).store_response(resp, info)
|
||||||
|
|
||||||
|
def get_step_up_acr_value(self):
|
||||||
|
"""returns the step_up_acr_value from settings
|
||||||
|
this helper function is called from djangooidc views"""
|
||||||
|
return self.behaviour.get("step_up_acr_value")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Client {} {} {}".format(
|
return "Client {} {} {}".format(
|
||||||
self.client_id,
|
self.client_id,
|
||||||
|
|
30
src/djangooidc/tests/test_oidc.py
Normal file
30
src/djangooidc/tests/test_oidc.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from djangooidc.oidc import Client
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OidcTest(TestCase):
|
||||||
|
def test_oidc_create_authn_request_with_acr_value(self):
|
||||||
|
"""Test that create_authn_request returns a redirect with an acr_value
|
||||||
|
when an acr_value is passed through session.
|
||||||
|
|
||||||
|
This test is only valid locally. On local, client can be initialized.
|
||||||
|
Client initialization does not work in pipeline, so test is useless in
|
||||||
|
pipeline. However, it will not fail in pipeline."""
|
||||||
|
try:
|
||||||
|
# Initialize provider using pyOICD
|
||||||
|
OP = getattr(settings, "OIDC_ACTIVE_PROVIDER")
|
||||||
|
CLIENT = Client(OP)
|
||||||
|
session = {"acr_value": "some_acr_value_maybe_ial2"}
|
||||||
|
response = CLIENT.create_authn_request(session)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertIn("some_acr_value_maybe_ial2", response.url)
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning(err)
|
||||||
|
logger.warning("Unable to configure OpenID Connect provider in pipeline. Cannot execute this test.")
|
|
@ -1,8 +1,9 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase, RequestFactory
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from ..views import login_callback
|
||||||
|
|
||||||
from .common import less_console_noise
|
from .common import less_console_noise
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ from .common import less_console_noise
|
||||||
class ViewsTest(TestCase):
|
class ViewsTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
def say_hi(*args):
|
def say_hi(*args):
|
||||||
return HttpResponse("Hi")
|
return HttpResponse("Hi")
|
||||||
|
@ -59,19 +61,83 @@ class ViewsTest(TestCase):
|
||||||
# mock
|
# mock
|
||||||
mock_client.callback.side_effect = self.user_info
|
mock_client.callback.side_effect = self.user_info
|
||||||
# test
|
# test
|
||||||
with less_console_noise():
|
with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise():
|
||||||
response = self.client.get(reverse("openid_login_callback"))
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
# assert
|
# assert
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse("logout"))
|
self.assertEqual(response.url, reverse("logout"))
|
||||||
|
|
||||||
|
def test_login_callback_no_step_up_auth(self, mock_client):
|
||||||
|
"""Walk through login_callback when requires_step_up_auth returns False
|
||||||
|
and assert that we have a redirect to /"""
|
||||||
|
# setup
|
||||||
|
session = self.client.session
|
||||||
|
session.save()
|
||||||
|
# mock
|
||||||
|
mock_client.callback.side_effect = self.user_info
|
||||||
|
# test
|
||||||
|
with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise():
|
||||||
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
|
# assert
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
|
def test_requires_step_up_auth(self, mock_client):
|
||||||
|
"""Invoke login_callback passing it a request when requires_step_up_auth returns True
|
||||||
|
and assert that session is updated and create_authn_request (mock) is called."""
|
||||||
|
# Configure the mock to return an expected value for get_step_up_acr_value
|
||||||
|
mock_client.return_value.get_step_up_acr_value.return_value = "step_up_acr_value"
|
||||||
|
|
||||||
|
# Create a mock request
|
||||||
|
request = self.factory.get("/some-url")
|
||||||
|
request.session = {"acr_value": ""}
|
||||||
|
|
||||||
|
# Ensure that the CLIENT instance used in login_callback is the mock
|
||||||
|
# patch requires_step_up_auth to return True
|
||||||
|
with patch("djangooidc.views.requires_step_up_auth", return_value=True), patch(
|
||||||
|
"djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock()
|
||||||
|
) as mock_create_authn_request:
|
||||||
|
login_callback(request)
|
||||||
|
|
||||||
|
# create_authn_request only gets called when requires_step_up_auth is True
|
||||||
|
# and it changes this acr_value in request.session
|
||||||
|
|
||||||
|
# Assert that acr_value is no longer empty string
|
||||||
|
self.assertNotEqual(request.session["acr_value"], "")
|
||||||
|
# And create_authn_request was called again
|
||||||
|
mock_create_authn_request.assert_called_once()
|
||||||
|
|
||||||
|
def test_does_not_requires_step_up_auth(self, mock_client):
|
||||||
|
"""Invoke login_callback passing it a request when requires_step_up_auth returns False
|
||||||
|
and assert that session is not updated and create_authn_request (mock) is not called.
|
||||||
|
|
||||||
|
Possibly redundant with test_login_callback_requires_step_up_auth"""
|
||||||
|
# Create a mock request
|
||||||
|
request = self.factory.get("/some-url")
|
||||||
|
request.session = {"acr_value": ""}
|
||||||
|
|
||||||
|
# Ensure that the CLIENT instance used in login_callback is the mock
|
||||||
|
# patch requires_step_up_auth to return False
|
||||||
|
with patch("djangooidc.views.requires_step_up_auth", return_value=False), patch(
|
||||||
|
"djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock()
|
||||||
|
) as mock_create_authn_request:
|
||||||
|
login_callback(request)
|
||||||
|
|
||||||
|
# create_authn_request only gets called when requires_step_up_auth is True
|
||||||
|
# and it changes this acr_value in request.session
|
||||||
|
|
||||||
|
# Assert that acr_value is NOT updated by testing that it is still an empty string
|
||||||
|
self.assertEqual(request.session["acr_value"], "")
|
||||||
|
# Assert create_authn_request was not called
|
||||||
|
mock_create_authn_request.assert_not_called()
|
||||||
|
|
||||||
@patch("djangooidc.views.authenticate")
|
@patch("djangooidc.views.authenticate")
|
||||||
def test_login_callback_raises(self, mock_auth, mock_client):
|
def test_login_callback_raises(self, mock_auth, mock_client):
|
||||||
# mock
|
# mock
|
||||||
mock_client.callback.side_effect = self.user_info
|
mock_client.callback.side_effect = self.user_info
|
||||||
mock_auth.return_value = None
|
mock_auth.return_value = None
|
||||||
# test
|
# test
|
||||||
with less_console_noise():
|
with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise():
|
||||||
response = self.client.get(reverse("openid_login_callback"))
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
# assert
|
# assert
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
|
@ -11,7 +11,7 @@ from urllib.parse import parse_qs, urlencode
|
||||||
|
|
||||||
from djangooidc.oidc import Client
|
from djangooidc.oidc import Client
|
||||||
from djangooidc import exceptions as o_e
|
from djangooidc import exceptions as o_e
|
||||||
|
from registrar.models import User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -68,6 +68,12 @@ def login_callback(request):
|
||||||
try:
|
try:
|
||||||
query = parse_qs(request.GET.urlencode())
|
query = parse_qs(request.GET.urlencode())
|
||||||
userinfo = CLIENT.callback(query, request.session)
|
userinfo = CLIENT.callback(query, request.session)
|
||||||
|
# test for need for identity verification and if it is satisfied
|
||||||
|
# if not satisfied, redirect user to login with stepped up acr_value
|
||||||
|
if requires_step_up_auth(userinfo):
|
||||||
|
# add acr_value to request.session
|
||||||
|
request.session["acr_value"] = CLIENT.get_step_up_acr_value()
|
||||||
|
return CLIENT.create_authn_request(request.session)
|
||||||
user = authenticate(request=request, **userinfo)
|
user = authenticate(request=request, **userinfo)
|
||||||
if user:
|
if user:
|
||||||
login(request, user)
|
login(request, user)
|
||||||
|
@ -79,10 +85,27 @@ def login_callback(request):
|
||||||
return error_page(request, err)
|
return error_page(request, err)
|
||||||
|
|
||||||
|
|
||||||
|
def requires_step_up_auth(userinfo):
|
||||||
|
"""if User.needs_identity_verification and step_up_acr_value not in
|
||||||
|
ial returned from callback, return True"""
|
||||||
|
step_up_acr_value = CLIENT.get_step_up_acr_value()
|
||||||
|
acr_value = userinfo.get("ial", "")
|
||||||
|
uuid = userinfo.get("sub", "")
|
||||||
|
email = userinfo.get("email", "")
|
||||||
|
if acr_value != step_up_acr_value:
|
||||||
|
# The acr of this attempt is not at the highest level
|
||||||
|
# so check if the user needs the higher level
|
||||||
|
return User.needs_identity_verification(email, uuid)
|
||||||
|
else:
|
||||||
|
# This attempt already came back at the highest level
|
||||||
|
# so does not require step up
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def logout(request, next_page=None):
|
def logout(request, next_page=None):
|
||||||
"""Redirect the user to the authentication provider (OP) logout page."""
|
"""Redirect the user to the authentication provider (OP) logout page."""
|
||||||
try:
|
try:
|
||||||
username = request.user.username
|
user = request.user
|
||||||
request_args = {
|
request_args = {
|
||||||
"client_id": CLIENT.client_id,
|
"client_id": CLIENT.client_id,
|
||||||
"state": request.session["state"],
|
"state": request.session["state"],
|
||||||
|
@ -94,7 +117,6 @@ def logout(request, next_page=None):
|
||||||
request_args.update(
|
request_args.update(
|
||||||
{"post_logout_redirect_uri": CLIENT.registration_response["post_logout_redirect_uris"][0]}
|
{"post_logout_redirect_uri": CLIENT.registration_response["post_logout_redirect_uris"][0]}
|
||||||
)
|
)
|
||||||
|
|
||||||
url = CLIENT.provider_info["end_session_endpoint"]
|
url = CLIENT.provider_info["end_session_endpoint"]
|
||||||
url += "?" + urlencode(request_args)
|
url += "?" + urlencode(request_args)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
@ -104,7 +126,7 @@ def logout(request, next_page=None):
|
||||||
# Always remove Django session stuff - even if not logged out from OP.
|
# Always remove Django session stuff - even if not logged out from OP.
|
||||||
# Don't wait for the callback as it may never come.
|
# Don't wait for the callback as it may never come.
|
||||||
auth_logout(request)
|
auth_logout(request)
|
||||||
logger.info("Successfully logged out user %s" % username)
|
logger.info("Successfully logged out user %s" % user)
|
||||||
next_page = getattr(settings, "LOGOUT_REDIRECT_URL", None)
|
next_page = getattr(settings, "LOGOUT_REDIRECT_URL", None)
|
||||||
if next_page:
|
if next_page:
|
||||||
request.session["next"] = next_page
|
request.session["next"] = next_page
|
||||||
|
|
|
@ -17,7 +17,7 @@ except ImportError:
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from .cert import Cert, Key
|
from .cert import Cert, Key
|
||||||
from .errors import LoginError, RegistryError
|
from .errors import ErrorCode, LoginError, RegistryError
|
||||||
from .socket import Socket
|
from .socket import Socket
|
||||||
from .utility.pool import EPPConnectionPool
|
from .utility.pool import EPPConnectionPool
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ class EPPLibWrapper:
|
||||||
except TransportError as err:
|
except TransportError as err:
|
||||||
message = f"{cmd_type} failed to execute due to a connection error."
|
message = f"{cmd_type} failed to execute due to a connection error."
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}", exc_info=True)
|
||||||
raise RegistryError(message) from err
|
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
||||||
except LoginError as err:
|
except LoginError as err:
|
||||||
# For linter due to it not liking this line length
|
# For linter due to it not liking this line length
|
||||||
text = "failed to execute due to a registry login error."
|
text = "failed to execute due to a registry login error."
|
||||||
|
@ -163,7 +163,8 @@ class EPPLibWrapper:
|
||||||
try:
|
try:
|
||||||
return self._send(command)
|
return self._send(command)
|
||||||
except RegistryError as err:
|
except RegistryError as err:
|
||||||
if err.should_retry() and counter < 3:
|
if counter < 3 and (err.should_retry() or err.is_transport_error()):
|
||||||
|
logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.")
|
||||||
counter += 1
|
counter += 1
|
||||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
||||||
else: # don't try again
|
else: # don't try again
|
||||||
|
|
|
@ -4,13 +4,15 @@ from enum import IntEnum
|
||||||
class ErrorCode(IntEnum):
|
class ErrorCode(IntEnum):
|
||||||
"""
|
"""
|
||||||
Overview of registry response codes from RFC 5730. See RFC 5730 for full text.
|
Overview of registry response codes from RFC 5730. See RFC 5730 for full text.
|
||||||
|
- 0 System connection error
|
||||||
- 1000 - 1500 Success
|
- 1000 - 1500 Success
|
||||||
- 2000 - 2308 Registrar did something silly
|
- 2000 - 2308 Registrar did something silly
|
||||||
- 2400 - 2500 Registry did something silly
|
- 2400 - 2500 Registry did something silly
|
||||||
- 2501 - 2502 Something malicious or abusive may have occurred
|
- 2501 - 2502 Something malicious or abusive may have occurred
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
TRANSPORT_ERROR = 0
|
||||||
|
|
||||||
COMMAND_COMPLETED_SUCCESSFULLY = 1000
|
COMMAND_COMPLETED_SUCCESSFULLY = 1000
|
||||||
COMMAND_COMPLETED_SUCCESSFULLY_ACTION_PENDING = 1001
|
COMMAND_COMPLETED_SUCCESSFULLY_ACTION_PENDING = 1001
|
||||||
COMMAND_COMPLETED_SUCCESSFULLY_NO_MESSAGES = 1300
|
COMMAND_COMPLETED_SUCCESSFULLY_NO_MESSAGES = 1300
|
||||||
|
@ -67,6 +69,9 @@ class RegistryError(Exception):
|
||||||
def should_retry(self):
|
def should_retry(self):
|
||||||
return self.code == ErrorCode.COMMAND_FAILED
|
return self.code == ErrorCode.COMMAND_FAILED
|
||||||
|
|
||||||
|
def is_transport_error(self):
|
||||||
|
return self.code == ErrorCode.TRANSPORT_ERROR
|
||||||
|
|
||||||
# connection errors have error code of None and [Errno 99] in the err message
|
# connection errors have error code of None and [Errno 99] in the err message
|
||||||
def is_connection_error(self):
|
def is_connection_error(self):
|
||||||
return self.code is None
|
return self.code is None
|
||||||
|
|
|
@ -527,14 +527,14 @@ class DomainApplicationAdminForm(forms.ModelForm):
|
||||||
current_state = application.status
|
current_state = application.status
|
||||||
|
|
||||||
# first option in status transitions is current state
|
# first option in status transitions is current state
|
||||||
available_transitions = [(current_state, current_state)]
|
available_transitions = [(current_state, application.get_status_display())]
|
||||||
|
|
||||||
transitions = get_available_FIELD_transitions(
|
transitions = get_available_FIELD_transitions(
|
||||||
application, models.DomainApplication._meta.get_field("status")
|
application, models.DomainApplication._meta.get_field("status")
|
||||||
)
|
)
|
||||||
|
|
||||||
for transition in transitions:
|
for transition in transitions:
|
||||||
available_transitions.append((transition.target, transition.target))
|
available_transitions.append((transition.target, transition.target.label))
|
||||||
|
|
||||||
# only set the available transitions if the user is not restricted
|
# only set the available transitions if the user is not restricted
|
||||||
# from editing the domain application; otherwise, the form will be
|
# from editing the domain application; otherwise, the form will be
|
||||||
|
@ -650,10 +650,10 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
if (
|
if (
|
||||||
obj
|
obj
|
||||||
and original_obj.status == models.DomainApplication.APPROVED
|
and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED
|
||||||
and (
|
and (
|
||||||
obj.status == models.DomainApplication.REJECTED
|
obj.status == models.DomainApplication.ApplicationStatus.REJECTED
|
||||||
or obj.status == models.DomainApplication.INELIGIBLE
|
or obj.status == models.DomainApplication.ApplicationStatus.INELIGIBLE
|
||||||
)
|
)
|
||||||
and not obj.domain_is_not_active()
|
and not obj.domain_is_not_active()
|
||||||
):
|
):
|
||||||
|
@ -675,14 +675,14 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
else:
|
else:
|
||||||
if obj.status != original_obj.status:
|
if obj.status != original_obj.status:
|
||||||
status_method_mapping = {
|
status_method_mapping = {
|
||||||
models.DomainApplication.STARTED: None,
|
models.DomainApplication.ApplicationStatus.STARTED: None,
|
||||||
models.DomainApplication.SUBMITTED: obj.submit,
|
models.DomainApplication.ApplicationStatus.SUBMITTED: obj.submit,
|
||||||
models.DomainApplication.IN_REVIEW: obj.in_review,
|
models.DomainApplication.ApplicationStatus.IN_REVIEW: obj.in_review,
|
||||||
models.DomainApplication.ACTION_NEEDED: obj.action_needed,
|
models.DomainApplication.ApplicationStatus.ACTION_NEEDED: obj.action_needed,
|
||||||
models.DomainApplication.APPROVED: obj.approve,
|
models.DomainApplication.ApplicationStatus.APPROVED: obj.approve,
|
||||||
models.DomainApplication.WITHDRAWN: obj.withdraw,
|
models.DomainApplication.ApplicationStatus.WITHDRAWN: obj.withdraw,
|
||||||
models.DomainApplication.REJECTED: obj.reject,
|
models.DomainApplication.ApplicationStatus.REJECTED: obj.reject,
|
||||||
models.DomainApplication.INELIGIBLE: (obj.reject_with_prejudice),
|
models.DomainApplication.ApplicationStatus.INELIGIBLE: (obj.reject_with_prejudice),
|
||||||
}
|
}
|
||||||
selected_method = status_method_mapping.get(obj.status)
|
selected_method = status_method_mapping.get(obj.status)
|
||||||
if selected_method is None:
|
if selected_method is None:
|
||||||
|
|
|
@ -541,6 +541,7 @@ OIDC_PROVIDERS = {
|
||||||
"scope": ["email", "profile:name", "phone"],
|
"scope": ["email", "profile:name", "phone"],
|
||||||
"user_info_request": ["email", "first_name", "last_name", "phone"],
|
"user_info_request": ["email", "first_name", "last_name", "phone"],
|
||||||
"acr_value": "http://idmanagement.gov/ns/assurance/ial/1",
|
"acr_value": "http://idmanagement.gov/ns/assurance/ial/1",
|
||||||
|
"step_up_acr_value": "http://idmanagement.gov/ns/assurance/ial/2",
|
||||||
},
|
},
|
||||||
"client_registration": {
|
"client_registration": {
|
||||||
"client_id": "cisa_dotgov_registrar",
|
"client_id": "cisa_dotgov_registrar",
|
||||||
|
@ -558,6 +559,7 @@ OIDC_PROVIDERS = {
|
||||||
"scope": ["email", "profile:name", "phone"],
|
"scope": ["email", "profile:name", "phone"],
|
||||||
"user_info_request": ["email", "first_name", "last_name", "phone"],
|
"user_info_request": ["email", "first_name", "last_name", "phone"],
|
||||||
"acr_value": "http://idmanagement.gov/ns/assurance/ial/1",
|
"acr_value": "http://idmanagement.gov/ns/assurance/ial/1",
|
||||||
|
"step_up_acr_value": "http://idmanagement.gov/ns/assurance/ial/2",
|
||||||
},
|
},
|
||||||
"client_registration": {
|
"client_registration": {
|
||||||
"client_id": ("urn:gov:cisa:openidconnect.profiles:sp:sso:cisa:dotgov_registrar"),
|
"client_id": ("urn:gov:cisa:openidconnect.profiles:sp:sso:cisa:dotgov_registrar"),
|
||||||
|
|
|
@ -49,28 +49,28 @@ class DomainApplicationFixture:
|
||||||
# },
|
# },
|
||||||
DA = [
|
DA = [
|
||||||
{
|
{
|
||||||
"status": "started",
|
"status": DomainApplication.ApplicationStatus.STARTED,
|
||||||
"organization_name": "Example - Finished but not Submitted",
|
"organization_name": "Example - Finished but not submitted",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "submitted",
|
"status": DomainApplication.ApplicationStatus.SUBMITTED,
|
||||||
"organization_name": "Example - Submitted but pending Investigation",
|
"organization_name": "Example - Submitted but pending investigation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "in review",
|
"status": DomainApplication.ApplicationStatus.IN_REVIEW,
|
||||||
"organization_name": "Example - In Investigation",
|
"organization_name": "Example - In investigation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "in review",
|
"status": DomainApplication.ApplicationStatus.IN_REVIEW,
|
||||||
"organization_name": "Example - Approved",
|
"organization_name": "Example - Approved",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "withdrawn",
|
"status": DomainApplication.ApplicationStatus.WITHDRAWN,
|
||||||
"organization_name": "Example - Withdrawn",
|
"organization_name": "Example - Withdrawn",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "action needed",
|
"status": DomainApplication.ApplicationStatus.ACTION_NEEDED,
|
||||||
"organization_name": "Example - Action Needed",
|
"organization_name": "Example - Action needed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "rejected",
|
"status": "rejected",
|
||||||
|
@ -214,7 +214,9 @@ class DomainFixture(DomainApplicationFixture):
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
# approve one of each users in review status domains
|
# approve one of each users in review status domains
|
||||||
application = DomainApplication.objects.filter(creator=user, status=DomainApplication.IN_REVIEW).last()
|
application = DomainApplication.objects.filter(
|
||||||
|
creator=user, status=DomainApplication.ApplicationStatus.IN_REVIEW
|
||||||
|
).last()
|
||||||
logger.debug(f"Approving {application} for {user}")
|
logger.debug(f"Approving {application} for {user}")
|
||||||
application.approve()
|
application.approve()
|
||||||
application.save()
|
application.save()
|
||||||
|
|
|
@ -262,7 +262,7 @@ class OrganizationContactForm(RegistrarForm):
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(
|
RegexValidator(
|
||||||
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
|
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
|
||||||
message="Enter a zip code in the form of 12345 or 12345-6789.",
|
message="Enter a zip code in the required format, like 12345 or 12345-6789.",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# common.py
|
# common.py
|
||||||
#
|
#
|
||||||
# ALGORITHM_CHOICES are options for alg attribute in DS Data
|
# ALGORITHM_CHOICES are options for alg attribute in DS data
|
||||||
# reference:
|
# reference:
|
||||||
# https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
|
# https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
|
||||||
ALGORITHM_CHOICES = [
|
ALGORITHM_CHOICES = [
|
||||||
|
@ -18,7 +18,7 @@ ALGORITHM_CHOICES = [
|
||||||
(15, "(15) Ed25519"),
|
(15, "(15) Ed25519"),
|
||||||
(16, "(16) Ed448"),
|
(16, "(16) Ed448"),
|
||||||
]
|
]
|
||||||
# DIGEST_TYPE_CHOICES are options for digestType attribute in DS Data
|
# DIGEST_TYPE_CHOICES are options for digestType attribute in DS data
|
||||||
# reference: https://datatracker.ietf.org/doc/html/rfc4034#appendix-A.2
|
# reference: https://datatracker.ietf.org/doc/html/rfc4034#appendix-A.2
|
||||||
DIGEST_TYPE_CHOICES = [
|
DIGEST_TYPE_CHOICES = [
|
||||||
(1, "(1) SHA-1"),
|
(1, "(1) SHA-1"),
|
||||||
|
|
|
@ -239,7 +239,7 @@ class DomainOrgNameAddressForm(forms.ModelForm):
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(
|
RegexValidator(
|
||||||
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
|
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
|
||||||
message="Enter a zip code in the form of 12345 or 12345-6789.",
|
message="Enter a zip code in the required format, like 12345 or 12345-6789.",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -302,7 +302,7 @@ class DomainDnssecForm(forms.Form):
|
||||||
|
|
||||||
|
|
||||||
class DomainDsdataForm(forms.Form):
|
class DomainDsdataForm(forms.Form):
|
||||||
"""Form for adding or editing DNSSEC DS Data to a domain."""
|
"""Form for adding or editing DNSSEC DS data to a domain."""
|
||||||
|
|
||||||
def validate_hexadecimal(value):
|
def validate_hexadecimal(value):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -62,7 +62,7 @@ class Command(BaseCommand):
|
||||||
DomainInvitation(
|
DomainInvitation(
|
||||||
email=email_address.lower(),
|
email=email_address.lower(),
|
||||||
domain=domain,
|
domain=domain,
|
||||||
status=DomainInvitation.INVITED,
|
status=DomainInvitation.DomainInvitationStatus.INVITED,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.info("Creating %d invitations", len(to_create))
|
logger.info("Creating %d invitations", len(to_create))
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
# Generated by Django 4.2.7 on 2023-12-06 16:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django_fsm
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0054_alter_domainapplication_federal_agency_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domain",
|
||||||
|
name="state",
|
||||||
|
field=django_fsm.FSMField(
|
||||||
|
choices=[
|
||||||
|
("unknown", "Unknown"),
|
||||||
|
("dns needed", "Dns needed"),
|
||||||
|
("ready", "Ready"),
|
||||||
|
("on hold", "On hold"),
|
||||||
|
("deleted", "Deleted"),
|
||||||
|
],
|
||||||
|
default="unknown",
|
||||||
|
help_text="Very basic info about the lifecycle of this domain object",
|
||||||
|
max_length=21,
|
||||||
|
protected=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="status",
|
||||||
|
field=django_fsm.FSMField(
|
||||||
|
choices=[
|
||||||
|
("started", "Started"),
|
||||||
|
("submitted", "Submitted"),
|
||||||
|
("in review", "In review"),
|
||||||
|
("action needed", "Action needed"),
|
||||||
|
("approved", "Approved"),
|
||||||
|
("withdrawn", "Withdrawn"),
|
||||||
|
("rejected", "Rejected"),
|
||||||
|
("ineligible", "Ineligible"),
|
||||||
|
],
|
||||||
|
default="started",
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininvitation",
|
||||||
|
name="status",
|
||||||
|
field=django_fsm.FSMField(
|
||||||
|
choices=[("invited", "Invited"), ("retrieved", "Retrieved")],
|
||||||
|
default="invited",
|
||||||
|
max_length=50,
|
||||||
|
protected=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="status",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[("ready", "Ready"), ("on hold", "On hold"), ("unknown", "Unknown")],
|
||||||
|
default="ready",
|
||||||
|
help_text="domain status during the transfer",
|
||||||
|
max_length=255,
|
||||||
|
verbose_name="Status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -122,20 +122,20 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
"""These capture (some of) the states a domain object can be in."""
|
"""These capture (some of) the states a domain object can be in."""
|
||||||
|
|
||||||
# the state is indeterminate
|
# the state is indeterminate
|
||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown", "Unknown"
|
||||||
|
|
||||||
# The domain object exists in the registry
|
# The domain object exists in the registry
|
||||||
# but nameservers don't exist for it yet
|
# but nameservers don't exist for it yet
|
||||||
DNS_NEEDED = "dns needed"
|
DNS_NEEDED = "dns needed", "Dns needed"
|
||||||
|
|
||||||
# Domain has had nameservers set, may or may not be active
|
# Domain has had nameservers set, may or may not be active
|
||||||
READY = "ready"
|
READY = "ready", "Ready"
|
||||||
|
|
||||||
# Registrar manually changed state to client hold
|
# Registrar manually changed state to client hold
|
||||||
ON_HOLD = "on hold"
|
ON_HOLD = "on hold", "On hold"
|
||||||
|
|
||||||
# previously existed but has been deleted from the registry
|
# previously existed but has been deleted from the registry
|
||||||
DELETED = "deleted"
|
DELETED = "deleted", "Deleted"
|
||||||
|
|
||||||
class Cache(property):
|
class Cache(property):
|
||||||
"""
|
"""
|
||||||
|
@ -174,7 +174,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
"""Check if a domain is available."""
|
"""Check if a domain is available."""
|
||||||
if not cls.string_could_be_domain(domain):
|
if not cls.string_could_be_domain(domain):
|
||||||
raise ValueError("Not a valid domain: %s" % str(domain))
|
raise ValueError("Not a valid domain: %s" % str(domain))
|
||||||
req = commands.CheckDomain([domain])
|
domain_name = domain.lower()
|
||||||
|
req = commands.CheckDomain([domain_name])
|
||||||
return registry.send(req, cleaned=True).res_data[0].avail
|
return registry.send(req, cleaned=True).res_data[0].avail
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -19,25 +19,16 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
"""A registrant's application for a new domain."""
|
"""A registrant's application for a new domain."""
|
||||||
|
|
||||||
# #### Constants for choice fields ####
|
# Constants for choice fields
|
||||||
STARTED = "started"
|
class ApplicationStatus(models.TextChoices):
|
||||||
SUBMITTED = "submitted"
|
STARTED = "started", "Started"
|
||||||
IN_REVIEW = "in review"
|
SUBMITTED = "submitted", "Submitted"
|
||||||
ACTION_NEEDED = "action needed"
|
IN_REVIEW = "in review", "In review"
|
||||||
APPROVED = "approved"
|
ACTION_NEEDED = "action needed", "Action needed"
|
||||||
WITHDRAWN = "withdrawn"
|
APPROVED = "approved", "Approved"
|
||||||
REJECTED = "rejected"
|
WITHDRAWN = "withdrawn", "Withdrawn"
|
||||||
INELIGIBLE = "ineligible"
|
REJECTED = "rejected", "Rejected"
|
||||||
STATUS_CHOICES = [
|
INELIGIBLE = "ineligible", "Ineligible"
|
||||||
(STARTED, STARTED),
|
|
||||||
(SUBMITTED, SUBMITTED),
|
|
||||||
(IN_REVIEW, IN_REVIEW),
|
|
||||||
(ACTION_NEEDED, ACTION_NEEDED),
|
|
||||||
(APPROVED, APPROVED),
|
|
||||||
(WITHDRAWN, WITHDRAWN),
|
|
||||||
(REJECTED, REJECTED),
|
|
||||||
(INELIGIBLE, INELIGIBLE),
|
|
||||||
]
|
|
||||||
|
|
||||||
class StateTerritoryChoices(models.TextChoices):
|
class StateTerritoryChoices(models.TextChoices):
|
||||||
ALABAMA = "AL", "Alabama (AL)"
|
ALABAMA = "AL", "Alabama (AL)"
|
||||||
|
@ -363,8 +354,8 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
# #### Internal fields about the application #####
|
# #### Internal fields about the application #####
|
||||||
status = FSMField(
|
status = FSMField(
|
||||||
choices=STATUS_CHOICES, # possible states as an array of constants
|
choices=ApplicationStatus.choices, # possible states as an array of constants
|
||||||
default=STARTED, # sensible default
|
default=ApplicationStatus.STARTED, # sensible default
|
||||||
protected=False, # can change state directly, particularly in Django admin
|
protected=False, # can change state directly, particularly in Django admin
|
||||||
)
|
)
|
||||||
# This is the application user who created this application. The contact
|
# This is the application user who created this application. The contact
|
||||||
|
@ -592,7 +583,11 @@ class DomainApplication(TimeStampedModel):
|
||||||
except EmailSendingError:
|
except EmailSendingError:
|
||||||
logger.warning("Failed to send confirmation email", exc_info=True)
|
logger.warning("Failed to send confirmation email", exc_info=True)
|
||||||
|
|
||||||
@transition(field="status", source=[STARTED, ACTION_NEEDED, WITHDRAWN], target=SUBMITTED)
|
@transition(
|
||||||
|
field="status",
|
||||||
|
source=[ApplicationStatus.STARTED, ApplicationStatus.ACTION_NEEDED, ApplicationStatus.WITHDRAWN],
|
||||||
|
target=ApplicationStatus.SUBMITTED,
|
||||||
|
)
|
||||||
def submit(self):
|
def submit(self):
|
||||||
"""Submit an application that is started.
|
"""Submit an application that is started.
|
||||||
|
|
||||||
|
@ -618,7 +613,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
"emails/submission_confirmation_subject.txt",
|
"emails/submission_confirmation_subject.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
@transition(field="status", source=SUBMITTED, target=IN_REVIEW)
|
@transition(field="status", source=ApplicationStatus.SUBMITTED, target=ApplicationStatus.IN_REVIEW)
|
||||||
def in_review(self):
|
def in_review(self):
|
||||||
"""Investigate an application that has been submitted.
|
"""Investigate an application that has been submitted.
|
||||||
|
|
||||||
|
@ -630,7 +625,11 @@ class DomainApplication(TimeStampedModel):
|
||||||
"emails/status_change_in_review_subject.txt",
|
"emails/status_change_in_review_subject.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
@transition(field="status", source=[IN_REVIEW, REJECTED], target=ACTION_NEEDED)
|
@transition(
|
||||||
|
field="status",
|
||||||
|
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.REJECTED],
|
||||||
|
target=ApplicationStatus.ACTION_NEEDED,
|
||||||
|
)
|
||||||
def action_needed(self):
|
def action_needed(self):
|
||||||
"""Send back an application that is under investigation or rejected.
|
"""Send back an application that is under investigation or rejected.
|
||||||
|
|
||||||
|
@ -644,8 +643,13 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[SUBMITTED, IN_REVIEW, REJECTED, INELIGIBLE],
|
source=[
|
||||||
target=APPROVED,
|
ApplicationStatus.SUBMITTED,
|
||||||
|
ApplicationStatus.IN_REVIEW,
|
||||||
|
ApplicationStatus.REJECTED,
|
||||||
|
ApplicationStatus.INELIGIBLE,
|
||||||
|
],
|
||||||
|
target=ApplicationStatus.APPROVED,
|
||||||
)
|
)
|
||||||
def approve(self):
|
def approve(self):
|
||||||
"""Approve an application that has been submitted.
|
"""Approve an application that has been submitted.
|
||||||
|
@ -678,7 +682,11 @@ class DomainApplication(TimeStampedModel):
|
||||||
"emails/status_change_approved_subject.txt",
|
"emails/status_change_approved_subject.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
@transition(field="status", source=[SUBMITTED, IN_REVIEW], target=WITHDRAWN)
|
@transition(
|
||||||
|
field="status",
|
||||||
|
source=[ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW],
|
||||||
|
target=ApplicationStatus.WITHDRAWN,
|
||||||
|
)
|
||||||
def withdraw(self):
|
def withdraw(self):
|
||||||
"""Withdraw an application that has been submitted."""
|
"""Withdraw an application that has been submitted."""
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
|
@ -689,8 +697,8 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[IN_REVIEW, APPROVED],
|
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED],
|
||||||
target=REJECTED,
|
target=ApplicationStatus.REJECTED,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def reject(self):
|
def reject(self):
|
||||||
|
@ -698,7 +706,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
As side effects this will delete the domain and domain_information
|
As side effects this will delete the domain and domain_information
|
||||||
(will cascade), and send an email notification."""
|
(will cascade), and send an email notification."""
|
||||||
if self.status == self.APPROVED:
|
if self.status == self.ApplicationStatus.APPROVED:
|
||||||
domain_state = self.approved_domain.state
|
domain_state = self.approved_domain.state
|
||||||
# Only reject if it exists on EPP
|
# Only reject if it exists on EPP
|
||||||
if domain_state != Domain.State.UNKNOWN:
|
if domain_state != Domain.State.UNKNOWN:
|
||||||
|
@ -714,8 +722,8 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[IN_REVIEW, APPROVED],
|
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED],
|
||||||
target=INELIGIBLE,
|
target=ApplicationStatus.INELIGIBLE,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def reject_with_prejudice(self):
|
def reject_with_prejudice(self):
|
||||||
|
@ -727,7 +735,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
permissions classes test against. This will also delete the domain
|
permissions classes test against. This will also delete the domain
|
||||||
and domain_information (will cascade) when they exist."""
|
and domain_information (will cascade) when they exist."""
|
||||||
|
|
||||||
if self.status == self.APPROVED:
|
if self.status == self.ApplicationStatus.APPROVED:
|
||||||
domain_state = self.approved_domain.state
|
domain_state = self.approved_domain.state
|
||||||
# Only reject if it exists on EPP
|
# Only reject if it exists on EPP
|
||||||
if domain_state != Domain.State.UNKNOWN:
|
if domain_state != Domain.State.UNKNOWN:
|
||||||
|
|
|
@ -15,8 +15,10 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainInvitation(TimeStampedModel):
|
class DomainInvitation(TimeStampedModel):
|
||||||
INVITED = "invited"
|
# Constants for status field
|
||||||
RETRIEVED = "retrieved"
|
class DomainInvitationStatus(models.TextChoices):
|
||||||
|
INVITED = "invited", "Invited"
|
||||||
|
RETRIEVED = "retrieved", "Retrieved"
|
||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
null=False,
|
null=False,
|
||||||
|
@ -31,18 +33,15 @@ class DomainInvitation(TimeStampedModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
status = FSMField(
|
status = FSMField(
|
||||||
choices=[
|
choices=DomainInvitationStatus.choices,
|
||||||
(INVITED, INVITED),
|
default=DomainInvitationStatus.INVITED,
|
||||||
(RETRIEVED, RETRIEVED),
|
|
||||||
],
|
|
||||||
default=INVITED,
|
|
||||||
protected=True, # can't alter state except through transition methods!
|
protected=True, # can't alter state except through transition methods!
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Invitation for {self.email} on {self.domain} is {self.status}"
|
return f"Invitation for {self.email} on {self.domain} is {self.status}"
|
||||||
|
|
||||||
@transition(field="status", source=INVITED, target=RETRIEVED)
|
@transition(field="status", source=DomainInvitationStatus.INVITED, target=DomainInvitationStatus.RETRIEVED)
|
||||||
def retrieve(self):
|
def retrieve(self):
|
||||||
"""When an invitation is retrieved, create the corresponding permission.
|
"""When an invitation is retrieved, create the corresponding permission.
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
class StatusChoices(models.TextChoices):
|
class StatusChoices(models.TextChoices):
|
||||||
READY = "ready", "Ready"
|
READY = "ready", "Ready"
|
||||||
ON_HOLD = "on hold", "On Hold"
|
ON_HOLD = "on hold", "On hold"
|
||||||
UNKNOWN = "unknown", "Unknown"
|
UNKNOWN = "unknown", "Unknown"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ import logging
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from registrar.models.user_domain_role import UserDomainRole
|
||||||
|
|
||||||
from .domain_invitation import DomainInvitation
|
from .domain_invitation import DomainInvitation
|
||||||
from .transition_domain import TransitionDomain
|
from .transition_domain import TransitionDomain
|
||||||
from .domain import Domain
|
from .domain import Domain
|
||||||
|
@ -64,10 +66,42 @@ class User(AbstractUser):
|
||||||
def is_restricted(self):
|
def is_restricted(self):
|
||||||
return self.status == self.RESTRICTED
|
return self.status == self.RESTRICTED
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def needs_identity_verification(cls, email, uuid):
|
||||||
|
"""A method used by our oidc classes to test whether a user needs email/uuid verification
|
||||||
|
or the full identity PII verification"""
|
||||||
|
|
||||||
|
# An existing user who is a domain manager of a domain (that is,
|
||||||
|
# they have an entry in UserDomainRole for their User)
|
||||||
|
try:
|
||||||
|
existing_user = cls.objects.get(username=uuid)
|
||||||
|
if existing_user and UserDomainRole.objects.filter(user=existing_user).exists():
|
||||||
|
return False
|
||||||
|
except cls.DoesNotExist:
|
||||||
|
# Do nothing when the user is not found, as we're checking for existence.
|
||||||
|
pass
|
||||||
|
except Exception as err:
|
||||||
|
raise err
|
||||||
|
|
||||||
|
# A new incoming user who is a domain manager for one of the domains
|
||||||
|
# that we inputted from Verisign (that is, their email address appears
|
||||||
|
# in the username field of a TransitionDomain)
|
||||||
|
if TransitionDomain.objects.filter(username=email).exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# A new incoming user who is being invited to be a domain manager (that is,
|
||||||
|
# their email address is in DomainInvitation for an invitation that is not yet "retrieved").
|
||||||
|
if DomainInvitation.objects.filter(email=email, status=DomainInvitation.INVITED).exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def check_domain_invitations_on_login(self):
|
def check_domain_invitations_on_login(self):
|
||||||
"""When a user first arrives on the site, we need to retrieve any domain
|
"""When a user first arrives on the site, we need to retrieve any domain
|
||||||
invitations that match their email address."""
|
invitations that match their email address."""
|
||||||
for invitation in DomainInvitation.objects.filter(email=self.email, status=DomainInvitation.INVITED):
|
for invitation in DomainInvitation.objects.filter(
|
||||||
|
email=self.email, status=DomainInvitation.DomainInvitationStatus.INVITED
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
invitation.retrieve()
|
invitation.retrieve()
|
||||||
invitation.save()
|
invitation.save()
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
Status:
|
Status:
|
||||||
</span>
|
</span>
|
||||||
{% if domainapplication.status == 'approved' %} Approved
|
{% if domainapplication.status == 'approved' %} Approved
|
||||||
{% elif domainapplication.status == 'in review' %} In Review
|
{% elif domainapplication.status == 'in review' %} In review
|
||||||
{% elif domainapplication.status == 'rejected' %} Rejected
|
{% elif domainapplication.status == 'rejected' %} Rejected
|
||||||
{% elif domainapplication.status == 'submitted' %} Submitted
|
{% elif domainapplication.status == 'submitted' %} Submitted
|
||||||
{% elif domainapplication.status == 'ineligible' %} Ineligible
|
{% elif domainapplication.status == 'ineligible' %} Ineligible
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
Status:
|
Status:
|
||||||
</span>
|
</span>
|
||||||
{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%}
|
{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%}
|
||||||
DNS Needed
|
DNS needed
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ domain.state|title }}
|
{{ domain.state|title }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
{% extends "domain_base.html" %}
|
{% extends "domain_base.html" %}
|
||||||
{% load static field_helpers url_helpers %}
|
{% load static field_helpers url_helpers %}
|
||||||
|
|
||||||
{% block title %}DS Data | {{ domain.name }} | {% endblock %}
|
{% block title %}DS data | {{ domain.name }} | {% endblock %}
|
||||||
|
|
||||||
{% block domain_content %}
|
{% block domain_content %}
|
||||||
{% if domain.dnssecdata is None %}
|
{% if domain.dnssecdata is None %}
|
||||||
<div class="usa-alert usa-alert--info usa-alert--slim margin-bottom-3">
|
<div class="usa-alert usa-alert--info usa-alert--slim margin-bottom-3">
|
||||||
<div class="usa-alert__body">
|
<div class="usa-alert__body">
|
||||||
You have no DS Data added. Enable DNSSEC by adding DS Data.
|
You have no DS data added. Enable DNSSEC by adding DS data.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -16,11 +16,11 @@
|
||||||
{% include "includes/form_errors.html" with form=form %}
|
{% include "includes/form_errors.html" with form=form %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<h1>DS Data</h1>
|
<h1>DS data</h1>
|
||||||
|
|
||||||
<p>In order to enable DNSSEC, you must first configure it with your DNS hosting service.</p>
|
<p>In order to enable DNSSEC, you must first configure it with your DNS hosting service.</p>
|
||||||
|
|
||||||
<p>Enter the values given by your DNS provider for DS Data.</p>
|
<p>Enter the values given by your DNS provider for DS data.</p>
|
||||||
|
|
||||||
{% include "includes/required_fields.html" %}
|
{% include "includes/required_fields.html" %}
|
||||||
|
|
||||||
|
@ -31,9 +31,9 @@
|
||||||
{% for form in formset %}
|
{% for form in formset %}
|
||||||
<fieldset class="repeatable-form">
|
<fieldset class="repeatable-form">
|
||||||
|
|
||||||
<legend class="sr-only">DS Data record {{forloop.counter}}</legend>
|
<legend class="sr-only">DS data record {{forloop.counter}}</legend>
|
||||||
|
|
||||||
<h2 class="margin-top-0">DS Data record {{forloop.counter}}</h2>
|
<h2 class="margin-top-0">DS data record {{forloop.counter}}</h2>
|
||||||
|
|
||||||
<div class="grid-row grid-gap-2 flex-end">
|
<div class="grid-row grid-gap-2 flex-end">
|
||||||
<div class="tablet:grid-col-4">
|
<div class="tablet:grid-col-4">
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<a href="{{ url }}"
|
<a href="{{ url }}"
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
{% if request.path == url %}class="usa-current"{% endif %}
|
||||||
>
|
>
|
||||||
DS Data
|
DS data
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<td data-sort-value="{{ domain.created_time|date:"U" }}" data-label="Date created">{{ domain.created_time|date }}</td>
|
<td data-sort-value="{{ domain.created_time|date:"U" }}" data-label="Date created">{{ domain.created_time|date }}</td>
|
||||||
<td data-label="Status">
|
<td data-label="Status">
|
||||||
{% if domain.state == "unknown" or domain.state == "dns needed"%}
|
{% if domain.state == "unknown" or domain.state == "dns needed"%}
|
||||||
DNS Needed
|
DNS needed
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ domain.state|title }}
|
{{ domain.state|title }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
{{ application.requested_domain.name|default:"New domain request" }}
|
{{ application.requested_domain.name|default:"New domain request" }}
|
||||||
</th>
|
</th>
|
||||||
<td data-sort-value="{{ application.created_at|date:"U" }}" data-label="Date created">{{ application.created_at|date }}</td>
|
<td data-sort-value="{{ application.created_at|date:"U" }}" data-label="Date created">{{ application.created_at|date }}</td>
|
||||||
<td data-label="Status">{{ application.status|title }}</td>
|
<td data-label="Status">{{ application.get_status_display }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %}
|
{% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %}
|
||||||
<a href="{% url 'edit-application' application.pk %}">
|
<a href="{% url 'edit-application' application.pk %}">
|
||||||
|
|
|
@ -294,7 +294,7 @@ class AuditedAdminMockData:
|
||||||
self,
|
self,
|
||||||
domain_type,
|
domain_type,
|
||||||
item_name,
|
item_name,
|
||||||
status=DomainApplication.STARTED,
|
status=DomainApplication.ApplicationStatus.STARTED,
|
||||||
org_type="federal",
|
org_type="federal",
|
||||||
federal_type="executive",
|
federal_type="executive",
|
||||||
purpose="Purpose of the site",
|
purpose="Purpose of the site",
|
||||||
|
@ -311,7 +311,7 @@ class AuditedAdminMockData:
|
||||||
title, email, and username.
|
title, email, and username.
|
||||||
|
|
||||||
status (str - optional): Defines the status for DomainApplication,
|
status (str - optional): Defines the status for DomainApplication,
|
||||||
e.g. DomainApplication.STARTED
|
e.g. DomainApplication.ApplicationStatus.STARTED
|
||||||
|
|
||||||
org_type (str - optional): Sets a domains org_type
|
org_type (str - optional): Sets a domains org_type
|
||||||
|
|
||||||
|
@ -344,23 +344,23 @@ class AuditedAdminMockData:
|
||||||
full_arg_dict = dict(
|
full_arg_dict = dict(
|
||||||
email="test_mail@mail.com",
|
email="test_mail@mail.com",
|
||||||
domain=self.dummy_domain(item_name, True),
|
domain=self.dummy_domain(item_name, True),
|
||||||
status=DomainInvitation.INVITED,
|
status=DomainInvitation.DomainInvitationStatus.INVITED,
|
||||||
)
|
)
|
||||||
return full_arg_dict
|
return full_arg_dict
|
||||||
|
|
||||||
def create_full_dummy_domain_application(self, item_name, status=DomainApplication.STARTED):
|
def create_full_dummy_domain_application(self, item_name, status=DomainApplication.ApplicationStatus.STARTED):
|
||||||
"""Creates a dummy domain application object"""
|
"""Creates a dummy domain application object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.APPLICATION, item_name, status)
|
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.APPLICATION, item_name, status)
|
||||||
application = DomainApplication.objects.get_or_create(**domain_application_kwargs)[0]
|
application = DomainApplication.objects.get_or_create(**domain_application_kwargs)[0]
|
||||||
return application
|
return application
|
||||||
|
|
||||||
def create_full_dummy_domain_information(self, item_name, status=DomainApplication.STARTED):
|
def create_full_dummy_domain_information(self, item_name, status=DomainApplication.ApplicationStatus.STARTED):
|
||||||
"""Creates a dummy domain information object"""
|
"""Creates a dummy domain information object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INFORMATION, item_name, status)
|
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INFORMATION, item_name, status)
|
||||||
application = DomainInformation.objects.get_or_create(**domain_application_kwargs)[0]
|
application = DomainInformation.objects.get_or_create(**domain_application_kwargs)[0]
|
||||||
return application
|
return application
|
||||||
|
|
||||||
def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.STARTED):
|
def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.ApplicationStatus.STARTED):
|
||||||
"""Creates a dummy domain invitation object"""
|
"""Creates a dummy domain invitation object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INVITATION, item_name, status)
|
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INVITATION, item_name, status)
|
||||||
application = DomainInvitation.objects.get_or_create(**domain_application_kwargs)[0]
|
application = DomainInvitation.objects.get_or_create(**domain_application_kwargs)[0]
|
||||||
|
@ -374,7 +374,7 @@ class AuditedAdminMockData:
|
||||||
has_other_contacts=True,
|
has_other_contacts=True,
|
||||||
has_current_website=True,
|
has_current_website=True,
|
||||||
has_alternative_gov_domain=True,
|
has_alternative_gov_domain=True,
|
||||||
status=DomainApplication.STARTED,
|
status=DomainApplication.ApplicationStatus.STARTED,
|
||||||
):
|
):
|
||||||
"""A helper to create a dummy domain application object"""
|
"""A helper to create a dummy domain application object"""
|
||||||
application = None
|
application = None
|
||||||
|
@ -455,7 +455,7 @@ def completed_application(
|
||||||
has_alternative_gov_domain=True,
|
has_alternative_gov_domain=True,
|
||||||
has_about_your_organization=True,
|
has_about_your_organization=True,
|
||||||
has_anything_else=True,
|
has_anything_else=True,
|
||||||
status=DomainApplication.STARTED,
|
status=DomainApplication.ApplicationStatus.STARTED,
|
||||||
user=False,
|
user=False,
|
||||||
name="city.gov",
|
name="city.gov",
|
||||||
):
|
):
|
||||||
|
@ -829,10 +829,8 @@ class MockEppLib(TestCase):
|
||||||
def mockCheckDomainCommand(self, _request, cleaned):
|
def mockCheckDomainCommand(self, _request, cleaned):
|
||||||
if "gsa.gov" in getattr(_request, "names", None):
|
if "gsa.gov" in getattr(_request, "names", None):
|
||||||
return self._mockDomainName("gsa.gov", False)
|
return self._mockDomainName("gsa.gov", False)
|
||||||
elif "GSA.gov" in getattr(_request, "names", None):
|
|
||||||
return self._mockDomainName("GSA.gov", False)
|
|
||||||
elif "igorville.gov" in getattr(_request, "names", None):
|
elif "igorville.gov" in getattr(_request, "names", None):
|
||||||
return self._mockDomainName("igorvilleremixed.gov", True)
|
return self._mockDomainName("igorville.gov", True)
|
||||||
elif "top-level-agency.gov" in getattr(_request, "names", None):
|
elif "top-level-agency.gov" in getattr(_request, "names", None):
|
||||||
return self._mockDomainName("top-level-agency.gov", True)
|
return self._mockDomainName("top-level-agency.gov", True)
|
||||||
elif "city.gov" in getattr(_request, "names", None):
|
elif "city.gov" in getattr(_request, "names", None):
|
||||||
|
|
|
@ -61,7 +61,7 @@ class TestDomainAdmin(MockEppLib):
|
||||||
Make sure the short name is displaying in admin on the list page
|
Make sure the short name is displaying in admin on the list page
|
||||||
"""
|
"""
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
application.approve()
|
application.approve()
|
||||||
|
|
||||||
response = self.client.get("/admin/registrar/domain/")
|
response = self.client.get("/admin/registrar/domain/")
|
||||||
|
@ -282,7 +282,7 @@ class TestDomainApplicationAdminForm(TestCase):
|
||||||
form = DomainApplicationAdminForm(instance=self.application)
|
form = DomainApplicationAdminForm(instance=self.application)
|
||||||
|
|
||||||
# Verify that the form choices match the available transitions for started
|
# Verify that the form choices match the available transitions for started
|
||||||
expected_choices = [("started", "started"), ("submitted", "submitted")]
|
expected_choices = [("started", "Started"), ("submitted", "Submitted")]
|
||||||
self.assertEqual(form.fields["status"].widget.choices, expected_choices)
|
self.assertEqual(form.fields["status"].widget.choices, expected_choices)
|
||||||
|
|
||||||
def test_form_choices_when_no_instance(self):
|
def test_form_choices_when_no_instance(self):
|
||||||
|
@ -355,7 +355,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
@ -390,13 +390,13 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.IN_REVIEW
|
application.status = DomainApplication.ApplicationStatus.IN_REVIEW
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
@ -431,13 +431,13 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.APPROVED
|
application.status = DomainApplication.ApplicationStatus.APPROVED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
@ -467,13 +467,13 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
User.objects.filter(email=EMAIL).delete()
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.APPROVED
|
application.status = DomainApplication.ApplicationStatus.APPROVED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
@ -492,13 +492,13 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.ACTION_NEEDED
|
application.status = DomainApplication.ApplicationStatus.ACTION_NEEDED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
@ -533,13 +533,13 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.REJECTED
|
application.status = DomainApplication.ApplicationStatus.REJECTED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
@ -569,13 +569,13 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
User.objects.filter(email=EMAIL).delete()
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.INELIGIBLE
|
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
@ -584,7 +584,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
self.assertEqual(application.creator.status, "restricted")
|
self.assertEqual(application.creator.status, "restricted")
|
||||||
|
|
||||||
def test_readonly_when_restricted_creator(self):
|
def test_readonly_when_restricted_creator(self):
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
application.creator.status = User.RESTRICTED
|
application.creator.status = User.RESTRICTED
|
||||||
application.creator.save()
|
application.creator.save()
|
||||||
|
|
||||||
|
@ -662,7 +662,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
def test_saving_when_restricted_creator(self):
|
def test_saving_when_restricted_creator(self):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
application.creator.status = User.RESTRICTED
|
application.creator.status = User.RESTRICTED
|
||||||
application.creator.save()
|
application.creator.save()
|
||||||
|
|
||||||
|
@ -681,11 +681,11 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assert that the status has not changed
|
# Assert that the status has not changed
|
||||||
self.assertEqual(application.status, DomainApplication.IN_REVIEW)
|
self.assertEqual(application.status, DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
def test_change_view_with_restricted_creator(self):
|
def test_change_view_with_restricted_creator(self):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
application.creator.status = User.RESTRICTED
|
application.creator.status = User.RESTRICTED
|
||||||
application.creator.save()
|
application.creator.save()
|
||||||
|
|
||||||
|
@ -704,7 +704,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
def test_error_when_saving_approved_to_rejected_and_domain_is_active(self):
|
def test_error_when_saving_approved_to_rejected_and_domain_is_active(self):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
application.save()
|
application.save()
|
||||||
|
@ -724,7 +724,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
stack.enter_context(patch.object(messages, "error"))
|
||||||
|
|
||||||
# Simulate saving the model
|
# Simulate saving the model
|
||||||
application.status = DomainApplication.REJECTED
|
application.status = DomainApplication.ApplicationStatus.REJECTED
|
||||||
self.admin.save_model(request, application, None, True)
|
self.admin.save_model(request, application, None, True)
|
||||||
|
|
||||||
# Assert that the error message was called with the correct argument
|
# Assert that the error message was called with the correct argument
|
||||||
|
@ -735,7 +735,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
def test_side_effects_when_saving_approved_to_rejected(self):
|
def test_side_effects_when_saving_approved_to_rejected(self):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
|
domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
|
@ -756,7 +756,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
stack.enter_context(patch.object(messages, "error"))
|
||||||
|
|
||||||
# Simulate saving the model
|
# Simulate saving the model
|
||||||
application.status = DomainApplication.REJECTED
|
application.status = DomainApplication.ApplicationStatus.REJECTED
|
||||||
self.admin.save_model(request, application, None, True)
|
self.admin.save_model(request, application, None, True)
|
||||||
|
|
||||||
# Assert that the error message was never called
|
# Assert that the error message was never called
|
||||||
|
@ -774,7 +774,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self):
|
def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
application.save()
|
application.save()
|
||||||
|
@ -794,7 +794,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
stack.enter_context(patch.object(messages, "error"))
|
||||||
|
|
||||||
# Simulate saving the model
|
# Simulate saving the model
|
||||||
application.status = DomainApplication.INELIGIBLE
|
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
|
||||||
self.admin.save_model(request, application, None, True)
|
self.admin.save_model(request, application, None, True)
|
||||||
|
|
||||||
# Assert that the error message was called with the correct argument
|
# Assert that the error message was called with the correct argument
|
||||||
|
@ -805,7 +805,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
def test_side_effects_when_saving_approved_to_ineligible(self):
|
def test_side_effects_when_saving_approved_to_ineligible(self):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
|
domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
|
@ -826,7 +826,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
stack.enter_context(patch.object(messages, "error"))
|
||||||
|
|
||||||
# Simulate saving the model
|
# Simulate saving the model
|
||||||
application.status = DomainApplication.INELIGIBLE
|
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
|
||||||
self.admin.save_model(request, application, None, True)
|
self.admin.save_model(request, application, None, True)
|
||||||
|
|
||||||
# Assert that the error message was never called
|
# Assert that the error message was never called
|
||||||
|
@ -877,12 +877,14 @@ class DomainInvitationAdminTest(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assert that the filters are added
|
# Assert that the filters are added
|
||||||
self.assertContains(response, "invited", count=4)
|
self.assertContains(response, "invited", count=2)
|
||||||
self.assertContains(response, "retrieved", count=4)
|
self.assertContains(response, "Invited", count=2)
|
||||||
|
self.assertContains(response, "retrieved", count=2)
|
||||||
|
self.assertContains(response, "Retrieved", count=2)
|
||||||
|
|
||||||
# Check for the HTML context specificially
|
# Check for the HTML context specificially
|
||||||
invited_html = '<a href="?status__exact=invited">invited</a>'
|
invited_html = '<a href="?status__exact=invited">Invited</a>'
|
||||||
retrieved_html = '<a href="?status__exact=retrieved">retrieved</a>'
|
retrieved_html = '<a href="?status__exact=retrieved">Retrieved</a>'
|
||||||
|
|
||||||
self.assertContains(response, invited_html, count=1)
|
self.assertContains(response, invited_html, count=1)
|
||||||
self.assertContains(response, retrieved_html, count=1)
|
self.assertContains(response, retrieved_html, count=1)
|
||||||
|
|
|
@ -30,7 +30,7 @@ class TestFormValidation(MockEppLib):
|
||||||
form = OrganizationContactForm(data={"zipcode": "nah"})
|
form = OrganizationContactForm(data={"zipcode": "nah"})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.errors["zipcode"],
|
form.errors["zipcode"],
|
||||||
["Enter a zip code in the form of 12345 or 12345-6789."],
|
["Enter a zip code in the required format, like 12345 or 12345-6789."],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_org_contact_zip_valid(self):
|
def test_org_contact_zip_valid(self):
|
||||||
|
|
|
@ -35,7 +35,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Can create with just a creator."""
|
"""Can create with just a creator."""
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user)
|
application = DomainApplication.objects.create(creator=user)
|
||||||
self.assertEqual(application.status, DomainApplication.STARTED)
|
self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
def test_full_create(self):
|
def test_full_create(self):
|
||||||
"""Can create with all fields."""
|
"""Can create with all fields."""
|
||||||
|
@ -108,7 +108,7 @@ class TestDomainApplication(TestCase):
|
||||||
# no submitter email so this emits a log warning
|
# no submitter email so this emits a log warning
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
self.assertEqual(application.status, application.SUBMITTED)
|
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
def test_submit_sends_email(self):
|
def test_submit_sends_email(self):
|
||||||
"""Create an application and submit it and see if email was sent."""
|
"""Create an application and submit it and see if email was sent."""
|
||||||
|
@ -139,7 +139,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status submitted and call submit
|
"""Create an application with status submitted and call submit
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -148,7 +148,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status in review and call submit
|
"""Create an application with status in review and call submit
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -157,7 +157,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status approved and call submit
|
"""Create an application with status approved and call submit
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -166,7 +166,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status rejected and call submit
|
"""Create an application with status rejected and call submit
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.REJECTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -175,7 +175,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status ineligible and call submit
|
"""Create an application with status ineligible and call submit
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.INELIGIBLE)
|
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -184,7 +184,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status started and call in_review
|
"""Create an application with status started and call in_review
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.STARTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.in_review()
|
application.in_review()
|
||||||
|
@ -193,7 +193,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status in review and call in_review
|
"""Create an application with status in review and call in_review
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.in_review()
|
application.in_review()
|
||||||
|
@ -202,7 +202,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status approved and call in_review
|
"""Create an application with status approved and call in_review
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.in_review()
|
application.in_review()
|
||||||
|
@ -211,7 +211,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status action needed and call in_review
|
"""Create an application with status action needed and call in_review
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.ACTION_NEEDED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.in_review()
|
application.in_review()
|
||||||
|
@ -220,7 +220,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status rejected and call in_review
|
"""Create an application with status rejected and call in_review
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.REJECTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.in_review()
|
application.in_review()
|
||||||
|
@ -229,7 +229,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status withdrawn and call in_review
|
"""Create an application with status withdrawn and call in_review
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.WITHDRAWN)
|
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.in_review()
|
application.in_review()
|
||||||
|
@ -238,7 +238,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status ineligible and call in_review
|
"""Create an application with status ineligible and call in_review
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.INELIGIBLE)
|
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.in_review()
|
application.in_review()
|
||||||
|
@ -247,7 +247,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status started and call action_needed
|
"""Create an application with status started and call action_needed
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.STARTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.action_needed()
|
application.action_needed()
|
||||||
|
@ -256,7 +256,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status submitted and call action_needed
|
"""Create an application with status submitted and call action_needed
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.action_needed()
|
application.action_needed()
|
||||||
|
@ -265,7 +265,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status action needed and call action_needed
|
"""Create an application with status action needed and call action_needed
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.ACTION_NEEDED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.action_needed()
|
application.action_needed()
|
||||||
|
@ -274,7 +274,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status approved and call action_needed
|
"""Create an application with status approved and call action_needed
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.action_needed()
|
application.action_needed()
|
||||||
|
@ -283,7 +283,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status withdrawn and call action_needed
|
"""Create an application with status withdrawn and call action_needed
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.WITHDRAWN)
|
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.action_needed()
|
application.action_needed()
|
||||||
|
@ -292,7 +292,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status ineligible and call action_needed
|
"""Create an application with status ineligible and call action_needed
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.INELIGIBLE)
|
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.action_needed()
|
application.action_needed()
|
||||||
|
@ -301,7 +301,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status started and call approve
|
"""Create an application with status started and call approve
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.STARTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -310,7 +310,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status approved and call approve
|
"""Create an application with status approved and call approve
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -319,7 +319,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status action needed and call approve
|
"""Create an application with status action needed and call approve
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.ACTION_NEEDED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -328,7 +328,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status withdrawn and call approve
|
"""Create an application with status withdrawn and call approve
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.WITHDRAWN)
|
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -337,7 +337,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status started and call withdraw
|
"""Create an application with status started and call withdraw
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.STARTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.withdraw()
|
application.withdraw()
|
||||||
|
@ -346,7 +346,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status approved and call withdraw
|
"""Create an application with status approved and call withdraw
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.withdraw()
|
application.withdraw()
|
||||||
|
@ -355,7 +355,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status action needed and call withdraw
|
"""Create an application with status action needed and call withdraw
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.ACTION_NEEDED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.withdraw()
|
application.withdraw()
|
||||||
|
@ -364,7 +364,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status rejected and call withdraw
|
"""Create an application with status rejected and call withdraw
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.REJECTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.withdraw()
|
application.withdraw()
|
||||||
|
@ -373,7 +373,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status withdrawn and call withdraw
|
"""Create an application with status withdrawn and call withdraw
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.WITHDRAWN)
|
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.withdraw()
|
application.withdraw()
|
||||||
|
@ -382,7 +382,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status ineligible and call withdraw
|
"""Create an application with status ineligible and call withdraw
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.INELIGIBLE)
|
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.withdraw()
|
application.withdraw()
|
||||||
|
@ -391,7 +391,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status started and call reject
|
"""Create an application with status started and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.STARTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject()
|
application.reject()
|
||||||
|
@ -400,7 +400,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status submitted and call reject
|
"""Create an application with status submitted and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject()
|
application.reject()
|
||||||
|
@ -409,7 +409,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status action needed and call reject
|
"""Create an application with status action needed and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.ACTION_NEEDED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject()
|
application.reject()
|
||||||
|
@ -418,7 +418,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status withdrawn and call reject
|
"""Create an application with status withdrawn and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.WITHDRAWN)
|
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject()
|
application.reject()
|
||||||
|
@ -427,7 +427,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status rejected and call reject
|
"""Create an application with status rejected and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.REJECTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject()
|
application.reject()
|
||||||
|
@ -436,7 +436,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status ineligible and call reject
|
"""Create an application with status ineligible and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.INELIGIBLE)
|
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject()
|
application.reject()
|
||||||
|
@ -445,7 +445,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status approved, create a matching domain that
|
"""Create an application with status approved, create a matching domain that
|
||||||
is active, and call reject against transition rules"""
|
is active, and call reject against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
application.save()
|
application.save()
|
||||||
|
@ -464,7 +464,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status started and call reject
|
"""Create an application with status started and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.STARTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject_with_prejudice()
|
application.reject_with_prejudice()
|
||||||
|
@ -473,7 +473,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status submitted and call reject
|
"""Create an application with status submitted and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject_with_prejudice()
|
application.reject_with_prejudice()
|
||||||
|
@ -482,7 +482,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status action needed and call reject
|
"""Create an application with status action needed and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.ACTION_NEEDED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject_with_prejudice()
|
application.reject_with_prejudice()
|
||||||
|
@ -491,7 +491,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status withdrawn and call reject
|
"""Create an application with status withdrawn and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.WITHDRAWN)
|
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject_with_prejudice()
|
application.reject_with_prejudice()
|
||||||
|
@ -500,7 +500,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status rejected and call reject
|
"""Create an application with status rejected and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.REJECTED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject_with_prejudice()
|
application.reject_with_prejudice()
|
||||||
|
@ -509,7 +509,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status ineligible and call reject
|
"""Create an application with status ineligible and call reject
|
||||||
against transition rules"""
|
against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.INELIGIBLE)
|
application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
|
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
application.reject_with_prejudice()
|
application.reject_with_prejudice()
|
||||||
|
@ -518,7 +518,7 @@ class TestDomainApplication(TestCase):
|
||||||
"""Create an application with status approved, create a matching domain that
|
"""Create an application with status approved, create a matching domain that
|
||||||
is active, and call reject_with_prejudice against transition rules"""
|
is active, and call reject_with_prejudice against transition rules"""
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
application.save()
|
application.save()
|
||||||
|
@ -543,7 +543,7 @@ class TestPermissions(TestCase):
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
|
||||||
# should be a role for this user
|
# should be a role for this user
|
||||||
|
@ -560,7 +560,7 @@ class TestDomainInfo(TestCase):
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
|
||||||
# should be an information present for this domain
|
# should be an information present for this domain
|
||||||
|
@ -597,7 +597,7 @@ class TestInvitations(TestCase):
|
||||||
# this is not an error but does produce a console warning
|
# this is not an error but does produce a console warning
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
self.invitation.retrieve()
|
self.invitation.retrieve()
|
||||||
self.assertEqual(self.invitation.status, DomainInvitation.RETRIEVED)
|
self.assertEqual(self.invitation.status, DomainInvitation.DomainInvitationStatus.RETRIEVED)
|
||||||
|
|
||||||
def test_retrieve_on_each_login(self):
|
def test_retrieve_on_each_login(self):
|
||||||
"""A user's authenticate on_each_login callback retrieves their invitations."""
|
"""A user's authenticate on_each_login callback retrieves their invitations."""
|
||||||
|
@ -606,19 +606,15 @@ class TestInvitations(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestUser(TestCase):
|
class TestUser(TestCase):
|
||||||
"""For now, just test actions that
|
"""Test actions that occur on user login,
|
||||||
occur on user login."""
|
test class method that controls how users get validated."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.email = "mayor@igorville.gov"
|
self.email = "mayor@igorville.gov"
|
||||||
self.domain_name = "igorvilleInTransition.gov"
|
self.domain_name = "igorvilleInTransition.gov"
|
||||||
|
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
self.user, _ = User.objects.get_or_create(email=self.email)
|
self.user, _ = User.objects.get_or_create(email=self.email)
|
||||||
|
|
||||||
# clean out the roles each time
|
|
||||||
UserDomainRole.objects.all().delete()
|
|
||||||
|
|
||||||
TransitionDomain.objects.get_or_create(username="mayor@igorville.gov", domain_name=self.domain_name)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
|
@ -626,6 +622,7 @@ class TestUser(TestCase):
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
TransitionDomain.objects.all().delete()
|
TransitionDomain.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
UserDomainRole.objects.all().delete()
|
||||||
|
|
||||||
def test_check_transition_domains_without_domains_on_login(self):
|
def test_check_transition_domains_without_domains_on_login(self):
|
||||||
"""A user's on_each_login callback does not check transition domains.
|
"""A user's on_each_login callback does not check transition domains.
|
||||||
|
@ -634,3 +631,26 @@ class TestUser(TestCase):
|
||||||
are created."""
|
are created."""
|
||||||
self.user.on_each_login()
|
self.user.on_each_login()
|
||||||
self.assertFalse(Domain.objects.filter(name=self.domain_name).exists())
|
self.assertFalse(Domain.objects.filter(name=self.domain_name).exists())
|
||||||
|
|
||||||
|
def test_identity_verification_with_domain_manager(self):
|
||||||
|
"""A domain manager should return False when tested with class
|
||||||
|
method needs_identity_verification"""
|
||||||
|
UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
|
||||||
|
self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username))
|
||||||
|
|
||||||
|
def test_identity_verification_with_transition_user(self):
|
||||||
|
"""A user from the Verisign transition should return False
|
||||||
|
when tested with class method needs_identity_verification"""
|
||||||
|
TransitionDomain.objects.get_or_create(username=self.user.email, domain_name=self.domain_name)
|
||||||
|
self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username))
|
||||||
|
|
||||||
|
def test_identity_verification_with_invited_user(self):
|
||||||
|
"""An invited user should return False when tested with class
|
||||||
|
method needs_identity_verification"""
|
||||||
|
DomainInvitation.objects.get_or_create(email=self.user.email, domain=self.domain)
|
||||||
|
self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username))
|
||||||
|
|
||||||
|
def test_identity_verification_with_new_user(self):
|
||||||
|
"""A new user who's neither transitioned nor invited should
|
||||||
|
return True when tested with class method needs_identity_verification"""
|
||||||
|
self.assertTrue(User.needs_identity_verification(self.user.email, self.user.username))
|
||||||
|
|
|
@ -261,7 +261,7 @@ class TestDomainCreation(MockEppLib):
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
# transition to approve state
|
# transition to approve state
|
||||||
application.approve()
|
application.approve()
|
||||||
# should have information present for this domain
|
# should have information present for this domain
|
||||||
|
@ -1506,7 +1506,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_setting_not_allowed(self):
|
def test_setting_not_allowed(self):
|
||||||
"""Scenario: A domain state is not Ready or DNS Needed
|
"""Scenario: A domain state is not Ready or DNS needed
|
||||||
then setting nameservers is not allowed"""
|
then setting nameservers is not allowed"""
|
||||||
domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD)
|
domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD)
|
||||||
with self.assertRaises(ActionNotAllowed):
|
with self.assertRaises(ActionNotAllowed):
|
||||||
|
|
|
@ -258,7 +258,6 @@ class ExportDataTest(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Dummy push - will remove
|
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
|
@ -100,7 +100,7 @@ class LoggedInTests(TestWithUser):
|
||||||
response = self.client.get("/")
|
response = self.client.get("/")
|
||||||
# count = 2 because it is also in screenreader content
|
# count = 2 because it is also in screenreader content
|
||||||
self.assertContains(response, "igorville.gov", count=2)
|
self.assertContains(response, "igorville.gov", count=2)
|
||||||
self.assertContains(response, "DNS Needed")
|
self.assertContains(response, "DNS needed")
|
||||||
# clean up
|
# clean up
|
||||||
role.delete()
|
role.delete()
|
||||||
|
|
||||||
|
@ -1079,7 +1079,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
Make sure the long name is displaying in the application summary
|
Make sure the long name is displaying in the application summary
|
||||||
page (manage your application)
|
page (manage your application)
|
||||||
"""
|
"""
|
||||||
completed_application(status=DomainApplication.SUBMITTED, user=self.user)
|
completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "city.gov")
|
self.assertContains(home_page, "city.gov")
|
||||||
# click the "Edit" link
|
# click the "Edit" link
|
||||||
|
@ -1941,19 +1941,19 @@ class TestDomainDNSSEC(TestDomainOverview):
|
||||||
self.assertContains(updated_page, "Enable DNSSEC")
|
self.assertContains(updated_page, "Enable DNSSEC")
|
||||||
|
|
||||||
def test_ds_form_loads_with_no_domain_data(self):
|
def test_ds_form_loads_with_no_domain_data(self):
|
||||||
"""DNSSEC Add DS Data page loads when there is no
|
"""DNSSEC Add DS data page loads when there is no
|
||||||
domain DNSSEC data and shows a button to Add new record"""
|
domain DNSSEC data and shows a button to Add new record"""
|
||||||
|
|
||||||
page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id}))
|
page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id}))
|
||||||
self.assertContains(page, "You have no DS Data added")
|
self.assertContains(page, "You have no DS data added")
|
||||||
self.assertContains(page, "Add new record")
|
self.assertContains(page, "Add new record")
|
||||||
|
|
||||||
def test_ds_form_loads_with_ds_data(self):
|
def test_ds_form_loads_with_ds_data(self):
|
||||||
"""DNSSEC Add DS Data page loads when there is
|
"""DNSSEC Add DS data page loads when there is
|
||||||
domain DNSSEC DS data and shows the data"""
|
domain DNSSEC DS data and shows the data"""
|
||||||
|
|
||||||
page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
|
page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
|
||||||
self.assertContains(page, "DS Data record 1")
|
self.assertContains(page, "DS data record 1")
|
||||||
|
|
||||||
def test_ds_data_form_modal(self):
|
def test_ds_data_form_modal(self):
|
||||||
"""When user clicks on save, a modal pops up."""
|
"""When user clicks on save, a modal pops up."""
|
||||||
|
@ -1974,7 +1974,7 @@ class TestDomainDNSSEC(TestDomainOverview):
|
||||||
self.assertContains(response, "Trigger Disable DNSSEC Modal")
|
self.assertContains(response, "Trigger Disable DNSSEC Modal")
|
||||||
|
|
||||||
def test_ds_data_form_submits(self):
|
def test_ds_data_form_submits(self):
|
||||||
"""DS Data form submits successfully
|
"""DS data form submits successfully
|
||||||
|
|
||||||
Uses self.app WebTest because we need to interact with forms.
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
"""
|
"""
|
||||||
|
@ -1991,10 +1991,10 @@ class TestDomainDNSSEC(TestDomainOverview):
|
||||||
)
|
)
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
page = result.follow()
|
page = result.follow()
|
||||||
self.assertContains(page, "The DS Data records for this domain have been updated.")
|
self.assertContains(page, "The DS data records for this domain have been updated.")
|
||||||
|
|
||||||
def test_ds_data_form_invalid(self):
|
def test_ds_data_form_invalid(self):
|
||||||
"""DS Data form errors with invalid data (missing required fields)
|
"""DS data form errors with invalid data (missing required fields)
|
||||||
|
|
||||||
Uses self.app WebTest because we need to interact with forms.
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
"""
|
"""
|
||||||
|
@ -2017,7 +2017,7 @@ class TestDomainDNSSEC(TestDomainOverview):
|
||||||
self.assertContains(result, "Digest is required", count=2, status_code=200)
|
self.assertContains(result, "Digest is required", count=2, status_code=200)
|
||||||
|
|
||||||
def test_ds_data_form_invalid_keytag(self):
|
def test_ds_data_form_invalid_keytag(self):
|
||||||
"""DS Data form errors with invalid data (key tag too large)
|
"""DS data form errors with invalid data (key tag too large)
|
||||||
|
|
||||||
Uses self.app WebTest because we need to interact with forms.
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
"""
|
"""
|
||||||
|
@ -2040,7 +2040,7 @@ class TestDomainDNSSEC(TestDomainOverview):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ds_data_form_invalid_digest_chars(self):
|
def test_ds_data_form_invalid_digest_chars(self):
|
||||||
"""DS Data form errors with invalid data (digest contains non hexadecimal chars)
|
"""DS data form errors with invalid data (digest contains non hexadecimal chars)
|
||||||
|
|
||||||
Uses self.app WebTest because we need to interact with forms.
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
"""
|
"""
|
||||||
|
@ -2063,7 +2063,7 @@ class TestDomainDNSSEC(TestDomainOverview):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ds_data_form_invalid_digest_sha1(self):
|
def test_ds_data_form_invalid_digest_sha1(self):
|
||||||
"""DS Data form errors with invalid data (digest is invalid sha-1)
|
"""DS data form errors with invalid data (digest is invalid sha-1)
|
||||||
|
|
||||||
Uses self.app WebTest because we need to interact with forms.
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
"""
|
"""
|
||||||
|
@ -2086,7 +2086,7 @@ class TestDomainDNSSEC(TestDomainOverview):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ds_data_form_invalid_digest_sha256(self):
|
def test_ds_data_form_invalid_digest_sha256(self):
|
||||||
"""DS Data form errors with invalid data (digest is invalid sha-256)
|
"""DS data form errors with invalid data (digest is invalid sha-256)
|
||||||
|
|
||||||
Uses self.app WebTest because we need to interact with forms.
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
"""
|
"""
|
||||||
|
@ -2117,7 +2117,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_status(self):
|
def test_application_status(self):
|
||||||
"""Checking application status page"""
|
"""Checking application status page"""
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED, user=self.user)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
|
@ -2137,7 +2137,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
self.user.status = "ineligible"
|
self.user.status = "ineligible"
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED, user=self.user)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
|
@ -2152,7 +2152,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_withdraw(self):
|
def test_application_withdraw(self):
|
||||||
"""Checking application status page"""
|
"""Checking application status page"""
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED, user=self.user)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
|
@ -2182,7 +2182,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_status_no_permissions(self):
|
def test_application_status_no_permissions(self):
|
||||||
"""Can't access applications without being the creator."""
|
"""Can't access applications without being the creator."""
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED, user=self.user)
|
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)
|
||||||
other_user = User()
|
other_user = User()
|
||||||
other_user.save()
|
other_user.save()
|
||||||
application.creator = other_user
|
application.creator = other_user
|
||||||
|
@ -2202,7 +2202,7 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
def test_approved_application_not_in_active_requests(self):
|
def test_approved_application_not_in_active_requests(self):
|
||||||
"""An approved application is not shown in the Active
|
"""An approved application is not shown in the Active
|
||||||
Requests table on home.html."""
|
Requests table on home.html."""
|
||||||
application = completed_application(status=DomainApplication.APPROVED, user=self.user)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED, user=self.user)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
|
|
|
@ -293,9 +293,9 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
return self.pending_applications()
|
return self.pending_applications()
|
||||||
|
|
||||||
def approved_applications_exist(self):
|
def approved_applications_exist(self):
|
||||||
"""Checks if user is creator of applications with APPROVED status"""
|
"""Checks if user is creator of applications with ApplicationStatus.APPROVED status"""
|
||||||
approved_application_count = DomainApplication.objects.filter(
|
approved_application_count = DomainApplication.objects.filter(
|
||||||
creator=self.request.user, status=DomainApplication.APPROVED
|
creator=self.request.user, status=DomainApplication.ApplicationStatus.APPROVED
|
||||||
).count()
|
).count()
|
||||||
return approved_application_count > 0
|
return approved_application_count > 0
|
||||||
|
|
||||||
|
@ -308,11 +308,15 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
|
|
||||||
def pending_applications(self):
|
def pending_applications(self):
|
||||||
"""Returns a List of user's applications with one of the following states:
|
"""Returns a List of user's applications with one of the following states:
|
||||||
SUBMITTED, IN_REVIEW, ACTION_NEEDED"""
|
ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW, ApplicationStatus.ACTION_NEEDED"""
|
||||||
# if the current application has ACTION_NEEDED status, this check should not be performed
|
# if the current application has ApplicationStatus.ACTION_NEEDED status, this check should not be performed
|
||||||
if self.application.status == DomainApplication.ACTION_NEEDED:
|
if self.application.status == DomainApplication.ApplicationStatus.ACTION_NEEDED:
|
||||||
return []
|
return []
|
||||||
check_statuses = [DomainApplication.SUBMITTED, DomainApplication.IN_REVIEW, DomainApplication.ACTION_NEEDED]
|
check_statuses = [
|
||||||
|
DomainApplication.ApplicationStatus.SUBMITTED,
|
||||||
|
DomainApplication.ApplicationStatus.IN_REVIEW,
|
||||||
|
DomainApplication.ApplicationStatus.ACTION_NEEDED,
|
||||||
|
]
|
||||||
return DomainApplication.objects.filter(creator=self.request.user, status__in=check_statuses)
|
return DomainApplication.objects.filter(creator=self.request.user, status__in=check_statuses)
|
||||||
|
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
|
|
|
@ -434,7 +434,7 @@ class DomainDsDataView(DomainFormBaseView):
|
||||||
return initial_data
|
return initial_data
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to the DS Data page for the domain."""
|
"""Redirect to the DS data page for the domain."""
|
||||||
return reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.object.pk})
|
return reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.object.pk})
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -473,7 +473,7 @@ class DomainDsDataView(DomainFormBaseView):
|
||||||
modal_button = (
|
modal_button = (
|
||||||
'<button type="submit" '
|
'<button type="submit" '
|
||||||
'class="usa-button usa-button--secondary" '
|
'class="usa-button usa-button--secondary" '
|
||||||
'name="disable-override-click">Remove all DS Data</button>'
|
'name="disable-override-click">Remove all DS data</button>'
|
||||||
)
|
)
|
||||||
|
|
||||||
# context to back out of a broken form on all fields delete
|
# context to back out of a broken form on all fields delete
|
||||||
|
@ -523,7 +523,7 @@ class DomainDsDataView(DomainFormBaseView):
|
||||||
logger.error(f"Registry error: {err}")
|
logger.error(f"Registry error: {err}")
|
||||||
return self.form_invalid(formset)
|
return self.form_invalid(formset)
|
||||||
else:
|
else:
|
||||||
messages.success(self.request, "The DS Data records for this domain have been updated.")
|
messages.success(self.request, "The DS data records for this domain have been updated.")
|
||||||
# superclass has the redirect
|
# superclass has the redirect
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
|
|
|
@ -101,10 +101,10 @@ class DomainPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
# Analysts may manage domains, when they are in these statuses:
|
# Analysts may manage domains, when they are in these statuses:
|
||||||
valid_domain_statuses = [
|
valid_domain_statuses = [
|
||||||
DomainApplication.APPROVED,
|
DomainApplication.ApplicationStatus.APPROVED,
|
||||||
DomainApplication.IN_REVIEW,
|
DomainApplication.ApplicationStatus.IN_REVIEW,
|
||||||
DomainApplication.REJECTED,
|
DomainApplication.ApplicationStatus.REJECTED,
|
||||||
DomainApplication.ACTION_NEEDED,
|
DomainApplication.ApplicationStatus.ACTION_NEEDED,
|
||||||
# Edge case - some domains do not have
|
# Edge case - some domains do not have
|
||||||
# a status or DomainInformation... aka a status of 'None'.
|
# a status or DomainInformation... aka a status of 'None'.
|
||||||
# It is necessary to access those to correct errors.
|
# It is necessary to access those to correct errors.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue