mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-04 17:01:56 +02:00
Merge branch 'main' into dk/1217-domain-expiration-epp
This commit is contained in:
commit
021f0454c6
23 changed files with 496 additions and 120 deletions
|
@ -106,6 +106,7 @@ class EPPLibWrapper:
|
|||
# Flag that the pool is frozen,
|
||||
# then restart the pool.
|
||||
self.pool_status.pool_hanging = True
|
||||
logger.error("Pool timed out")
|
||||
self.start_connection_pool()
|
||||
except (ValueError, ParsingError) as err:
|
||||
message = f"{cmd_type} failed to execute due to some syntax error."
|
||||
|
@ -174,6 +175,7 @@ class EPPLibWrapper:
|
|||
|
||||
def _create_pool(self, client, login, options):
|
||||
"""Creates and returns new pool instance"""
|
||||
logger.info("New pool was created")
|
||||
return EPPConnectionPool(client, login, options)
|
||||
|
||||
def start_connection_pool(self, restart_pool_if_exists=True):
|
||||
|
@ -187,7 +189,7 @@ class EPPLibWrapper:
|
|||
# Since we reuse the same creds for each pool, we can test on
|
||||
# one socket, and if successful, then we know we can connect.
|
||||
if not self._test_registry_connection_success():
|
||||
logger.warning("Cannot contact the Registry")
|
||||
logger.warning("start_connection_pool() -> Cannot contact the Registry")
|
||||
self.pool_status.connection_success = False
|
||||
else:
|
||||
self.pool_status.connection_success = True
|
||||
|
@ -197,6 +199,7 @@ class EPPLibWrapper:
|
|||
if self._pool is not None and restart_pool_if_exists:
|
||||
logger.info("Connection pool restarting...")
|
||||
self.kill_pool()
|
||||
logger.info("Old pool killed")
|
||||
|
||||
self._pool = self._create_pool(self._client, self._login, self.pool_options)
|
||||
|
||||
|
@ -221,6 +224,7 @@ class EPPLibWrapper:
|
|||
credentials are valid, and/or if the Registrar
|
||||
can be contacted
|
||||
"""
|
||||
# This is closed in test_connection_success
|
||||
socket = Socket(self._client, self._login)
|
||||
can_login = False
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ class Socket:
|
|||
|
||||
def connect(self):
|
||||
"""Use epplib to connect."""
|
||||
logger.info("Opening socket on connection pool")
|
||||
self.client.connect()
|
||||
response = self.client.send(self.login)
|
||||
if self.is_login_error(response.code):
|
||||
|
@ -40,11 +41,13 @@ class Socket:
|
|||
|
||||
def disconnect(self):
|
||||
"""Close the connection."""
|
||||
logger.info("Closing socket on connection pool")
|
||||
try:
|
||||
self.client.send(commands.Logout())
|
||||
self.client.close()
|
||||
except Exception:
|
||||
except Exception as err:
|
||||
logger.warning("Connection to registry was not cleanly closed.")
|
||||
logger.error(err)
|
||||
|
||||
def send(self, command):
|
||||
"""Sends a command to the registry.
|
||||
|
@ -77,19 +80,17 @@ class Socket:
|
|||
try:
|
||||
self.client.connect()
|
||||
response = self.client.send(self.login)
|
||||
except LoginError as err:
|
||||
if err.should_retry() and counter < 10:
|
||||
except (LoginError, OSError) as err:
|
||||
logger.error(err)
|
||||
should_retry = True
|
||||
if isinstance(err, LoginError):
|
||||
should_retry = err.should_retry()
|
||||
if should_retry and counter < 3:
|
||||
counter += 1
|
||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
||||
else: # don't try again
|
||||
return False
|
||||
# Occurs when an invalid creds are passed in - such as on localhost
|
||||
except OSError as err:
|
||||
logger.error(err)
|
||||
return False
|
||||
else:
|
||||
self.disconnect()
|
||||
|
||||
# If we encounter a login error, fail
|
||||
if self.is_login_error(response.code):
|
||||
logger.warning("A login error was found in test_connection_success")
|
||||
|
@ -97,3 +98,5 @@ class Socket:
|
|||
|
||||
# Otherwise, just return true
|
||||
return True
|
||||
finally:
|
||||
self.disconnect()
|
||||
|
|
|
@ -98,6 +98,7 @@ class EPPConnectionPool(ConnectionPool):
|
|||
"""Kills all active connections in the pool."""
|
||||
try:
|
||||
if len(self.conn) > 0 or len(self.greenlets) > 0:
|
||||
logger.info("Attempting to kill connections")
|
||||
gevent.killall(self.greenlets)
|
||||
|
||||
self.greenlets.clear()
|
||||
|
@ -107,6 +108,7 @@ class EPPConnectionPool(ConnectionPool):
|
|||
|
||||
# Clear the semaphore
|
||||
self.lock = BoundedSemaphore(self.size)
|
||||
logger.info("Finished killing connections")
|
||||
else:
|
||||
logger.info("No connections to kill.")
|
||||
except Exception as err:
|
||||
|
|
|
@ -619,6 +619,8 @@ SECURE_SSL_REDIRECT = True
|
|||
ALLOWED_HOSTS = [
|
||||
"getgov-stable.app.cloud.gov",
|
||||
"getgov-staging.app.cloud.gov",
|
||||
"getgov-development.app.cloud.gov",
|
||||
"getgov-ky.app.cloud.gov",
|
||||
"getgov-es.app.cloud.gov",
|
||||
"getgov-nl.app.cloud.gov",
|
||||
"getgov-rh.app.cloud.gov",
|
||||
|
|
|
@ -8,6 +8,10 @@ from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
|||
from registrar.utility.errors import (
|
||||
NameserverError,
|
||||
NameserverErrorCodes as nsErrorCodes,
|
||||
DsDataError,
|
||||
DsDataErrorCodes,
|
||||
SecurityEmailError,
|
||||
SecurityEmailErrorCodes,
|
||||
)
|
||||
|
||||
from ..models import Contact, DomainInformation, Domain
|
||||
|
@ -16,6 +20,8 @@ from .common import (
|
|||
DIGEST_TYPE_CHOICES,
|
||||
)
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class DomainAddUserForm(forms.Form):
|
||||
"""Form for adding a user to a domain."""
|
||||
|
@ -152,7 +158,13 @@ class ContactForm(forms.ModelForm):
|
|||
class DomainSecurityEmailForm(forms.Form):
|
||||
"""Form for adding or editing a security email to a domain."""
|
||||
|
||||
security_email = forms.EmailField(label="Security email", required=False)
|
||||
security_email = forms.EmailField(
|
||||
label="Security email",
|
||||
required=False,
|
||||
error_messages={
|
||||
"invalid": str(SecurityEmailError(code=SecurityEmailErrorCodes.BAD_DATA)),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DomainOrgNameAddressForm(forms.ModelForm):
|
||||
|
@ -228,12 +240,22 @@ class DomainDnssecForm(forms.Form):
|
|||
class DomainDsdataForm(forms.Form):
|
||||
"""Form for adding or editing DNSSEC DS Data to a domain."""
|
||||
|
||||
def validate_hexadecimal(value):
|
||||
"""
|
||||
Tests that string matches all hexadecimal values.
|
||||
|
||||
Raise validation error to display error in form
|
||||
if invalid characters entered
|
||||
"""
|
||||
if not re.match(r"^[0-9a-fA-F]+$", value):
|
||||
raise forms.ValidationError(str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_CHARS)))
|
||||
|
||||
key_tag = forms.IntegerField(
|
||||
required=True,
|
||||
label="Key tag",
|
||||
validators=[
|
||||
MinValueValidator(0, message="Value must be between 0 and 65535"),
|
||||
MaxValueValidator(65535, message="Value must be between 0 and 65535"),
|
||||
MinValueValidator(0, message=str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE))),
|
||||
MaxValueValidator(65535, message=str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE))),
|
||||
],
|
||||
error_messages={"required": ("Key tag is required.")},
|
||||
)
|
||||
|
@ -251,15 +273,38 @@ class DomainDsdataForm(forms.Form):
|
|||
label="Digest type",
|
||||
coerce=int, # need to coerce into int so dsData objects can be compared
|
||||
choices=[(None, "--Select--")] + DIGEST_TYPE_CHOICES, # type: ignore
|
||||
error_messages={"required": ("Digest Type is required.")},
|
||||
error_messages={"required": ("Digest type is required.")},
|
||||
)
|
||||
|
||||
digest = forms.CharField(
|
||||
required=True,
|
||||
label="Digest",
|
||||
error_messages={"required": ("Digest is required.")},
|
||||
validators=[validate_hexadecimal],
|
||||
error_messages={
|
||||
"required": "Digest is required.",
|
||||
},
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
# clean is called from clean_forms, which is called from is_valid
|
||||
# after clean_fields. it is used to determine form level errors.
|
||||
# is_valid is typically called from view during a post
|
||||
cleaned_data = super().clean()
|
||||
digest_type = cleaned_data.get("digest_type", 0)
|
||||
digest = cleaned_data.get("digest", "")
|
||||
# validate length of digest depending on digest_type
|
||||
if digest_type == 1 and len(digest) != 40:
|
||||
self.add_error(
|
||||
"digest",
|
||||
DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA1),
|
||||
)
|
||||
elif digest_type == 2 and len(digest) != 64:
|
||||
self.add_error(
|
||||
"digest",
|
||||
DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA256),
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
DomainDsdataFormset = formset_factory(
|
||||
DomainDsdataForm,
|
||||
|
|
|
@ -642,7 +642,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
|
||||
# if unable to update domain raise error and stop
|
||||
if responseCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY:
|
||||
raise NameserverError(code=nsErrorCodes.UNABLE_TO_UPDATE_DOMAIN)
|
||||
raise NameserverError(code=nsErrorCodes.BAD_DATA)
|
||||
|
||||
successTotalNameservers = len(oldNameservers) - deleteCount + addToDomainCount
|
||||
|
||||
|
|
|
@ -11,23 +11,18 @@
|
|||
<h1>
|
||||
{% translate "You are not authorized to view this page" %}
|
||||
</h1>
|
||||
|
||||
<h2>
|
||||
{% translate "Status 401" %}
|
||||
</h2>
|
||||
|
||||
|
||||
{% if friendly_message %}
|
||||
<p>{{ friendly_message }}</p>
|
||||
{% else %}
|
||||
<p>{% translate "Authorization failed." %}</p>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
You must be an authorized user and need to be signed in to view this page.
|
||||
Would you like to <a href="{% url 'login' %}"> try logging in again?</a>
|
||||
You must be an authorized user and signed in to view this page. If you are an authorized user,
|
||||
<strong><a href="{% url 'login' %}"> try signing in again</a>.</strong>
|
||||
</p>
|
||||
<p>
|
||||
If you'd like help with this error <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact/' %}">contact us</a>.
|
||||
</p>
|
||||
If you'd like help with this error <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact/' %}">contact us</a>.</p>
|
||||
|
||||
|
||||
{% if log_identifier %}
|
||||
<p>Here's a unique identifier for this error.</p>
|
||||
|
@ -35,6 +30,7 @@
|
|||
<p>{% translate "Please include it if you contact us." %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="tablet:grid-col-4">
|
||||
<img
|
||||
src="{% static 'img/registrar/dotgov_401_illo.svg' %}"
|
||||
|
@ -43,4 +39,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -114,7 +114,7 @@
|
|||
aria-describedby="Your DNSSEC records will be deleted from the registry."
|
||||
data-force-action
|
||||
>
|
||||
{% include 'includes/modal.html' with cancel_button_resets_ds_form=True modal_heading="Warning: You are about to delete all DS records on your domain" modal_description="To fully disable DNSSEC: In addition to deleting your DS records here you’ll also need to delete the DS records at your DNS host. To avoid causing your domain to appear offline you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button=modal_button|safe %}
|
||||
{% include 'includes/modal.html' with cancel_button_resets_ds_form=True modal_heading="Warning: You are about to remove all DS records on your domain" modal_description="To fully disable DNSSEC: In addition to removing your DS records here you’ll also need to delete the DS records at your DNS host. To avoid causing your domain to appear offline you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button=modal_button|safe %}
|
||||
</div>
|
||||
|
||||
{% endblock %} {# domain_content #}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{% block title %}Security email | {{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block domain_content %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
<h1>Security email</h1>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{% block title %}Your contact information | {{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block domain_content %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
<h1>Your contact information</h1>
|
||||
|
||||
|
|
|
@ -11,12 +11,22 @@ If you’re not affiliated with the above domain{% if domains|length > 1 %}s{% e
|
|||
|
||||
CREATE A LOGIN.GOV ACCOUNT
|
||||
|
||||
You can’t use your old credentials to access the new registrar. Access is now managed through Login.gov, a simple and secure process for signing into many government services with one account. Follow these steps to create your Login.gov account <https://login.gov/help/get-started/create-your-account/>.
|
||||
You can’t use your old credentials to access the new registrar. Access is now managed through Login.gov, a simple and secure process for signing in to many government services with one account.
|
||||
|
||||
When creating an account, you’ll need to provide the same email address you used to log in to the old registrar. That will ensure your domains are linked to your Login.gov account.
|
||||
When creating a Login.gov account, you’ll need to provide the same email address you used to sign in to the old registrar. That will link your domain{% if domains|length > 1 %}s{% endif %} to your account.
|
||||
|
||||
If you need help finding the email address you used in the past, let us know in a reply to this email.
|
||||
|
||||
YOU MUST VERIFY YOUR IDENTITY WITH LOGIN.GOV
|
||||
|
||||
We require you to verify your identity with Login.gov as part of the account creation process. This is an extra layer of security that requires you to prove you are you, and not someone pretending to be you.
|
||||
|
||||
When you try to access the registrar with your Login.gov account, we’ll ask you to verify your identity if you haven’t already. You’ll only have to verify your identity once. You’ll need a state-issued ID, a Social Security number, and a phone number for identity verification.
|
||||
|
||||
Follow these steps to create your Login.gov account <https://login.gov/help/get-started/create-your-account/>.
|
||||
|
||||
Read more about verifying your identity with Login.gov <https://login.gov/help/verify-your-identity/how-to-verify-your-identity/>.
|
||||
|
||||
CHECK YOUR .GOV DOMAIN CONTACTS
|
||||
|
||||
This is a good time to check who has access to your .gov domain{% if domains|length > 1 %}s{% endif %}. The admin, technical, and billing contacts listed for your domain{% if domains|length > 1 %}s{% endif %} in our old system also received this email. In our new registrar, these contacts are all considered “domain managers.” We no longer have the admin, technical, and billing roles, and you aren’t limited to three domain managers like in the old system.
|
||||
|
|
|
@ -13,6 +13,12 @@ import boto3_mocking # type: ignore
|
|||
from registrar.utility.errors import (
|
||||
NameserverError,
|
||||
NameserverErrorCodes,
|
||||
SecurityEmailError,
|
||||
SecurityEmailErrorCodes,
|
||||
GenericError,
|
||||
GenericErrorCodes,
|
||||
DsDataError,
|
||||
DsDataErrorCodes,
|
||||
)
|
||||
|
||||
from registrar.models import (
|
||||
|
@ -1734,13 +1740,13 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
|||
(
|
||||
"RegistryError",
|
||||
form_data_registry_error,
|
||||
"""
|
||||
We’re experiencing a system connection error. Please wait a few minutes
|
||||
and try again. If you continue to receive this error after a few tries,
|
||||
contact help@get.gov
|
||||
""",
|
||||
str(GenericError(code=GenericErrorCodes.CANNOT_CONTACT_REGISTRY)),
|
||||
),
|
||||
(
|
||||
"ContactError",
|
||||
form_data_contact_error,
|
||||
str(SecurityEmailError(code=SecurityEmailErrorCodes.BAD_DATA)),
|
||||
),
|
||||
("ContactError", form_data_contact_error, "Value entered was wrong."),
|
||||
(
|
||||
"RegistrySuccess",
|
||||
form_data_success,
|
||||
|
@ -1874,7 +1880,30 @@ class TestDomainDNSSEC(TestDomainOverview):
|
|||
self.assertContains(page, "The DS Data records for this domain have been updated.")
|
||||
|
||||
def test_ds_data_form_invalid(self):
|
||||
"""DS Data form errors with invalid data
|
||||
"""DS Data form errors with invalid data (missing required fields)
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# all four form fields are required, so will test with each blank
|
||||
add_data_page.forms[0]["form-0-key_tag"] = ""
|
||||
add_data_page.forms[0]["form-0-algorithm"] = ""
|
||||
add_data_page.forms[0]["form-0-digest_type"] = ""
|
||||
add_data_page.forms[0]["form-0-digest"] = ""
|
||||
with less_console_noise(): # swallow logged warning message
|
||||
result = add_data_page.forms[0].submit()
|
||||
# form submission was a post with an error, response should be a 200
|
||||
# error text appears twice, once at the top of the page, once around
|
||||
# the field.
|
||||
self.assertContains(result, "Key tag is required", count=2, status_code=200)
|
||||
self.assertContains(result, "Algorithm is required", count=2, status_code=200)
|
||||
self.assertContains(result, "Digest type 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):
|
||||
"""DS Data form errors with invalid data (key tag too large)
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
|
@ -1883,13 +1912,87 @@ class TestDomainDNSSEC(TestDomainOverview):
|
|||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# first two nameservers are required, so if we empty one out we should
|
||||
# get a form error
|
||||
add_data_page.forms[0]["form-0-key_tag"] = ""
|
||||
add_data_page.forms[0]["form-0-key_tag"] = "65536" # > 65535
|
||||
add_data_page.forms[0]["form-0-algorithm"] = ""
|
||||
add_data_page.forms[0]["form-0-digest_type"] = ""
|
||||
add_data_page.forms[0]["form-0-digest"] = ""
|
||||
with less_console_noise(): # swallow logged warning message
|
||||
result = add_data_page.forms[0].submit()
|
||||
# form submission was a post with an error, response should be a 200
|
||||
# error text appears twice, once at the top of the page, once around
|
||||
# the field.
|
||||
self.assertContains(result, "Key tag is required", count=2, status_code=200)
|
||||
self.assertContains(
|
||||
result, str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE)), count=2, status_code=200
|
||||
)
|
||||
|
||||
def test_ds_data_form_invalid_digest_chars(self):
|
||||
"""DS Data form errors with invalid data (digest contains non hexadecimal chars)
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# first two nameservers are required, so if we empty one out we should
|
||||
# get a form error
|
||||
add_data_page.forms[0]["form-0-key_tag"] = "1234"
|
||||
add_data_page.forms[0]["form-0-algorithm"] = "3"
|
||||
add_data_page.forms[0]["form-0-digest_type"] = "1"
|
||||
add_data_page.forms[0]["form-0-digest"] = "GG1234"
|
||||
with less_console_noise(): # swallow logged warning message
|
||||
result = add_data_page.forms[0].submit()
|
||||
# form submission was a post with an error, response should be a 200
|
||||
# error text appears twice, once at the top of the page, once around
|
||||
# the field.
|
||||
self.assertContains(
|
||||
result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_CHARS)), count=2, status_code=200
|
||||
)
|
||||
|
||||
def test_ds_data_form_invalid_digest_sha1(self):
|
||||
"""DS Data form errors with invalid data (digest is invalid sha-1)
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# first two nameservers are required, so if we empty one out we should
|
||||
# get a form error
|
||||
add_data_page.forms[0]["form-0-key_tag"] = "1234"
|
||||
add_data_page.forms[0]["form-0-algorithm"] = "3"
|
||||
add_data_page.forms[0]["form-0-digest_type"] = "1" # SHA-1
|
||||
add_data_page.forms[0]["form-0-digest"] = "A123"
|
||||
with less_console_noise(): # swallow logged warning message
|
||||
result = add_data_page.forms[0].submit()
|
||||
# form submission was a post with an error, response should be a 200
|
||||
# error text appears twice, once at the top of the page, once around
|
||||
# the field.
|
||||
self.assertContains(
|
||||
result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA1)), count=2, status_code=200
|
||||
)
|
||||
|
||||
def test_ds_data_form_invalid_digest_sha256(self):
|
||||
"""DS Data form errors with invalid data (digest is invalid sha-256)
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# first two nameservers are required, so if we empty one out we should
|
||||
# get a form error
|
||||
add_data_page.forms[0]["form-0-key_tag"] = "1234"
|
||||
add_data_page.forms[0]["form-0-algorithm"] = "3"
|
||||
add_data_page.forms[0]["form-0-digest_type"] = "2" # SHA-256
|
||||
add_data_page.forms[0]["form-0-digest"] = "GG1234"
|
||||
with less_console_noise(): # swallow logged warning message
|
||||
result = add_data_page.forms[0].submit()
|
||||
# form submission was a post with an error, response should be a 200
|
||||
# error text appears twice, once at the top of the page, once around
|
||||
# the field.
|
||||
self.assertContains(
|
||||
result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA256)), count=2, status_code=200
|
||||
)
|
||||
|
||||
|
||||
class TestApplicationStatus(TestWithUser, WebTest):
|
||||
|
|
|
@ -66,18 +66,18 @@ class NameserverErrorCodes(IntEnum):
|
|||
value but is not a subdomain
|
||||
- 3 INVALID_IP invalid ip address format or invalid version
|
||||
- 4 TOO_MANY_HOSTS more than the max allowed host values
|
||||
- 5 UNABLE_TO_UPDATE_DOMAIN unable to update the domain
|
||||
- 6 MISSING_HOST host is missing for a nameserver
|
||||
- 7 INVALID_HOST host is invalid for a nameserver
|
||||
- 5 MISSING_HOST host is missing for a nameserver
|
||||
- 6 INVALID_HOST host is invalid for a nameserver
|
||||
- 7 BAD_DATA bad data input for nameserver
|
||||
"""
|
||||
|
||||
MISSING_IP = 1
|
||||
GLUE_RECORD_NOT_ALLOWED = 2
|
||||
INVALID_IP = 3
|
||||
TOO_MANY_HOSTS = 4
|
||||
UNABLE_TO_UPDATE_DOMAIN = 5
|
||||
MISSING_HOST = 6
|
||||
INVALID_HOST = 7
|
||||
MISSING_HOST = 5
|
||||
INVALID_HOST = 6
|
||||
BAD_DATA = 7
|
||||
|
||||
|
||||
class NameserverError(Exception):
|
||||
|
@ -91,11 +91,12 @@ class NameserverError(Exception):
|
|||
NameserverErrorCodes.GLUE_RECORD_NOT_ALLOWED: ("Name server address does not match domain name"),
|
||||
NameserverErrorCodes.INVALID_IP: ("{}: Enter an IP address in the required format."),
|
||||
NameserverErrorCodes.TOO_MANY_HOSTS: ("Too many hosts provided, you may not have more than 13 nameservers."),
|
||||
NameserverErrorCodes.UNABLE_TO_UPDATE_DOMAIN: (
|
||||
"Unable to update domain, changes were not applied. Check logs as a Registry Error is the likely cause"
|
||||
),
|
||||
NameserverErrorCodes.MISSING_HOST: ("Name server must be provided to enter IP address."),
|
||||
NameserverErrorCodes.INVALID_HOST: ("Enter a name server in the required format, like ns1.example.com"),
|
||||
NameserverErrorCodes.BAD_DATA: (
|
||||
"There’s something wrong with the name server information you provided. "
|
||||
"If you need help email us at help@get.gov."
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, *args, code=None, nameserver=None, ip=None, **kwargs):
|
||||
|
@ -112,3 +113,77 @@ class NameserverError(Exception):
|
|||
|
||||
def __str__(self):
|
||||
return f"{self.message}"
|
||||
|
||||
|
||||
class DsDataErrorCodes(IntEnum):
|
||||
"""Used in the DsDataError class for
|
||||
error mapping.
|
||||
Overview of ds data error codes:
|
||||
- 1 BAD_DATA bad data input in ds data
|
||||
- 2 INVALID_DIGEST_SHA1 invalid digest for digest type SHA-1
|
||||
- 3 INVALID_DIGEST_SHA256 invalid digest for digest type SHA-256
|
||||
- 4 INVALID_DIGEST_CHARS invalid chars in digest
|
||||
- 5 INVALID_KEYTAG_SIZE invalid key tag size > 65535
|
||||
"""
|
||||
|
||||
BAD_DATA = 1
|
||||
INVALID_DIGEST_SHA1 = 2
|
||||
INVALID_DIGEST_SHA256 = 3
|
||||
INVALID_DIGEST_CHARS = 4
|
||||
INVALID_KEYTAG_SIZE = 5
|
||||
|
||||
|
||||
class DsDataError(Exception):
|
||||
"""
|
||||
DsDataError class used to raise exceptions on
|
||||
the ds data getter
|
||||
"""
|
||||
|
||||
_error_mapping = {
|
||||
DsDataErrorCodes.BAD_DATA: (
|
||||
"There’s something wrong with the DS data you provided. If you need help email us at help@get.gov."
|
||||
),
|
||||
DsDataErrorCodes.INVALID_DIGEST_SHA1: ("SHA-1 digest must be exactly 40 characters."),
|
||||
DsDataErrorCodes.INVALID_DIGEST_SHA256: ("SHA-256 digest must be exactly 64 characters."),
|
||||
DsDataErrorCodes.INVALID_DIGEST_CHARS: ("Digest must contain only alphanumeric characters [0-9,a-f]."),
|
||||
DsDataErrorCodes.INVALID_KEYTAG_SIZE: ("Key tag must be less than 65535"),
|
||||
}
|
||||
|
||||
def __init__(self, *args, code=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.code = code
|
||||
if self.code in self._error_mapping:
|
||||
self.message = self._error_mapping.get(self.code)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.message}"
|
||||
|
||||
|
||||
class SecurityEmailErrorCodes(IntEnum):
|
||||
"""Used in the SecurityEmailError class for
|
||||
error mapping.
|
||||
Overview of security email error codes:
|
||||
- 1 BAD_DATA bad data input in security email
|
||||
"""
|
||||
|
||||
BAD_DATA = 1
|
||||
|
||||
|
||||
class SecurityEmailError(Exception):
|
||||
"""
|
||||
SecurityEmailError class used to raise exceptions on
|
||||
the security email form
|
||||
"""
|
||||
|
||||
_error_mapping = {
|
||||
SecurityEmailErrorCodes.BAD_DATA: ("Enter an email address in the required format, like name@example.com.")
|
||||
}
|
||||
|
||||
def __init__(self, *args, code=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.code = code
|
||||
if self.code in self._error_mapping:
|
||||
self.message = self._error_mapping.get(self.code)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.message}"
|
||||
|
|
|
@ -28,6 +28,10 @@ from registrar.utility.errors import (
|
|||
GenericErrorCodes,
|
||||
NameserverError,
|
||||
NameserverErrorCodes as nsErrorCodes,
|
||||
DsDataError,
|
||||
DsDataErrorCodes,
|
||||
SecurityEmailError,
|
||||
SecurityEmailErrorCodes,
|
||||
)
|
||||
from registrar.models.utility.contact_error import ContactError
|
||||
|
||||
|
@ -304,7 +308,7 @@ class DomainNameserversView(DomainFormBaseView):
|
|||
except NameserverError as Err:
|
||||
# NamserverErrors *should* be caught in form; if reached here,
|
||||
# there was an uncaught error in submission (through EPP)
|
||||
messages.error(self.request, NameserverError(code=nsErrorCodes.UNABLE_TO_UPDATE_DOMAIN))
|
||||
messages.error(self.request, NameserverError(code=nsErrorCodes.BAD_DATA))
|
||||
logger.error(f"Nameservers error: {Err}")
|
||||
# TODO: registry is not throwing an error when no connection
|
||||
except RegistryError as Err:
|
||||
|
@ -315,7 +319,7 @@ class DomainNameserversView(DomainFormBaseView):
|
|||
)
|
||||
logger.error(f"Registry connection error: {Err}")
|
||||
else:
|
||||
messages.error(self.request, GenericError(code=GenericErrorCodes.GENERIC_ERROR))
|
||||
messages.error(self.request, NameserverError(code=nsErrorCodes.BAD_DATA))
|
||||
logger.error(f"Registry error: {Err}")
|
||||
else:
|
||||
messages.success(
|
||||
|
@ -445,7 +449,7 @@ class DomainDsDataView(DomainFormBaseView):
|
|||
modal_button = (
|
||||
'<button type="submit" '
|
||||
'class="usa-button usa-button--secondary" '
|
||||
'name="disable-override-click">Delete all records</button>'
|
||||
'name="disable-override-click">Remove all DS Data</button>'
|
||||
)
|
||||
|
||||
# context to back out of a broken form on all fields delete
|
||||
|
@ -491,7 +495,7 @@ class DomainDsDataView(DomainFormBaseView):
|
|||
)
|
||||
logger.error(f"Registry connection error: {err}")
|
||||
else:
|
||||
messages.error(self.request, GenericError(code=GenericErrorCodes.GENERIC_ERROR))
|
||||
messages.error(self.request, DsDataError(code=DsDataErrorCodes.BAD_DATA))
|
||||
logger.error(f"Registry error: {err}")
|
||||
return self.form_invalid(formset)
|
||||
else:
|
||||
|
@ -581,10 +585,10 @@ class DomainSecurityEmailView(DomainFormBaseView):
|
|||
)
|
||||
logger.error(f"Registry connection error: {Err}")
|
||||
else:
|
||||
messages.error(self.request, GenericError(code=GenericErrorCodes.GENERIC_ERROR))
|
||||
messages.error(self.request, SecurityEmailError(code=SecurityEmailErrorCodes.BAD_DATA))
|
||||
logger.error(f"Registry error: {Err}")
|
||||
except ContactError as Err:
|
||||
messages.error(self.request, GenericError(code=GenericErrorCodes.GENERIC_ERROR))
|
||||
messages.error(self.request, SecurityEmailError(code=SecurityEmailErrorCodes.BAD_DATA))
|
||||
logger.error(f"Generic registry error: {Err}")
|
||||
else:
|
||||
messages.success(self.request, "The security email for this domain has been updated.")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue