mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-27 04:58:42 +02:00
Merge branch 'dk/1091-dnssec' into dk/1122-dnssec-rewrite
This commit is contained in:
commit
faf70b9d17
16 changed files with 643 additions and 426 deletions
|
@ -279,17 +279,6 @@ function prepareDeleteButtons() {
|
||||||
let formNum2 = forms.length;
|
let formNum2 = forms.length;
|
||||||
totalForms.setAttribute('value', `${formNum2}`);
|
totalForms.setAttribute('value', `${formNum2}`);
|
||||||
|
|
||||||
// We need to fix the indicies of every existing form otherwise
|
|
||||||
// the frontend and backend will not match and will error on submit
|
|
||||||
// let formNumberRegex = RegExp(`form-(\\d){1}-`,'g');
|
|
||||||
// let formLabelRegex = RegExp(`DS Data record (\\d){1}`, 'g');
|
|
||||||
// forms.forEach((form, index) => {
|
|
||||||
// form.innerHTML = form.innerHTML.replace(formNumberRegex, `form-${index}-`);
|
|
||||||
// form.innerHTML = form.innerHTML.replace(formLabelRegex, `DS Data Record ${index+1}`);
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let formNumberRegex = RegExp(`form-(\\d){1}-`, 'g');
|
let formNumberRegex = RegExp(`form-(\\d){1}-`, 'g');
|
||||||
let formLabelRegex = RegExp(`DS Data record (\\d){1}`, 'g');
|
let formLabelRegex = RegExp(`DS Data record (\\d){1}`, 'g');
|
||||||
|
|
||||||
|
@ -311,10 +300,6 @@ function prepareDeleteButtons() {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,9 +316,8 @@ function prepareDeleteButtons() {
|
||||||
// Attach click event listener on the delete buttons of the existing forms
|
// Attach click event listener on the delete buttons of the existing forms
|
||||||
prepareDeleteButtons();
|
prepareDeleteButtons();
|
||||||
|
|
||||||
if (addButton) {
|
if (addButton)
|
||||||
addButton.addEventListener('click', addForm);
|
addButton.addEventListener('click', addForm);
|
||||||
}
|
|
||||||
|
|
||||||
function addForm(e){
|
function addForm(e){
|
||||||
let forms = document.querySelectorAll(".ds-record");
|
let forms = document.querySelectorAll(".ds-record");
|
||||||
|
@ -389,84 +373,6 @@ function prepareDeleteButtons() {
|
||||||
|
|
||||||
// Attach click event listener on the delete buttons of the new form
|
// Attach click event listener on the delete buttons of the new form
|
||||||
prepareDeleteButtons();
|
prepareDeleteButtons();
|
||||||
|
|
||||||
// We need to fix the indicies of every existing form otherwise
|
|
||||||
// the frontend and backend will not match and will error on submit
|
|
||||||
// forms.forEach((form, index) => {
|
|
||||||
// form.innerHTML = form.innerHTML.replace(formNumberRegex, `form-${index}-`);
|
|
||||||
// form.innerHTML = form.innerHTML.replace(formLabelRegex, `DS Data Record ${index+1}`);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
// (function prepareCancelButtons() {
|
|
||||||
// const cancelButton = document.querySelector('.btn-cancel');
|
|
||||||
|
|
||||||
// if (cancelButton) {
|
|
||||||
// cancelButton.addEventListener('click', () => {
|
|
||||||
|
|
||||||
// // Option 2: Redirect to another page (e.g., the homepage)
|
|
||||||
|
|
||||||
// localStorage.clear(); // Clear all localStorage data
|
|
||||||
// sessionStorage.clear(); // Clear all sessionStorage data
|
|
||||||
|
|
||||||
// location.reload();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// })();
|
|
||||||
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * An IIFE that attaches a click handler on form cancel buttons
|
|
||||||
// *
|
|
||||||
// */
|
|
||||||
// (function prepareCancelButtons() {
|
|
||||||
// const cancelButton = document.querySelector('.btn-cancel');
|
|
||||||
|
|
||||||
// const formsetContainer = document.querySelector('#form-container');
|
|
||||||
// const originalFormHTML = document.querySelector('.ds-record').innerHTML;
|
|
||||||
// const numberOfFormsToReset = document.querySelectorAll('.ds-record').length;
|
|
||||||
// const addNewRecordButton = document.querySelector('#add-ds-form');
|
|
||||||
// const submitButton = document.querySelector('button[type="submit"]');
|
|
||||||
|
|
||||||
// if (cancelButton) {
|
|
||||||
// cancelButton.addEventListener('click', () => {
|
|
||||||
// // Reset the form to its initial values
|
|
||||||
// const form = document.querySelector('form');
|
|
||||||
// resetForm(form);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function resetForm(form) {
|
|
||||||
|
|
||||||
// // Remove all existing forms within the container
|
|
||||||
// formsetContainer.innerHTML = '';
|
|
||||||
// for (let i = 0; i < numberOfFormsToReset; i++) {
|
|
||||||
// formsetContainer.innerHTML += originalFormHTML;
|
|
||||||
// }
|
|
||||||
// formsetContainer.innerHTML += addNewRecordButton
|
|
||||||
// formsetContainer.innerHTML += submitButton
|
|
||||||
|
|
||||||
// const dsRecords = form.querySelectorAll('.ds-record');
|
|
||||||
|
|
||||||
// dsRecords.forEach((record) => {
|
|
||||||
// const initialValuesField = record.querySelector('.initial-values');
|
|
||||||
// const formFields = record.querySelectorAll('input, textarea, select');
|
|
||||||
|
|
||||||
// if (initialValuesField) {
|
|
||||||
// const initialValues = JSON.parse(initialValuesField.value);
|
|
||||||
|
|
||||||
// formFields.forEach((field) => {
|
|
||||||
// const fieldName = field.name;
|
|
||||||
// if (fieldName in initialValues) {
|
|
||||||
// field.value = initialValues[fieldName];
|
|
||||||
// } else {
|
|
||||||
// field.value = ''; // Set to empty if no initial value
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// })();
|
|
|
@ -24,6 +24,14 @@ a.breadcrumb__back {
|
||||||
|
|
||||||
a.usa-button {
|
a.usa-button {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
color: color('white');
|
||||||
|
}
|
||||||
|
|
||||||
|
a.usa-button:visited,
|
||||||
|
a.usa-button:hover,
|
||||||
|
a.usa-button:focus,
|
||||||
|
a.usa-button:active {
|
||||||
|
color: color('white');
|
||||||
}
|
}
|
||||||
|
|
||||||
a.usa-button--outline,
|
a.usa-button--outline,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# common.py
|
# common.py
|
||||||
#
|
#
|
||||||
# ALGORITHM_CHOICES are options for alg attribute in DS Data and Key Data
|
# ALGORITHM_CHOICES are options for alg attribute in DS Data and Key Data
|
||||||
# reference: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
|
# reference:
|
||||||
|
# https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
|
||||||
ALGORITHM_CHOICES = [
|
ALGORITHM_CHOICES = [
|
||||||
(1, "(1) ERSA/MD5 [RSAMD5]"),
|
(1, "(1) ERSA/MD5 [RSAMD5]"),
|
||||||
(2, "(2) Diffie-Hellman [DH]"),
|
(2, "(2) Diffie-Hellman [DH]"),
|
||||||
|
|
|
@ -7,7 +7,13 @@ from django.forms import formset_factory
|
||||||
from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
||||||
|
|
||||||
from ..models import Contact, DomainInformation
|
from ..models import Contact, DomainInformation
|
||||||
from .common import ALGORITHM_CHOICES, DIGEST_TYPE_CHOICES, FLAG_CHOICES, PROTOCOL_CHOICES
|
from .common import (
|
||||||
|
ALGORITHM_CHOICES,
|
||||||
|
DIGEST_TYPE_CHOICES,
|
||||||
|
FLAG_CHOICES,
|
||||||
|
PROTOCOL_CHOICES,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DomainAddUserForm(forms.Form):
|
class DomainAddUserForm(forms.Form):
|
||||||
|
|
||||||
|
@ -154,27 +160,30 @@ class DomainDsdataForm(forms.Form):
|
||||||
required=True,
|
required=True,
|
||||||
label="Key tag",
|
label="Key tag",
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(0, "Value must be between 0 and 65535"),
|
MinValueValidator(0, message="Value must be between 0 and 65535"),
|
||||||
MaxValueValidator(65535, "Value must be between 0 and 65535"),
|
MaxValueValidator(65535, message="Value must be between 0 and 65535"),
|
||||||
],
|
],
|
||||||
|
error_messages={"required": ("Key tag is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
algorithm = forms.TypedChoiceField(
|
algorithm = forms.TypedChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
label="Algorithm",
|
label="Algorithm",
|
||||||
choices=[(None, "--Select--")] + ALGORITHM_CHOICES,
|
choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
|
||||||
|
error_messages={"required": ("Algorithm is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
digest_type = forms.TypedChoiceField(
|
digest_type = forms.TypedChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
label="Digest Type",
|
label="Digest Type",
|
||||||
choices=[(None, "--Select--")] + DIGEST_TYPE_CHOICES,
|
choices=[(None, "--Select--")] + DIGEST_TYPE_CHOICES, # type: ignore
|
||||||
|
error_messages={"required": ("Digest Type is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
digest = forms.CharField(
|
digest = forms.CharField(
|
||||||
required=True,
|
required=True,
|
||||||
label="Digest",
|
label="Digest",
|
||||||
# TODO: Validation of digests in registrar?
|
error_messages={"required": ("Digest is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -193,23 +202,27 @@ class DomainKeydataForm(forms.Form):
|
||||||
required=True,
|
required=True,
|
||||||
label="Flag",
|
label="Flag",
|
||||||
choices=FLAG_CHOICES,
|
choices=FLAG_CHOICES,
|
||||||
|
error_messages={"required": ("Flag is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
protocol = forms.TypedChoiceField(
|
protocol = forms.TypedChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
label="Protocol",
|
label="Protocol",
|
||||||
choices=PROTOCOL_CHOICES,
|
choices=PROTOCOL_CHOICES,
|
||||||
|
error_messages={"required": ("Protocol is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
algorithm = forms.TypedChoiceField(
|
algorithm = forms.TypedChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
label="Algorithm",
|
label="Algorithm",
|
||||||
choices=[(None, "--Select--")] + ALGORITHM_CHOICES,
|
choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
|
||||||
|
error_messages={"required": ("Algorithm is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
pub_key = forms.CharField(
|
pub_key = forms.CharField(
|
||||||
required=True,
|
required=True,
|
||||||
label="Pub key",
|
label="Pub key",
|
||||||
|
error_messages={"required": ("Pub key is required.")},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Generated by Django 4.2.1 on 2023-10-03 06:36
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("registrar", "0032_alter_transitiondomain_status"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="domain",
|
|
||||||
name="dnssec_enabled",
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False, help_text="Boolean indicating if dnssec is enabled"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Generated by Django 4.2.1 on 2023-10-03 17:34
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("registrar", "0033_domain_dnssec_enabled"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="domain",
|
|
||||||
name="dnssec_ds_confirmed",
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Boolean indicating if DS record adding is confirmed",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="domain",
|
|
||||||
name="dnssec_key_confirmed",
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Boolean indicating if Key record adding is confirmed",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -5,13 +5,42 @@
|
||||||
|
|
||||||
{% block domain_content %}
|
{% block domain_content %}
|
||||||
|
|
||||||
<h1>DNSSEC</h1>
|
<h1>{% if dnssec_enabled %}Set up {% endif %}DNSSEC</h1>
|
||||||
|
|
||||||
<p>DNSSEC, or DNS Security Extensions, is additional security layer to protect your website. Enabling DNSSEC ensures that when someone visits your website, they can be certain that it's connecting to the correct server, preventing potential hijacking or tampering with your domain's records. <a href="https://www.icann.org/resources/pages/dnssec-what-is-it-why-important-2019-03-05-en">Read more about DNSSEC and why it is important.</a></p>
|
<p>DNSSEC, or DNS Security Extensions, is additional security layer to protect your website. Enabling DNSSEC ensures that when someone visits your website, they can be certain that it's connecting to the correct server, preventing potential hijacking or tampering with your domain's records.</p>
|
||||||
|
|
||||||
<form class="usa-form usa-form--large" method="post">
|
<form class="usa-form usa-form--large" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if not domain.dnssec_enabled %}
|
{% if has_dnssec_records %}
|
||||||
|
<div class="usa-alert usa-alert--info usa-alert--slim margin-bottom-3">
|
||||||
|
<div class="usa-alert__body">
|
||||||
|
In order to fully disable DNSSEC on your domain, you will need to work with your DNS provider to remove your DNSSEC-related records from your zone.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#toggle-dnssec-alert"
|
||||||
|
class="usa-button"
|
||||||
|
aria-controls="toggle-dnssec-alert"
|
||||||
|
data-open-modal
|
||||||
|
>Disable DNSSEC</a
|
||||||
|
>
|
||||||
|
{% elif dnssec_enabled %}
|
||||||
|
<div id="add-records">
|
||||||
|
<h2> Add DS Records </h2>
|
||||||
|
<p>In order to enable DNSSEC and add Delegation Signer (DS) records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'domain-dns-dnssec-dsdata' pk=domain.id %}" class="usa-button usa-button--outline">Add DS Data</a>
|
||||||
|
<a href="{% url 'domain-dns-dnssec-keydata' pk=domain.id %}" class="usa-button usa-button--outline">Add Key Data</a>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="usa-button usa-button--unstyled"
|
||||||
|
name="cancel_dnssec"
|
||||||
|
id="cancel_dnssec"
|
||||||
|
>Cancel</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div id="enable-dnssec">
|
||||||
<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">
|
||||||
It is strongly recommended that you do not enable this unless you fully understand DNSSEC and know how to set it up properly. If you make a mistake, it could cause your domain name to stop working.
|
It is strongly recommended that you do not enable this unless you fully understand DNSSEC and know how to set it up properly. If you make a mistake, it could cause your domain name to stop working.
|
||||||
|
@ -21,30 +50,19 @@
|
||||||
type="submit"
|
type="submit"
|
||||||
class="usa-button"
|
class="usa-button"
|
||||||
name="enable_dnssec"
|
name="enable_dnssec"
|
||||||
|
id="enable_dnssec"
|
||||||
>Enable DNSSEC</button>
|
>Enable DNSSEC</button>
|
||||||
{% elif domain.dnssecdata is None %}
|
|
||||||
<h2> Add DS Records </h2>
|
|
||||||
<p>In order to enable DNSSEC and add Delegation Signer (DS) records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
|
||||||
<p>
|
|
||||||
<a href="{% url 'domain-dns-dnssec-dsdata' pk=domain.id %}" class="usa-button usa-button--outline">Add DS Data</a>
|
|
||||||
<a href="{% url 'domain-dns-dnssec-keydata' pk=domain.id %}" class="usa-button usa-button--outline">Add Key Data</a>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="usa-button usa-button--unstyled"
|
|
||||||
name="cancel"
|
|
||||||
>Cancel</button>
|
|
||||||
</p>
|
|
||||||
{% else %}
|
|
||||||
<div class="usa-alert usa-alert--info usa-alert--slim margin-bottom-3">
|
|
||||||
<div class="usa-alert__body">
|
|
||||||
In order to fully disable DNSSEC on your domain, you will need to work with your DNS provider to remove your DNSSEC-related records from your zone.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="usa-button"
|
|
||||||
name="disable_dnssec"
|
|
||||||
>Disable DNSSEC</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="usa-modal"
|
||||||
|
id="toggle-dnssec-alert"
|
||||||
|
aria-labelledby="Are you sure you want to continue?"
|
||||||
|
aria-describedby="Your DNSSEC records will be deleted from the registry."
|
||||||
|
>
|
||||||
|
{% include 'includes/modal.html' with modal_heading="Are you sure you want to continue?" modal_description="Your DNSSEC records will be deleted from the registry." modal_button=modal_button|safe %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %} {# domain_content #}
|
{% endblock %} {# domain_content #}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
{% block title %}DS Data | {{ domain.name }} | {% endblock %}
|
{% block title %}DS Data | {{ domain.name }} | {% endblock %}
|
||||||
|
|
||||||
{% block domain_content %}
|
{% block domain_content %}
|
||||||
|
{% for form in formset %}
|
||||||
|
{% include "includes/form_errors.html" with form=form %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<h1>DS Data</h1>
|
<h1>DS Data</h1>
|
||||||
|
|
||||||
|
@ -16,7 +19,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% elif not domain.dnssec_ds_confirmed %}
|
{% elif not dnssec_ds_confirmed %}
|
||||||
<p>In order to enable DNSSEC and add DS records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
<p>In order to enable DNSSEC and add DS records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
||||||
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -70,9 +73,6 @@
|
||||||
|
|
||||||
<div class="grid-row margin-top-1">
|
<div class="grid-row margin-top-1">
|
||||||
<div class="grid-col">
|
<div class="grid-col">
|
||||||
{% comment %} {% with add_group_class="float-right-tablet" %}
|
|
||||||
{% input_with_errors form.delete %}
|
|
||||||
{% endwith %} {% endcomment %}
|
|
||||||
<button type="button" class="usa-button usa-button--unstyled display-block float-right-tablet delete-record">
|
<button type="button" class="usa-button usa-button--unstyled display-block float-right-tablet delete-record">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
||||||
|
@ -81,9 +81,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hidden field to store initial values -->
|
|
||||||
{% comment %} <input type="hidden" class="initial-values" value="{{ form.initial|escapejs }}"> {% endcomment %}
|
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
{% block title %}Key Data | {{ domain.name }} | {% endblock %}
|
{% block title %}Key Data | {{ domain.name }} | {% endblock %}
|
||||||
|
|
||||||
{% block domain_content %}
|
{% block domain_content %}
|
||||||
|
{% for form in formset %}
|
||||||
|
{% include "includes/form_errors.html" with form=form %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<h1>Key Data</h1>
|
<h1>Key Data</h1>
|
||||||
|
|
||||||
|
@ -16,7 +19,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% elif not domain.dnssec_key_confirmed %}
|
{% elif not dnssec_key_confirmed %}
|
||||||
<p>In order to enable DNSSEC and add DS records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
<p>In order to enable DNSSEC and add DS records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
||||||
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -38,7 +41,7 @@
|
||||||
{% for form in formset %}
|
{% for form in formset %}
|
||||||
<fieldset class="ds-record">
|
<fieldset class="ds-record">
|
||||||
|
|
||||||
<legend class="sr-only">Key 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>
|
||||||
|
|
||||||
|
@ -70,9 +73,6 @@
|
||||||
|
|
||||||
<div class="grid-row margin-top-2">
|
<div class="grid-row margin-top-2">
|
||||||
<div class="grid-col">
|
<div class="grid-col">
|
||||||
{% comment %} {% with add_group_class="float-right-tablet" %}
|
|
||||||
{% input_with_errors form.delete %}
|
|
||||||
{% endwith %} {% endcomment %}
|
|
||||||
<button type="button" class="usa-button usa-button--unstyled display-block float-right-tablet delete-record">
|
<button type="button" class="usa-button usa-button--unstyled display-block float-right-tablet delete-record">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
>
|
>
|
||||||
DNSSEC
|
DNSSEC
|
||||||
</a>
|
</a>
|
||||||
{% if domain.dnssec_enabled %}
|
{% if domain.dnssecdata is not None or request.path|startswith:url and request.path|endswith:'data' %}
|
||||||
<ul class="usa-sidenav__sublist">
|
<ul class="usa-sidenav__sublist">
|
||||||
<li class="usa-sidenav__item">
|
<li class="usa-sidenav__item">
|
||||||
{% url 'domain-dns-dnssec-dsdata' pk=domain.id as url %}
|
{% url 'domain-dns-dnssec-dsdata' pk=domain.id as url %}
|
||||||
|
|
42
src/registrar/templates/includes/modal.html
Normal file
42
src/registrar/templates/includes/modal.html
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<div class="usa-modal__content">
|
||||||
|
<div class="usa-modal__main">
|
||||||
|
<h2 class="usa-modal__heading" id="modal-1-heading">
|
||||||
|
{{ modal_heading }}
|
||||||
|
</h2>
|
||||||
|
<div class="usa-prose">
|
||||||
|
<p id="modal-1-description">
|
||||||
|
{{ modal_description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-modal__footer">
|
||||||
|
<ul class="usa-button-group">
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ modal_button }}
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-modal__close"
|
||||||
|
aria-label="Close this window"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||||
|
<use xlink:href="/assets/img/sprite.svg#close"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
|
@ -19,6 +19,13 @@ def startswith(text, starts):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter("endswith")
|
||||||
|
def endswith(text, ends):
|
||||||
|
if isinstance(text, str):
|
||||||
|
return text.endswith(ends)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def public_site_url(url_path):
|
def public_site_url(url_path):
|
||||||
"""Make a full URL for this path at our public site.
|
"""Make a full URL for this path at our public site.
|
||||||
|
|
|
@ -7,7 +7,7 @@ import random
|
||||||
from string import ascii_uppercase
|
from string import ascii_uppercase
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from unittest.mock import MagicMock, Mock, patch
|
from unittest.mock import MagicMock, Mock, patch
|
||||||
from typing import List, Dict
|
from typing import List, Dict, Mapping, Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model, login
|
from django.contrib.auth import get_user_model, login
|
||||||
|
@ -27,6 +27,7 @@ from registrar.models import (
|
||||||
from epplibwrapper import (
|
from epplibwrapper import (
|
||||||
commands,
|
commands,
|
||||||
common,
|
common,
|
||||||
|
extensions,
|
||||||
info,
|
info,
|
||||||
RegistryError,
|
RegistryError,
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
|
@ -685,6 +686,37 @@ class MockEppLib(TestCase):
|
||||||
mockDataInfoHosts = fakedEppObject(
|
mockDataInfoHosts = fakedEppObject(
|
||||||
"lastPw", cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35)
|
"lastPw", cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35)
|
||||||
)
|
)
|
||||||
|
addDsData1 = {
|
||||||
|
"keyTag": 1234,
|
||||||
|
"alg": 3,
|
||||||
|
"digestType": 1,
|
||||||
|
"digest": "ec0bdd990b39feead889f0ba613db4adec0bdd99",
|
||||||
|
}
|
||||||
|
addDsData2 = {
|
||||||
|
"keyTag": 2345,
|
||||||
|
"alg": 3,
|
||||||
|
"digestType": 1,
|
||||||
|
"digest": "ec0bdd990b39feead889f0ba613db4adecb4adec",
|
||||||
|
}
|
||||||
|
keyDataDict = {
|
||||||
|
"flags": 257,
|
||||||
|
"protocol": 3,
|
||||||
|
"alg": 1,
|
||||||
|
"pubKey": "AQPJ////4Q==",
|
||||||
|
}
|
||||||
|
dnssecExtensionWithDsData: Mapping[Any, Any] = {
|
||||||
|
"dsData": [common.DSData(**addDsData1)] # type: ignore
|
||||||
|
}
|
||||||
|
dnssecExtensionWithMultDsData: Mapping[str, Any] = {
|
||||||
|
"dsData": [
|
||||||
|
common.DSData(**addDsData1), # type: ignore
|
||||||
|
common.DSData(**addDsData2), # type: ignore
|
||||||
|
],
|
||||||
|
}
|
||||||
|
dnssecExtensionWithKeyData: Mapping[str, Any] = {
|
||||||
|
"maxSigLife": 3215,
|
||||||
|
"keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
|
||||||
|
}
|
||||||
|
|
||||||
def mockSend(self, _request, cleaned):
|
def mockSend(self, _request, cleaned):
|
||||||
"""Mocks the registry.send function used inside of domain.py
|
"""Mocks the registry.send function used inside of domain.py
|
||||||
|
@ -692,30 +724,9 @@ class MockEppLib(TestCase):
|
||||||
returns objects that simulate what would be in a epp response
|
returns objects that simulate what would be in a epp response
|
||||||
but only relevant pieces for tests"""
|
but only relevant pieces for tests"""
|
||||||
if isinstance(_request, commands.InfoDomain):
|
if isinstance(_request, commands.InfoDomain):
|
||||||
if getattr(_request, "name", None) == "security.gov":
|
return self.mockInfoDomainCommands(_request, cleaned)
|
||||||
return MagicMock(res_data=[self.infoDomainNoContact])
|
|
||||||
elif getattr(_request, "name", None) == "freeman.gov":
|
|
||||||
return MagicMock(res_data=[self.InfoDomainWithContacts])
|
|
||||||
else:
|
|
||||||
return MagicMock(res_data=[self.mockDataInfoDomain])
|
|
||||||
elif isinstance(_request, commands.InfoContact):
|
elif isinstance(_request, commands.InfoContact):
|
||||||
mocked_result: info.InfoContactResultData
|
return self.mockInfoContactCommands(_request, cleaned)
|
||||||
|
|
||||||
# For testing contact types
|
|
||||||
match getattr(_request, "id", None):
|
|
||||||
case "securityContact":
|
|
||||||
mocked_result = self.mockSecurityContact
|
|
||||||
case "technicalContact":
|
|
||||||
mocked_result = self.mockTechnicalContact
|
|
||||||
case "adminContact":
|
|
||||||
mocked_result = self.mockAdministrativeContact
|
|
||||||
case "regContact":
|
|
||||||
mocked_result = self.mockRegistrantContact
|
|
||||||
case _:
|
|
||||||
# Default contact return
|
|
||||||
mocked_result = self.mockDataInfoContact
|
|
||||||
|
|
||||||
return MagicMock(res_data=[mocked_result])
|
|
||||||
elif (
|
elif (
|
||||||
isinstance(_request, commands.CreateContact)
|
isinstance(_request, commands.CreateContact)
|
||||||
and getattr(_request, "id", None) == "fail"
|
and getattr(_request, "id", None) == "fail"
|
||||||
|
@ -734,8 +745,64 @@ class MockEppLib(TestCase):
|
||||||
raise RegistryError(
|
raise RegistryError(
|
||||||
code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
|
code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
|
||||||
)
|
)
|
||||||
|
elif (
|
||||||
|
isinstance(_request, commands.UpdateDomain)
|
||||||
|
and getattr(_request, "name", None) == "dnssec-invalid.gov"
|
||||||
|
):
|
||||||
|
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
|
||||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||||
|
|
||||||
|
def mockInfoDomainCommands(self, _request, cleaned):
|
||||||
|
if getattr(_request, "name", None) == "security.gov":
|
||||||
|
return MagicMock(res_data=[self.infoDomainNoContact])
|
||||||
|
elif getattr(_request, "name", None) == "dnssec-dsdata.gov":
|
||||||
|
return MagicMock(
|
||||||
|
res_data=[self.mockDataInfoDomain],
|
||||||
|
extensions=[
|
||||||
|
extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
elif getattr(_request, "name", None) == "dnssec-multdsdata.gov":
|
||||||
|
return MagicMock(
|
||||||
|
res_data=[self.mockDataInfoDomain],
|
||||||
|
extensions=[
|
||||||
|
extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
elif getattr(_request, "name", None) == "dnssec-keydata.gov":
|
||||||
|
return MagicMock(
|
||||||
|
res_data=[self.mockDataInfoDomain],
|
||||||
|
extensions=[
|
||||||
|
extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
elif getattr(_request, "name", None) == "dnssec-none.gov":
|
||||||
|
# this case is not necessary, but helps improve readability
|
||||||
|
return MagicMock(res_data=[self.mockDataInfoDomain])
|
||||||
|
elif getattr(_request, "name", None) == "freeman.gov":
|
||||||
|
return MagicMock(res_data=[self.InfoDomainWithContacts])
|
||||||
|
else:
|
||||||
|
return MagicMock(res_data=[self.mockDataInfoDomain])
|
||||||
|
|
||||||
|
def mockInfoContactCommands(self, _request, cleaned):
|
||||||
|
mocked_result: info.InfoContactResultData
|
||||||
|
|
||||||
|
# For testing contact types
|
||||||
|
match getattr(_request, "id", None):
|
||||||
|
case "securityContact":
|
||||||
|
mocked_result = self.mockSecurityContact
|
||||||
|
case "technicalContact":
|
||||||
|
mocked_result = self.mockTechnicalContact
|
||||||
|
case "adminContact":
|
||||||
|
mocked_result = self.mockAdministrativeContact
|
||||||
|
case "regContact":
|
||||||
|
mocked_result = self.mockRegistrantContact
|
||||||
|
case _:
|
||||||
|
# Default contact return
|
||||||
|
mocked_result = self.mockDataInfoContact
|
||||||
|
|
||||||
|
return MagicMock(res_data=[mocked_result])
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""mock epp send function as this will fail locally"""
|
"""mock epp send function as this will fail locally"""
|
||||||
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
||||||
|
|
|
@ -3,7 +3,6 @@ Feature being tested: Registry Integration
|
||||||
|
|
||||||
This file tests the various ways in which the registrar interacts with the registry.
|
This file tests the various ways in which the registrar interacts with the registry.
|
||||||
"""
|
"""
|
||||||
from typing import Mapping, Any
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from unittest.mock import MagicMock, patch, call
|
from unittest.mock import MagicMock, patch, call
|
||||||
|
@ -1004,37 +1003,6 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
# for the tests, need a domain in the unknown state
|
# for the tests, need a domain in the unknown state
|
||||||
self.domain, _ = Domain.objects.get_or_create(name="fake.gov")
|
self.domain, _ = Domain.objects.get_or_create(name="fake.gov")
|
||||||
self.addDsData1 = {
|
|
||||||
"keyTag": 1234,
|
|
||||||
"alg": 3,
|
|
||||||
"digestType": 1,
|
|
||||||
"digest": "ec0bdd990b39feead889f0ba613db4adec0bdd99",
|
|
||||||
}
|
|
||||||
self.addDsData2 = {
|
|
||||||
"keyTag": 2345,
|
|
||||||
"alg": 3,
|
|
||||||
"digestType": 1,
|
|
||||||
"digest": "ec0bdd990b39feead889f0ba613db4adecb4adec",
|
|
||||||
}
|
|
||||||
self.keyDataDict = {
|
|
||||||
"flags": 257,
|
|
||||||
"protocol": 3,
|
|
||||||
"alg": 1,
|
|
||||||
"pubKey": "AQPJ////4Q==",
|
|
||||||
}
|
|
||||||
self.dnssecExtensionWithDsData: Mapping[str, Any] = {
|
|
||||||
"dsData": [common.DSData(**self.addDsData1)]
|
|
||||||
}
|
|
||||||
self.dnssecExtensionWithMultDsData: Mapping[str, Any] = {
|
|
||||||
"dsData": [
|
|
||||||
common.DSData(**self.addDsData1),
|
|
||||||
common.DSData(**self.addDsData2),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
self.dnssecExtensionWithKeyData: Mapping[str, Any] = {
|
|
||||||
"maxSigLife": 3215,
|
|
||||||
"keyData": [common.DNSSECKeyData(**self.keyDataDict)],
|
|
||||||
}
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
|
@ -1053,26 +1021,13 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# make sure to stop any other patcher so there are no conflicts
|
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
||||||
self.mockSendPatch.stop()
|
|
||||||
|
|
||||||
def side_effect(_request, cleaned):
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
return MagicMock(
|
|
||||||
res_data=[self.mockDataInfoDomain],
|
|
||||||
extensions=[
|
|
||||||
extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
|
||||||
mocked_send = patcher.start()
|
|
||||||
mocked_send.side_effect = side_effect
|
|
||||||
|
|
||||||
self.domain.dnssecdata = self.dnssecExtensionWithDsData
|
|
||||||
# get the DNS SEC extension added to the UpdateDomain command and
|
# get the DNS SEC extension added to the UpdateDomain command and
|
||||||
# verify that it is properly sent
|
# verify that it is properly sent
|
||||||
# args[0] is the _request sent to registry
|
# args[0] is the _request sent to registry
|
||||||
args, _ = mocked_send.call_args
|
args, _ = self.mockedSendFunction.call_args
|
||||||
# assert that the extension matches
|
# assert that the extension matches
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
args[0].extensions[0],
|
args[0].extensions[0],
|
||||||
|
@ -1081,12 +1036,12 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
# test that the dnssecdata getter is functioning properly
|
# test that the dnssecdata getter is functioning properly
|
||||||
dnssecdata_get = self.domain.dnssecdata
|
dnssecdata_get = domain.dnssecdata
|
||||||
mocked_send.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(
|
call(
|
||||||
commands.UpdateDomain(
|
commands.UpdateDomain(
|
||||||
name="fake.gov",
|
name="dnssec-dsdata.gov",
|
||||||
nsset=None,
|
nsset=None,
|
||||||
keyset=None,
|
keyset=None,
|
||||||
registrant=None,
|
registrant=None,
|
||||||
|
@ -1096,7 +1051,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
call(
|
call(
|
||||||
commands.InfoDomain(
|
commands.InfoDomain(
|
||||||
name="fake.gov",
|
name="dnssec-dsdata.gov",
|
||||||
),
|
),
|
||||||
cleaned=True,
|
cleaned=True,
|
||||||
),
|
),
|
||||||
|
@ -1107,8 +1062,6 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
|
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher.stop()
|
|
||||||
|
|
||||||
def test_dnssec_is_idempotent(self):
|
def test_dnssec_is_idempotent(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Registrant adds DNS data twice, due to a UI glitch
|
Scenario: Registrant adds DNS data twice, due to a UI glitch
|
||||||
|
@ -1124,32 +1077,19 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# make sure to stop any other patcher so there are no conflicts
|
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
||||||
self.mockSendPatch.stop()
|
|
||||||
|
|
||||||
def side_effect(_request, cleaned):
|
|
||||||
return MagicMock(
|
|
||||||
res_data=[self.mockDataInfoDomain],
|
|
||||||
extensions=[
|
|
||||||
extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
|
||||||
mocked_send = patcher.start()
|
|
||||||
mocked_send.side_effect = side_effect
|
|
||||||
|
|
||||||
# set the dnssecdata once
|
# set the dnssecdata once
|
||||||
self.domain.dnssecdata = self.dnssecExtensionWithDsData
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
# set the dnssecdata again
|
# set the dnssecdata again
|
||||||
self.domain.dnssecdata = self.dnssecExtensionWithDsData
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
# test that the dnssecdata getter is functioning properly
|
# test that the dnssecdata getter is functioning properly
|
||||||
dnssecdata_get = self.domain.dnssecdata
|
dnssecdata_get = domain.dnssecdata
|
||||||
mocked_send.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(
|
call(
|
||||||
commands.UpdateDomain(
|
commands.UpdateDomain(
|
||||||
name="fake.gov",
|
name="dnssec-dsdata.gov",
|
||||||
nsset=None,
|
nsset=None,
|
||||||
keyset=None,
|
keyset=None,
|
||||||
registrant=None,
|
registrant=None,
|
||||||
|
@ -1159,7 +1099,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
call(
|
call(
|
||||||
commands.UpdateDomain(
|
commands.UpdateDomain(
|
||||||
name="fake.gov",
|
name="dnssec-dsdata.gov",
|
||||||
nsset=None,
|
nsset=None,
|
||||||
keyset=None,
|
keyset=None,
|
||||||
registrant=None,
|
registrant=None,
|
||||||
|
@ -1169,7 +1109,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
call(
|
call(
|
||||||
commands.InfoDomain(
|
commands.InfoDomain(
|
||||||
name="fake.gov",
|
name="dnssec-dsdata.gov",
|
||||||
),
|
),
|
||||||
cleaned=True,
|
cleaned=True,
|
||||||
),
|
),
|
||||||
|
@ -1180,8 +1120,6 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
|
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher.stop()
|
|
||||||
|
|
||||||
def test_user_adds_dnssec_data_multiple_dsdata(self):
|
def test_user_adds_dnssec_data_multiple_dsdata(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Registrant adds DNSSEC data with multiple DSData.
|
Scenario: Registrant adds DNSSEC data with multiple DSData.
|
||||||
|
@ -1195,26 +1133,13 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# make sure to stop any other patcher so there are no conflicts
|
domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
|
||||||
self.mockSendPatch.stop()
|
|
||||||
|
|
||||||
def side_effect(_request, cleaned):
|
domain.dnssecdata = self.dnssecExtensionWithMultDsData
|
||||||
return MagicMock(
|
|
||||||
res_data=[self.mockDataInfoDomain],
|
|
||||||
extensions=[
|
|
||||||
extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
|
||||||
mocked_send = patcher.start()
|
|
||||||
mocked_send.side_effect = side_effect
|
|
||||||
|
|
||||||
self.domain.dnssecdata = self.dnssecExtensionWithMultDsData
|
|
||||||
# get the DNS SEC extension added to the UpdateDomain command
|
# get the DNS SEC extension added to the UpdateDomain command
|
||||||
# and verify that it is properly sent
|
# and verify that it is properly sent
|
||||||
# args[0] is the _request sent to registry
|
# args[0] is the _request sent to registry
|
||||||
args, _ = mocked_send.call_args
|
args, _ = self.mockedSendFunction.call_args
|
||||||
# assert that the extension matches
|
# assert that the extension matches
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
args[0].extensions[0],
|
args[0].extensions[0],
|
||||||
|
@ -1223,12 +1148,12 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
# test that the dnssecdata getter is functioning properly
|
# test that the dnssecdata getter is functioning properly
|
||||||
dnssecdata_get = self.domain.dnssecdata
|
dnssecdata_get = domain.dnssecdata
|
||||||
mocked_send.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(
|
call(
|
||||||
commands.UpdateDomain(
|
commands.UpdateDomain(
|
||||||
name="fake.gov",
|
name="dnssec-multdsdata.gov",
|
||||||
nsset=None,
|
nsset=None,
|
||||||
keyset=None,
|
keyset=None,
|
||||||
registrant=None,
|
registrant=None,
|
||||||
|
@ -1238,7 +1163,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
call(
|
call(
|
||||||
commands.InfoDomain(
|
commands.InfoDomain(
|
||||||
name="fake.gov",
|
name="dnssec-multdsdata.gov",
|
||||||
),
|
),
|
||||||
cleaned=True,
|
cleaned=True,
|
||||||
),
|
),
|
||||||
|
@ -1249,8 +1174,6 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData["dsData"]
|
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData["dsData"]
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher.stop()
|
|
||||||
|
|
||||||
def test_user_adds_dnssec_keydata(self):
|
def test_user_adds_dnssec_keydata(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Registrant adds DNSSEC data.
|
Scenario: Registrant adds DNSSEC data.
|
||||||
|
@ -1264,26 +1187,13 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# make sure to stop any other patcher so there are no conflicts
|
domain, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
|
||||||
self.mockSendPatch.stop()
|
|
||||||
|
|
||||||
def side_effect(_request, cleaned):
|
domain.dnssecdata = self.dnssecExtensionWithKeyData
|
||||||
return MagicMock(
|
|
||||||
res_data=[self.mockDataInfoDomain],
|
|
||||||
extensions=[
|
|
||||||
extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
|
||||||
mocked_send = patcher.start()
|
|
||||||
mocked_send.side_effect = side_effect
|
|
||||||
|
|
||||||
self.domain.dnssecdata = self.dnssecExtensionWithKeyData
|
|
||||||
# get the DNS SEC extension added to the UpdateDomain command
|
# get the DNS SEC extension added to the UpdateDomain command
|
||||||
# and verify that it is properly sent
|
# and verify that it is properly sent
|
||||||
# args[0] is the _request sent to registry
|
# args[0] is the _request sent to registry
|
||||||
args, _ = mocked_send.call_args
|
args, _ = self.mockedSendFunction.call_args
|
||||||
# assert that the extension matches
|
# assert that the extension matches
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
args[0].extensions[0],
|
args[0].extensions[0],
|
||||||
|
@ -1292,12 +1202,12 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
# test that the dnssecdata getter is functioning properly
|
# test that the dnssecdata getter is functioning properly
|
||||||
dnssecdata_get = self.domain.dnssecdata
|
dnssecdata_get = domain.dnssecdata
|
||||||
mocked_send.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(
|
call(
|
||||||
commands.UpdateDomain(
|
commands.UpdateDomain(
|
||||||
name="fake.gov",
|
name="dnssec-keydata.gov",
|
||||||
nsset=None,
|
nsset=None,
|
||||||
keyset=None,
|
keyset=None,
|
||||||
registrant=None,
|
registrant=None,
|
||||||
|
@ -1307,7 +1217,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
call(
|
call(
|
||||||
commands.InfoDomain(
|
commands.InfoDomain(
|
||||||
name="fake.gov",
|
name="dnssec-keydata.gov",
|
||||||
),
|
),
|
||||||
cleaned=True,
|
cleaned=True,
|
||||||
),
|
),
|
||||||
|
@ -1318,8 +1228,6 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
dnssecdata_get.keyData, self.dnssecExtensionWithKeyData["keyData"]
|
dnssecdata_get.keyData, self.dnssecExtensionWithKeyData["keyData"]
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher.stop()
|
|
||||||
|
|
||||||
def test_update_is_unsuccessful(self):
|
def test_update_is_unsuccessful(self):
|
||||||
"""
|
"""
|
||||||
Scenario: An update to the dns data is unsuccessful
|
Scenario: An update to the dns data is unsuccessful
|
||||||
|
@ -1327,27 +1235,14 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
Then a user-friendly error message is returned for displaying on the web
|
Then a user-friendly error message is returned for displaying on the web
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# make sure to stop any other patcher so there are no conflicts
|
domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov")
|
||||||
self.mockSendPatch.stop()
|
|
||||||
|
|
||||||
def side_effect(_request, cleaned):
|
|
||||||
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
|
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
|
||||||
mocked_send = patcher.start()
|
|
||||||
mocked_send.side_effect = side_effect
|
|
||||||
|
|
||||||
# if RegistryError is raised, view formats user-friendly
|
|
||||||
# error message if error is_client_error, is_session_error, or
|
|
||||||
# is_server_error; so test for those conditions
|
|
||||||
with self.assertRaises(RegistryError) as err:
|
with self.assertRaises(RegistryError) as err:
|
||||||
self.domain.dnssecdata = self.dnssecExtensionWithDsData
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
err.is_client_error() or err.is_session_error() or err.is_server_error()
|
err.is_client_error() or err.is_session_error() or err.is_server_error()
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher.stop()
|
|
||||||
|
|
||||||
|
|
||||||
class TestAnalystClientHold(MockEppLib):
|
class TestAnalystClientHold(MockEppLib):
|
||||||
"""Rule: Analysts may suspend or restore a domain by using client hold"""
|
"""Rule: Analysts may suspend or restore a domain by using client hold"""
|
||||||
|
|
|
@ -18,6 +18,7 @@ from registrar.models import (
|
||||||
DraftDomain,
|
DraftDomain,
|
||||||
DomainInvitation,
|
DomainInvitation,
|
||||||
Contact,
|
Contact,
|
||||||
|
PublicContact,
|
||||||
Website,
|
Website,
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
User,
|
User,
|
||||||
|
@ -1070,21 +1071,60 @@ class TestWithDomainPermissions(TestWithUser):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
|
self.domain_dsdata, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
||||||
|
self.domain_multdsdata, _ = Domain.objects.get_or_create(
|
||||||
|
name="dnssec-multdsdata.gov"
|
||||||
|
)
|
||||||
|
self.domain_keydata, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
|
||||||
|
# We could simply use domain (igorville) but this will be more readable in tests
|
||||||
|
# that inherit this setUp
|
||||||
|
self.domain_dnssec_none, _ = Domain.objects.get_or_create(
|
||||||
|
name="dnssec-none.gov"
|
||||||
|
)
|
||||||
self.domain_information, _ = DomainInformation.objects.get_or_create(
|
self.domain_information, _ = DomainInformation.objects.get_or_create(
|
||||||
creator=self.user, domain=self.domain
|
creator=self.user, domain=self.domain
|
||||||
)
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=self.domain_dsdata
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=self.domain_multdsdata
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=self.domain_keydata
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=self.domain_dnssec_none
|
||||||
|
)
|
||||||
self.role, _ = UserDomainRole.objects.get_or_create(
|
self.role, _ = UserDomainRole.objects.get_or_create(
|
||||||
user=self.user, domain=self.domain, role=UserDomainRole.Roles.ADMIN
|
user=self.user, domain=self.domain, role=UserDomainRole.Roles.ADMIN
|
||||||
)
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=self.domain_dsdata, role=UserDomainRole.Roles.ADMIN
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
domain=self.domain_multdsdata,
|
||||||
|
role=UserDomainRole.Roles.ADMIN,
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=self.domain_keydata, role=UserDomainRole.Roles.ADMIN
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user,
|
||||||
|
domain=self.domain_dnssec_none,
|
||||||
|
role=UserDomainRole.Roles.ADMIN,
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
try:
|
try:
|
||||||
self.domain_information.delete()
|
UserDomainRole.objects.all().delete()
|
||||||
if hasattr(self.domain, "contacts"):
|
if hasattr(self.domain, "contacts"):
|
||||||
self.domain.contacts.all().delete()
|
self.domain.contacts.all().delete()
|
||||||
DomainApplication.objects.all().delete()
|
DomainApplication.objects.all().delete()
|
||||||
self.domain.delete()
|
PublicContact.objects.all().delete()
|
||||||
self.role.delete()
|
Domain.objects.all().delete()
|
||||||
|
UserDomainRole.objects.all().delete()
|
||||||
except ValueError: # pass if already deleted
|
except ValueError: # pass if already deleted
|
||||||
pass
|
pass
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
@ -1132,7 +1172,7 @@ class TestDomainPermissions(TestWithDomainPermissions):
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
|
||||||
class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.app.set_user(self.user.username)
|
self.app.set_user(self.user.username)
|
||||||
|
@ -1142,10 +1182,24 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "igorville.gov")
|
self.assertContains(home_page, "igorville.gov")
|
||||||
# click the "Edit" link
|
# click the "Edit" link
|
||||||
detail_page = home_page.click("Manage")
|
detail_page = home_page.click("Manage", index=0)
|
||||||
self.assertContains(detail_page, "igorville.gov")
|
self.assertContains(detail_page, "igorville.gov")
|
||||||
self.assertContains(detail_page, "Status")
|
self.assertContains(detail_page, "Status")
|
||||||
|
|
||||||
|
def test_domain_overview_blocked_for_ineligible_user(self):
|
||||||
|
"""We could easily duplicate this test for all domain management
|
||||||
|
views, but a single url test should be solid enough since all domain
|
||||||
|
management pages share the same permissions class"""
|
||||||
|
self.user.status = User.RESTRICTED
|
||||||
|
self.user.save()
|
||||||
|
home_page = self.app.get("/")
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
with less_console_noise():
|
||||||
|
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainUserManagement(TestDomainOverview):
|
||||||
def test_domain_user_management(self):
|
def test_domain_user_management(self):
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("domain-users", kwargs={"pk": self.domain.id})
|
reverse("domain-users", kwargs={"pk": self.domain.id})
|
||||||
|
@ -1304,6 +1358,8 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
||||||
home_page = self.app.get(reverse("home"))
|
home_page = self.app.get(reverse("home"))
|
||||||
self.assertContains(home_page, self.domain.name)
|
self.assertContains(home_page, self.domain.name)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainNameservers(TestDomainOverview):
|
||||||
def test_domain_nameservers(self):
|
def test_domain_nameservers(self):
|
||||||
"""Can load domain's nameservers page."""
|
"""Can load domain's nameservers page."""
|
||||||
page = self.client.get(
|
page = self.client.get(
|
||||||
|
@ -1355,6 +1411,8 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
||||||
# the field.
|
# the field.
|
||||||
self.assertContains(result, "This field is required", count=2, status_code=200)
|
self.assertContains(result, "This field is required", count=2, status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainAuthorizingOfficial(TestDomainOverview):
|
||||||
def test_domain_authorizing_official(self):
|
def test_domain_authorizing_official(self):
|
||||||
"""Can load domain's authorizing official page."""
|
"""Can load domain's authorizing official page."""
|
||||||
page = self.client.get(
|
page = self.client.get(
|
||||||
|
@ -1373,6 +1431,8 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
||||||
)
|
)
|
||||||
self.assertContains(page, "Testy")
|
self.assertContains(page, "Testy")
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainOrganization(TestDomainOverview):
|
||||||
def test_domain_org_name_address(self):
|
def test_domain_org_name_address(self):
|
||||||
"""Can load domain's org name and mailing address page."""
|
"""Can load domain's org name and mailing address page."""
|
||||||
page = self.client.get(
|
page = self.client.get(
|
||||||
|
@ -1409,6 +1469,8 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
||||||
self.assertContains(success_result_page, "Not igorville")
|
self.assertContains(success_result_page, "Not igorville")
|
||||||
self.assertContains(success_result_page, "Faketown")
|
self.assertContains(success_result_page, "Faketown")
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainContactInformation(TestDomainOverview):
|
||||||
def test_domain_your_contact_information(self):
|
def test_domain_your_contact_information(self):
|
||||||
"""Can load domain's your contact information page."""
|
"""Can load domain's your contact information page."""
|
||||||
page = self.client.get(
|
page = self.client.get(
|
||||||
|
@ -1425,6 +1487,8 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
||||||
)
|
)
|
||||||
self.assertContains(page, "Testy")
|
self.assertContains(page, "Testy")
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
def test_domain_security_email_existing_security_contact(self):
|
def test_domain_security_email_existing_security_contact(self):
|
||||||
"""Can load domain's security email page."""
|
"""Can load domain's security email page."""
|
||||||
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
||||||
|
@ -1490,17 +1554,213 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
|
||||||
success_page, "The security email for this domain has been updated"
|
success_page, "The security email for this domain has been updated"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_domain_overview_blocked_for_ineligible_user(self):
|
|
||||||
"""We could easily duplicate this test for all domain management
|
class TestDomainDNSSEC(TestDomainOverview):
|
||||||
views, but a single url test should be solid enough since all domain
|
|
||||||
management pages share the same permissions class"""
|
"""MockEPPLib is already inherited."""
|
||||||
self.user.status = User.RESTRICTED
|
|
||||||
self.user.save()
|
def test_dnssec_page_refreshes_enable_button(self):
|
||||||
home_page = self.app.get("/")
|
"""DNSSEC overview page loads when domain has no DNSSEC data
|
||||||
self.assertContains(home_page, "igorville.gov")
|
and shows a 'Enable DNSSEC' button. When button is clicked the template
|
||||||
with less_console_noise():
|
updates. When user navigates away then comes back to the page, the
|
||||||
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
'Enable DNSSEC' button is shown again."""
|
||||||
self.assertEqual(response.status_code, 403)
|
# home_page = self.app.get("/")
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})
|
||||||
|
)
|
||||||
|
self.assertContains(page, "Enable DNSSEC")
|
||||||
|
|
||||||
|
# Prepare the data for the POST request
|
||||||
|
post_data = {
|
||||||
|
"enable_dnssec": "Enable DNSSEC",
|
||||||
|
}
|
||||||
|
updated_page = self.client.post(
|
||||||
|
reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}),
|
||||||
|
post_data,
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(updated_page.status_code, 200)
|
||||||
|
|
||||||
|
self.assertContains(updated_page, "Add DS Data")
|
||||||
|
self.assertContains(updated_page, "Add Key Data")
|
||||||
|
|
||||||
|
self.app.get("/")
|
||||||
|
|
||||||
|
back_to_page = self.client.get(
|
||||||
|
reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})
|
||||||
|
)
|
||||||
|
self.assertContains(back_to_page, "Enable DNSSEC")
|
||||||
|
|
||||||
|
def test_dnssec_page_loads_with_data_in_domain(self):
|
||||||
|
"""DNSSEC overview page loads when domain has DNSSEC data
|
||||||
|
and the template contains a button to disable DNSSEC."""
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse("domain-dns-dnssec", kwargs={"pk": self.domain_multdsdata.id})
|
||||||
|
)
|
||||||
|
self.assertContains(page, "Disable DNSSEC")
|
||||||
|
|
||||||
|
# Prepare the data for the POST request
|
||||||
|
post_data = {
|
||||||
|
"disable_dnssec": "Disable DNSSEC",
|
||||||
|
}
|
||||||
|
updated_page = self.client.post(
|
||||||
|
reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}),
|
||||||
|
post_data,
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(updated_page.status_code, 200)
|
||||||
|
|
||||||
|
self.assertContains(updated_page, "Enable DNSSEC")
|
||||||
|
|
||||||
|
def test_ds_form_loads_with_no_domain_data(self):
|
||||||
|
"""DNSSEC Add DS Data page loads when there is no
|
||||||
|
domain DNSSEC data and shows a button to Add DS Data record"""
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertContains(page, "Add DS Data record")
|
||||||
|
|
||||||
|
def test_ds_form_loads_with_ds_data(self):
|
||||||
|
"""DNSSEC Add DS Data page loads when there is
|
||||||
|
domain DNSSEC DS data and shows the data"""
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})
|
||||||
|
)
|
||||||
|
self.assertContains(page, "DS Data record 1")
|
||||||
|
|
||||||
|
def test_ds_form_loads_with_key_data(self):
|
||||||
|
"""DNSSEC Add DS Data page loads when there is
|
||||||
|
domain DNSSEC KEY data and shows an alert"""
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_keydata.id})
|
||||||
|
)
|
||||||
|
self.assertContains(page, "Warning, you cannot add DS Data")
|
||||||
|
|
||||||
|
def test_key_form_loads_with_no_domain_data(self):
|
||||||
|
"""DNSSEC Add Key Data page loads when there is no
|
||||||
|
domain DNSSEC data and shows a button to Add DS Key record"""
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"domain-dns-dnssec-keydata", kwargs={"pk": self.domain_dnssec_none.id}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertContains(page, "Add DS Key record")
|
||||||
|
|
||||||
|
def test_key_form_loads_with_key_data(self):
|
||||||
|
"""DNSSEC Add Key Data page loads when there is
|
||||||
|
domain DNSSEC Key data and shows the data"""
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse("domain-dns-dnssec-keydata", kwargs={"pk": self.domain_keydata.id})
|
||||||
|
)
|
||||||
|
self.assertContains(page, "DS Data record 1")
|
||||||
|
|
||||||
|
def test_key_form_loads_with_ds_data(self):
|
||||||
|
"""DNSSEC Add Key Data page loads when there is
|
||||||
|
domain DNSSEC DS data and shows an alert"""
|
||||||
|
|
||||||
|
page = self.client.get(
|
||||||
|
reverse("domain-dns-dnssec-keydata", kwargs={"pk": self.domain_dsdata.id})
|
||||||
|
)
|
||||||
|
self.assertContains(page, "Warning, you cannot add Key Data")
|
||||||
|
|
||||||
|
def test_ds_data_form_submits(self):
|
||||||
|
"""DS Data form submits successfully
|
||||||
|
|
||||||
|
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)
|
||||||
|
with less_console_noise(): # swallow log warning message
|
||||||
|
result = add_data_page.forms[0].submit()
|
||||||
|
# form submission was a post, response should be a redirect
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(
|
||||||
|
result["Location"],
|
||||||
|
reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}),
|
||||||
|
)
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
page = result.follow()
|
||||||
|
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
|
||||||
|
|
||||||
|
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"] = ""
|
||||||
|
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)
|
||||||
|
|
||||||
|
def test_key_data_form_submits(self):
|
||||||
|
"""Key Data form submits successfully
|
||||||
|
|
||||||
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
|
"""
|
||||||
|
add_data_page = self.app.get(
|
||||||
|
reverse("domain-dns-dnssec-keydata", kwargs={"pk": self.domain_keydata.id})
|
||||||
|
)
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
with less_console_noise(): # swallow log warning message
|
||||||
|
result = add_data_page.forms[0].submit()
|
||||||
|
# form submission was a post, response should be a redirect
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(
|
||||||
|
result["Location"],
|
||||||
|
reverse("domain-dns-dnssec-keydata", kwargs={"pk": self.domain_keydata.id}),
|
||||||
|
)
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
page = result.follow()
|
||||||
|
self.assertContains(
|
||||||
|
page, "The Key Data records for this domain have been updated."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_key_data_form_invalid(self):
|
||||||
|
"""Key Data form errors with invalid data
|
||||||
|
|
||||||
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
|
"""
|
||||||
|
add_data_page = self.app.get(
|
||||||
|
reverse("domain-dns-dnssec-keydata", kwargs={"pk": self.domain_keydata.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-pub_key"] = ""
|
||||||
|
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, "Pub key is required", count=2, status_code=200)
|
||||||
|
|
||||||
|
|
||||||
class TestApplicationStatus(TestWithUser, WebTest):
|
class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
|
|
|
@ -234,37 +234,48 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
|
||||||
template_name = "domain_dnssec.html"
|
template_name = "domain_dnssec.html"
|
||||||
form_class = DomainDnssecForm
|
form_class = DomainDnssecForm
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
"""The initial value for the form (which is a formset here)."""
|
||||||
|
self.domain = self.get_object()
|
||||||
|
|
||||||
|
has_dnssec_records = self.domain.dnssecdata is not None
|
||||||
|
|
||||||
|
# Create HTML for the buttons
|
||||||
|
modal_button = (
|
||||||
|
'<button type="submit" '
|
||||||
|
'class="usa-button" '
|
||||||
|
'name="disable_dnssec">Disable DNSSEC</button>'
|
||||||
|
)
|
||||||
|
|
||||||
|
context["modal_button"] = modal_button
|
||||||
|
context["has_dnssec_records"] = has_dnssec_records
|
||||||
|
context["dnssec_enabled"] = self.request.session.pop("dnssec_enabled", False)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to the DNSSEC page for the domain."""
|
"""Redirect to the DNSSEC page for the domain."""
|
||||||
return reverse("domain-dns-dnssec", kwargs={"pk": self.domain.pk})
|
return reverse("domain-dns-dnssec", kwargs={"pk": self.domain.pk})
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Form submission posts to this view.
|
"""Form submission posts to this view."""
|
||||||
"""
|
|
||||||
self.domain = self.get_object()
|
self.domain = self.get_object()
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
if 'enable_dnssec' in request.POST:
|
if "disable_dnssec" in request.POST:
|
||||||
self.domain.dnssec_enabled = True
|
|
||||||
self.domain.save()
|
|
||||||
elif 'cancel' in request.POST:
|
|
||||||
self.domain.dnssec_enabled = False
|
|
||||||
self.domain.dnssec_ds_confirmed = False
|
|
||||||
self.domain.dnssec_key_confirmed = False
|
|
||||||
self.domain.save()
|
|
||||||
elif 'disable_dnssec' in request.POST:
|
|
||||||
try:
|
try:
|
||||||
self.domain.dnssecdata = {}
|
self.domain.dnssecdata = {}
|
||||||
except RegistryError as err:
|
except RegistryError as err:
|
||||||
errmsg = "Error removing existing DNSSEC record(s)."
|
errmsg = "Error removing existing DNSSEC record(s)."
|
||||||
logger.error(errmsg + ": " + err)
|
logger.error(errmsg + ": " + err)
|
||||||
messages.error(
|
messages.error(self.request, errmsg)
|
||||||
self.request, errmsg
|
request.session["dnssec_ds_confirmed"] = False
|
||||||
)
|
request.session["dnssec_key_confirmed"] = False
|
||||||
self.domain.dnssec_enabled = False
|
elif "enable_dnssec" in request.POST:
|
||||||
self.domain.dnssec_ds_confirmed = False
|
request.session["dnssec_enabled"] = True
|
||||||
self.domain.dnssec_key_confirmed = False
|
request.session["dnssec_ds_confirmed"] = False
|
||||||
self.domain.save()
|
request.session["dnssec_key_confirmed"] = False
|
||||||
|
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
|
|
||||||
|
@ -284,14 +295,21 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
|
||||||
initial_data = []
|
initial_data = []
|
||||||
|
|
||||||
if dnssecdata is not None:
|
if dnssecdata is not None:
|
||||||
|
|
||||||
if dnssecdata.keyData is not None:
|
if dnssecdata.keyData is not None:
|
||||||
# TODO: Throw an error
|
# TODO: Throw an error
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if dnssecdata.dsData is not None:
|
if dnssecdata.dsData is not None:
|
||||||
# Add existing nameservers as initial data
|
# Add existing nameservers as initial data
|
||||||
initial_data.extend({"key_tag": record.keyTag, "algorithm": record.alg, "digest_type": record.digestType, "digest": record.digest} for record in dnssecdata.dsData)
|
initial_data.extend(
|
||||||
|
{
|
||||||
|
"key_tag": record.keyTag,
|
||||||
|
"algorithm": record.alg,
|
||||||
|
"digest_type": record.digestType,
|
||||||
|
"digest": record.digest,
|
||||||
|
}
|
||||||
|
for record in dnssecdata.dsData
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure at least 3 fields, filled or empty
|
# Ensure at least 3 fields, filled or empty
|
||||||
while len(initial_data) == 0:
|
while len(initial_data) == 0:
|
||||||
|
@ -308,6 +326,19 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
# use "formset" instead of "form" for the key
|
# use "formset" instead of "form" for the key
|
||||||
context["formset"] = context.pop("form")
|
context["formset"] = context.pop("form")
|
||||||
|
|
||||||
|
# set the dnssec_ds_confirmed flag in the context for this view
|
||||||
|
# based either on the existence of DS Data in the domain,
|
||||||
|
# or on the flag stored in the session
|
||||||
|
domain = self.get_object()
|
||||||
|
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
|
||||||
|
|
||||||
|
if dnssecdata is not None and dnssecdata.dsData is not None:
|
||||||
|
self.request.session["dnssec_ds_confirmed"] = True
|
||||||
|
|
||||||
|
context["dnssec_ds_confirmed"] = self.request.session.get(
|
||||||
|
"dnssec_ds_confirmed", False
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
@ -315,14 +346,13 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
formset = self.get_form()
|
formset = self.get_form()
|
||||||
|
|
||||||
if 'confirm-ds' in request.POST:
|
if "confirm-ds" in request.POST:
|
||||||
self.object.dnssec_ds_confirmed = True
|
request.session["dnssec_ds_confirmed"] = True
|
||||||
self.object.dnssec_key_confirmed = False
|
request.session["dnssec_key_confirmed"] = False
|
||||||
self.object.save()
|
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
if 'btn-cancel-click' in request.POST:
|
if "btn-cancel-click" in request.POST:
|
||||||
return redirect('/', {'formset': formset},RequestContext(request))
|
return redirect("/", {"formset": formset}, RequestContext(request))
|
||||||
|
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
return self.form_valid(formset)
|
return self.form_valid(formset)
|
||||||
|
@ -333,11 +363,12 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
|
||||||
"""The formset is valid, perform something with it."""
|
"""The formset is valid, perform something with it."""
|
||||||
|
|
||||||
# Set the nameservers from the formset
|
# Set the nameservers from the formset
|
||||||
dnssecdata = {"dsData":[]}
|
dnssecdata = {"dsData": []}
|
||||||
|
|
||||||
for form in formset:
|
for form in formset:
|
||||||
try:
|
try:
|
||||||
# if 'delete' not in form.cleaned_data or form.cleaned_data['delete'] == False:
|
# if 'delete' not in form.cleaned_data
|
||||||
|
# or form.cleaned_data['delete'] == False:
|
||||||
dsrecord = {
|
dsrecord = {
|
||||||
"keyTag": form.cleaned_data["key_tag"],
|
"keyTag": form.cleaned_data["key_tag"],
|
||||||
"alg": form.cleaned_data["algorithm"],
|
"alg": form.cleaned_data["algorithm"],
|
||||||
|
@ -358,9 +389,7 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
|
||||||
errmsg = "Error updating DNSSEC data in the registry."
|
errmsg = "Error updating DNSSEC data in the registry."
|
||||||
logger.error(errmsg)
|
logger.error(errmsg)
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
messages.error(
|
messages.error(self.request, errmsg)
|
||||||
self.request, errmsg
|
|
||||||
)
|
|
||||||
return self.form_invalid(formset)
|
return self.form_invalid(formset)
|
||||||
else:
|
else:
|
||||||
messages.success(
|
messages.success(
|
||||||
|
@ -370,7 +399,6 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DomainKeydataView(DomainPermissionView, FormMixin):
|
class DomainKeydataView(DomainPermissionView, FormMixin):
|
||||||
|
|
||||||
"""Domain DNSSEC key data editing view."""
|
"""Domain DNSSEC key data editing view."""
|
||||||
|
@ -386,14 +414,21 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
|
||||||
initial_data = []
|
initial_data = []
|
||||||
|
|
||||||
if dnssecdata is not None:
|
if dnssecdata is not None:
|
||||||
|
|
||||||
if dnssecdata.dsData is not None:
|
if dnssecdata.dsData is not None:
|
||||||
# TODO: Throw an error
|
# TODO: Throw an error
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if dnssecdata.keyData is not None:
|
if dnssecdata.keyData is not None:
|
||||||
# Add existing keydata as initial data
|
# Add existing keydata as initial data
|
||||||
initial_data.extend({"flag": record.flags, "protocol": record.protocol, "algorithm": record.alg, "pub_key": record.pubKey} for record in dnssecdata.keyData)
|
initial_data.extend(
|
||||||
|
{
|
||||||
|
"flag": record.flags,
|
||||||
|
"protocol": record.protocol,
|
||||||
|
"algorithm": record.alg,
|
||||||
|
"pub_key": record.pubKey,
|
||||||
|
}
|
||||||
|
for record in dnssecdata.keyData
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure at least 3 fields, filled or empty
|
# Ensure at least 3 fields, filled or empty
|
||||||
while len(initial_data) == 0:
|
while len(initial_data) == 0:
|
||||||
|
@ -410,6 +445,19 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
# use "formset" instead of "form" for the key
|
# use "formset" instead of "form" for the key
|
||||||
context["formset"] = context.pop("form")
|
context["formset"] = context.pop("form")
|
||||||
|
|
||||||
|
# set the dnssec_key_confirmed flag in the context for this view
|
||||||
|
# based either on the existence of Key Data in the domain,
|
||||||
|
# or on the flag stored in the session
|
||||||
|
domain = self.get_object()
|
||||||
|
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
|
||||||
|
|
||||||
|
if dnssecdata is not None and dnssecdata.keyData is not None:
|
||||||
|
self.request.session["dnssec_key_confirmed"] = True
|
||||||
|
|
||||||
|
context["dnssec_key_confirmed"] = self.request.session.get(
|
||||||
|
"dnssec_key_confirmed", False
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
@ -417,12 +465,15 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
formset = self.get_form()
|
formset = self.get_form()
|
||||||
|
|
||||||
if 'confirm-key' in request.POST:
|
if "confirm-key" in request.POST:
|
||||||
self.object.dnssec_key_confirmed = True
|
request.session["dnssec_key_confirmed"] = True
|
||||||
self.object.dnssec_ds_confirmed = False
|
request.session["dnssec_ds_confirmed"] = False
|
||||||
self.object.save()
|
self.object.save()
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
|
if "btn-cancel-click" in request.POST:
|
||||||
|
return redirect("/", {"formset": formset}, RequestContext(request))
|
||||||
|
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
return self.form_valid(formset)
|
return self.form_valid(formset)
|
||||||
else:
|
else:
|
||||||
|
@ -432,11 +483,12 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
|
||||||
"""The formset is valid, perform something with it."""
|
"""The formset is valid, perform something with it."""
|
||||||
|
|
||||||
# Set the nameservers from the formset
|
# Set the nameservers from the formset
|
||||||
dnssecdata = {"keyData":[]}
|
dnssecdata = {"keyData": []}
|
||||||
|
|
||||||
for form in formset:
|
for form in formset:
|
||||||
try:
|
try:
|
||||||
# if 'delete' not in form.cleaned_data or form.cleaned_data['delete'] == False:
|
# if 'delete' not in form.cleaned_data
|
||||||
|
# or form.cleaned_data['delete'] == False:
|
||||||
keyrecord = {
|
keyrecord = {
|
||||||
"flags": form.cleaned_data["flag"],
|
"flags": form.cleaned_data["flag"],
|
||||||
"protocol": form.cleaned_data["protocol"],
|
"protocol": form.cleaned_data["protocol"],
|
||||||
|
@ -456,9 +508,7 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
|
||||||
errmsg = "Error updating DNSSEC data in the registry."
|
errmsg = "Error updating DNSSEC data in the registry."
|
||||||
logger.error(errmsg)
|
logger.error(errmsg)
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
messages.error(
|
messages.error(self.request, errmsg)
|
||||||
self.request, errmsg
|
|
||||||
)
|
|
||||||
return self.form_invalid(formset)
|
return self.form_invalid(formset)
|
||||||
else:
|
else:
|
||||||
messages.success(
|
messages.success(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue