The Domain Name System (DNS) is the internet service that translates your domain name into an IP address. Before your .gov domain can be used, you'll need to connect it to your DNS hosting service and provide us with your name server information.
+
+
You can enter your name services, as well as other DNS-related information, in the following sections:
+
+ {% url 'domain-nameservers' pk=domain.id as url %}
+
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. Read more about DNSSEC and why it is important.
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. Read more about DNSSEC and why it is important.
+
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. Read more about DNSSEC and why it is important.
+ {% csrf_token %}
+ {{ formset.management_form }}
+
+ {% for form in formset %}
+
+ {% endfor %}
+
+
+
+
+
+ {% endif %}
{% endblock %} {# domain_content #}
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 5094c2bed..111e75812 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -31,6 +31,8 @@ from ..forms import (
DomainDnssecForm,
DomainDsdataFormset,
DomainDsdataForm,
+ DomainKeydataFormset,
+ DomainKeydataForm,
)
from epplibwrapper import (
@@ -371,11 +373,116 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
-class DomainKeydataView(DomainPermissionView):
+class DomainKeydataView(DomainPermissionView, FormMixin):
"""Domain DNSSEC key data editing view."""
template_name = "domain_keydata.html"
+ form_class = DomainKeydataFormset
+ form = DomainKeydataForm
+
+ def get_initial(self):
+ """The initial value for the form (which is a formset here)."""
+ domain = self.get_object()
+ dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
+ initial_data = []
+
+ if dnssecdata is not None:
+
+ if dnssecdata.dsData is not None:
+ # TODO: Throw an error
+ pass
+
+ if dnssecdata.keyData is not None:
+ # 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)
+
+ return initial_data
+
+ def get_success_url(self):
+ """Redirect to the Key Data page for the domain."""
+ return reverse("domain-dns-dnssec-keydata", kwargs={"pk": self.object.pk})
+
+ def get_context_data(self, **kwargs):
+ """Adjust context from FormMixin for formsets."""
+ context = super().get_context_data(**kwargs)
+ # use "formset" instead of "form" for the key
+ context["formset"] = context.pop("form")
+ return context
+
+ def post(self, request, *args, **kwargs):
+ """Formset submission posts to this view."""
+ self.object = self.get_object()
+ formset = self.get_form()
+
+ if formset.is_valid():
+ return self.form_valid(formset)
+ else:
+ #
+ #
+ #
+ # testing delete
+ try:
+ for form in formset:
+ if 'delete' in form.cleaned_data:
+ logger.debug(f"delete: {form.cleaned_data['delete']}")
+ else:
+ logger.debug(f"delete key does not exist, harcoding false")
+ except KeyError:
+ logger.debug(f"KeyError: {KeyError}")
+ #
+ #
+ #
+ #
+
+ return self.form_invalid(formset)
+
+ def form_valid(self, formset):
+ """The formset is valid, perform something with it."""
+
+ # Set the nameservers from the formset
+ dnssecdata = {"keyData":[]}
+
+ for form in formset:
+ try:
+ #
+ #
+ #
+ # untested
+ if 'delete' in form.cleaned_data:
+ if form.cleaned_data['delete'] == False:
+ pass
+ else:
+ # delete key exists and is true, delete this record
+ logger.debug(f"delete: {form.cleaned_data['delete']}")
+
+ else:
+ logger.debug(f"delete key does not exist, pass")
+ pass
+ #
+ #
+ #
+ #
+
+ keyrecord = {
+ "flags": form.cleaned_data["flag"],
+ "protocol": form.cleaned_data["protocol"],
+ "alg": form.cleaned_data["algorithm"],
+ "pubKey": form.cleaned_data["pub_key"],
+ }
+ dnssecdata["keyData"].append(common.DNSSECKeyData(**keyrecord))
+ except KeyError:
+ # no server information in this field, skip it
+ pass
+ domain = self.get_object()
+ domain.dnssecdata = dnssecdata
+
+ messages.success(
+ self.request, "The Key Data records for this domain have been updated."
+ )
+
+ # superclass has the redirect
+ return super().form_valid(formset)
class DomainYourContactInformationView(DomainPermissionView, FormMixin):
From 0725b3c244203c75524f4ad1afe7e847ba32762e Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 3 Oct 2023 12:26:41 -0400
Subject: [PATCH 15/79] some updates to error handling, deletes, etc
---
src/registrar/templates/domain_dnssec.html | 4 +-
src/registrar/views/domain.py | 163 ++++++++-------------
2 files changed, 61 insertions(+), 106 deletions(-)
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index 675cbc087..032992014 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -27,11 +27,11 @@
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.
{% else %}
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 111e75812..5d3ca47c8 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -38,6 +38,7 @@ from ..forms import (
from epplibwrapper import (
common,
extensions,
+ RegistryError,
)
from ..utility.email import send_templated_email, EmailSendingError
@@ -252,8 +253,18 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
if 'enable_dnssec' in request.POST:
self.domain.dnssec_enabled = True
self.domain.save()
+ elif 'cancel' in request.POST:
+ self.domain.dnssec_enabled = False
+ self.domain.save()
elif 'disable_dnssec' in request.POST:
- self.domain.dnssecdata = {}
+ try:
+ self.domain.dnssecdata = {}
+ except RegistryError as err:
+ errmsg = "Error removing existing DNSSEC record(s)."
+ logger.error(errmsg + ": " + err)
+ messages.error(
+ self.request, errmsg
+ )
self.domain.dnssec_enabled = False
self.domain.save()
@@ -305,23 +316,6 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
if formset.is_valid():
return self.form_valid(formset)
else:
- #
- #
- #
- # testing delete
- try:
- for form in formset:
- if 'delete' in form.cleaned_data:
- logger.debug(f"delete: {form.cleaned_data['delete']}")
- else:
- logger.debug(f"delete key does not exist, harcoding false")
- except KeyError:
- logger.debug(f"KeyError: {KeyError}")
- #
- #
- #
- #
-
return self.form_invalid(formset)
def form_valid(self, formset):
@@ -332,45 +326,34 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
for form in formset:
try:
- #
- #
- #
- # untested
- if 'delete' in form.cleaned_data:
- if form.cleaned_data['delete'] == False:
- pass
- else:
- # delete key exists and is true, delete this record
- logger.debug(f"delete: {form.cleaned_data['delete']}")
-
- else:
- logger.debug(f"delete key does not exist, pass")
- pass
- #
- #
- #
- #
-
- dsrecord = {
- "keyTag": form.cleaned_data["key_tag"],
- "alg": form.cleaned_data["algorithm"],
- "digestType": form.cleaned_data["digest_type"],
- "digest": form.cleaned_data["digest"],
- }
- dnssecdata["dsData"].append(common.DSData(**dsrecord))
+ if 'delete' in form.cleaned_data and form.cleaned_data['delete'] == True:
+ dsrecord = {
+ "keyTag": form.cleaned_data["key_tag"],
+ "alg": form.cleaned_data["algorithm"],
+ "digestType": form.cleaned_data["digest_type"],
+ "digest": form.cleaned_data["digest"],
+ }
+ dnssecdata["dsData"].append(common.DSData(**dsrecord))
except KeyError:
# no server information in this field, skip it
pass
domain = self.get_object()
- domain.dnssecdata = dnssecdata
-
- messages.success(
- self.request, "The DS Data records for this domain have been updated."
- )
-
- # superclass has the redirect
- return super().form_valid(formset)
-
+ try:
+ domain.dnssecdata = dnssecdata
+ except RegistryError as err:
+ errmsg = "Error updating DNSSEC data in the registry."
+ logger.error(f"{{ errmsg }}: {{ err }}")
+ messages.error(
+ self.request, errmsg
+ )
+ return self.form_invalid(formset)
+ else:
+ messages.success(
+ self.request, "The DS Data records for this domain have been updated."
+ )
+ # superclass has the redirect
+ return super().form_valid(formset)
+
class DomainKeydataView(DomainPermissionView, FormMixin):
@@ -418,23 +401,6 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
if formset.is_valid():
return self.form_valid(formset)
else:
- #
- #
- #
- # testing delete
- try:
- for form in formset:
- if 'delete' in form.cleaned_data:
- logger.debug(f"delete: {form.cleaned_data['delete']}")
- else:
- logger.debug(f"delete key does not exist, harcoding false")
- except KeyError:
- logger.debug(f"KeyError: {KeyError}")
- #
- #
- #
- #
-
return self.form_invalid(formset)
def form_valid(self, formset):
@@ -445,44 +411,33 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
for form in formset:
try:
- #
- #
- #
- # untested
- if 'delete' in form.cleaned_data:
- if form.cleaned_data['delete'] == False:
- pass
- else:
- # delete key exists and is true, delete this record
- logger.debug(f"delete: {form.cleaned_data['delete']}")
-
- else:
- logger.debug(f"delete key does not exist, pass")
- pass
- #
- #
- #
- #
-
- keyrecord = {
- "flags": form.cleaned_data["flag"],
- "protocol": form.cleaned_data["protocol"],
- "alg": form.cleaned_data["algorithm"],
- "pubKey": form.cleaned_data["pub_key"],
- }
- dnssecdata["keyData"].append(common.DNSSECKeyData(**keyrecord))
+ if 'delete' in form.cleaned_data and form.cleaned_data['delete'] == True:
+ keyrecord = {
+ "flags": form.cleaned_data["flag"],
+ "protocol": form.cleaned_data["protocol"],
+ "alg": form.cleaned_data["algorithm"],
+ "pubKey": form.cleaned_data["pub_key"],
+ }
+ dnssecdata["keyData"].append(common.DNSSECKeyData(**keyrecord))
except KeyError:
# no server information in this field, skip it
pass
domain = self.get_object()
- domain.dnssecdata = dnssecdata
-
- messages.success(
- self.request, "The Key Data records for this domain have been updated."
- )
-
- # superclass has the redirect
- return super().form_valid(formset)
+ try:
+ domain.dnssecdata = dnssecdata
+ except RegistryError as err:
+ errmsg = "Error updating DNSSEC data in the registry."
+ logger.error(f"{{ errmsg }}: {{ err }}")
+ messages.error(
+ self.request, errmsg
+ )
+ return self.form_invalid(formset)
+ else:
+ messages.success(
+ self.request, "The Key Data records for this domain have been updated."
+ )
+ # superclass has the redirect
+ return super().form_valid(formset)
class DomainYourContactInformationView(DomainPermissionView, FormMixin):
From faae7693c4b45cbda0833a607abb25485765fb29 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Tue, 3 Oct 2023 14:06:59 -0400
Subject: [PATCH 16/79] implemented new in domain model dnssec_ds_confirmed and
dnssec_key_confirmed to control intial blurb msg on the add pages, cleaned up
JS
---
src/registrar/assets/js/get-gov.js | 41 ++++++++++++++++++-
...034_domain_dnssec_ds_confirmed_and_more.py | 28 +++++++++++++
src/registrar/models/domain.py | 10 +++++
src/registrar/templates/domain_dnssec.html | 2 +-
src/registrar/templates/domain_dsdata.html | 10 +++++
src/registrar/templates/domain_keydata.html | 10 +++++
src/registrar/views/domain.py | 16 ++++++++
7 files changed, 115 insertions(+), 2 deletions(-)
create mode 100644 src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 1ffdeb1c6..109d18b3d 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -292,7 +292,46 @@ function handleValidationClick(e) {
newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `DS Data Record ${formNum+1}`)
// newForm.innerHTML = newForm.innerHTML.replace(formExampleRegex, `ns${formNum+1}`)
container.insertBefore(newForm, addButton)
- newForm.querySelector("input").value = ""
+
+ let inputs = newForm.querySelectorAll("input");
+
+ // Reset the values of each input to blank
+ inputs.forEach((input) => {
+ input.classList.remove("usa-input--error");
+ if (input.type === "text" || input.type === "number" || input.type === "password") {
+ input.value = ""; // Set the value to an empty string
+
+ } else if (input.type === "checkbox" || input.type === "radio") {
+ input.checked = false; // Uncheck checkboxes and radios
+ }
+ });
+
+ let selects = newForm.querySelectorAll("select");
+
+ selects.forEach((select) => {
+ select.classList.remove("usa-input--error");
+ select.selectedIndex = 0; // Set the value to an empty string
+ });
+
+
+ let labels = newForm.querySelectorAll("label");
+ labels.forEach((label) => {
+ label.classList.remove("usa-label--error");
+ });
+
+
+ let usaFormGroups = newForm.querySelectorAll(".usa-form-group");
+ usaFormGroups.forEach((usaFormGroup) => {
+ usaFormGroup.classList.remove("usa-form-group--error");
+ });
+
+ let usaErrorMessages = newForm.querySelectorAll(".usa-error-message");
+ usaErrorMessages.forEach((usaErrorMessage) => {
+ let parentDiv = usaErrorMessage.closest('div');
+ if (parentDiv) {
+ parentDiv.remove(); // Remove the parent div if it exists
+ }
+ });
totalForms.setAttribute('value', `${formNum+1}`)
}
diff --git a/src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py b/src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py
new file mode 100644
index 000000000..72513a401
--- /dev/null
+++ b/src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py
@@ -0,0 +1,28 @@
+# 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",
+ ),
+ ),
+ ]
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 18ac8262f..0f2436bf7 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -711,6 +711,16 @@ class Domain(TimeStampedModel, DomainHelper):
default=False,
help_text="Boolean indicating if dnssec is enabled",
)
+
+ dnssec_ds_confirmed = models.BooleanField(
+ default=False,
+ help_text="Boolean indicating if DS record adding is confirmed",
+ )
+
+ dnssec_key_confirmed = models.BooleanField(
+ default=False,
+ help_text="Boolean indicating if Key record adding is confirmed",
+ )
# ForeignKey on UserDomainRole creates a "permissions" member for
# all of the user-roles that are in place for this domain
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index 032992014..95af919d3 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -9,7 +9,7 @@
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. Read more about DNSSEC and why it is important.
-
+
{% csrf_token %}
{% if not domain.dnssec_enabled %}
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.
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.
{% url 'domain-dns-dnssec-dsdata' pk=domain.id as url %}
diff --git a/src/registrar/templatetags/url_helpers.py b/src/registrar/templatetags/url_helpers.py
index 5b76c116f..2b983930f 100644
--- a/src/registrar/templatetags/url_helpers.py
+++ b/src/registrar/templatetags/url_helpers.py
@@ -18,6 +18,11 @@ def startswith(text, starts):
return text.startswith(starts)
return False
+@register.filter("endswith")
+def endswith(text, ends):
+ if isinstance(text, str):
+ return text.endswith(ends)
+ return False
@register.simple_tag
def public_site_url(url_path):
From 170708ecf3bdd8144b61a637dd3ece29e6c9dee3 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Thu, 5 Oct 2023 16:29:19 -0400
Subject: [PATCH 31/79] modal and js toggler wip
---
src/registrar/assets/js/get-gov.js | 23 +++-
src/registrar/templates/domain_dnssec.html | 139 ++++++++++++++++----
src/registrar/templates/includes/modal.html | 42 ++++++
src/registrar/views/domain.py | 39 ++++--
4 files changed, 202 insertions(+), 41 deletions(-)
create mode 100644 src/registrar/templates/includes/modal.html
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 17d4c4d6a..cfe6066e8 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -469,4 +469,25 @@ function prepareDeleteButtons() {
// }
// });
// }
-// })();
\ No newline at end of file
+// })();
+
+
+/**
+ *
+ *
+ */
+(function toggleDNSSECWarning() {
+ document.getElementById("toggler1").addEventListener("click", function () {
+ var element = document.getElementById("step-1");
+ var element2 = document.getElementById("step-2");
+ element.classList.toggle("display-none");
+ element2.classList.toggle("display-none");
+ });
+
+ document.getElementById("toggler2").addEventListener("click", function () {
+ var element = document.getElementById("step-1");
+ var element2 = document.getElementById("step-2");
+ element.classList.toggle("display-none");
+ element2.classList.toggle("display-none");
+ });
+})();
\ No newline at end of file
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index e21bb9458..80bcd7b50 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -11,40 +11,121 @@
{% csrf_token %}
- {% if not domain.dnssec_enabled %}
-
-
- 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.
-
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.
+ 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.
+
+
+ Enable DNSSEC
+
+
+
+
Add DS Records
+
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.
+ {% 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 %}
+
+
+
+ {% comment %}
+
+
+
+ Are you sure you want to continue?
+
+
+
+ Your DNSSEC records will be deleted from the registry.
+
\ No newline at end of file
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index f7483c6e0..6358bf39a 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -233,6 +233,32 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
template_name = "domain_dnssec.html"
form_class = DomainDnssecForm
+ clicked_enable_dns = False
+
+ 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
+
+ logger.debug(f"clicked_enable_dns {self.clicked_enable_dns}")
+
+ # if does_not_have_dnssec_records and self.clicked_enable_dns == False:
+ # logger.debug(f"clicked_enable_dns {self.clicked_enable_dns}")
+ # self.domain.dnssec_enabled = False
+ # self.domain.dnssec_ds_confirmed = False
+ # self.domain.dnssec_key_confirmed = False
+ # self.domain.save()
+
+ # Create HTML for the buttons
+ modal_button = 'Disable DNSSEC'
+
+ context['modal_button'] = modal_button
+ context['has_dnssec_records'] = has_dnssec_records
+ context['domain'] = self.domain
+
+ return context
def get_success_url(self):
"""Redirect to the DNSSEC page for the domain."""
@@ -243,16 +269,8 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
"""
self.domain = self.get_object()
form = self.get_form()
- if form.is_valid():
- if 'enable_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:
+ if form.is_valid():
+ if 'disable_dnssec' in request.POST:
try:
self.domain.dnssecdata = {}
except RegistryError as err:
@@ -261,7 +279,6 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
messages.error(
self.request, errmsg
)
- self.domain.dnssec_enabled = False
self.domain.dnssec_ds_confirmed = False
self.domain.dnssec_key_confirmed = False
self.domain.save()
From 13e294d0d2d5bb1eb7b648e0f1798db8c4564f71 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Thu, 5 Oct 2023 16:54:01 -0400
Subject: [PATCH 32/79] clean up JS and dnssec template
---
src/registrar/assets/js/get-gov.js | 22 +++---
src/registrar/templates/domain_dnssec.html | 81 +++-------------------
src/registrar/views/domain.py | 6 +-
3 files changed, 20 insertions(+), 89 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index cfe6066e8..36d5f257a 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -472,22 +472,18 @@ function prepareDeleteButtons() {
// })();
+function toggleElements() {
+ let element1 = document.getElementById("enable-dnssec");
+ let element2 = document.getElementById("add-records");
+ element1.classList.toggle("display-none");
+ element2.classList.toggle("display-none");
+}
+
/**
*
*
*/
(function toggleDNSSECWarning() {
- document.getElementById("toggler1").addEventListener("click", function () {
- var element = document.getElementById("step-1");
- var element2 = document.getElementById("step-2");
- element.classList.toggle("display-none");
- element2.classList.toggle("display-none");
- });
-
- document.getElementById("toggler2").addEventListener("click", function () {
- var element = document.getElementById("step-1");
- var element2 = document.getElementById("step-2");
- element.classList.toggle("display-none");
- element2.classList.toggle("display-none");
- });
+ document.getElementById("enable_dnssec").addEventListener("click", toggleElements);
+ document.getElementById("cancel_dnssec").addEventListener("click", toggleElements);
})();
\ No newline at end of file
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index 80bcd7b50..903020c93 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -11,7 +11,7 @@
{% csrf_token %}
- {% if domain.has_dnssec_records %}
+ {% if has_dnssec_records %}
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.
@@ -25,7 +25,7 @@
>Disable DNSSEC
{% else %}
-
+
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.
@@ -35,11 +35,11 @@
type="button"
class="usa-button"
name="enable_dnssec"
- id="toggler1"
+ id="enable_dnssec"
>Enable DNSSEC
-
+
Add DS Records
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.
@@ -48,84 +48,21 @@
Cancel
-
-
-
-
-
-
{% endif %}
{% 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 %}
-
- {% comment %}
-
-
-
- Are you sure you want to continue?
-
-
-
- Your DNSSEC records will be deleted from the registry.
-
-
-
-
-
-
-
-
-
-
-
{% endcomment %}
-
-
{% endblock %} {# domain_content #}
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 6358bf39a..12ce50606 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -241,9 +241,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
self.domain = self.get_object()
has_dnssec_records = self.domain.dnssecdata is not None
-
- logger.debug(f"clicked_enable_dns {self.clicked_enable_dns}")
-
+
# if does_not_have_dnssec_records and self.clicked_enable_dns == False:
# logger.debug(f"clicked_enable_dns {self.clicked_enable_dns}")
# self.domain.dnssec_enabled = False
@@ -256,7 +254,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
context['modal_button'] = modal_button
context['has_dnssec_records'] = has_dnssec_records
- context['domain'] = self.domain
+ # context['domain'] = self.domain
return context
From 3e8dc743197dcf290c251c634b8416d3de2ed58c Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Thu, 5 Oct 2023 17:36:43 -0400
Subject: [PATCH 33/79] cleanup
---
src/registrar/assets/js/get-gov.js | 96 +--------------------
src/registrar/templates/domain_keydata.html | 5 +-
2 files changed, 2 insertions(+), 99 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 36d5f257a..99c689ddf 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -279,17 +279,6 @@ function prepareDeleteButtons() {
let formNum2 = forms.length;
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 formLabelRegex = RegExp(`DS Data record (\\d){1}`, 'g');
@@ -311,10 +300,6 @@ function prepareDeleteButtons() {
});
});
-
-
-
-
}
}
@@ -389,89 +374,10 @@ function prepareDeleteButtons() {
// Attach click event listener on the delete buttons of the new form
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
-// }
-// });
-// }
-// });
-// }
-// })();
-
-
function toggleElements() {
let element1 = document.getElementById("enable-dnssec");
let element2 = document.getElementById("add-records");
@@ -480,7 +386,7 @@ function toggleElements() {
}
/**
- *
+ * An IIFE that attaches a click handler to toggle the DNSSEC warning on the DNSSEC landing page
*
*/
(function toggleDNSSECWarning() {
diff --git a/src/registrar/templates/domain_keydata.html b/src/registrar/templates/domain_keydata.html
index 1602c5d87..34ce14a4e 100644
--- a/src/registrar/templates/domain_keydata.html
+++ b/src/registrar/templates/domain_keydata.html
@@ -38,7 +38,7 @@
{% for form in formset %}
+ {% comment %} display: none is sufficient on hidden elements for accessibility (removes accessibility tree) {% endcomment %}
Add DS Records
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.
diff --git a/src/registrar/templates/domain_dsdata.html b/src/registrar/templates/domain_dsdata.html
index 0de7f4730..585b4a981 100644
--- a/src/registrar/templates/domain_dsdata.html
+++ b/src/registrar/templates/domain_dsdata.html
@@ -4,6 +4,9 @@
{% block title %}DS Data | {{ domain.name }} | {% endblock %}
{% block domain_content %}
+ {% for form in formset %}
+ {% include "includes/form_errors.html" with form=form %}
+ {% endfor %}
-
- {% comment %} {% endcomment %}
-
{% endfor %}
From b899b35285796b113aa581001b884d65909f33fa Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Thu, 5 Oct 2023 18:09:37 -0400
Subject: [PATCH 36/79] form error msgs on keydata form
---
src/registrar/templates/domain_keydata.html | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/registrar/templates/domain_keydata.html b/src/registrar/templates/domain_keydata.html
index 34ce14a4e..cea62e71a 100644
--- a/src/registrar/templates/domain_keydata.html
+++ b/src/registrar/templates/domain_keydata.html
@@ -4,6 +4,9 @@
{% block title %}Key Data | {{ domain.name }} | {% endblock %}
{% block domain_content %}
+ {% for form in formset %}
+ {% include "includes/form_errors.html" with form=form %}
+ {% endfor %}
Key Data
From a6cfd343e8ea805cabba49eff5ee571940d64788 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 07:05:45 -0400
Subject: [PATCH 37/79] removed js for hide and show on dnssec main page;
handling flags in app with session variables rather than model attributes
---
src/registrar/assets/js/get-gov.js | 20 --------
src/registrar/templates/domain_dnssec.html | 37 +++++++--------
src/registrar/templates/domain_dsdata.html | 2 +-
src/registrar/templates/domain_keydata.html | 2 +-
src/registrar/views/domain.py | 52 ++++++++++++++-------
5 files changed, 54 insertions(+), 59 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 3a7d81ba5..4b5ff5e31 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -376,23 +376,3 @@ function prepareDeleteButtons() {
}
})();
-
-function toggleElements() {
- let element1 = document.getElementById("enable-dnssec");
- let element2 = document.getElementById("add-records");
- element1.classList.toggle("display-none");
- element2.classList.toggle("display-none");
-}
-
-/**
- * An IIFE that attaches a click handler to toggle the DNSSEC warning on the DNSSEC landing page
- *
- */
-(function toggleDNSSECWarning() {
- let toggle1 = document.getElementById("enable_dnssec");
- if (toggle1)
- toggle1.addEventListener("click", toggleElements);
- let toggle2 = document.getElementById("cancel_dnssec");
- if (toggle2)
- toggle2.addEventListener("click", toggleElements);
-})();
\ No newline at end of file
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index c87fc9152..4763ff3c1 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -5,9 +5,9 @@
{% block domain_content %}
-
DNSSEC
+
{% if dnssec_enabled %}Set up {% endif %}DNSSEC
-
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. Read more about DNSSEC and why it is important.
+
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.
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.
-
- {% comment %} display: none is sufficient on hidden elements for accessibility (removes accessibility tree) {% endcomment %}
-
-
Add DS Records
-
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.
- {% elif not domain.dnssec_ds_confirmed %}
+ {% elif not dnssec_ds_confirmed %}
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.
- {% elif not domain.dnssec_key_confirmed %}
+ {% elif not dnssec_key_confirmed %}
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.
{% csrf_token %}
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 105691c37..fd5d22651 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -233,7 +233,6 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
template_name = "domain_dnssec.html"
form_class = DomainDnssecForm
- clicked_enable_dns = False
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -242,20 +241,13 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
has_dnssec_records = self.domain.dnssecdata is not None
- # if does_not_have_dnssec_records and self.clicked_enable_dns == False:
- # logger.debug(f"clicked_enable_dns {self.clicked_enable_dns}")
- # self.domain.dnssec_enabled = False
- # self.domain.dnssec_ds_confirmed = False
- # self.domain.dnssec_key_confirmed = False
- # self.domain.save()
-
# Create HTML for the buttons
modal_button = 'Disable DNSSEC'
context['modal_button'] = modal_button
context['has_dnssec_records'] = has_dnssec_records
- # context['domain'] = self.domain
-
+ context['dnssec_enabled'] = self.request.session.pop('dnssec_enabled', False)
+
return context
def get_success_url(self):
@@ -277,9 +269,12 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
messages.error(
self.request, errmsg
)
- self.domain.dnssec_ds_confirmed = False
- self.domain.dnssec_key_confirmed = False
- self.domain.save()
+ request.session['dnssec_ds_confirmed'] = False
+ request.session['dnssec_key_confirmed'] = False
+ elif 'enable_dnssec' in request.POST:
+ request.session['dnssec_enabled'] = True
+ request.session['dnssec_ds_confirmed'] = False
+ request.session['dnssec_key_confirmed'] = False
return self.form_valid(form)
@@ -323,6 +318,17 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
context = super().get_context_data(**kwargs)
# use "formset" instead of "form" for the key
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
def post(self, request, *args, **kwargs):
@@ -331,9 +337,8 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
formset = self.get_form()
if 'confirm-ds' in request.POST:
- self.object.dnssec_ds_confirmed = True
- self.object.dnssec_key_confirmed = False
- self.object.save()
+ request.session['dnssec_ds_confirmed'] = True
+ request.session['dnssec_key_confirmed'] = False
return super().form_valid(formset)
if 'btn-cancel-click' in request.POST:
@@ -425,6 +430,17 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
context = super().get_context_data(**kwargs)
# use "formset" instead of "form" for the key
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
def post(self, request, *args, **kwargs):
@@ -433,8 +449,8 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
formset = self.get_form()
if 'confirm-key' in request.POST:
- self.object.dnssec_key_confirmed = True
- self.object.dnssec_ds_confirmed = False
+ request.session['dnssec_key_confirmed'] = True
+ request.session['dnssec_ds_confirmed'] = False
self.object.save()
return super().form_valid(formset)
From ba8901bf272822427aa622f9d9d56542da4c552c Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 07:56:44 -0400
Subject: [PATCH 38/79] removing unnecessary domain attributes
---
...ove_domain_dnssec_ds_confirmed_and_more.py | 24 +++++++++++++++++++
src/registrar/models/domain.py | 15 ------------
2 files changed, 24 insertions(+), 15 deletions(-)
create mode 100644 src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py
diff --git a/src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py b/src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py
new file mode 100644
index 000000000..8b7b7566e
--- /dev/null
+++ b/src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.2.1 on 2023-10-06 11:50
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("registrar", "0034_domain_dnssec_ds_confirmed_and_more"),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name="domain",
+ name="dnssec_ds_confirmed",
+ ),
+ migrations.RemoveField(
+ model_name="domain",
+ name="dnssec_enabled",
+ ),
+ migrations.RemoveField(
+ model_name="domain",
+ name="dnssec_key_confirmed",
+ ),
+ ]
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 56a1a53cb..7a890632c 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -702,21 +702,6 @@ class Domain(TimeStampedModel, DomainHelper):
help_text="Very basic info about the lifecycle of this domain object",
)
- dnssec_enabled = models.BooleanField(
- default=False,
- help_text="Boolean indicating if dnssec is enabled",
- )
-
- dnssec_ds_confirmed = models.BooleanField(
- default=False,
- help_text="Boolean indicating if DS record adding is confirmed",
- )
-
- dnssec_key_confirmed = models.BooleanField(
- default=False,
- help_text="Boolean indicating if Key record adding is confirmed",
- )
-
# ForeignKey on UserDomainRole creates a "permissions" member for
# all of the user-roles that are in place for this domain
From 88bcb1eedcccae3b8f997324890923111da90a92 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Fri, 6 Oct 2023 10:12:10 -0400
Subject: [PATCH 39/79] fix modal ui
---
src/registrar/assets/sass/_theme/_buttons.scss | 8 ++++++++
src/registrar/templates/domain_dnssec.html | 4 ++--
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss
index 45ea6620c..f4c72f4fa 100644
--- a/src/registrar/assets/sass/_theme/_buttons.scss
+++ b/src/registrar/assets/sass/_theme/_buttons.scss
@@ -24,6 +24,14 @@ a.breadcrumb__back {
a.usa-button {
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,
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index 4763ff3c1..6a0a15389 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -18,9 +18,9 @@
Disable DNSSEC
From a0f7cd5e1dda8d8320d99af921a6bff16c82b88f Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 12:04:56 -0400
Subject: [PATCH 40/79] shared mock data for dnssec unit tests
---
src/registrar/tests/common.py | 63 +++++++-
src/registrar/tests/test_models_domain.py | 168 +++++-----------------
2 files changed, 94 insertions(+), 137 deletions(-)
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index 10c387099..9291aa271 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -7,7 +7,7 @@ import random
from string import ascii_uppercase
from django.test import TestCase
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.contrib.auth import get_user_model, login
@@ -26,6 +26,7 @@ from registrar.models import (
from epplibwrapper import (
commands,
common,
+ extensions,
RegistryError,
ErrorCode,
)
@@ -584,6 +585,37 @@ class MockEppLib(TestCase):
mockDataInfoHosts = fakedEppObject(
"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[str, Any] = {
+ "dsData": [common.DSData(**addDsData1)]
+ }
+ dnssecExtensionWithMultDsData: Mapping[str, Any] = {
+ "dsData": [
+ common.DSData(**addDsData1),
+ common.DSData(**addDsData2),
+ ],
+ }
+ dnssecExtensionWithKeyData: Mapping[str, Any] = {
+ "maxSigLife": 3215,
+ "keyData": [common.DNSSECKeyData(**keyDataDict)],
+ }
def mockSend(self, _request, cleaned):
"""Mocks the registry.send function used inside of domain.py
@@ -593,6 +625,30 @@ class MockEppLib(TestCase):
if isinstance(_request, commands.InfoDomain):
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])
return MagicMock(res_data=[self.mockDataInfoDomain])
elif isinstance(_request, commands.InfoContact):
return MagicMock(res_data=[self.mockDataInfoContact])
@@ -614,6 +670,11 @@ class MockEppLib(TestCase):
raise RegistryError(
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])
def setUp(self):
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
index 16dd30017..6f164d1f4 100644
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -3,7 +3,6 @@ Feature being tested: Registry Integration
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.db.utils import IntegrityError
from unittest.mock import MagicMock, patch, call
@@ -803,37 +802,7 @@ class TestRegistrantDNSSEC(MockEppLib):
super().setUp()
# for the tests, need a domain in the unknown state
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):
Domain.objects.all().delete()
@@ -852,26 +821,13 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
- # make sure to stop any other patcher so there are no conflicts
- self.mockSendPatch.stop()
+ domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
- 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
-
- self.domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = self.dnssecExtensionWithDsData
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
- args, _ = mocked_send.call_args
+ args, _ = self.mockedSendFunction.call_args
# assert that the extension matches
self.assertEquals(
args[0].extensions[0],
@@ -880,12 +836,12 @@ class TestRegistrantDNSSEC(MockEppLib):
),
)
# test that the dnssecdata getter is functioning properly
- dnssecdata_get = self.domain.dnssecdata
- mocked_send.assert_has_calls(
+ dnssecdata_get = domain.dnssecdata
+ self.mockedSendFunction.assert_has_calls(
[
call(
commands.UpdateDomain(
- name="fake.gov",
+ name="dnssec-dsdata.gov",
nsset=None,
keyset=None,
registrant=None,
@@ -895,7 +851,7 @@ class TestRegistrantDNSSEC(MockEppLib):
),
call(
commands.InfoDomain(
- name="fake.gov",
+ name="dnssec-dsdata.gov",
),
cleaned=True,
),
@@ -906,8 +862,6 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
)
- patcher.stop()
-
def test_dnssec_is_idempotent(self):
"""
Scenario: Registrant adds DNS data twice, due to a UI glitch
@@ -923,32 +877,19 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
- # make sure to stop any other patcher so there are no conflicts
- 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
+ domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# set the dnssecdata once
- self.domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = self.dnssecExtensionWithDsData
# set the dnssecdata again
- self.domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = self.dnssecExtensionWithDsData
# test that the dnssecdata getter is functioning properly
- dnssecdata_get = self.domain.dnssecdata
- mocked_send.assert_has_calls(
+ dnssecdata_get = domain.dnssecdata
+ self.mockedSendFunction.assert_has_calls(
[
call(
commands.UpdateDomain(
- name="fake.gov",
+ name="dnssec-dsdata.gov",
nsset=None,
keyset=None,
registrant=None,
@@ -958,7 +899,7 @@ class TestRegistrantDNSSEC(MockEppLib):
),
call(
commands.UpdateDomain(
- name="fake.gov",
+ name="dnssec-dsdata.gov",
nsset=None,
keyset=None,
registrant=None,
@@ -968,7 +909,7 @@ class TestRegistrantDNSSEC(MockEppLib):
),
call(
commands.InfoDomain(
- name="fake.gov",
+ name="dnssec-dsdata.gov",
),
cleaned=True,
),
@@ -979,8 +920,6 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
)
- patcher.stop()
-
def test_user_adds_dnssec_data_multiple_dsdata(self):
"""
Scenario: Registrant adds DNSSEC data with multiple DSData.
@@ -994,26 +933,13 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
- # make sure to stop any other patcher so there are no conflicts
- self.mockSendPatch.stop()
+ domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
- def side_effect(_request, cleaned):
- 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
+ domain.dnssecdata = self.dnssecExtensionWithMultDsData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
- args, _ = mocked_send.call_args
+ args, _ = self.mockedSendFunction.call_args
# assert that the extension matches
self.assertEquals(
args[0].extensions[0],
@@ -1022,12 +948,12 @@ class TestRegistrantDNSSEC(MockEppLib):
),
)
# test that the dnssecdata getter is functioning properly
- dnssecdata_get = self.domain.dnssecdata
- mocked_send.assert_has_calls(
+ dnssecdata_get = domain.dnssecdata
+ self.mockedSendFunction.assert_has_calls(
[
call(
commands.UpdateDomain(
- name="fake.gov",
+ name="dnssec-multdsdata.gov",
nsset=None,
keyset=None,
registrant=None,
@@ -1037,7 +963,7 @@ class TestRegistrantDNSSEC(MockEppLib):
),
call(
commands.InfoDomain(
- name="fake.gov",
+ name="dnssec-multdsdata.gov",
),
cleaned=True,
),
@@ -1048,8 +974,6 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData["dsData"]
)
- patcher.stop()
-
def test_user_adds_dnssec_keydata(self):
"""
Scenario: Registrant adds DNSSEC data.
@@ -1063,26 +987,13 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
- # make sure to stop any other patcher so there are no conflicts
- self.mockSendPatch.stop()
+ domain, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
- def side_effect(_request, cleaned):
- 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
+ domain.dnssecdata = self.dnssecExtensionWithKeyData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
- args, _ = mocked_send.call_args
+ args, _ = self.mockedSendFunction.call_args
# assert that the extension matches
self.assertEquals(
args[0].extensions[0],
@@ -1091,12 +1002,12 @@ class TestRegistrantDNSSEC(MockEppLib):
),
)
# test that the dnssecdata getter is functioning properly
- dnssecdata_get = self.domain.dnssecdata
- mocked_send.assert_has_calls(
+ dnssecdata_get = domain.dnssecdata
+ self.mockedSendFunction.assert_has_calls(
[
call(
commands.UpdateDomain(
- name="fake.gov",
+ name="dnssec-keydata.gov",
nsset=None,
keyset=None,
registrant=None,
@@ -1106,7 +1017,7 @@ class TestRegistrantDNSSEC(MockEppLib):
),
call(
commands.InfoDomain(
- name="fake.gov",
+ name="dnssec-keydata.gov",
),
cleaned=True,
),
@@ -1117,8 +1028,6 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get.keyData, self.dnssecExtensionWithKeyData["keyData"]
)
- patcher.stop()
-
def test_update_is_unsuccessful(self):
"""
Scenario: An update to the dns data is unsuccessful
@@ -1126,27 +1035,14 @@ class TestRegistrantDNSSEC(MockEppLib):
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
- self.mockSendPatch.stop()
+ domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov")
- 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:
- self.domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = self.dnssecExtensionWithDsData
self.assertTrue(
err.is_client_error() or err.is_session_error() or err.is_server_error()
)
- patcher.stop()
-
class TestAnalystClientHold(MockEppLib):
"""Rule: Analysts may suspend or restore a domain by using client hold"""
From 136db5bab2022c34482f35fb9b8f5f31fa5bd948 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 12:12:43 -0400
Subject: [PATCH 41/79] initial breakdown of test_views
---
src/registrar/tests/test_views.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index c49ec8f3f..83e9a8749 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1133,7 +1133,7 @@ class TestDomainPermissions(TestWithDomainPermissions):
self.assertEqual(response.status_code, 403)
-class TestDomainDetail(TestWithDomainPermissions, WebTest):
+class TestDomainOverview(TestWithDomainPermissions, WebTest):
def setUp(self):
super().setUp()
self.app.set_user(self.user.username)
@@ -1147,6 +1147,13 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest):
self.assertContains(detail_page, "igorville.gov")
self.assertContains(detail_page, "Status")
+
+class TestDomainUserManagement(TestWithDomainPermissions, WebTest):
+ def setUp(self):
+ super().setUp()
+ self.app.set_user(self.user.username)
+ self.client.force_login(self.user)
+
def test_domain_user_management(self):
response = self.client.get(
reverse("domain-users", kwargs={"pk": self.domain.id})
From bb61ce9f642eb853a8bb0d0b599f9494bff2b09d Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Fri, 6 Oct 2023 13:26:07 -0400
Subject: [PATCH 42/79] 50% coverage on DNSSEC code (testing for statuses and
flows)
---
src/registrar/tests/test_views.py | 181 ++++++++++++++++++++++++++----
1 file changed, 160 insertions(+), 21 deletions(-)
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index 83e9a8749..8e984be85 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1071,21 +1071,51 @@ class TestWithDomainPermissions(TestWithUser):
def setUp(self):
super().setUp()
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 (igoreville) 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(
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(
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):
try:
- self.domain_information.delete()
+ UserDomainRole.objects.all().delete()
if hasattr(self.domain, "contacts"):
self.domain.contacts.all().delete()
DomainApplication.objects.all().delete()
- self.domain.delete()
- self.role.delete()
+ Domain.objects.all().delete()
+ UserDomainRole.objects.all().delete()
except ValueError: # pass if already deleted
pass
super().tearDown()
@@ -1143,17 +1173,25 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
home_page = self.app.get("/")
self.assertContains(home_page, "igorville.gov")
# 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, "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(TestWithDomainPermissions, WebTest):
- def setUp(self):
- super().setUp()
- self.app.set_user(self.user.username)
- self.client.force_login(self.user)
-
+class TestDomainUserManagement(TestDomainOverview):
+
def test_domain_user_management(self):
response = self.client.get(
reverse("domain-users", kwargs={"pk": self.domain.id})
@@ -1311,6 +1349,8 @@ class TestDomainUserManagement(TestWithDomainPermissions, WebTest):
# Now load the home page and make sure our domain appears there
home_page = self.app.get(reverse("home"))
self.assertContains(home_page, self.domain.name)
+
+class TestDomainNameservers(TestDomainOverview):
def test_domain_nameservers(self):
"""Can load domain's nameservers page."""
@@ -1362,6 +1402,8 @@ class TestDomainUserManagement(TestWithDomainPermissions, WebTest):
# error text appears twice, once at the top of the page, once around
# the field.
self.assertContains(result, "This field is required", count=2, status_code=200)
+
+class TestDomainAuthorizingOfficial(TestDomainOverview):
def test_domain_authorizing_official(self):
"""Can load domain's authorizing official page."""
@@ -1380,6 +1422,8 @@ class TestDomainUserManagement(TestWithDomainPermissions, WebTest):
reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})
)
self.assertContains(page, "Testy")
+
+class TestDomainOrganization(TestDomainOverview):
def test_domain_org_name_address(self):
"""Can load domain's org name and mailing address page."""
@@ -1416,6 +1460,8 @@ class TestDomainUserManagement(TestWithDomainPermissions, WebTest):
self.assertContains(success_result_page, "Not igorville")
self.assertContains(success_result_page, "Faketown")
+
+class TestDomainContactInformation(TestDomainOverview):
def test_domain_your_contact_information(self):
"""Can load domain's your contact information page."""
@@ -1432,6 +1478,8 @@ class TestDomainUserManagement(TestWithDomainPermissions, WebTest):
reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})
)
self.assertContains(page, "Testy")
+
+class TestDomainSecurityEmail(TestDomainOverview):
def test_domain_security_email(self):
"""Can load domain's security email page."""
@@ -1465,18 +1513,109 @@ class TestDomainUserManagement(TestWithDomainPermissions, WebTest):
self.assertContains(
success_page, "The security email for this domain have been updated"
)
+
+
+class TestDomainDNSSEC(TestDomainOverview):
+
+ """MockEPPLib is already inherited."""
+
+ def test_dnssec_page_refreshes_enable_button(self):
+ """DNSSEC overview page loads when domain has no DNSSEC data
+ and shows a 'Enable DNSSEC' button. When button is clicked the template
+ updates. When user navigates away then comes back to the page, the
+ 'Enable DNSSEC' button is shown again."""
+ # 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', # Replace with the actual form field and value
+ # Add other form fields as needed
+ }
+ 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")
+
+ 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_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 TestApplicationStatus(TestWithUser, WebTest):
From a6f4c2f000c18ba6b04fc2cb6c018c2f160976d4 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 13:59:20 -0400
Subject: [PATCH 43/79] unit tests on ds data form and disable dnssec
---
src/registrar/tests/test_views.py | 52 ++++++++++++++++++++++++++++++-
1 file changed, 51 insertions(+), 1 deletion(-)
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index 8e984be85..5c24b33f4 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1550,7 +1550,6 @@ class TestDomainDNSSEC(TestDomainOverview):
)
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."""
@@ -1559,6 +1558,16 @@ class TestDomainDNSSEC(TestDomainOverview):
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', # Replace with the actual form field and value
+ }
+ 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
@@ -1614,6 +1623,47 @@ class TestDomainDNSSEC(TestDomainOverview):
)
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_domain_nameservers_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)
From c39384df8c8842076d818659fd977e1b2945d111 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 14:04:37 -0400
Subject: [PATCH 44/79] unit tests completed
---
src/registrar/tests/test_views.py | 46 +++++++++++++++++++++++++++++--
1 file changed, 43 insertions(+), 3 deletions(-)
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index 5c24b33f4..cff7d1614 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1645,7 +1645,7 @@ class TestDomainDNSSEC(TestDomainOverview):
page = result.follow()
self.assertContains(page, "The DS Data records for this domain have been updated.")
- def test_domain_nameservers_form_invalid(self):
+ 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.
@@ -1665,9 +1665,49 @@ class TestDomainDNSSEC(TestDomainOverview):
# 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):
def setUp(self):
super().setUp()
From 6ece85796c6ae4ccf834ae24799546cc820b7ea9 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 15:22:03 -0400
Subject: [PATCH 45/79] removing old migrations
---
.../migrations/0033_domain_dnssec_enabled.py | 19 -------------
...034_domain_dnssec_ds_confirmed_and_more.py | 28 -------------------
...ove_domain_dnssec_ds_confirmed_and_more.py | 24 ----------------
3 files changed, 71 deletions(-)
delete mode 100644 src/registrar/migrations/0033_domain_dnssec_enabled.py
delete mode 100644 src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py
delete mode 100644 src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py
diff --git a/src/registrar/migrations/0033_domain_dnssec_enabled.py b/src/registrar/migrations/0033_domain_dnssec_enabled.py
deleted file mode 100644
index a4695a02b..000000000
--- a/src/registrar/migrations/0033_domain_dnssec_enabled.py
+++ /dev/null
@@ -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"
- ),
- ),
- ]
diff --git a/src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py b/src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py
deleted file mode 100644
index 72513a401..000000000
--- a/src/registrar/migrations/0034_domain_dnssec_ds_confirmed_and_more.py
+++ /dev/null
@@ -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",
- ),
- ),
- ]
diff --git a/src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py b/src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py
deleted file mode 100644
index 8b7b7566e..000000000
--- a/src/registrar/migrations/0035_remove_domain_dnssec_ds_confirmed_and_more.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Generated by Django 4.2.1 on 2023-10-06 11:50
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ("registrar", "0034_domain_dnssec_ds_confirmed_and_more"),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name="domain",
- name="dnssec_ds_confirmed",
- ),
- migrations.RemoveField(
- model_name="domain",
- name="dnssec_enabled",
- ),
- migrations.RemoveField(
- model_name="domain",
- name="dnssec_key_confirmed",
- ),
- ]
From a9d57cda079bf91a532f202ed4b5bad131a16334 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 15:38:50 -0400
Subject: [PATCH 46/79] fixed unit tests
---
src/registrar/tests/test_views.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index 3f583ea77..da4d2dce8 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -18,6 +18,7 @@ from registrar.models import (
DraftDomain,
DomainInvitation,
Contact,
+ PublicContact,
Website,
UserDomainRole,
User,
@@ -1113,6 +1114,7 @@ class TestWithDomainPermissions(TestWithUser):
if hasattr(self.domain, "contacts"):
self.domain.contacts.all().delete()
DomainApplication.objects.all().delete()
+ PublicContact.objects.all().delete()
Domain.objects.all().delete()
UserDomainRole.objects.all().delete()
except ValueError: # pass if already deleted
From 93d8b8227e046f4dbdd2eb043f03bb59b973c86d Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 6 Oct 2023 16:18:59 -0400
Subject: [PATCH 47/79] refactored complicated test code in common, satisfied
the linter
---
src/registrar/forms/common.py | 5 +-
src/registrar/forms/domain.py | 52 ++++----
src/registrar/templatetags/url_helpers.py | 2 +
src/registrar/tests/common.py | 110 ++++++++--------
src/registrar/tests/test_models_domain.py | 1 -
src/registrar/tests/test_views.py | 130 +++++++++++--------
src/registrar/views/domain.py | 146 ++++++++++++----------
7 files changed, 241 insertions(+), 205 deletions(-)
diff --git a/src/registrar/forms/common.py b/src/registrar/forms/common.py
index 377f59797..159113488 100644
--- a/src/registrar/forms/common.py
+++ b/src/registrar/forms/common.py
@@ -1,7 +1,8 @@
# common.py
-#
+#
# 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 = [
(1, "(1) ERSA/MD5 [RSAMD5]"),
(2, "(2) Diffie-Hellman [DH]"),
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index a9c736175..4ee17a72d 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -7,7 +7,13 @@ from django.forms import formset_factory
from phonenumber_field.widgets import RegionalPhoneNumberWidget
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):
@@ -157,35 +163,27 @@ class DomainDsdataForm(forms.Form):
MinValueValidator(0, message="Value must be between 0 and 65535"),
MaxValueValidator(65535, message="Value must be between 0 and 65535"),
],
- error_messages={
- "required": ("Key tag is required.")
- },
+ error_messages={"required": ("Key tag is required.")},
)
-
+
algorithm = forms.TypedChoiceField(
required=True,
label="Algorithm",
- choices=[(None, "--Select--")] + ALGORITHM_CHOICES,
- error_messages={
- "required": ("Algorithm is required.")
- },
+ choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
+ error_messages={"required": ("Algorithm is required.")},
)
digest_type = forms.TypedChoiceField(
required=True,
label="Digest Type",
- choices=[(None, "--Select--")] + DIGEST_TYPE_CHOICES,
- error_messages={
- "required": ("Digest Type is required.")
- },
+ choices=[(None, "--Select--")] + DIGEST_TYPE_CHOICES, # type: ignore
+ error_messages={"required": ("Digest Type is required.")},
)
digest = forms.CharField(
required=True,
label="Digest",
- error_messages={
- "required": ("Digest is required.")
- },
+ error_messages={"required": ("Digest is required.")},
)
@@ -204,35 +202,27 @@ class DomainKeydataForm(forms.Form):
required=True,
label="Flag",
choices=FLAG_CHOICES,
- error_messages={
- "required": ("Flag is required.")
- },
+ error_messages={"required": ("Flag is required.")},
)
protocol = forms.TypedChoiceField(
required=True,
label="Protocol",
choices=PROTOCOL_CHOICES,
- error_messages={
- "required": ("Protocol is required.")
- },
+ error_messages={"required": ("Protocol is required.")},
)
algorithm = forms.TypedChoiceField(
required=True,
label="Algorithm",
- choices=[(None, "--Select--")] + ALGORITHM_CHOICES,
- error_messages={
- "required": ("Algorithm is required.")
- },
+ choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
+ error_messages={"required": ("Algorithm is required.")},
)
-
+
pub_key = forms.CharField(
required=True,
label="Pub key",
- error_messages={
- "required": ("Pub key is required.")
- },
+ error_messages={"required": ("Pub key is required.")},
)
@@ -240,4 +230,4 @@ DomainKeydataFormset = formset_factory(
DomainKeydataForm,
extra=0,
can_delete=True,
-)
\ No newline at end of file
+)
diff --git a/src/registrar/templatetags/url_helpers.py b/src/registrar/templatetags/url_helpers.py
index 2b983930f..931eedc92 100644
--- a/src/registrar/templatetags/url_helpers.py
+++ b/src/registrar/templatetags/url_helpers.py
@@ -18,12 +18,14 @@ def startswith(text, starts):
return text.startswith(starts)
return False
+
@register.filter("endswith")
def endswith(text, ends):
if isinstance(text, str):
return text.endswith(ends)
return False
+
@register.simple_tag
def public_site_url(url_path):
"""Make a full URL for this path at our public site.
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index b44e21d01..70d0cd1c9 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -689,18 +689,18 @@ class MockEppLib(TestCase):
"alg": 1,
"pubKey": "AQPJ////4Q==",
}
- dnssecExtensionWithDsData: Mapping[str, Any] = {
- "dsData": [common.DSData(**addDsData1)]
+ dnssecExtensionWithDsData: Mapping[Any, Any] = {
+ "dsData": [common.DSData(**addDsData1)] # type: ignore
}
dnssecExtensionWithMultDsData: Mapping[str, Any] = {
"dsData": [
- common.DSData(**addDsData1),
- common.DSData(**addDsData2),
+ common.DSData(**addDsData1), # type: ignore
+ common.DSData(**addDsData2), # type: ignore
],
}
dnssecExtensionWithKeyData: Mapping[str, Any] = {
"maxSigLife": 3215,
- "keyData": [common.DNSSECKeyData(**keyDataDict)],
+ "keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
}
def mockSend(self, _request, cleaned):
@@ -709,54 +709,9 @@ class MockEppLib(TestCase):
returns objects that simulate what would be in a epp response
but only relevant pieces for tests"""
if isinstance(_request, commands.InfoDomain):
- 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])
+ return self.mockInfoDomainCommands(_request, cleaned)
elif isinstance(_request, commands.InfoContact):
- 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])
+ return self.mockInfoContactCommands(_request, cleaned)
elif (
isinstance(_request, commands.CreateContact)
and getattr(_request, "id", None) == "fail"
@@ -782,6 +737,57 @@ class MockEppLib(TestCase):
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
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):
"""mock epp send function as this will fail locally"""
self.mockSendPatch = patch("registrar.models.domain.registry.send")
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
index 5b299c112..d0718b4ba 100644
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -1003,7 +1003,6 @@ class TestRegistrantDNSSEC(MockEppLib):
super().setUp()
# for the tests, need a domain in the unknown state
self.domain, _ = Domain.objects.get_or_create(name="fake.gov")
-
def tearDown(self):
Domain.objects.all().delete()
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index da4d2dce8..553b45135 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1072,11 +1072,15 @@ class TestWithDomainPermissions(TestWithUser):
super().setUp()
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_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 (igoreville) but this will be more readable in tests
+ # 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_dnssec_none, _ = Domain.objects.get_or_create(
+ name="dnssec-none.gov"
+ )
self.domain_information, _ = DomainInformation.objects.get_or_create(
creator=self.user, domain=self.domain
)
@@ -1099,13 +1103,17 @@ class TestWithDomainPermissions(TestWithUser):
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
+ 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
+ user=self.user,
+ domain=self.domain_dnssec_none,
+ role=UserDomainRole.Roles.ADMIN,
)
def tearDown(self):
@@ -1177,7 +1185,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
detail_page = home_page.click("Manage", index=0)
self.assertContains(detail_page, "igorville.gov")
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
@@ -1192,7 +1200,6 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
class TestDomainUserManagement(TestDomainOverview):
-
def test_domain_user_management(self):
response = self.client.get(
reverse("domain-users", kwargs={"pk": self.domain.id})
@@ -1350,9 +1357,9 @@ class TestDomainUserManagement(TestDomainOverview):
# Now load the home page and make sure our domain appears there
home_page = self.app.get(reverse("home"))
self.assertContains(home_page, self.domain.name)
-
-class TestDomainNameservers(TestDomainOverview):
+
+class TestDomainNameservers(TestDomainOverview):
def test_domain_nameservers(self):
"""Can load domain's nameservers page."""
page = self.client.get(
@@ -1403,9 +1410,9 @@ class TestDomainNameservers(TestDomainOverview):
# error text appears twice, once at the top of the page, once around
# the field.
self.assertContains(result, "This field is required", count=2, status_code=200)
-
-class TestDomainAuthorizingOfficial(TestDomainOverview):
+
+class TestDomainAuthorizingOfficial(TestDomainOverview):
def test_domain_authorizing_official(self):
"""Can load domain's authorizing official page."""
page = self.client.get(
@@ -1423,9 +1430,9 @@ class TestDomainAuthorizingOfficial(TestDomainOverview):
reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})
)
self.assertContains(page, "Testy")
-
-class TestDomainOrganization(TestDomainOverview):
+
+class TestDomainOrganization(TestDomainOverview):
def test_domain_org_name_address(self):
"""Can load domain's org name and mailing address page."""
page = self.client.get(
@@ -1461,9 +1468,9 @@ class TestDomainOrganization(TestDomainOverview):
self.assertContains(success_result_page, "Not igorville")
self.assertContains(success_result_page, "Faketown")
-
-class TestDomainContactInformation(TestDomainOverview):
+
+class TestDomainContactInformation(TestDomainOverview):
def test_domain_your_contact_information(self):
"""Can load domain's your contact information page."""
page = self.client.get(
@@ -1479,9 +1486,9 @@ class TestDomainContactInformation(TestDomainOverview):
reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})
)
self.assertContains(page, "Testy")
-
-class TestDomainSecurityEmail(TestDomainOverview):
+
+class TestDomainSecurityEmail(TestDomainOverview):
def test_domain_security_email_existing_security_contact(self):
"""Can load domain's security email page."""
self.mockSendPatch = patch("registrar.models.domain.registry.send")
@@ -1546,47 +1553,50 @@ class TestDomainSecurityEmail(TestDomainOverview):
self.assertContains(
success_page, "The security email for this domain has been updated"
)
-
-
+
+
class TestDomainDNSSEC(TestDomainOverview):
-
+
"""MockEPPLib is already inherited."""
-
+
def test_dnssec_page_refreshes_enable_button(self):
"""DNSSEC overview page loads when domain has no DNSSEC data
and shows a 'Enable DNSSEC' button. When button is clicked the template
updates. When user navigates away then comes back to the page, the
'Enable DNSSEC' button is shown again."""
# 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', # Replace with the actual form field and value
- # Add other form fields as needed
+ "enable_dnssec": "Enable DNSSEC",
}
- updated_page = self.client.post(reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}), post_data, follow=True)
-
+ 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})
)
@@ -1594,63 +1604,71 @@ class TestDomainDNSSEC(TestDomainOverview):
# Prepare the data for the POST request
post_data = {
- 'disable_dnssec': 'Disable DNSSEC', # Replace with the actual form field and value
+ "disable_dnssec": "Disable DNSSEC",
}
- updated_page = self.client.post(reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}), post_data, follow=True)
-
+ 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})
+ 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})
+ 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})
)
@@ -1676,7 +1694,9 @@ class TestDomainDNSSEC(TestDomainOverview):
)
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.")
+ 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
@@ -1718,7 +1738,9 @@ class TestDomainDNSSEC(TestDomainOverview):
)
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.")
+ 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
@@ -1739,8 +1761,8 @@ class TestDomainDNSSEC(TestDomainOverview):
# 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):
def setUp(self):
super().setUp()
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 9334700ef..e51395ba3 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -233,20 +233,24 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
template_name = "domain_dnssec.html"
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 = 'Disable DNSSEC'
- context['modal_button'] = modal_button
- context['has_dnssec_records'] = has_dnssec_records
- context['dnssec_enabled'] = self.request.session.pop('dnssec_enabled', False)
+ has_dnssec_records = self.domain.dnssecdata is not None
+
+ # Create HTML for the buttons
+ modal_button = (
+ 'Disable DNSSEC'
+ )
+
+ context["modal_button"] = modal_button
+ context["has_dnssec_records"] = has_dnssec_records
+ context["dnssec_enabled"] = self.request.session.pop("dnssec_enabled", False)
return context
@@ -255,27 +259,24 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
return reverse("domain-dns-dnssec", kwargs={"pk": self.domain.pk})
def post(self, request, *args, **kwargs):
- """Form submission posts to this view.
- """
+ """Form submission posts to this view."""
self.domain = self.get_object()
form = self.get_form()
- if form.is_valid():
- if 'disable_dnssec' in request.POST:
+ if form.is_valid():
+ if "disable_dnssec" in request.POST:
try:
self.domain.dnssecdata = {}
except RegistryError as err:
errmsg = "Error removing existing DNSSEC record(s)."
logger.error(errmsg + ": " + err)
- messages.error(
- self.request, errmsg
- )
- request.session['dnssec_ds_confirmed'] = False
- request.session['dnssec_key_confirmed'] = False
- elif 'enable_dnssec' in request.POST:
- request.session['dnssec_enabled'] = True
- request.session['dnssec_ds_confirmed'] = False
- request.session['dnssec_key_confirmed'] = False
-
+ messages.error(self.request, errmsg)
+ request.session["dnssec_ds_confirmed"] = False
+ request.session["dnssec_key_confirmed"] = False
+ elif "enable_dnssec" in request.POST:
+ request.session["dnssec_enabled"] = True
+ request.session["dnssec_ds_confirmed"] = False
+ request.session["dnssec_key_confirmed"] = False
+
return self.form_valid(form)
@@ -292,21 +293,28 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
domain = self.get_object()
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
initial_data = []
-
- if dnssecdata is not None:
+ if dnssecdata is not None:
if dnssecdata.keyData is not None:
# TODO: Throw an error
pass
if dnssecdata.dsData is not None:
# 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
while len(initial_data) == 0:
initial_data.append({})
-
+
return initial_data
def get_success_url(self):
@@ -319,16 +327,18 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
# use "formset" instead of "form" for the key
context["formset"] = context.pop("form")
- # set the dnssec_ds_confirmed flag in the context for this view
+ # set the dnssec_ds_confirmed flag in the context for this view
# based either on the existence of DS Data in the domain,
# 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
+ self.request.session["dnssec_ds_confirmed"] = True
- context['dnssec_ds_confirmed'] = self.request.session.get('dnssec_ds_confirmed', False)
+ context["dnssec_ds_confirmed"] = self.request.session.get(
+ "dnssec_ds_confirmed", False
+ )
return context
def post(self, request, *args, **kwargs):
@@ -336,14 +346,14 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
self.object = self.get_object()
formset = self.get_form()
- if 'confirm-ds' in request.POST:
- request.session['dnssec_ds_confirmed'] = True
- request.session['dnssec_key_confirmed'] = False
+ if "confirm-ds" in request.POST:
+ request.session["dnssec_ds_confirmed"] = True
+ request.session["dnssec_key_confirmed"] = False
return super().form_valid(formset)
-
- if 'btn-cancel-click' in request.POST:
- return redirect('/', {'formset': formset},RequestContext(request))
-
+
+ if "btn-cancel-click" in request.POST:
+ return redirect("/", {"formset": formset}, RequestContext(request))
+
if formset.is_valid():
return self.form_valid(formset)
else:
@@ -353,11 +363,12 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
"""The formset is valid, perform something with it."""
# Set the nameservers from the formset
- dnssecdata = {"dsData":[]}
+ dnssecdata = {"dsData": []}
for form in formset:
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 = {
"keyTag": form.cleaned_data["key_tag"],
"alg": form.cleaned_data["algorithm"],
@@ -378,9 +389,7 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
errmsg = "Error updating DNSSEC data in the registry."
logger.error(errmsg)
logger.error(err)
- messages.error(
- self.request, errmsg
- )
+ messages.error(self.request, errmsg)
return self.form_invalid(formset)
else:
messages.success(
@@ -388,7 +397,6 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
)
# superclass has the redirect
return super().form_valid(formset)
-
class DomainKeydataView(DomainPermissionView, FormMixin):
@@ -404,21 +412,28 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
domain = self.get_object()
dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
initial_data = []
-
- if dnssecdata is not None:
+ if dnssecdata is not None:
if dnssecdata.dsData is not None:
# TODO: Throw an error
pass
if dnssecdata.keyData is not None:
# 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
while len(initial_data) == 0:
initial_data.append({})
-
+
return initial_data
def get_success_url(self):
@@ -431,32 +446,34 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
# use "formset" instead of "form" for the key
context["formset"] = context.pop("form")
- # set the dnssec_key_confirmed flag in the context for this view
+ # set the dnssec_key_confirmed flag in the context for this view
# based either on the existence of Key Data in the domain,
# 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
+ self.request.session["dnssec_key_confirmed"] = True
- context['dnssec_key_confirmed'] = self.request.session.get('dnssec_key_confirmed', False)
+ context["dnssec_key_confirmed"] = self.request.session.get(
+ "dnssec_key_confirmed", False
+ )
return context
def post(self, request, *args, **kwargs):
"""Formset submission posts to this view."""
self.object = self.get_object()
formset = self.get_form()
-
- if 'confirm-key' in request.POST:
- request.session['dnssec_key_confirmed'] = True
- request.session['dnssec_ds_confirmed'] = False
+
+ if "confirm-key" in request.POST:
+ request.session["dnssec_key_confirmed"] = True
+ request.session["dnssec_ds_confirmed"] = False
self.object.save()
return super().form_valid(formset)
-
- if 'btn-cancel-click' in request.POST:
- return redirect('/', {'formset': formset},RequestContext(request))
-
+
+ if "btn-cancel-click" in request.POST:
+ return redirect("/", {"formset": formset}, RequestContext(request))
+
if formset.is_valid():
return self.form_valid(formset)
else:
@@ -466,11 +483,12 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
"""The formset is valid, perform something with it."""
# Set the nameservers from the formset
- dnssecdata = {"keyData":[]}
+ dnssecdata = {"keyData": []}
for form in formset:
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 = {
"flags": form.cleaned_data["flag"],
"protocol": form.cleaned_data["protocol"],
@@ -490,9 +508,7 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
errmsg = "Error updating DNSSEC data in the registry."
logger.error(errmsg)
logger.error(err)
- messages.error(
- self.request, errmsg
- )
+ messages.error(self.request, errmsg)
return self.form_invalid(formset)
else:
messages.success(
From 93696c31c6822fb36d49aa808a52453cc0f5aa38 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 9 Oct 2023 16:30:04 -0400
Subject: [PATCH 48/79] first pass at dnssec rewrite
---
src/registrar/models/domain.py | 73 ++++++++++++++++++++++++++++++----
1 file changed, 66 insertions(+), 7 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 0268cc4a4..90a0168c4 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -292,21 +292,80 @@ class Domain(TimeStampedModel, DomainHelper):
logger.info("Domain does not have dnssec data defined %s" % err)
return None
+ def getDnssecdataChanges(
+ self, _dnssecdata: dict
+ ) -> tuple[dict, dict]:
+ """
+ calls self.dnssecdata, it should pull from cache but may result
+ in an epp call
+ returns tuple of 2 values as follows:
+ addExtension: dict
+ remExtension: dict
+
+ addExtension includes all dsData or keyData to be added
+ remExtension includes all dsData or keyData to be removed
+
+ method operates on dsData OR keyData, never a mix of the two;
+ operates based on which is present in _dnssecdata;
+ if neither is present, addExtension will be empty dict, and
+ remExtension will be all existing dnssecdata to be deleted
+ """
+
+ oldDnssecdata = self.dnssecdata
+ addDnssecdata = {}
+ remDnssecdata = {}
+
+ if len(_dnssecdata["dsData"]) > 0:
+ # initialize addDnssecdata and remDnssecdata for dsData
+ addDnssecdata["dsData"] = []
+ remDnssecdata["dsData"] = []
+
+ # if existing dsData not in new dsData, mark for removal
+ remDnssecdata["dsData"] = [dsData for dsData in oldDnssecdata["dsData"] if dsData not in _dnssecdata["dsData"]]
+
+ # if new dsData not in existing dsData, mark for add
+ addDnssecdata["dsData"] = [dsData for dsData in _dnssecdata["dsData"] if dsData not in oldDnssecdata["dsData"]]
+ elif len(_dnssecdata["keyData"]) > 0:
+ # initialize addDnssecdata and remDnssecdata for keyData
+ addDnssecdata["keyData"] = []
+ remDnssecdata["keyData"] = []
+
+ # if existing keyData not in new keyData, mark for removal
+ remDnssecdata["keyData"] = [keyData for keyData in oldDnssecdata["keyData"] if keyData not in _dnssecdata["keyData"]]
+
+ # if new keyData not in existing keyData, mark for add
+ addDnssecdata["keyData"] = [keyData for keyData in _dnssecdata["keyData"] if keyData not in oldDnssecdata["keyData"]]
+ else:
+ # there are no new dsData or keyData, remove all
+ remDnssecdata["dsData"] = oldDnssecdata["dsData"]
+ remDnssecdata["keyData"] = oldDnssecdata["keyData"]
+
+ return addDnssecdata, remDnssecdata
+
@dnssecdata.setter # type: ignore
def dnssecdata(self, _dnssecdata: dict):
- updateParams = {
+ _addDnssecdata, _remDnssecdata = self.getDnssecdataChanges(_dnssecdata)
+ addParams = {
"maxSigLife": _dnssecdata.get("maxSigLife", None),
"dsData": _dnssecdata.get("dsData", None),
"keyData": _dnssecdata.get("keyData", None),
- "remAllDsKeyData": True,
}
- request = commands.UpdateDomain(name=self.name)
- extension = commands.UpdateDomainDNSSECExtension(**updateParams)
- request.add_extension(extension)
+ remParams = {
+ "maxSigLife": _dnssecdata.get("maxSigLife", None),
+ "dsData": _dnssecdata.get("dsData", None),
+ "keyData": _dnssecdata.get("keyData", None),
+ }
+ addRequest = commands.UpdateDomain(name=self.name)
+ addExtension = commands.UpdateDomainDNSSECExtension(**addParams)
+ addRequest.add_extension(addExtension)
+ remRequest = commands.UpdateDomain(name=self.name)
+ remExtension = commands.UpdateDomainDNSSECExtension(**remParams)
+ remRequest.add_extension(remExtension)
try:
- registry.send(request, cleaned=True)
+ registry.send(addRequest, cleaned=True)
+ registry.send(remRequest, cleaned=True)
except RegistryError as e:
- logger.error("Error adding DNSSEC, code was %s error was %s" % (e.code, e))
+ logger.error("Error updating DNSSEC, code was %s error was %s" % (e.code, e))
raise e
@nameservers.setter # type: ignore
From 6bd93d56e21357cf57c80ac9b8dbbbaf3eac34d3 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 9 Oct 2023 19:45:44 -0400
Subject: [PATCH 49/79] updated logic for setter of dnssec
---
src/registrar/forms/domain.py | 5 +++
src/registrar/models/domain.py | 56 +++++++++++++++++++---------------
src/registrar/views/domain.py | 10 +++---
3 files changed, 41 insertions(+), 30 deletions(-)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 4ee17a72d..41b15e688 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -169,6 +169,7 @@ class DomainDsdataForm(forms.Form):
algorithm = forms.TypedChoiceField(
required=True,
label="Algorithm",
+ coerce=int, # need to coerce into int so dsData objects can be compared
choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
error_messages={"required": ("Algorithm is required.")},
)
@@ -176,6 +177,7 @@ class DomainDsdataForm(forms.Form):
digest_type = forms.TypedChoiceField(
required=True,
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.")},
)
@@ -201,6 +203,7 @@ class DomainKeydataForm(forms.Form):
flag = forms.TypedChoiceField(
required=True,
label="Flag",
+ coerce=int,
choices=FLAG_CHOICES,
error_messages={"required": ("Flag is required.")},
)
@@ -208,6 +211,7 @@ class DomainKeydataForm(forms.Form):
protocol = forms.TypedChoiceField(
required=True,
label="Protocol",
+ coerce=int,
choices=PROTOCOL_CHOICES,
error_messages={"required": ("Protocol is required.")},
)
@@ -215,6 +219,7 @@ class DomainKeydataForm(forms.Form):
algorithm = forms.TypedChoiceField(
required=True,
label="Algorithm",
+ coerce=int,
choices=[(None, "--Select--")] + ALGORITHM_CHOICES, # type: ignore
error_messages={"required": ("Algorithm is required.")},
)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 90a0168c4..2188a1764 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -291,7 +291,7 @@ class Domain(TimeStampedModel, DomainHelper):
# TODO - 433 error handling ticket should address this
logger.info("Domain does not have dnssec data defined %s" % err)
return None
-
+
def getDnssecdataChanges(
self, _dnssecdata: dict
) -> tuple[dict, dict]:
@@ -312,33 +312,37 @@ class Domain(TimeStampedModel, DomainHelper):
"""
oldDnssecdata = self.dnssecdata
- addDnssecdata = {}
- remDnssecdata = {}
+ addDnssecdata = {"dsData": [], "keyData": [],}
+ remDnssecdata = {"dsData": [], "keyData": [],}
+
+ if _dnssecdata and len(_dnssecdata["dsData"]) > 0:
- if len(_dnssecdata["dsData"]) > 0:
# initialize addDnssecdata and remDnssecdata for dsData
- addDnssecdata["dsData"] = []
+ addDnssecdata["dsData"] = _dnssecdata["dsData"]
remDnssecdata["dsData"] = []
- # if existing dsData not in new dsData, mark for removal
- remDnssecdata["dsData"] = [dsData for dsData in oldDnssecdata["dsData"] if dsData not in _dnssecdata["dsData"]]
+ if oldDnssecdata and len(oldDnssecdata.dsData) > 0:
+ # if existing dsData not in new dsData, mark for removal
+ remDnssecdata["dsData"] = [dsData for dsData in oldDnssecdata.dsData if dsData not in _dnssecdata["dsData"]]
- # if new dsData not in existing dsData, mark for add
- addDnssecdata["dsData"] = [dsData for dsData in _dnssecdata["dsData"] if dsData not in oldDnssecdata["dsData"]]
- elif len(_dnssecdata["keyData"]) > 0:
+ # if new dsData not in existing dsData, mark for add
+ addDnssecdata["dsData"] = [dsData for dsData in _dnssecdata["dsData"] if dsData not in oldDnssecdata.dsData]
+
+ elif _dnssecdata and len(_dnssecdata["keyData"]) > 0:
# initialize addDnssecdata and remDnssecdata for keyData
- addDnssecdata["keyData"] = []
+ addDnssecdata["keyData"] = _dnssecdata["keyData"]
remDnssecdata["keyData"] = []
- # if existing keyData not in new keyData, mark for removal
- remDnssecdata["keyData"] = [keyData for keyData in oldDnssecdata["keyData"] if keyData not in _dnssecdata["keyData"]]
+ if oldDnssecdata and len(oldDnssecdata.keyData) > 0:
+ # if existing keyData not in new keyData, mark for removal
+ remDnssecdata["keyData"] = [keyData for keyData in oldDnssecdata.keyData if keyData not in _dnssecdata["keyData"]]
- # if new keyData not in existing keyData, mark for add
- addDnssecdata["keyData"] = [keyData for keyData in _dnssecdata["keyData"] if keyData not in oldDnssecdata["keyData"]]
+ # if new keyData not in existing keyData, mark for add
+ addDnssecdata["keyData"] = [keyData for keyData in _dnssecdata["keyData"] if keyData not in oldDnssecdata.keyData]
else:
# there are no new dsData or keyData, remove all
- remDnssecdata["dsData"] = oldDnssecdata["dsData"]
- remDnssecdata["keyData"] = oldDnssecdata["keyData"]
+ remDnssecdata["dsData"] = getattr(oldDnssecdata, "dsData", None)
+ remDnssecdata["keyData"] = getattr(oldDnssecdata, "keyData", None)
return addDnssecdata, remDnssecdata
@@ -346,14 +350,14 @@ class Domain(TimeStampedModel, DomainHelper):
def dnssecdata(self, _dnssecdata: dict):
_addDnssecdata, _remDnssecdata = self.getDnssecdataChanges(_dnssecdata)
addParams = {
- "maxSigLife": _dnssecdata.get("maxSigLife", None),
- "dsData": _dnssecdata.get("dsData", None),
- "keyData": _dnssecdata.get("keyData", None),
+ "maxSigLife": _addDnssecdata.get("maxSigLife", None),
+ "dsData": _addDnssecdata.get("dsData", None),
+ "keyData": _addDnssecdata.get("keyData", None),
}
remParams = {
- "maxSigLife": _dnssecdata.get("maxSigLife", None),
- "dsData": _dnssecdata.get("dsData", None),
- "keyData": _dnssecdata.get("keyData", None),
+ "maxSigLife": _remDnssecdata.get("maxSigLife", None),
+ "remDsData": _remDnssecdata.get("dsData", None),
+ "remKeyData": _remDnssecdata.get("keyData", None),
}
addRequest = commands.UpdateDomain(name=self.name)
addExtension = commands.UpdateDomainDNSSECExtension(**addParams)
@@ -362,8 +366,10 @@ class Domain(TimeStampedModel, DomainHelper):
remExtension = commands.UpdateDomainDNSSECExtension(**remParams)
remRequest.add_extension(remExtension)
try:
- registry.send(addRequest, cleaned=True)
- registry.send(remRequest, cleaned=True)
+ if len(_addDnssecdata.get("dsData", [])) > 0 or len(_addDnssecdata.get("keyData",[])) > 0:
+ registry.send(addRequest, cleaned=True)
+ if len(_remDnssecdata.get("dsData", [])) > 0 or len(_remDnssecdata.get("keyData", [])) > 0:
+ registry.send(remRequest, cleaned=True)
except RegistryError as e:
logger.error("Error updating DNSSEC, code was %s error was %s" % (e.code, e))
raise e
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index e51395ba3..b4509b162 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -371,8 +371,8 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
# or form.cleaned_data['delete'] == False:
dsrecord = {
"keyTag": form.cleaned_data["key_tag"],
- "alg": form.cleaned_data["algorithm"],
- "digestType": form.cleaned_data["digest_type"],
+ "alg": int(form.cleaned_data["algorithm"]),
+ "digestType": int(form.cleaned_data["digest_type"]),
"digest": form.cleaned_data["digest"],
}
dnssecdata["dsData"].append(common.DSData(**dsrecord))
@@ -490,9 +490,9 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
# if 'delete' not in form.cleaned_data
# or form.cleaned_data['delete'] == False:
keyrecord = {
- "flags": form.cleaned_data["flag"],
- "protocol": form.cleaned_data["protocol"],
- "alg": form.cleaned_data["algorithm"],
+ "flags": int(form.cleaned_data["flag"]),
+ "protocol": int(form.cleaned_data["protocol"]),
+ "alg": int(form.cleaned_data["algorithm"]),
"pubKey": form.cleaned_data["pub_key"],
}
dnssecdata["keyData"].append(common.DNSSECKeyData(**keyrecord))
From 95b1a02789c9fb80222740c2a369c259708db5df Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 10 Oct 2023 05:25:15 -0400
Subject: [PATCH 50/79] modified unit tests
---
src/registrar/tests/common.py | 55 ++++++----
src/registrar/tests/test_models_domain.py | 126 +++++++++++++++++-----
2 files changed, 136 insertions(+), 45 deletions(-)
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index 60a23b3b4..6ede8965b 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -705,17 +705,23 @@ class MockEppLib(TestCase):
"pubKey": "AQPJ////4Q==",
}
dnssecExtensionWithDsData: Mapping[Any, Any] = {
- "dsData": [common.DSData(**addDsData1)] # type: ignore
+ "dsData": [common.DSData(**addDsData1)], # type: ignore
+ "keyData": [],
}
dnssecExtensionWithMultDsData: Mapping[str, Any] = {
"dsData": [
common.DSData(**addDsData1), # type: ignore
common.DSData(**addDsData2), # type: ignore
],
+ "keyData": [],
}
dnssecExtensionWithKeyData: Mapping[str, Any] = {
- "maxSigLife": 3215,
"keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
+ "dsData": [],
+ }
+ dnssecExtensionRemovingDsData: Mapping[Any, Any] = {
+ "dsData": [],
+ "keyData": [],
}
def mockSend(self, _request, cleaned):
@@ -756,26 +762,35 @@ class MockEppLib(TestCase):
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)
- ],
- )
+ if self.mockedSendFunction.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ 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)
- ],
- )
+ if self.mockedSendFunction.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ 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)
- ],
- )
+ if self.mockedSendFunction.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ 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])
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
index d0718b4ba..b8d648e45 100644
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -984,15 +984,25 @@ class TestRegistrantDNSSEC(MockEppLib):
"""Rule: Registrants may modify their secure DNS data"""
# helper function to create UpdateDomainDNSSECExtention object for verification
- def createUpdateExtension(self, dnssecdata: extensions.DNSSECExtension):
- return commands.UpdateDomainDNSSECExtension(
- maxSigLife=dnssecdata.maxSigLife,
- dsData=dnssecdata.dsData,
- keyData=dnssecdata.keyData,
- remDsData=None,
- remKeyData=None,
- remAllDsKeyData=True,
- )
+ def createUpdateExtension(self, dnssecdata: extensions.DNSSECExtension, remove=False):
+ if not remove:
+ return commands.UpdateDomainDNSSECExtension(
+ maxSigLife=dnssecdata.maxSigLife,
+ dsData=dnssecdata.dsData,
+ keyData=dnssecdata.keyData,
+ remDsData=None,
+ remKeyData=None,
+ remAllDsKeyData=False,
+ )
+ else:
+ return commands.UpdateDomainDNSSECExtension(
+ maxSigLife=dnssecdata.maxSigLife,
+ dsData=None,
+ keyData=None,
+ remDsData=dnssecdata.dsData,
+ remKeyData=dnssecdata.keyData,
+ remAllDsKeyData=False,
+ )
def setUp(self):
"""
@@ -1010,25 +1020,25 @@ class TestRegistrantDNSSEC(MockEppLib):
def test_user_adds_dnssec_data(self):
"""
- Scenario: Registrant adds DNSSEC data.
+ Scenario: Registrant adds DNSSEC ds data.
Verify that both the setter and getter are functioning properly
This test verifies:
- 1 - setter calls UpdateDomain command
- 2 - setter adds the UpdateDNSSECExtension extension to the command
- 3 - setter causes the getter to call info domain on next get from cache
- 4 - getter properly parses dnssecdata from InfoDomain response and sets to cache
+ 1 - setter initially calls InfoDomain command
+ 2 - setter then calls UpdateDomain command
+ 3 - setter adds the UpdateDNSSECExtension extension to the command
+ 4 - setter causes the getter to call info domain on next get from cache
+ 5 - getter properly parses dnssecdata from InfoDomain response and sets to cache
"""
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
-
domain.dnssecdata = self.dnssecExtensionWithDsData
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
args, _ = self.mockedSendFunction.call_args
- # assert that the extension matches
+ # assert that the extension on the update matches
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
@@ -1039,6 +1049,12 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get = domain.dnssecdata
self.mockedSendFunction.assert_has_calls(
[
+ call(
+ commands.InfoDomain(
+ name="dnssec-dsdata.gov",
+ ),
+ cleaned=True,
+ ),
call(
commands.UpdateDomain(
name="dnssec-dsdata.gov",
@@ -1071,9 +1087,11 @@ class TestRegistrantDNSSEC(MockEppLib):
# registry normally sends in this case
This test verifies:
- 1 - UpdateDomain command called twice
- 2 - setter causes the getter to call info domain on next get from cache
- 3 - getter properly parses dnssecdata from InfoDomain response and sets to cache
+ 1 - InfoDomain command is called first
+ 2 - UpdateDomain command called on the initial setter
+ 3 - setter causes the getter to call info domain on next get from cache
+ 4 - UpdateDomain command is not called on second setter (no change)
+ 5 - getter properly parses dnssecdata from InfoDomain response and sets to cache
"""
@@ -1088,12 +1106,8 @@ class TestRegistrantDNSSEC(MockEppLib):
self.mockedSendFunction.assert_has_calls(
[
call(
- commands.UpdateDomain(
+ commands.InfoDomain(
name="dnssec-dsdata.gov",
- nsset=None,
- keyset=None,
- registrant=None,
- auth_info=None,
),
cleaned=True,
),
@@ -1113,6 +1127,12 @@ class TestRegistrantDNSSEC(MockEppLib):
),
cleaned=True,
),
+ call(
+ commands.InfoDomain(
+ name="dnssec-dsdata.gov",
+ ),
+ cleaned=True,
+ ),
]
)
@@ -1174,9 +1194,65 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData["dsData"]
)
+ def test_user_removes_dnssec_data(self):
+ """
+ Scenario: Registrant removes DNSSEC ds data.
+ Verify that both the setter and getter are functioning properly
+
+ This test verifies:
+ 1 - setter initially calls InfoDomain command
+ 2 - invalidate cache forces second InfoDomain command (to match mocks)
+ 3 - setter then calls UpdateDomain command
+ 4 - setter adds the UpdateDNSSECExtension extension to the command with rem
+
+ """
+
+ domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
+ dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
+ domain._invalidate_cache()
+ domain.dnssecdata = self.dnssecExtensionRemovingDsData
+ # get the DNS SEC extension added to the UpdateDomain command and
+ # verify that it is properly sent
+ # args[0] is the _request sent to registry
+ args, _ = self.mockedSendFunction.call_args
+ # assert that the extension on the update matches
+ self.assertEquals(
+ args[0].extensions[0],
+ self.createUpdateExtension(
+ extensions.DNSSECExtension(**self.dnssecExtensionWithDsData),
+ remove=True
+ ),
+ )
+ self.mockedSendFunction.assert_has_calls(
+ [
+ call(
+ commands.InfoDomain(
+ name="dnssec-dsdata.gov",
+ ),
+ cleaned=True,
+ ),
+ call(
+ commands.InfoDomain(
+ name="dnssec-dsdata.gov",
+ ),
+ cleaned=True,
+ ),
+ call(
+ commands.UpdateDomain(
+ name="dnssec-dsdata.gov",
+ nsset=None,
+ keyset=None,
+ registrant=None,
+ auth_info=None,
+ ),
+ cleaned=True,
+ ),
+ ]
+ )
+
def test_user_adds_dnssec_keydata(self):
"""
- Scenario: Registrant adds DNSSEC data.
+ Scenario: Registrant adds DNSSEC key data.
Verify that both the setter and getter are functioning properly
This test verifies:
From 9e8aa2dee33266cf1291cb830151ac4e3da71970 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 10 Oct 2023 08:08:16 -0400
Subject: [PATCH 51/79] properly typed extensions.DNSSECExtension
---
src/registrar/models/domain.py | 104 +++++++++++++++++-----
src/registrar/tests/common.py | 7 +-
src/registrar/tests/test_models_domain.py | 42 ++++++---
src/registrar/views/domain.py | 16 ++--
4 files changed, 120 insertions(+), 49 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 2188a1764..c539838e0 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -2,6 +2,7 @@ from itertools import zip_longest
import logging
from datetime import date
from string import digits
+from typing import Optional
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore
from django.db import models
@@ -283,7 +284,7 @@ class Domain(TimeStampedModel, DomainHelper):
return e.code
@Cache
- def dnssecdata(self) -> extensions.DNSSECExtension:
+ def dnssecdata(self) -> Optional[extensions.DNSSECExtension]:
try:
return self._get_property("dnssecdata")
except Exception as err:
@@ -291,17 +292,18 @@ class Domain(TimeStampedModel, DomainHelper):
# TODO - 433 error handling ticket should address this
logger.info("Domain does not have dnssec data defined %s" % err)
return None
-
+
def getDnssecdataChanges(
- self, _dnssecdata: dict
- ) -> tuple[dict, dict]:
+ self,
+ _dnssecdata: Optional[extensions.DNSSECExtension]
+ ) -> tuple[dict, dict]:
"""
calls self.dnssecdata, it should pull from cache but may result
in an epp call
returns tuple of 2 values as follows:
addExtension: dict
remExtension: dict
-
+
addExtension includes all dsData or keyData to be added
remExtension includes all dsData or keyData to be removed
@@ -311,34 +313,76 @@ class Domain(TimeStampedModel, DomainHelper):
remExtension will be all existing dnssecdata to be deleted
"""
+ if isinstance(_dnssecdata, extensions.DNSSECExtension):
+ logger.info("extension is properly typed")
+ else:
+ logger.info("extension is NOT properly typed")
+
oldDnssecdata = self.dnssecdata
- addDnssecdata = {"dsData": [], "keyData": [],}
- remDnssecdata = {"dsData": [], "keyData": [],}
-
- if _dnssecdata and len(_dnssecdata["dsData"]) > 0:
+ addDnssecdata: dict = {}
+ # "dsData": [],
+ # "keyData": [],
+ # }
+ remDnssecdata: dict = {}
+ # "dsData": [],
+ # "keyData": [],
+ # }
+ if _dnssecdata and _dnssecdata.dsData is not None:
+ logger.info("there is submitted dsdata for comparison")
+ logger.info("there is %s submitted records", len(_dnssecdata.dsData))
# initialize addDnssecdata and remDnssecdata for dsData
- addDnssecdata["dsData"] = _dnssecdata["dsData"]
- remDnssecdata["dsData"] = []
+ addDnssecdata["dsData"] = _dnssecdata.dsData
+ # remDnssecdata["dsData"] = []
if oldDnssecdata and len(oldDnssecdata.dsData) > 0:
+ logger.info("there is existing ds data for comparison")
+ logger.info("there is %s existing records for compare", len(oldDnssecdata.dsData))
# if existing dsData not in new dsData, mark for removal
- remDnssecdata["dsData"] = [dsData for dsData in oldDnssecdata.dsData if dsData not in _dnssecdata["dsData"]]
+ dsDataForRemoval = [
+ dsData
+ for dsData in oldDnssecdata.dsData
+ if dsData not in _dnssecdata.dsData
+ ]
+ if len(dsDataForRemoval) > 0:
+ logger.info("ds data marked for removal")
+ remDnssecdata["dsData"] = dsDataForRemoval
# if new dsData not in existing dsData, mark for add
- addDnssecdata["dsData"] = [dsData for dsData in _dnssecdata["dsData"] if dsData not in oldDnssecdata.dsData]
-
- elif _dnssecdata and len(_dnssecdata["keyData"]) > 0:
+ dsDataForAdd = [
+ dsData
+ for dsData in _dnssecdata.dsData
+ if dsData not in oldDnssecdata.dsData
+ ]
+ if len(dsDataForAdd) > 0:
+ logger.info("ds data marked for add")
+ addDnssecdata["dsData"] = dsDataForAdd
+ else:
+ addDnssecdata["dsData"] = None
+
+ elif _dnssecdata and _dnssecdata.keyData is not None:
# initialize addDnssecdata and remDnssecdata for keyData
- addDnssecdata["keyData"] = _dnssecdata["keyData"]
- remDnssecdata["keyData"] = []
+ addDnssecdata["keyData"] = _dnssecdata.keyData
+ # remDnssecdata["keyData"] = []
if oldDnssecdata and len(oldDnssecdata.keyData) > 0:
# if existing keyData not in new keyData, mark for removal
- remDnssecdata["keyData"] = [keyData for keyData in oldDnssecdata.keyData if keyData not in _dnssecdata["keyData"]]
+ keyDataForRemoval = [
+ keyData
+ for keyData in oldDnssecdata.keyData
+ if keyData not in _dnssecdata.keyData
+ ]
+ if len(keyDataForRemoval) > 0:
+ remDnssecdata["keyData"] = keyDataForRemoval
# if new keyData not in existing keyData, mark for add
- addDnssecdata["keyData"] = [keyData for keyData in _dnssecdata["keyData"] if keyData not in oldDnssecdata.keyData]
+ keyDataForAdd = [
+ keyData
+ for keyData in _dnssecdata.keyData
+ if keyData not in oldDnssecdata.keyData
+ ]
+ if len(keyDataForAdd) > 0:
+ addDnssecdata["keyData"] = keyDataForAdd
else:
# there are no new dsData or keyData, remove all
remDnssecdata["dsData"] = getattr(oldDnssecdata, "dsData", None)
@@ -347,7 +391,7 @@ class Domain(TimeStampedModel, DomainHelper):
return addDnssecdata, remDnssecdata
@dnssecdata.setter # type: ignore
- def dnssecdata(self, _dnssecdata: dict):
+ def dnssecdata(self, _dnssecdata: Optional[extensions.DNSSECExtension]):
_addDnssecdata, _remDnssecdata = self.getDnssecdataChanges(_dnssecdata)
addParams = {
"maxSigLife": _addDnssecdata.get("maxSigLife", None),
@@ -366,12 +410,26 @@ class Domain(TimeStampedModel, DomainHelper):
remExtension = commands.UpdateDomainDNSSECExtension(**remParams)
remRequest.add_extension(remExtension)
try:
- if len(_addDnssecdata.get("dsData", [])) > 0 or len(_addDnssecdata.get("keyData",[])) > 0:
+ if (
+ "dsData" in _addDnssecdata and
+ _addDnssecdata["dsData"] is not None
+ or "keyData" in _addDnssecdata and
+ _addDnssecdata["keyData"] is not None
+ ):
+ logger.info("sending addition")
registry.send(addRequest, cleaned=True)
- if len(_remDnssecdata.get("dsData", [])) > 0 or len(_remDnssecdata.get("keyData", [])) > 0:
+ if (
+ "dsData" in _remDnssecdata and
+ _remDnssecdata["dsData"] is not None
+ or "keyData" in _remDnssecdata and
+ _remDnssecdata["keyData"] is not None
+ ):
+ logger.info("sending removal")
registry.send(remRequest, cleaned=True)
except RegistryError as e:
- logger.error("Error updating DNSSEC, code was %s error was %s" % (e.code, e))
+ logger.error(
+ "Error updating DNSSEC, code was %s error was %s" % (e.code, e)
+ )
raise e
@nameservers.setter # type: ignore
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index 6ede8965b..4d399fd29 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -706,22 +706,19 @@ class MockEppLib(TestCase):
}
dnssecExtensionWithDsData: Mapping[Any, Any] = {
"dsData": [common.DSData(**addDsData1)], # type: ignore
- "keyData": [],
}
dnssecExtensionWithMultDsData: Mapping[str, Any] = {
"dsData": [
common.DSData(**addDsData1), # type: ignore
common.DSData(**addDsData2), # type: ignore
],
- "keyData": [],
}
dnssecExtensionWithKeyData: Mapping[str, Any] = {
"keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
- "dsData": [],
}
dnssecExtensionRemovingDsData: Mapping[Any, Any] = {
- "dsData": [],
- "keyData": [],
+ "dsData": None,
+ "keyData": None,
}
def mockSend(self, _request, cleaned):
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
index b8d648e45..2a0a820ec 100644
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -984,7 +984,9 @@ class TestRegistrantDNSSEC(MockEppLib):
"""Rule: Registrants may modify their secure DNS data"""
# helper function to create UpdateDomainDNSSECExtention object for verification
- def createUpdateExtension(self, dnssecdata: extensions.DNSSECExtension, remove=False):
+ def createUpdateExtension(
+ self, dnssecdata: extensions.DNSSECExtension, remove=False
+ ):
if not remove:
return commands.UpdateDomainDNSSECExtension(
maxSigLife=dnssecdata.maxSigLife,
@@ -1033,7 +1035,9 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
- domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = extensions.DNSSECExtension(
+ **self.dnssecExtensionWithDsData
+ )
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1098,9 +1102,9 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# set the dnssecdata once
- domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
# set the dnssecdata again
- domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
self.mockedSendFunction.assert_has_calls(
@@ -1155,7 +1159,7 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
- domain.dnssecdata = self.dnssecExtensionWithMultDsData
+ domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1201,16 +1205,18 @@ class TestRegistrantDNSSEC(MockEppLib):
This test verifies:
1 - setter initially calls InfoDomain command
- 2 - invalidate cache forces second InfoDomain command (to match mocks)
+ 2 - first setter calls UpdateDomain command
+ 3 - second setter calls InfoDomain command again
3 - setter then calls UpdateDomain command
4 - setter adds the UpdateDNSSECExtension extension to the command with rem
"""
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
- dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
- domain._invalidate_cache()
- domain.dnssecdata = self.dnssecExtensionRemovingDsData
+ # dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
+ # domain._invalidate_cache()
+ domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
+ domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionRemovingDsData)
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1220,7 +1226,7 @@ class TestRegistrantDNSSEC(MockEppLib):
args[0].extensions[0],
self.createUpdateExtension(
extensions.DNSSECExtension(**self.dnssecExtensionWithDsData),
- remove=True
+ remove=True,
),
)
self.mockedSendFunction.assert_has_calls(
@@ -1231,6 +1237,16 @@ class TestRegistrantDNSSEC(MockEppLib):
),
cleaned=True,
),
+ call(
+ commands.UpdateDomain(
+ name="dnssec-dsdata.gov",
+ nsset=None,
+ keyset=None,
+ registrant=None,
+ auth_info=None,
+ ),
+ cleaned=True,
+ ),
call(
commands.InfoDomain(
name="dnssec-dsdata.gov",
@@ -1246,7 +1262,7 @@ class TestRegistrantDNSSEC(MockEppLib):
auth_info=None,
),
cleaned=True,
- ),
+ ),
]
)
@@ -1265,7 +1281,7 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
- domain.dnssecdata = self.dnssecExtensionWithKeyData
+ domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1314,7 +1330,7 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov")
with self.assertRaises(RegistryError) as err:
- domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
self.assertTrue(
err.is_client_error() or err.is_session_error() or err.is_server_error()
)
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index b4509b162..6cc89682e 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -362,8 +362,8 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
def form_valid(self, formset):
"""The formset is valid, perform something with it."""
- # Set the nameservers from the formset
- dnssecdata = {"dsData": []}
+ # Set the dnssecdata from the formset
+ dnssecdata = extensions.DNSSECExtension()
for form in formset:
try:
@@ -375,13 +375,13 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
"digestType": int(form.cleaned_data["digest_type"]),
"digest": form.cleaned_data["digest"],
}
- dnssecdata["dsData"].append(common.DSData(**dsrecord))
+ if dnssecdata.dsData is None:
+ dnssecdata.dsData = []
+ dnssecdata.dsData.append(common.DSData(**dsrecord))
except KeyError:
# no server information in this field, skip it
pass
domain = self.get_object()
- if len(dnssecdata["dsData"]) == 0:
- dnssecdata = {}
try:
domain.dnssecdata = dnssecdata
except RegistryError as err:
@@ -483,7 +483,7 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
"""The formset is valid, perform something with it."""
# Set the nameservers from the formset
- dnssecdata = {"keyData": []}
+ dnssecdata = extensions.DNSSECExtension()
for form in formset:
try:
@@ -495,13 +495,13 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
"alg": int(form.cleaned_data["algorithm"]),
"pubKey": form.cleaned_data["pub_key"],
}
+ if dnssecdata.keyData is None:
+ dnssecdata.keyData = []
dnssecdata["keyData"].append(common.DNSSECKeyData(**keyrecord))
except KeyError:
# no server information in this field, skip it
pass
domain = self.get_object()
- if len(dnssecdata["keyData"]) == 0:
- dnssecdata = {}
try:
domain.dnssecdata = dnssecdata
except RegistryError as err:
From cb15b7d0296315190cb29adac71391731f5eba4d Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 10 Oct 2023 08:43:36 -0400
Subject: [PATCH 52/79] cleaned up tests
---
src/registrar/tests/common.py | 25 +++++++--------
src/registrar/tests/test_models_domain.py | 37 +++++++++++------------
2 files changed, 29 insertions(+), 33 deletions(-)
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index 4d399fd29..d28171155 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -704,22 +704,19 @@ class MockEppLib(TestCase):
"alg": 1,
"pubKey": "AQPJ////4Q==",
}
- dnssecExtensionWithDsData: Mapping[Any, Any] = {
- "dsData": [common.DSData(**addDsData1)], # type: ignore
- }
- dnssecExtensionWithMultDsData: Mapping[str, Any] = {
+ dnssecExtensionWithDsData = extensions.DNSSECExtension(**{
+ "dsData": [common.DSData(**addDsData1)],
+ })
+ dnssecExtensionWithMultDsData = extensions.DNSSECExtension(**{
"dsData": [
common.DSData(**addDsData1), # type: ignore
common.DSData(**addDsData2), # type: ignore
],
- }
- dnssecExtensionWithKeyData: Mapping[str, Any] = {
+ })
+ dnssecExtensionWithKeyData = extensions.DNSSECExtension(**{
"keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
- }
- dnssecExtensionRemovingDsData: Mapping[Any, Any] = {
- "dsData": None,
- "keyData": None,
- }
+ })
+ dnssecExtensionRemovingDsData = extensions.DNSSECExtension()
def mockSend(self, _request, cleaned):
"""Mocks the registry.send function used inside of domain.py
@@ -765,7 +762,7 @@ class MockEppLib(TestCase):
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[
- extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
+ self.dnssecExtensionWithDsData
],
)
elif getattr(_request, "name", None) == "dnssec-multdsdata.gov":
@@ -775,7 +772,7 @@ class MockEppLib(TestCase):
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[
- extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
+ self.dnssecExtensionWithMultDsData
],
)
elif getattr(_request, "name", None) == "dnssec-keydata.gov":
@@ -785,7 +782,7 @@ class MockEppLib(TestCase):
return MagicMock(
res_data=[self.mockDataInfoDomain],
extensions=[
- extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
+ self.dnssecExtensionWithKeyData
],
)
elif getattr(_request, "name", None) == "dnssec-none.gov":
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
index 2a0a820ec..05f56e0f4 100644
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -1035,9 +1035,8 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
- domain.dnssecdata = extensions.DNSSECExtension(
- **self.dnssecExtensionWithDsData
- )
+ domain.dnssecdata = self.dnssecExtensionWithDsData
+
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1046,7 +1045,7 @@ class TestRegistrantDNSSEC(MockEppLib):
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
- extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
+ self.dnssecExtensionWithDsData
),
)
# test that the dnssecdata getter is functioning properly
@@ -1079,7 +1078,7 @@ class TestRegistrantDNSSEC(MockEppLib):
)
self.assertEquals(
- dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
+ dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData
)
def test_dnssec_is_idempotent(self):
@@ -1096,15 +1095,15 @@ class TestRegistrantDNSSEC(MockEppLib):
3 - setter causes the getter to call info domain on next get from cache
4 - UpdateDomain command is not called on second setter (no change)
5 - getter properly parses dnssecdata from InfoDomain response and sets to cache
-
+
"""
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# set the dnssecdata once
- domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
+ domain.dnssecdata = self.dnssecExtensionWithDsData
# set the dnssecdata again
- domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
+ domain.dnssecdata = self.dnssecExtensionWithDsData
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
self.mockedSendFunction.assert_has_calls(
@@ -1141,7 +1140,7 @@ class TestRegistrantDNSSEC(MockEppLib):
)
self.assertEquals(
- dnssecdata_get.dsData, self.dnssecExtensionWithDsData["dsData"]
+ dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData
)
def test_user_adds_dnssec_data_multiple_dsdata(self):
@@ -1159,7 +1158,7 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
- domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
+ domain.dnssecdata = self.dnssecExtensionWithMultDsData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1168,7 +1167,7 @@ class TestRegistrantDNSSEC(MockEppLib):
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
- extensions.DNSSECExtension(**self.dnssecExtensionWithMultDsData)
+ self.dnssecExtensionWithMultDsData
),
)
# test that the dnssecdata getter is functioning properly
@@ -1195,7 +1194,7 @@ class TestRegistrantDNSSEC(MockEppLib):
)
self.assertEquals(
- dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData["dsData"]
+ dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData
)
def test_user_removes_dnssec_data(self):
@@ -1215,8 +1214,8 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
# domain._invalidate_cache()
- domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
- domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionRemovingDsData)
+ domain.dnssecdata = self.dnssecExtensionWithDsData
+ domain.dnssecdata = self.dnssecExtensionRemovingDsData
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1225,7 +1224,7 @@ class TestRegistrantDNSSEC(MockEppLib):
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
- extensions.DNSSECExtension(**self.dnssecExtensionWithDsData),
+ self.dnssecExtensionWithDsData,
remove=True,
),
)
@@ -1281,7 +1280,7 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
- domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
+ domain.dnssecdata = self.dnssecExtensionWithKeyData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
@@ -1290,7 +1289,7 @@ class TestRegistrantDNSSEC(MockEppLib):
self.assertEquals(
args[0].extensions[0],
self.createUpdateExtension(
- extensions.DNSSECExtension(**self.dnssecExtensionWithKeyData)
+ self.dnssecExtensionWithKeyData
),
)
# test that the dnssecdata getter is functioning properly
@@ -1317,7 +1316,7 @@ class TestRegistrantDNSSEC(MockEppLib):
)
self.assertEquals(
- dnssecdata_get.keyData, self.dnssecExtensionWithKeyData["keyData"]
+ dnssecdata_get.keyData, self.dnssecExtensionWithKeyData.keyData
)
def test_update_is_unsuccessful(self):
@@ -1330,7 +1329,7 @@ class TestRegistrantDNSSEC(MockEppLib):
domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov")
with self.assertRaises(RegistryError) as err:
- domain.dnssecdata = extensions.DNSSECExtension(**self.dnssecExtensionWithDsData)
+ domain.dnssecdata = self.dnssecExtensionWithDsData
self.assertTrue(
err.is_client_error() or err.is_session_error() or err.is_server_error()
)
From 950014ea4780d67318692877fb95b12c740e1820 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 10 Oct 2023 08:48:14 -0400
Subject: [PATCH 53/79] removed some comments and debug statements
---
src/registrar/models/domain.py | 21 ---------------------
1 file changed, 21 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index c539838e0..4226e6ac9 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -313,31 +313,15 @@ class Domain(TimeStampedModel, DomainHelper):
remExtension will be all existing dnssecdata to be deleted
"""
- if isinstance(_dnssecdata, extensions.DNSSECExtension):
- logger.info("extension is properly typed")
- else:
- logger.info("extension is NOT properly typed")
-
oldDnssecdata = self.dnssecdata
addDnssecdata: dict = {}
- # "dsData": [],
- # "keyData": [],
- # }
remDnssecdata: dict = {}
- # "dsData": [],
- # "keyData": [],
- # }
if _dnssecdata and _dnssecdata.dsData is not None:
- logger.info("there is submitted dsdata for comparison")
- logger.info("there is %s submitted records", len(_dnssecdata.dsData))
# initialize addDnssecdata and remDnssecdata for dsData
addDnssecdata["dsData"] = _dnssecdata.dsData
- # remDnssecdata["dsData"] = []
if oldDnssecdata and len(oldDnssecdata.dsData) > 0:
- logger.info("there is existing ds data for comparison")
- logger.info("there is %s existing records for compare", len(oldDnssecdata.dsData))
# if existing dsData not in new dsData, mark for removal
dsDataForRemoval = [
dsData
@@ -345,7 +329,6 @@ class Domain(TimeStampedModel, DomainHelper):
if dsData not in _dnssecdata.dsData
]
if len(dsDataForRemoval) > 0:
- logger.info("ds data marked for removal")
remDnssecdata["dsData"] = dsDataForRemoval
# if new dsData not in existing dsData, mark for add
@@ -355,7 +338,6 @@ class Domain(TimeStampedModel, DomainHelper):
if dsData not in oldDnssecdata.dsData
]
if len(dsDataForAdd) > 0:
- logger.info("ds data marked for add")
addDnssecdata["dsData"] = dsDataForAdd
else:
addDnssecdata["dsData"] = None
@@ -363,7 +345,6 @@ class Domain(TimeStampedModel, DomainHelper):
elif _dnssecdata and _dnssecdata.keyData is not None:
# initialize addDnssecdata and remDnssecdata for keyData
addDnssecdata["keyData"] = _dnssecdata.keyData
- # remDnssecdata["keyData"] = []
if oldDnssecdata and len(oldDnssecdata.keyData) > 0:
# if existing keyData not in new keyData, mark for removal
@@ -416,7 +397,6 @@ class Domain(TimeStampedModel, DomainHelper):
or "keyData" in _addDnssecdata and
_addDnssecdata["keyData"] is not None
):
- logger.info("sending addition")
registry.send(addRequest, cleaned=True)
if (
"dsData" in _remDnssecdata and
@@ -424,7 +404,6 @@ class Domain(TimeStampedModel, DomainHelper):
or "keyData" in _remDnssecdata and
_remDnssecdata["keyData"] is not None
):
- logger.info("sending removal")
registry.send(remRequest, cleaned=True)
except RegistryError as e:
logger.error(
From f447df6d6468f0d21b250eb22851292c25df0be7 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 10 Oct 2023 10:50:24 -0400
Subject: [PATCH 54/79] tests cleanup and formatting for linter
---
src/registrar/models/domain.py | 21 ++--
src/registrar/tests/common.py | 73 +++++------
src/registrar/tests/test_models_domain.py | 147 ++++++++++++++++++----
src/registrar/views/domain.py | 2 +-
4 files changed, 165 insertions(+), 78 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 4226e6ac9..f5e229b0a 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -294,9 +294,8 @@ class Domain(TimeStampedModel, DomainHelper):
return None
def getDnssecdataChanges(
- self,
- _dnssecdata: Optional[extensions.DNSSECExtension]
- ) -> tuple[dict, dict]:
+ self, _dnssecdata: Optional[extensions.DNSSECExtension]
+ ) -> tuple[dict, dict]:
"""
calls self.dnssecdata, it should pull from cache but may result
in an epp call
@@ -392,17 +391,17 @@ class Domain(TimeStampedModel, DomainHelper):
remRequest.add_extension(remExtension)
try:
if (
- "dsData" in _addDnssecdata and
- _addDnssecdata["dsData"] is not None
- or "keyData" in _addDnssecdata and
- _addDnssecdata["keyData"] is not None
+ "dsData" in _addDnssecdata
+ and _addDnssecdata["dsData"] is not None
+ or "keyData" in _addDnssecdata
+ and _addDnssecdata["keyData"] is not None
):
registry.send(addRequest, cleaned=True)
if (
- "dsData" in _remDnssecdata and
- _remDnssecdata["dsData"] is not None
- or "keyData" in _remDnssecdata and
- _remDnssecdata["keyData"] is not None
+ "dsData" in _remDnssecdata
+ and _remDnssecdata["dsData"] is not None
+ or "keyData" in _remDnssecdata
+ and _remDnssecdata["keyData"] is not None
):
registry.send(remRequest, cleaned=True)
except RegistryError as e:
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index d28171155..f27713454 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -7,7 +7,7 @@ import random
from string import ascii_uppercase
from django.test import TestCase
from unittest.mock import MagicMock, Mock, patch
-from typing import List, Dict, Mapping, Any
+from typing import List, Dict
from django.conf import settings
from django.contrib.auth import get_user_model, login
@@ -704,18 +704,26 @@ class MockEppLib(TestCase):
"alg": 1,
"pubKey": "AQPJ////4Q==",
}
- dnssecExtensionWithDsData = extensions.DNSSECExtension(**{
- "dsData": [common.DSData(**addDsData1)],
- })
- dnssecExtensionWithMultDsData = extensions.DNSSECExtension(**{
- "dsData": [
- common.DSData(**addDsData1), # type: ignore
- common.DSData(**addDsData2), # type: ignore
- ],
- })
- dnssecExtensionWithKeyData = extensions.DNSSECExtension(**{
- "keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
- })
+ dnssecExtensionWithDsData = extensions.DNSSECExtension(
+ **{
+ "dsData": [
+ common.DSData(**addDsData1) # type: ignore
+ ], # type: ignore
+ }
+ )
+ dnssecExtensionWithMultDsData = extensions.DNSSECExtension(
+ **{
+ "dsData": [
+ common.DSData(**addDsData1), # type: ignore
+ common.DSData(**addDsData2), # type: ignore
+ ], # type: ignore
+ }
+ )
+ dnssecExtensionWithKeyData = extensions.DNSSECExtension(
+ **{
+ "keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
+ }
+ )
dnssecExtensionRemovingDsData = extensions.DNSSECExtension()
def mockSend(self, _request, cleaned):
@@ -756,35 +764,20 @@ class MockEppLib(TestCase):
if getattr(_request, "name", None) == "security.gov":
return MagicMock(res_data=[self.infoDomainNoContact])
elif getattr(_request, "name", None) == "dnssec-dsdata.gov":
- if self.mockedSendFunction.call_count == 1:
- return MagicMock(res_data=[self.mockDataInfoDomain])
- else:
- return MagicMock(
- res_data=[self.mockDataInfoDomain],
- extensions=[
- self.dnssecExtensionWithDsData
- ],
- )
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[self.dnssecExtensionWithDsData],
+ )
elif getattr(_request, "name", None) == "dnssec-multdsdata.gov":
- if self.mockedSendFunction.call_count == 1:
- return MagicMock(res_data=[self.mockDataInfoDomain])
- else:
- return MagicMock(
- res_data=[self.mockDataInfoDomain],
- extensions=[
- self.dnssecExtensionWithMultDsData
- ],
- )
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[self.dnssecExtensionWithMultDsData],
+ )
elif getattr(_request, "name", None) == "dnssec-keydata.gov":
- if self.mockedSendFunction.call_count == 1:
- return MagicMock(res_data=[self.mockDataInfoDomain])
- else:
- return MagicMock(
- res_data=[self.mockDataInfoDomain],
- extensions=[
- self.dnssecExtensionWithKeyData
- ],
- )
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[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])
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
index 05f56e0f4..cf2d002f5 100644
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -1034,23 +1034,40 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
+ # need to use a separate patcher and side_effect for this test, as
+ # response from InfoDomain must be different for different iterations
+ # of the same command
+ def side_effect(_request, cleaned):
+ if isinstance(_request, commands.InfoDomain):
+ if mocked_send.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[self.dnssecExtensionWithDsData],
+ )
+ else:
+ return MagicMock(res_data=[self.mockDataInfoHosts])
+
+ patcher = patch("registrar.models.domain.registry.send")
+ mocked_send = patcher.start()
+ mocked_send.side_effect = side_effect
+
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
domain.dnssecdata = self.dnssecExtensionWithDsData
-
+
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
- args, _ = self.mockedSendFunction.call_args
+ args, _ = mocked_send.call_args
# assert that the extension on the update matches
self.assertEquals(
args[0].extensions[0],
- self.createUpdateExtension(
- self.dnssecExtensionWithDsData
- ),
+ self.createUpdateExtension(self.dnssecExtensionWithDsData),
)
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
- self.mockedSendFunction.assert_has_calls(
+ mocked_send.assert_has_calls(
[
call(
commands.InfoDomain(
@@ -1077,9 +1094,9 @@ class TestRegistrantDNSSEC(MockEppLib):
]
)
- self.assertEquals(
- dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData
- )
+ self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
+
+ patcher.stop()
def test_dnssec_is_idempotent(self):
"""
@@ -1095,9 +1112,28 @@ class TestRegistrantDNSSEC(MockEppLib):
3 - setter causes the getter to call info domain on next get from cache
4 - UpdateDomain command is not called on second setter (no change)
5 - getter properly parses dnssecdata from InfoDomain response and sets to cache
-
+
"""
+ # need to use a separate patcher and side_effect for this test, as
+ # response from InfoDomain must be different for different iterations
+ # of the same command
+ def side_effect(_request, cleaned):
+ if isinstance(_request, commands.InfoDomain):
+ if mocked_send.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[self.dnssecExtensionWithDsData],
+ )
+ else:
+ return MagicMock(res_data=[self.mockDataInfoHosts])
+
+ patcher = patch("registrar.models.domain.registry.send")
+ mocked_send = patcher.start()
+ mocked_send.side_effect = side_effect
+
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# set the dnssecdata once
@@ -1106,7 +1142,7 @@ class TestRegistrantDNSSEC(MockEppLib):
domain.dnssecdata = self.dnssecExtensionWithDsData
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
- self.mockedSendFunction.assert_has_calls(
+ mocked_send.assert_has_calls(
[
call(
commands.InfoDomain(
@@ -1139,9 +1175,9 @@ class TestRegistrantDNSSEC(MockEppLib):
]
)
- self.assertEquals(
- dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData
- )
+ self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
+
+ patcher.stop()
def test_user_adds_dnssec_data_multiple_dsdata(self):
"""
@@ -1156,23 +1192,40 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
+ # need to use a separate patcher and side_effect for this test, as
+ # response from InfoDomain must be different for different iterations
+ # of the same command
+ def side_effect(_request, cleaned):
+ if isinstance(_request, commands.InfoDomain):
+ if mocked_send.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[self.dnssecExtensionWithMultDsData],
+ )
+ else:
+ return MagicMock(res_data=[self.mockDataInfoHosts])
+
+ patcher = patch("registrar.models.domain.registry.send")
+ mocked_send = patcher.start()
+ mocked_send.side_effect = side_effect
+
domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
domain.dnssecdata = self.dnssecExtensionWithMultDsData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
- args, _ = self.mockedSendFunction.call_args
+ args, _ = mocked_send.call_args
# assert that the extension matches
self.assertEquals(
args[0].extensions[0],
- self.createUpdateExtension(
- self.dnssecExtensionWithMultDsData
- ),
+ self.createUpdateExtension(self.dnssecExtensionWithMultDsData),
)
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
- self.mockedSendFunction.assert_has_calls(
+ mocked_send.assert_has_calls(
[
call(
commands.UpdateDomain(
@@ -1197,6 +1250,8 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData
)
+ patcher.stop()
+
def test_user_removes_dnssec_data(self):
"""
Scenario: Registrant removes DNSSEC ds data.
@@ -1211,6 +1266,25 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
+ # need to use a separate patcher and side_effect for this test, as
+ # response from InfoDomain must be different for different iterations
+ # of the same command
+ def side_effect(_request, cleaned):
+ if isinstance(_request, commands.InfoDomain):
+ if mocked_send.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[self.dnssecExtensionWithDsData],
+ )
+ else:
+ return MagicMock(res_data=[self.mockDataInfoHosts])
+
+ patcher = patch("registrar.models.domain.registry.send")
+ mocked_send = patcher.start()
+ mocked_send.side_effect = side_effect
+
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
# dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
# domain._invalidate_cache()
@@ -1219,7 +1293,7 @@ class TestRegistrantDNSSEC(MockEppLib):
# get the DNS SEC extension added to the UpdateDomain command and
# verify that it is properly sent
# args[0] is the _request sent to registry
- args, _ = self.mockedSendFunction.call_args
+ args, _ = mocked_send.call_args
# assert that the extension on the update matches
self.assertEquals(
args[0].extensions[0],
@@ -1228,7 +1302,7 @@ class TestRegistrantDNSSEC(MockEppLib):
remove=True,
),
)
- self.mockedSendFunction.assert_has_calls(
+ mocked_send.assert_has_calls(
[
call(
commands.InfoDomain(
@@ -1265,6 +1339,8 @@ class TestRegistrantDNSSEC(MockEppLib):
]
)
+ patcher.stop()
+
def test_user_adds_dnssec_keydata(self):
"""
Scenario: Registrant adds DNSSEC key data.
@@ -1278,23 +1354,40 @@ class TestRegistrantDNSSEC(MockEppLib):
"""
+ # need to use a separate patcher and side_effect for this test, as
+ # response from InfoDomain must be different for different iterations
+ # of the same command
+ def side_effect(_request, cleaned):
+ if isinstance(_request, commands.InfoDomain):
+ if mocked_send.call_count == 1:
+ return MagicMock(res_data=[self.mockDataInfoDomain])
+ else:
+ return MagicMock(
+ res_data=[self.mockDataInfoDomain],
+ extensions=[self.dnssecExtensionWithKeyData],
+ )
+ else:
+ return MagicMock(res_data=[self.mockDataInfoHosts])
+
+ patcher = patch("registrar.models.domain.registry.send")
+ mocked_send = patcher.start()
+ mocked_send.side_effect = side_effect
+
domain, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
domain.dnssecdata = self.dnssecExtensionWithKeyData
# get the DNS SEC extension added to the UpdateDomain command
# and verify that it is properly sent
# args[0] is the _request sent to registry
- args, _ = self.mockedSendFunction.call_args
+ args, _ = mocked_send.call_args
# assert that the extension matches
self.assertEquals(
args[0].extensions[0],
- self.createUpdateExtension(
- self.dnssecExtensionWithKeyData
- ),
+ self.createUpdateExtension(self.dnssecExtensionWithKeyData),
)
# test that the dnssecdata getter is functioning properly
dnssecdata_get = domain.dnssecdata
- self.mockedSendFunction.assert_has_calls(
+ mocked_send.assert_has_calls(
[
call(
commands.UpdateDomain(
@@ -1319,6 +1412,8 @@ class TestRegistrantDNSSEC(MockEppLib):
dnssecdata_get.keyData, self.dnssecExtensionWithKeyData.keyData
)
+ patcher.stop()
+
def test_update_is_unsuccessful(self):
"""
Scenario: An update to the dns data is unsuccessful
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 6cc89682e..2781be038 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -497,7 +497,7 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
}
if dnssecdata.keyData is None:
dnssecdata.keyData = []
- dnssecdata["keyData"].append(common.DNSSECKeyData(**keyrecord))
+ dnssecdata.keyData.append(common.DNSSECKeyData(**keyrecord))
except KeyError:
# no server information in this field, skip it
pass
From 4dc94a5410ac4946fce57e3ef6f76644a64aa111 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 10 Oct 2023 11:47:07 -0400
Subject: [PATCH 55/79] copy changes
---
src/registrar/templates/domain_dns.html | 2 +-
src/registrar/templates/domain_dnssec.html | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/registrar/templates/domain_dns.html b/src/registrar/templates/domain_dns.html
index 0133a8b07..b16c1cb8b 100644
--- a/src/registrar/templates/domain_dns.html
+++ b/src/registrar/templates/domain_dns.html
@@ -9,7 +9,7 @@
The Domain Name System (DNS) is the internet service that translates your domain name into an IP address. Before your .gov domain can be used, you'll need to connect it to your DNS hosting service and provide us with your name server information.
-
You can enter your name services, as well as other DNS-related information, in the following sections:
+
You can enter your name servers, as well as other DNS-related information, in the following sections:
{% url 'domain-dns-nameservers' pk=domain.id as url %}
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.
+
DNSSEC, or DNS Security Extensions, is additional security layer to protect your domain. Enabling DNSSEC ensures that when someone visits your domain, they can be certain that it's connecting to the correct server, preventing potential hijacking or tampering with your domain's records.
{% csrf_token %}
@@ -43,7 +43,7 @@
- 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 only enable DNSSEC if you know how to set it up properly at your hosting service. If you make a mistake, it could cause your domain name to stop working.
Date: Tue, 10 Oct 2023 19:54:43 -0400
Subject: [PATCH 56/79] trailing lines and some cleanup
---
src/registrar/assets/sass/_theme/_admin.scss | 2 +-
src/registrar/assets/sass/_theme/_alerts.scss | 2 +-
src/registrar/assets/sass/_theme/_base.scss | 2 +-
src/registrar/assets/sass/_theme/_buttons.scss | 2 +-
src/registrar/assets/sass/_theme/_fieldsets.scss | 2 +-
src/registrar/assets/sass/_theme/_forms.scss | 2 +-
src/registrar/assets/sass/_theme/_register-form.scss | 2 +-
src/registrar/assets/sass/_theme/_sidenav.scss | 2 +-
src/registrar/assets/sass/_theme/_tables.scss | 2 +-
src/registrar/assets/sass/_theme/_typography.scss | 2 +-
src/registrar/templates/includes/modal.html | 2 +-
src/registrar/views/domain.py | 2 +-
12 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss
index a2e32bd21..35d089cbd 100644
--- a/src/registrar/assets/sass/_theme/_admin.scss
+++ b/src/registrar/assets/sass/_theme/_admin.scss
@@ -179,4 +179,4 @@ h1, h2, h3 {
text-align: left;
background: var(--primary);
color: var(--header-link-color);
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_alerts.scss b/src/registrar/assets/sass/_theme/_alerts.scss
index 077708990..dd51734ed 100644
--- a/src/registrar/assets/sass/_theme/_alerts.scss
+++ b/src/registrar/assets/sass/_theme/_alerts.scss
@@ -14,4 +14,4 @@
left: 1rem !important;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss
index 729049444..668a6ace6 100644
--- a/src/registrar/assets/sass/_theme/_base.scss
+++ b/src/registrar/assets/sass/_theme/_base.scss
@@ -124,4 +124,4 @@ abbr[title] {
.flex-end {
align-items: flex-end;
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss
index f4c72f4fa..4815ff82e 100644
--- a/src/registrar/assets/sass/_theme/_buttons.scss
+++ b/src/registrar/assets/sass/_theme/_buttons.scss
@@ -106,4 +106,4 @@ a.usa-button--unstyled:visited {
position: relative;
top: -39.2px;
left: 88px;
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_fieldsets.scss b/src/registrar/assets/sass/_theme/_fieldsets.scss
index e7da538e3..c60080cb9 100644
--- a/src/registrar/assets/sass/_theme/_fieldsets.scss
+++ b/src/registrar/assets/sass/_theme/_fieldsets.scss
@@ -7,4 +7,4 @@ fieldset {
fieldset:not(:first-child) {
margin-top: units(2);
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_forms.scss b/src/registrar/assets/sass/_theme/_forms.scss
index 13e1a7a61..cdb2889c5 100644
--- a/src/registrar/assets/sass/_theme/_forms.scss
+++ b/src/registrar/assets/sass/_theme/_forms.scss
@@ -18,4 +18,4 @@
margin-left: 0;
padding-left: 0;
border-left: none;
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_register-form.scss b/src/registrar/assets/sass/_theme/_register-form.scss
index 7bf4eebf3..d0405a3c3 100644
--- a/src/registrar/assets/sass/_theme/_register-form.scss
+++ b/src/registrar/assets/sass/_theme/_register-form.scss
@@ -77,4 +77,4 @@
color: color('primary-dark');
font-weight: font-weight('semibold');
margin-bottom: units(0.5);
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_sidenav.scss b/src/registrar/assets/sass/_theme/_sidenav.scss
index c1be03a00..caafa7dd4 100644
--- a/src/registrar/assets/sass/_theme/_sidenav.scss
+++ b/src/registrar/assets/sass/_theme/_sidenav.scss
@@ -27,4 +27,4 @@
.stepnav {
margin-top: units(2);
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss
index 947c75005..6dcc6f3bc 100644
--- a/src/registrar/assets/sass/_theme/_tables.scss
+++ b/src/registrar/assets/sass/_theme/_tables.scss
@@ -90,4 +90,4 @@
border-top: none;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/sass/_theme/_typography.scss b/src/registrar/assets/sass/_theme/_typography.scss
index e13e9ee3b..4fc2bb819 100644
--- a/src/registrar/assets/sass/_theme/_typography.scss
+++ b/src/registrar/assets/sass/_theme/_typography.scss
@@ -21,4 +21,4 @@ h2 {
line-height: line-height('heading', 3);
margin: units(4) 0 units(1);
color: color('primary-darker');
-}
\ No newline at end of file
+}
diff --git a/src/registrar/templates/includes/modal.html b/src/registrar/templates/includes/modal.html
index b456a59e1..996759576 100644
--- a/src/registrar/templates/includes/modal.html
+++ b/src/registrar/templates/includes/modal.html
@@ -39,4 +39,4 @@
-
\ No newline at end of file
+
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 9334700ef..c0e121fee 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -241,7 +241,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
has_dnssec_records = self.domain.dnssecdata is not None
- # Create HTML for the buttons
+ # Create HTML for the modal button
modal_button = 'Disable DNSSEC'
context['modal_button'] = modal_button
From 4c2eaac59f9d8800fe21146b100f8c262211203f Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Tue, 10 Oct 2023 20:02:02 -0400
Subject: [PATCH 57/79] comments clean up
---
src/registrar/views/domain.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 2cf5ca3c2..24a704d94 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -297,6 +297,8 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
if dnssecdata is not None:
if dnssecdata.keyData is not None:
# TODO: Throw an error
+ # Note: This is moot if we're
+ # removing key data
pass
if dnssecdata.dsData is not None:
@@ -311,7 +313,7 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
for record in dnssecdata.dsData
)
- # Ensure at least 3 fields, filled or empty
+ # Ensure at least 1 record, filled or empty
while len(initial_data) == 0:
initial_data.append({})
@@ -385,7 +387,6 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
try:
domain.dnssecdata = dnssecdata
except RegistryError as err:
- # Alysia: Check client hold error handling and duplicate this here
errmsg = "Error updating DNSSEC data in the registry."
logger.error(errmsg)
logger.error(err)
@@ -415,7 +416,9 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
if dnssecdata is not None:
if dnssecdata.dsData is not None:
- # TODO: Throw an error
+ # TODO: Throw an error?
+ # Note: this is moot if we're
+ # removing Key data
pass
if dnssecdata.keyData is not None:
@@ -430,7 +433,7 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
for record in dnssecdata.keyData
)
- # Ensure at least 3 fields, filled or empty
+ # Ensure at least 1 record, filled or empty
while len(initial_data) == 0:
initial_data.append({})
From bddf791489c1e82f2266b5e66568d12ac4439982 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Wed, 11 Oct 2023 18:42:13 -0400
Subject: [PATCH 58/79] some content updates
---
src/registrar/templates/domain_dnssec.html | 2 +-
src/registrar/templates/domain_dsdata.html | 17 +++++++++++------
src/registrar/templates/domain_sidebar.html | 2 +-
3 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index 91d8bb7b8..9913b041c 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -5,7 +5,7 @@
{% block domain_content %}
-
{% if dnssec_enabled %}Set up {% endif %}DNSSEC
+
DNSSEC
DNSSEC, or DNS Security Extensions, is additional security layer to protect your domain. Enabling DNSSEC ensures that when someone visits your domain, they can be certain that it's connecting to the correct server, preventing potential hijacking or tampering with your domain's records.
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.
+
In order to enable DNSSEC, you must first configure it with your DNS hosting service.
+
Enter the values given by your DNS provider for DS Data.
+
Required fields are marked with an asterisk (*).
{% csrf_token %}
- Add DS Data record
+
+ Add new record
+
{% else %}
diff --git a/src/registrar/templates/domain_sidebar.html b/src/registrar/templates/domain_sidebar.html
index aff9f5dab..1acd87eeb 100644
--- a/src/registrar/templates/domain_sidebar.html
+++ b/src/registrar/templates/domain_sidebar.html
@@ -23,7 +23,7 @@
- DNS name servers
+ Name servers
From c7c50fb3d5b19a1d836368e483900e15da225f84 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Wed, 11 Oct 2023 18:48:24 -0400
Subject: [PATCH 59/79] minor js update
---
src/registrar/assets/js/get-gov.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 4b5ff5e31..f5dffba12 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -276,8 +276,7 @@ function prepareDeleteButtons() {
let formToRemove = e.target.closest(".ds-record");
formToRemove.remove();
let forms = document.querySelectorAll(".ds-record");
- let formNum2 = forms.length;
- totalForms.setAttribute('value', `${formNum2}`);
+ totalForms.setAttribute('value', `${forms.length}`);
let formNumberRegex = RegExp(`form-(\\d){1}-`, 'g');
let formLabelRegex = RegExp(`DS Data record (\\d){1}`, 'g');
From bac08277b2fe2e8bd811c0857c3bdca58d052131 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Wed, 11 Oct 2023 19:04:10 -0400
Subject: [PATCH 60/79] updated copy and fixed failing test
---
src/registrar/templates/domain_dsdata.html | 8 ++++++++
src/registrar/tests/test_views.py | 5 +++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/registrar/templates/domain_dsdata.html b/src/registrar/templates/domain_dsdata.html
index eb0e73e49..c068a3752 100644
--- a/src/registrar/templates/domain_dsdata.html
+++ b/src/registrar/templates/domain_dsdata.html
@@ -8,6 +8,14 @@
{% include "includes/form_errors.html" with form=form %}
{% endfor %}
+ {% if domain.dnssecdata is None and not dnssec_ds_confirmed %}
+
+
+ You have no DS Data added. Enable DNSSEC by adding DS Data or return to the DNSSEC page and click 'enable.'
+
+
+ {% endif %}
+
DS Data
{% if domain.dnssecdata is not None and domain.dnssecdata.keyData is not None %}
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index af44cdeaa..06fddfde7 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1618,14 +1618,15 @@ class TestDomainDNSSEC(TestDomainOverview):
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"""
+ domain DNSSEC data and shows a button to Add new record"""
page = self.client.get(
reverse(
"domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id}
)
)
- self.assertContains(page, "Add DS Data record")
+ self.assertContains(page, "You have no DS Data added")
+ self.assertContains(page, "Add new record")
def test_ds_form_loads_with_ds_data(self):
"""DNSSEC Add DS Data page loads when there is
From 46ad6002cd0213fd0b46d1efe86ab1f5be2bf5c1 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Fri, 13 Oct 2023 12:07:35 -0400
Subject: [PATCH 61/79] CSS tweaks
---
src/registrar/assets/sass/_theme/_forms.scss | 4 ++++
src/registrar/forms/domain.py | 2 +-
src/registrar/templates/domain_dnssec.html | 8 ++++----
src/registrar/templates/domain_dsdata.html | 2 +-
src/registrar/templates/domain_keydata.html | 2 +-
5 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/src/registrar/assets/sass/_theme/_forms.scss b/src/registrar/assets/sass/_theme/_forms.scss
index cdb2889c5..ed118bb94 100644
--- a/src/registrar/assets/sass/_theme/_forms.scss
+++ b/src/registrar/assets/sass/_theme/_forms.scss
@@ -8,6 +8,10 @@
max-width: none;
}
+.usa-form--text-width {
+ max-width: measure(5);
+}
+
.usa-textarea {
@include at-media('tablet') {
height: units('mobile');
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 73a40bfa1..b28c9f6a4 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -176,7 +176,7 @@ class DomainDsdataForm(forms.Form):
digest_type = forms.TypedChoiceField(
required=True,
- label="Digest Type",
+ 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.")},
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index 9913b041c..4fa43bb4c 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -9,10 +9,10 @@
DNSSEC, or DNS Security Extensions, is additional security layer to protect your domain. Enabling DNSSEC ensures that when someone visits your domain, they can be certain that it's connecting to the correct server, preventing potential hijacking or tampering with your domain's records.
-
+
{% csrf_token %}
{% if has_dnssec_records %}
-
+
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.
It is strongly recommended that you only enable DNSSEC if you know how to set it up properly at your hosting service. If you make a mistake, it could cause your domain name to stop working.
diff --git a/src/registrar/templates/domain_dsdata.html b/src/registrar/templates/domain_dsdata.html
index c068a3752..ca4dce783 100644
--- a/src/registrar/templates/domain_dsdata.html
+++ b/src/registrar/templates/domain_dsdata.html
@@ -97,7 +97,7 @@
{% endfor %}
-
+ Add new record
diff --git a/src/registrar/templates/domain_keydata.html b/src/registrar/templates/domain_keydata.html
index a5d2f610c..167d86370 100644
--- a/src/registrar/templates/domain_keydata.html
+++ b/src/registrar/templates/domain_keydata.html
@@ -84,7 +84,7 @@
{% endfor %}
-
+ Add new record
From 5a5aa5e97a2e26e340436dd225cb105ae49cb9b4 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Fri, 13 Oct 2023 12:54:56 -0400
Subject: [PATCH 62/79] tweak cancel buttons
---
src/registrar/assets/sass/_theme/_buttons.scss | 18 +++++++++++++++++-
src/registrar/templates/domain_dnssec.html | 2 +-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss
index 4815ff82e..718bd5792 100644
--- a/src/registrar/assets/sass/_theme/_buttons.scss
+++ b/src/registrar/assets/sass/_theme/_buttons.scss
@@ -101,7 +101,23 @@ a.usa-button--unstyled:visited {
}
}
-// WARNING: crazy hack ahead
+// Cancel button used on the
+// DNSSEC main page
+// We want to center this button on mobile
+// and add some extra left margin on tablet+
+.usa-button--cancel {
+ text-align: center;
+ @include at-media('tablet') {
+ margin-left: units(2);
+ }
+}
+
+
+// WARNING: crazy hack ahead:
+// Cancel button(s) on the DNSSEC form pages
+// We want to position the cancel button on the
+// dnssec forms next to the submit button
+// This button's markup is in its own form
.btn-cancel {
position: relative;
top: -39.2px;
diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html
index 4fa43bb4c..5eedb2184 100644
--- a/src/registrar/templates/domain_dnssec.html
+++ b/src/registrar/templates/domain_dnssec.html
@@ -33,7 +33,7 @@
Add Key DataCancel
From c6abca99cad675ff8616e18e4f9dfba29cbfca80 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Fri, 13 Oct 2023 15:32:30 -0400
Subject: [PATCH 63/79] updates to tests
---
src/registrar/tests/common.py | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index d1021f45c..5df54de76 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -791,6 +791,8 @@ class MockEppLib(TestCase):
return self.mockInfoDomainCommands(_request, cleaned)
elif isinstance(_request, commands.InfoContact):
return self.mockInfoContactCommands(_request, cleaned)
+ elif isinstance(_request, commands.UpdateDomain):
+ return self.mockUpdateDomainCommands(_request, cleaned)
elif (
isinstance(_request, commands.CreateContact)
and getattr(_request, "id", None) == "fail"
@@ -809,16 +811,6 @@ class MockEppLib(TestCase):
res_data=[self.mockDataHostChange],
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
)
- elif (
- isinstance(_request, commands.UpdateDomain)
- and getattr(_request, "name", None) == "dnssec-invalid.gov"
- ):
- raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
- elif isinstance(_request, commands.UpdateDomain):
- return MagicMock(
- res_data=[self.mockDataHostChange],
- code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
- )
elif isinstance(_request, commands.DeleteHost):
return MagicMock(
res_data=[self.mockDataHostChange],
@@ -836,6 +828,15 @@ class MockEppLib(TestCase):
)
return MagicMock(res_data=[self.mockDataInfoHosts])
+ def mockUpdateDomainCommands(self, _request, cleaned):
+ if getattr(_request, "name", None) == "dnssec-invalid.gov":
+ raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
+ else:
+ return MagicMock(
+ res_data=[self.mockDataHostChange],
+ code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
+ )
+
def mockInfoDomainCommands(self, _request, cleaned):
if getattr(_request, "name", None) == "security.gov":
return MagicMock(res_data=[self.infoDomainNoContact])
From f94363a836a59b34291261f21cdb3296593b9adf Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Fri, 13 Oct 2023 15:43:24 -0400
Subject: [PATCH 64/79] refactor epp mockInfoDomainCommands function
---
src/registrar/tests/common.py | 74 ++++++++++++++++++-----------------
1 file changed, 39 insertions(+), 35 deletions(-)
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index 5df54de76..7ae107006 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -838,41 +838,45 @@ class MockEppLib(TestCase):
)
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=[self.dnssecExtensionWithDsData],
- )
- elif getattr(_request, "name", None) == "dnssec-multdsdata.gov":
- return MagicMock(
- res_data=[self.mockDataInfoDomain],
- extensions=[self.dnssecExtensionWithMultDsData],
- )
- elif getattr(_request, "name", None) == "dnssec-keydata.gov":
- return MagicMock(
- res_data=[self.mockDataInfoDomain],
- extensions=[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) == "my-nameserver.gov":
- if self.mockedSendFunction.call_count == 5:
- return MagicMock(res_data=[self.infoDomainTwoHosts])
- else:
- return MagicMock(res_data=[self.infoDomainNoHost])
- elif getattr(_request, "name", None) == "nameserverwithip.gov":
- return MagicMock(res_data=[self.infoDomainHasIP])
- elif getattr(_request, "name", None) == "namerserversubdomain.gov":
- return MagicMock(res_data=[self.infoDomainCheckHostIPCombo])
- elif getattr(_request, "name", None) == "freeman.gov":
- return MagicMock(res_data=[self.InfoDomainWithContacts])
- elif getattr(_request, "name", None) == "threenameserversDomain.gov":
- return MagicMock(res_data=[self.infoDomainThreeHosts])
- else:
- return MagicMock(res_data=[self.mockDataInfoDomain])
+ request_name = getattr(_request, "name", None)
+
+ # Define a dictionary to map request names to data and extension values
+ request_mappings = {
+ "security.gov": (self.infoDomainNoContact, None),
+ "dnssec-dsdata.gov": (
+ self.mockDataInfoDomain,
+ self.dnssecExtensionWithDsData,
+ ),
+ "dnssec-multdsdata.gov": (
+ self.mockDataInfoDomain,
+ self.dnssecExtensionWithMultDsData,
+ ),
+ "dnssec-keydata.gov": (
+ self.mockDataInfoDomain,
+ self.dnssecExtensionWithKeyData,
+ ),
+ "dnssec-none.gov": (self.mockDataInfoDomain, None),
+ "my-nameserver.gov": (
+ self.infoDomainTwoHosts
+ if self.mockedSendFunction.call_count == 5
+ else self.infoDomainNoHost,
+ None,
+ ),
+ "nameserverwithip.gov": (self.infoDomainHasIP, None),
+ "namerserversubdomain.gov": (self.infoDomainCheckHostIPCombo, None),
+ "freeman.gov": (self.InfoDomainWithContacts, None),
+ "threenameserversDomain.gov": (self.infoDomainThreeHosts, None),
+ }
+
+ # Retrieve the corresponding values from the dictionary
+ res_data, extensions = request_mappings.get(
+ request_name, (self.mockDataInfoDomain, None)
+ )
+
+ return MagicMock(
+ res_data=[res_data],
+ extensions=[extensions] if extensions is not None else [],
+ )
def mockInfoContactCommands(self, _request, cleaned):
mocked_result: info.InfoContactResultData
From 35570b6423d8a31472254a445733799218fd2bd2 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 13 Oct 2023 15:33:30 -0600
Subject: [PATCH 65/79] Progress save
---
...2-submit-domain-request-user-flow copy.md} | 0
.../decisions/0023-use-geventconnpool..md | 50 +++++++++++++++++++
2 files changed, 50 insertions(+)
rename docs/architecture/decisions/{0022-submit-domain-request-user-flow.md => 0022-submit-domain-request-user-flow copy.md} (100%)
create mode 100644 docs/architecture/decisions/0023-use-geventconnpool..md
diff --git a/docs/architecture/decisions/0022-submit-domain-request-user-flow.md b/docs/architecture/decisions/0022-submit-domain-request-user-flow copy.md
similarity index 100%
rename from docs/architecture/decisions/0022-submit-domain-request-user-flow.md
rename to docs/architecture/decisions/0022-submit-domain-request-user-flow copy.md
diff --git a/docs/architecture/decisions/0023-use-geventconnpool..md b/docs/architecture/decisions/0023-use-geventconnpool..md
new file mode 100644
index 000000000..512004ac7
--- /dev/null
+++ b/docs/architecture/decisions/0023-use-geventconnpool..md
@@ -0,0 +1,50 @@
+# 22. Use geventconnpool library for Connection Pooling
+
+Date: 2023-13-10
+
+## Status
+
+In Review
+
+## Context
+
+When sending and receiving data from the registry, we use the [EPPLib](https://github.com/cisagov/epplib) library to facilitate that process. To manage these connections within our application, we utilize a module named `epplibwrapper` which serves as a bridge between getgov and the EPPLib library. As part of this process, `epplibwrapper` will instantiate a client that handles sending/receiving data.
+
+At present, each time we need to send a command to the registry, the client establishes a new connection to handle this task. This becomes inefficient when dealing with multiple calls in parallel or in series, as we have to initiate a handshake for each of them. To mitigate this issue, a widely adopted solution is to use a [connection pool](https://en.wikipedia.org/wiki/Connection_pool). In general, a connection pool stores a cache of active connections so that rather than restarting the handshake process when it is unnecessary, we can utilize an existing connection to avoid this problem.
+
+In practice, the lack of a connection pool has resulted in performance issues when dealing with connections to and from the registry. Given the unique nature of our development stack, our options for prebuilt libraries are limited. Out of our available options, a library called [`geventconnpool`](https://github.com/rasky/geventconnpool) was identified that most closely matched our needs.
+
+## Considered Options
+
+**Option 1:** Use the existing connection pool library `geventconnpool` as a foundation for connection pooling.
+
+**Option 2:** Write our own connection pool logic.
+
+**Option 3:** Modify an existing library which we will tailor to our needs.
+
+## Tradeoffs
+
+**Option 1:**
+Pros:
+- Subtantially saves development time and effort.
+- It is a tiny library that is easy to audit and understand.
+- This library has been used for [EPP before](https://github.com/rasky/geventconnpool/issues/9)
+- Uses [`gevent`](http://www.gevent.org/) for coroutines, which is reliable and well maintained.
+-
+- Cons: May not be tailored to our specific needs, could introduce unwanted dependencies.
+
+**Option 2:**
+- Pros: Full control over functionality, can be tailored to our specific needs.
+- Cons: Requires significant development time and effort, needs thorough testing.
+
+**Option 3:**
+- Pros: Savings in development time and effort, can be tailored to our specific needs.
+- Cons: Could introduce complexity, potential issues with maintaining the modified library.
+
+## Decision
+
+We have decided to go with option 1. New users of the registrar will need to have at least one approved application OR prior registered .gov domain in order to submit another application. We chose this option because we would like to allow users be able to work on applications, even if they are unable to submit them.
+
+A [user flow diagram](https://miro.com/app/board/uXjVM3jz3Bs=/?share_link_id=875307531981) demonstrates our decision.
+
+## Consequences
\ No newline at end of file
From becaedc117b1b58602035292608760375de89163 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 16 Oct 2023 09:59:12 -0600
Subject: [PATCH 66/79] Update 0023-use-geventconnpool..md
---
.../decisions/0023-use-geventconnpool..md | 79 ++++++++++++++-----
1 file changed, 59 insertions(+), 20 deletions(-)
diff --git a/docs/architecture/decisions/0023-use-geventconnpool..md b/docs/architecture/decisions/0023-use-geventconnpool..md
index 512004ac7..c24318b4f 100644
--- a/docs/architecture/decisions/0023-use-geventconnpool..md
+++ b/docs/architecture/decisions/0023-use-geventconnpool..md
@@ -16,35 +16,74 @@ In practice, the lack of a connection pool has resulted in performance issues wh
## Considered Options
-**Option 1:** Use the existing connection pool library `geventconnpool` as a foundation for connection pooling.
+**Option 1:** Use the existing connection pool library `geventconnpool`.
+
+âž• Pros
+
+- Saves development time and effort.
+- A tiny library that is easy to audit and understand.
+- Built to be flexible, so every built-in function can be overridden with minimal effort.
+- This library has been used for [EPP before](https://github.com/rasky/geventconnpool/issues/9).
+- Uses [`gevent`](http://www.gevent.org/) for coroutines, which is reliable and well maintained.
+- [`gevent`](http://www.gevent.org/) is used in our WSGI web server.
+- This library is the closest match to our needs that we have found.
+
+
+
+âž– Cons
+
+- Not a well maintained library, could require a fork if a dependency breaks.
+- Heavily reliant on `gevent`.
+
+
**Option 2:** Write our own connection pool logic.
+
+âž• Pros
-**Option 3:** Modify an existing library which we will tailor to our needs.
+- Full control over functionality, can be tailored to our specific needs.
+- Highly specific to our stack, could be fine tuned for performance.
-## Tradeoffs
+
+
+âž– Cons
-**Option 1:**
-Pros:
-- Subtantially saves development time and effort.
-- It is a tiny library that is easy to audit and understand.
-- This library has been used for [EPP before](https://github.com/rasky/geventconnpool/issues/9)
-- Uses [`gevent`](http://www.gevent.org/) for coroutines, which is reliable and well maintained.
--
-- Cons: May not be tailored to our specific needs, could introduce unwanted dependencies.
+- Requires significant development time and effort, needs thorough testing.
+- Would require managing with and developing around concurrency.
+- Introduces the potential for many unseen bugs.
-**Option 2:**
-- Pros: Full control over functionality, can be tailored to our specific needs.
-- Cons: Requires significant development time and effort, needs thorough testing.
+
-**Option 3:**
-- Pros: Savings in development time and effort, can be tailored to our specific needs.
-- Cons: Could introduce complexity, potential issues with maintaining the modified library.
+**Option 3:** Modify an existing library which we will then tailor to our needs.
+
+âž• Pros
+
+- Savings in development time and effort, can be tailored to our specific needs.
+- Good middleground between the first two options.
+
+
+
+âž– Cons
+
+- Could introduce complexity, potential issues with maintaining the modified library.
+- May not be necessary if the given library is flexible enough.
+
+
## Decision
-We have decided to go with option 1. New users of the registrar will need to have at least one approved application OR prior registered .gov domain in order to submit another application. We chose this option because we would like to allow users be able to work on applications, even if they are unable to submit them.
+We have decided to go with option 1, which is to use the `geventconnpool` library. It closely matches our needs and offers several advantages. Of note, it significantly saves on development time and it is inherently flexible. This allows us to easily change functionality with minimal effort. In addition, the gevent library (which this uses) offers performance benefits due to it being a) written in [cython](https://cython.org/), b) very well maintained and purpose built for tasks such as these, and c) used in our WGSI server.
-A [user flow diagram](https://miro.com/app/board/uXjVM3jz3Bs=/?share_link_id=875307531981) demonstrates our decision.
+In summary, this decision was driven by the library's flexibility, simplicity, and compatibility with our tech stack. We acknowledge the risk associated with its maintenance status, but believe that the benefit outweighs the risk.
-## Consequences
\ No newline at end of file
+## Consequences
+
+While its small size makes it easy to work around, `geventconnpool` is not actively maintained. Its last update was in 2021, and as such there is a risk that its dependencies (gevent) will outpace this library and cause it to break. If such an event occurs, it would require that we fork the library and fix those issues. See option 3 pros/cons.
+
+## Mitigation Plan
+To manage this risk, we'll:
+
+1. Monitor the gevent library for updates.
+2. Design the connection pool logic abstractly such that we can easily swap the underlying logic out without needing (or minimizing the need) to rewrite code in `epplibwrapper`.
+3. Document a process for forking and maintaining the library if it becomes necessary, including testing procedures.
+4. Establish a contingency plan for reverting to a previous system state or switching to a different library if significant issues arise with `gevent` or `geventconnpool`.
\ No newline at end of file
From 605e75dc48fa2dff17015e39caffe141a0fa1a43 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 16 Oct 2023 10:00:10 -0600
Subject: [PATCH 67/79] Fix file rename
---
...-user-flow copy.md => 0022-submit-domain-request-user-flow.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename docs/architecture/decisions/{0022-submit-domain-request-user-flow copy.md => 0022-submit-domain-request-user-flow.md} (100%)
diff --git a/docs/architecture/decisions/0022-submit-domain-request-user-flow copy.md b/docs/architecture/decisions/0022-submit-domain-request-user-flow.md
similarity index 100%
rename from docs/architecture/decisions/0022-submit-domain-request-user-flow copy.md
rename to docs/architecture/decisions/0022-submit-domain-request-user-flow.md
From d4ec7eca8a734c94b2dabf2c9417dd811184cef2 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 16 Oct 2023 10:11:15 -0600
Subject: [PATCH 68/79] Fix weird file name
---
.../{0023-use-geventconnpool..md => 0023-use-geventconnpool.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename docs/architecture/decisions/{0023-use-geventconnpool..md => 0023-use-geventconnpool.md} (100%)
diff --git a/docs/architecture/decisions/0023-use-geventconnpool..md b/docs/architecture/decisions/0023-use-geventconnpool.md
similarity index 100%
rename from docs/architecture/decisions/0023-use-geventconnpool..md
rename to docs/architecture/decisions/0023-use-geventconnpool.md
From 8e700e0ecbb088e7ede30f66c8e56a140ec11507 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 16 Oct 2023 16:57:31 -0400
Subject: [PATCH 69/79] added debugging; removed duplicate get_object calls
from views
---
src/registrar/models/domain.py | 30 ++++++++++++++++++++++++++++++
src/registrar/views/domain.py | 15 +++++++++------
2 files changed, 39 insertions(+), 6 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 59edb707a..1980bd087 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1,5 +1,6 @@
from itertools import zip_longest
import logging
+import inspect
from datetime import date
from string import digits
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore
@@ -50,8 +51,33 @@ class Domain(TimeStampedModel, DomainHelper):
def __init__(self, *args, **kwargs):
self._cache = {}
+ #self.print_calling_function()
+ logger.info("__init__ being called on domain")
super(Domain, self).__init__(*args, **kwargs)
+ def print_calling_function(self):
+ # Get the current frame in the call stack
+ current_frame = inspect.currentframe()
+
+ i = 1
+ while True:
+ try:
+ # Get the calling frame
+ calling_frame = inspect.getouterframes(current_frame, 2)[i]
+
+ # Extract information about the calling function
+ calling_function_name = calling_frame.function
+ calling_module_name = calling_frame[0].f_globals['__name__']
+ calling_line_number = calling_frame[2]
+
+ # Print information about the calling function
+ print(f"Calling function: {calling_function_name} in module {calling_module_name} at line {calling_line_number}")
+
+ i+=1
+ except Exception as err:
+ print("========================================================")
+ break
+
class Status(models.TextChoices):
"""
The status codes we can receive from the registry.
@@ -144,10 +170,12 @@ class Domain(TimeStampedModel, DomainHelper):
def __get__(self, obj, objtype=None):
"""Called during get. Example: `r = domain.registrant`."""
+ logger.info("domain __get__ is called: %s", obj)
return super().__get__(obj, objtype)
def __set__(self, obj, value):
"""Called during set. Example: `domain.registrant = 'abc123'`."""
+ logger.info("domain __set__ is called: %s", obj)
super().__set__(obj, value)
# always invalidate cache after sending updates to the registry
obj._invalidate_cache()
@@ -1223,6 +1251,7 @@ class Domain(TimeStampedModel, DomainHelper):
raise NotImplementedError()
def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False):
+ logger.info("fetch_cache called")
"""Contact registry for info about a domain."""
try:
# get info from registry
@@ -1354,6 +1383,7 @@ class Domain(TimeStampedModel, DomainHelper):
def _invalidate_cache(self):
"""Remove cache data when updates are made."""
+ logger.debug("_invalidate_cache called")
self._cache = {}
def _get_property(self, property):
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index d8c3c80fa..8838407f4 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -54,7 +54,7 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
def get_form_kwargs(self, *args, **kwargs):
"""Add domain_info.organization_name instance to make a bound form."""
form_kwargs = super().get_form_kwargs(*args, **kwargs)
- form_kwargs["instance"] = self.get_object().domain_info
+ form_kwargs["instance"] = self.object.domain_info
return form_kwargs
def get_success_url(self):
@@ -97,7 +97,7 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
def get_form_kwargs(self, *args, **kwargs):
"""Add domain_info.authorizing_official instance to make a bound form."""
form_kwargs = super().get_form_kwargs(*args, **kwargs)
- form_kwargs["instance"] = self.get_object().domain_info.authorizing_official
+ form_kwargs["instance"] = self.object.domain_info.authorizing_official
return form_kwargs
def get_success_url(self):
@@ -137,8 +137,11 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
def get_initial(self):
"""The initial value for the form (which is a formset here)."""
- domain = self.get_object()
+ logger.info("DomainNameserversView.get_initial()")
+ domain = self.object
+ logger.info("DomainNameserversView.get_initial:: after get_object")
nameservers = domain.nameservers
+ logger.info("DomainNameserversView.get_initial:: after set nameservers")
initial_data = []
if nameservers is not None:
@@ -196,7 +199,7 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
except KeyError:
# no server information in this field, skip it
pass
- domain = self.get_object()
+ domain = self.object
domain.nameservers = nameservers
messages.success(
@@ -257,7 +260,7 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
def get_initial(self):
"""The initial value for the form."""
- domain = self.get_object()
+ domain = self.object
initial = super().get_initial()
security_contact = domain.security_contact
if security_contact is None or security_contact.email == "dotgov@cisa.dhs.gov":
@@ -286,7 +289,7 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
# Set the security email from the form
new_email = form.cleaned_data.get("security_email", "")
- domain = self.get_object()
+ domain = self.object
contact = domain.security_contact
contact.email = new_email
contact.save()
From 2d4ab0c7bd24571ff4ecefe36eb8ae671b33e5bd Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 17 Oct 2023 07:57:16 -0400
Subject: [PATCH 70/79] added DomainFormBaseView and DomainBaseView and session
cache for caching domains
---
src/registrar/config/settings.py | 8 ++
src/registrar/models/domain.py | 4 +-
src/registrar/views/domain.py | 151 ++++++++++++++++---------------
3 files changed, 88 insertions(+), 75 deletions(-)
diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py
index ceb215a4d..8de4c6caa 100644
--- a/src/registrar/config/settings.py
+++ b/src/registrar/config/settings.py
@@ -169,6 +169,11 @@ WSGI_APPLICATION = "registrar.config.wsgi.application"
# "BACKEND": "django.core.cache.backends.db.DatabaseCache",
# }
# }
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ }
+}
# Absolute path to the directory where `collectstatic`
# will place static files for deployment.
@@ -652,6 +657,9 @@ SESSION_COOKIE_SAMESITE = "Lax"
# instruct browser to only send cookie via HTTPS
SESSION_COOKIE_SECURE = True
+# session engine to cache session information
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
+
# ~ Set by django.middleware.clickjacking.XFrameOptionsMiddleware
# prevent clickjacking by instructing the browser not to load
# our site within an iframe
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index ae9d80c25..822451a49 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -61,7 +61,7 @@ class Domain(TimeStampedModel, DomainHelper):
def __init__(self, *args, **kwargs):
self._cache = {}
- #self.print_calling_function()
+ self.print_calling_function()
logger.info("__init__ being called on domain")
super(Domain, self).__init__(*args, **kwargs)
@@ -180,7 +180,7 @@ class Domain(TimeStampedModel, DomainHelper):
def __get__(self, obj, objtype=None):
"""Called during get. Example: `r = domain.registrant`."""
- logger.info("domain __get__ is called: %s", obj)
+ logger.info("domain __get__ is called: %s: %s", obj, objtype)
return super().__get__(obj, objtype)
def __set__(self, obj, value):
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index bc1f42b88..13ca3774a 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -36,8 +36,76 @@ from .utility import DomainPermissionView, DomainInvitationPermissionDeleteView
logger = logging.getLogger(__name__)
+class DomainBaseView(DomainPermissionView):
-class DomainView(DomainPermissionView):
+ def get(self, request, *args, **kwargs):
+ logger.info("DomainBaseView::get")
+ self._get_domain(request)
+ # pk = self.kwargs.get('pk')
+ # cached_domain = request.session.get(pk)
+
+ # if cached_domain:
+ # logger.info("reading object from session cache")
+ # self.object = cached_domain
+ # else:
+ # logger.info("reading object from db")
+ # self.object = self.get_object()
+ # logger.info("writing object to session cache")
+ # request.session[pk] = self.object
+ context = self.get_context_data(object=self.object)
+ return self.render_to_response(context)
+
+ def _get_domain(self, request):
+ # get domain from session cache or from db
+ # and set to self.object
+ # set session to self for downstream functions to
+ # update session cache
+ self.session = request.session
+ pk = self.kwargs.get('pk')
+ cached_domain = self.session.get(pk)
+
+ if cached_domain:
+ logger.info("reading object from session cache")
+ self.object = cached_domain
+ else:
+ logger.info("reading object from db")
+ self.object = self.get_object()
+ self._update_session_with_domain()
+
+ def _update_session_with_domain(self):
+ pk = self.kwargs.get('pk')
+ logger.info("writing object to session cache")
+ self.session[pk] = self.object
+
+
+class DomainFormBaseView(DomainBaseView, FormMixin):
+
+ def post(self, request, *args, **kwargs):
+ """Form submission posts to this view.
+
+ This post method harmonizes using DetailView and FormMixin together.
+ """
+ self._get_domain(request)
+ form = self.get_form()
+ if form.is_valid():
+ return self.form_valid(form)
+ else:
+ return self.form_invalid(form)
+
+ def form_valid(self, form):
+ self._update_session_with_domain()
+
+ # superclass has the redirect
+ return super().form_valid(form)
+
+ def form_invalid(self, form):
+ self._update_session_with_domain()
+
+ # superclass has the redirect
+ return super().form_invalid(form)
+
+
+class DomainView(DomainBaseView):
"""Domain detail overview page."""
@@ -46,10 +114,10 @@ class DomainView(DomainPermissionView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- default_email = Domain().get_default_security_contact().email
+ default_email = self.object.get_default_security_contact().email
context["default_security_email"] = default_email
- security_email = self.get_object().get_security_email()
+ security_email = self.object.get_security_email()
if security_email is None or security_email == default_email:
context["security_email"] = None
return context
@@ -57,7 +125,7 @@ class DomainView(DomainPermissionView):
return context
-class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
+class DomainOrgNameAddressView(DomainFormBaseView):
"""Organization name and mailing address view"""
model = Domain
@@ -75,18 +143,6 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
"""Redirect to the overview page for the domain."""
return reverse("domain-org-name-address", kwargs={"pk": self.object.pk})
- def post(self, request, *args, **kwargs):
- """Form submission posts to this view.
-
- This post method harmonizes using DetailView and FormMixin together.
- """
- self.object = self.get_object()
- form = self.get_form()
- if form.is_valid():
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
-
def form_valid(self, form):
"""The form is valid, save the organization name and mailing address."""
form.save()
@@ -99,7 +155,7 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
return super().form_valid(form)
-class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
+class DomainAuthorizingOfficialView(DomainFormBaseView):
"""Domain authorizing official editing view."""
@@ -118,18 +174,6 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
"""Redirect to the overview page for the domain."""
return reverse("domain-authorizing-official", kwargs={"pk": self.object.pk})
- def post(self, request, *args, **kwargs):
- """Form submission posts to this view.
-
- This post method harmonizes using DetailView and FormMixin together.
- """
- self.object = self.get_object()
- form = self.get_form()
- if form.is_valid():
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
-
def form_valid(self, form):
"""The form is valid, save the authorizing official."""
form.save()
@@ -142,7 +186,7 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
return super().form_valid(form)
-class DomainNameserversView(DomainPermissionView, FormMixin):
+class DomainNameserversView(DomainFormBaseView):
"""Domain nameserver editing view."""
@@ -191,16 +235,6 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
form.fields["server"].required = False
return formset
- def post(self, request, *args, **kwargs):
- """Formset submission posts to this view."""
- self.object = self.get_object()
- formset = self.get_form()
-
- if formset.is_valid():
- return self.form_valid(formset)
- else:
- return self.form_invalid(formset)
-
def form_valid(self, formset):
"""The formset is valid, perform something with it."""
@@ -224,7 +258,7 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
return super().form_valid(formset)
-class DomainYourContactInformationView(DomainPermissionView, FormMixin):
+class DomainYourContactInformationView(DomainFormBaseView):
"""Domain your contact information editing view."""
@@ -241,16 +275,6 @@ class DomainYourContactInformationView(DomainPermissionView, FormMixin):
"""Redirect to the your contact information for the domain."""
return reverse("domain-your-contact-information", kwargs={"pk": self.object.pk})
- def post(self, request, *args, **kwargs):
- """Form submission posts to this view."""
- self.object = self.get_object()
- form = self.get_form()
- if form.is_valid():
- # there is a valid email address in the form
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
-
def form_valid(self, form):
"""The form is valid, call setter in model."""
@@ -265,7 +289,7 @@ class DomainYourContactInformationView(DomainPermissionView, FormMixin):
return super().form_valid(form)
-class DomainSecurityEmailView(DomainPermissionView, FormMixin):
+class DomainSecurityEmailView(DomainFormBaseView):
"""Domain security email editing view."""
@@ -287,16 +311,6 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
"""Redirect to the security email page for the domain."""
return reverse("domain-security-email", kwargs={"pk": self.object.pk})
- def post(self, request, *args, **kwargs):
- """Form submission posts to this view."""
- self.object = self.get_object()
- form = self.get_form()
- if form.is_valid():
- # there is a valid email address in the form
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
-
def form_valid(self, form):
"""The form is valid, call setter in model."""
@@ -327,14 +341,14 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
return redirect(self.get_success_url())
-class DomainUsersView(DomainPermissionView):
+class DomainUsersView(DomainBaseView):
"""User management page in the domain details."""
template_name = "domain_users.html"
-class DomainAddUserView(DomainPermissionView, FormMixin):
+class DomainAddUserView(DomainFormBaseView):
"""Inside of a domain's user management, a form for adding users.
@@ -348,15 +362,6 @@ class DomainAddUserView(DomainPermissionView, FormMixin):
def get_success_url(self):
return reverse("domain-users", kwargs={"pk": self.object.pk})
- def post(self, request, *args, **kwargs):
- self.object = self.get_object()
- form = self.get_form()
- if form.is_valid():
- # there is a valid email address in the form
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
-
def _domain_abs_url(self):
"""Get an absolute URL for this domain."""
return self.request.build_absolute_uri(
From 5b4103e1eed42c5662ee78018af6d911a9b71318 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 17 Oct 2023 08:43:48 -0400
Subject: [PATCH 71/79] more debugging of domain model
---
src/registrar/models/domain.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 822451a49..05cae5add 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1736,6 +1736,7 @@ class Domain(TimeStampedModel, DomainHelper):
)
if property in self._cache:
+ logger.info("writing %s to cache", property)
return self._cache[property]
else:
raise KeyError(
From bcbf0699241c95d9634d5a8c3959b1cf1b1f4692 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 17 Oct 2023 12:24:52 -0400
Subject: [PATCH 72/79] added more logging; set hosts properly in cache when no
hosts exist
---
src/registrar/models/domain.py | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 05cae5add..8b8c8b7ce 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1450,8 +1450,10 @@ class Domain(TimeStampedModel, DomainHelper):
def _fetch_hosts(self, host_data):
"""Fetch host info."""
+ logger.info("calling _fetch_hosts on %s hosts", len(host_data))
hosts = []
for name in host_data:
+ logger.info("calling InfoHost on %s", name)
req = commands.InfoHost(name=name)
data = registry.send(req, cleaned=True).res_data[0]
host = {
@@ -1463,6 +1465,7 @@ class Domain(TimeStampedModel, DomainHelper):
"up_date": getattr(data, "up_date", ...),
}
hosts.append({k: v for k, v in host.items() if v is not ...})
+ logger.info("successfully called InfoHost on host_data, and have %s hosts to set to cache", len(hosts))
return hosts
def _convert_ips(self, ip_list: list[str]):
@@ -1593,6 +1596,8 @@ class Domain(TimeStampedModel, DomainHelper):
def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False):
logger.info("fetch_cache called")
+ logger.info("fetch_hosts = %s", fetch_hosts)
+ logger.info("fetch_contacts = %s", fetch_contacts)
"""Contact registry for info about a domain."""
try:
# get info from registry
@@ -1629,6 +1634,7 @@ class Domain(TimeStampedModel, DomainHelper):
cleaned["dnssecdata"] = extension
# Capture and store old hosts and contacts from cache if they exist
old_cache_hosts = self._cache.get("hosts")
+ logger.info("old_cache_hosts is %s", old_cache_hosts)
old_cache_contacts = self._cache.get("contacts")
# get contact info, if there are any
@@ -1643,22 +1649,30 @@ class Domain(TimeStampedModel, DomainHelper):
# hosts that existed in cache (if they existed)
# and pass them along.
if old_cache_hosts is not None:
+ logger.debug("resetting cleaned['hosts'] to old_cache_hosts")
cleaned["hosts"] = old_cache_hosts
# get nameserver info, if there are any
if (
fetch_hosts
- and "_hosts" in cleaned
- and isinstance(cleaned["_hosts"], list)
- and len(cleaned["_hosts"])
):
- cleaned["hosts"] = self._fetch_hosts(cleaned["_hosts"])
+ if (
+ "_hosts" in cleaned
+ and isinstance(cleaned["_hosts"], list)
+ and len(cleaned["_hosts"])
+ ):
+ cleaned["hosts"] = self._fetch_hosts(cleaned["_hosts"])
+ else:
+ cleaned["hosts"] = []
# We're only getting hosts, so retain the old
# contacts that existed in cache (if they existed)
# and pass them along.
+ logger.info("set cleaned['hosts'] to %s", cleaned["hosts"])
if old_cache_contacts is not None:
+ logger.info("resetting cleaned['contacts'] to old_cache_contacts")
cleaned["contacts"] = old_cache_contacts
# replace the prior cache with new data
+ logger.info("replacing the prior cache with new data")
self._cache = cleaned
except RegistryError as e:
@@ -1729,6 +1743,7 @@ class Domain(TimeStampedModel, DomainHelper):
def _get_property(self, property):
"""Get some piece of info about a domain."""
+ logger.info("__get_property(%s)", property)
if property not in self._cache:
self._fetch_cache(
fetch_hosts=(property == "hosts"),
From 181cfccfc5463ef1984563b4d4211107d13b3a32 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 17 Oct 2023 13:20:48 -0400
Subject: [PATCH 73/79] added comments, removed empty lines, and renamed
methods for code legibility
---
src/registrar/assets/js/get-gov.js | 8 ++++++++
src/registrar/config/urls.py | 4 ++--
src/registrar/forms/domain.py | 8 --------
src/registrar/models/domain.py | 12 +++++++++++-
src/registrar/views/__init__.py | 4 ++--
src/registrar/views/domain.py | 22 +++++++---------------
6 files changed, 30 insertions(+), 28 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index f5dffba12..c21060382 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -315,9 +315,17 @@ function prepareDeleteButtons() {
// Attach click event listener on the delete buttons of the existing forms
prepareDeleteButtons();
+ // Attack click event listener on the add button
if (addButton)
addButton.addEventListener('click', addForm);
+ /*
+ * Add a formset to the end of the form.
+ * For each element in the added formset, name the elements with the prefix,
+ * form-{#}-{element_name} where # is the index of the formset and element_name
+ * is the element's name.
+ * Additionally, update the form element's metadata, including totalForms' value.
+ */
function addForm(e){
let forms = document.querySelectorAll(".ds-record");
let formNum = forms.length;
diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py
index 7caa64e5c..bd2215620 100644
--- a/src/registrar/config/urls.py
+++ b/src/registrar/config/urls.py
@@ -97,12 +97,12 @@ urlpatterns = [
),
path(
"domain//dns/dnssec/dsdata",
- views.DomainDsdataView.as_view(),
+ views.DomainDsDataView.as_view(),
name="domain-dns-dnssec-dsdata",
),
path(
"domain//dns/dnssec/keydata",
- views.DomainKeydataView.as_view(),
+ views.DomainKeyDataView.as_view(),
name="domain-dns-dnssec-keydata",
),
path(
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 3878c1f43..8abc7e14a 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -16,14 +16,12 @@ from .common import (
class DomainAddUserForm(forms.Form):
-
"""Form for adding a user to a domain."""
email = forms.EmailField(label="Email")
class DomainNameserverForm(forms.Form):
-
"""Form for changing nameservers."""
server = forms.CharField(label="Name server", strip=True)
@@ -37,7 +35,6 @@ NameserverFormset = formset_factory(
class ContactForm(forms.ModelForm):
-
"""Form for updating contacts."""
class Meta:
@@ -68,14 +65,12 @@ 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)
class DomainOrgNameAddressForm(forms.ModelForm):
-
"""Form for updating the organization name and mailing address."""
zipcode = forms.CharField(
@@ -149,12 +144,10 @@ class DomainOrgNameAddressForm(forms.ModelForm):
class DomainDnssecForm(forms.Form):
-
"""Form for enabling and disabling dnssec"""
class DomainDsdataForm(forms.Form):
-
"""Form for adding or editing DNSSEC DS Data to a domain."""
key_tag = forms.IntegerField(
@@ -198,7 +191,6 @@ DomainDsdataFormset = formset_factory(
class DomainKeydataForm(forms.Form):
-
"""Form for adding or editing DNSSEC Key Data to a domain."""
flag = forms.TypedChoiceField(
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index c5ba4356b..0b107907e 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -458,11 +458,21 @@ class Domain(TimeStampedModel, DomainHelper):
@Cache
def dnssecdata(self) -> Optional[extensions.DNSSECExtension]:
+ """
+ Get a complete list of dnssecdata extensions for this domain.
+
+ dnssecdata are provided as a list of DNSSECExtension objects.
+
+ A DNSSECExtension object includes:
+ maxSigLife: Optional[int]
+ dsData: Optional[Sequence[DSData]]
+ keyData: Optional[Sequence[DNSSECKeyData]]
+
+ """
try:
return self._get_property("dnssecdata")
except Exception as err:
# Don't throw error as this is normal for a new domain
- # TODO - 433 error handling ticket should address this
logger.info("Domain does not have dnssec data defined %s" % err)
return None
diff --git a/src/registrar/views/__init__.py b/src/registrar/views/__init__.py
index 5bca4e1d5..5fd81df8c 100644
--- a/src/registrar/views/__init__.py
+++ b/src/registrar/views/__init__.py
@@ -6,8 +6,8 @@ from .domain import (
DomainDNSView,
DomainNameserversView,
DomainDNSSECView,
- DomainDsdataView,
- DomainKeydataView,
+ DomainDsDataView,
+ DomainKeyDataView,
DomainYourContactInformationView,
DomainSecurityEmailView,
DomainUsersView,
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 68d193e86..36b7a9445 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -51,7 +51,6 @@ logger = logging.getLogger(__name__)
class DomainView(DomainPermissionView):
-
"""Domain detail overview page."""
template_name = "domain_detail.html"
@@ -113,7 +112,6 @@ class DomainOrgNameAddressView(DomainPermissionView, FormMixin):
class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
-
"""Domain authorizing official editing view."""
model = Domain
@@ -156,14 +154,12 @@ class DomainAuthorizingOfficialView(DomainPermissionView, FormMixin):
class DomainDNSView(DomainPermissionView):
-
"""DNS Information View."""
template_name = "domain_dns.html"
class DomainNameserversView(DomainPermissionView, FormMixin):
-
"""Domain nameserver editing view."""
template_name = "domain_nameservers.html"
@@ -242,15 +238,15 @@ class DomainNameserversView(DomainPermissionView, FormMixin):
class DomainDNSSECView(DomainPermissionView, FormMixin):
-
"""Domain DNSSEC editing view."""
template_name = "domain_dnssec.html"
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)."""
+ context = super().get_context_data(**kwargs)
+
self.domain = self.get_object()
has_dnssec_records = self.domain.dnssecdata is not None
@@ -294,8 +290,7 @@ class DomainDNSSECView(DomainPermissionView, FormMixin):
return self.form_valid(form)
-class DomainDsdataView(DomainPermissionView, FormMixin):
-
+class DomainDsDataView(DomainPermissionView, FormMixin):
"""Domain DNSSEC ds data editing view."""
template_name = "domain_dsdata.html"
@@ -395,7 +390,9 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
dnssecdata.dsData = []
dnssecdata.dsData.append(common.DSData(**dsrecord))
except KeyError:
- # no server information in this field, skip it
+ # no cleaned_data provided for this form, but passed
+ # as valid; this can happen if form has been added but
+ # not been interacted with; in that case, want to ignore
pass
domain = self.get_object()
try:
@@ -414,8 +411,7 @@ class DomainDsdataView(DomainPermissionView, FormMixin):
return super().form_valid(formset)
-class DomainKeydataView(DomainPermissionView, FormMixin):
-
+class DomainKeyDataView(DomainPermissionView, FormMixin):
"""Domain DNSSEC key data editing view."""
template_name = "domain_keydata.html"
@@ -536,7 +532,6 @@ class DomainKeydataView(DomainPermissionView, FormMixin):
class DomainYourContactInformationView(DomainPermissionView, FormMixin):
-
"""Domain your contact information editing view."""
template_name = "domain_your_contact_information.html"
@@ -577,7 +572,6 @@ class DomainYourContactInformationView(DomainPermissionView, FormMixin):
class DomainSecurityEmailView(DomainPermissionView, FormMixin):
-
"""Domain security email editing view."""
template_name = "domain_security_email.html"
@@ -639,14 +633,12 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
class DomainUsersView(DomainPermissionView):
-
"""User management page in the domain details."""
template_name = "domain_users.html"
class DomainAddUserView(DomainPermissionView, FormMixin):
-
"""Inside of a domain's user management, a form for adding users.
Multiple inheritance is used here for permissions, form handling, and
From 0e5978011dec06809c982396fd981666227da2c8 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Tue, 17 Oct 2023 14:23:42 -0400
Subject: [PATCH 74/79] Error handling on the post for security contact
---
src/epplibwrapper/__init__.py | 4 ++-
src/epplibwrapper/errors.py | 3 ++
src/registrar/models/domain.py | 2 +-
src/registrar/tests/common.py | 16 +++++++++
src/registrar/tests/test_views.py | 60 +++++++++++++++++++++++++++++++
src/registrar/views/domain.py | 30 +++++++++++++---
6 files changed, 108 insertions(+), 7 deletions(-)
diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py
index dd6664a3a..d0138d73c 100644
--- a/src/epplibwrapper/__init__.py
+++ b/src/epplibwrapper/__init__.py
@@ -45,7 +45,7 @@ except NameError:
# Attn: these imports should NOT be at the top of the file
try:
from .client import CLIENT, commands
- from .errors import RegistryError, ErrorCode
+ from .errors import RegistryError, ErrorCode, CANNOT_CONTACT_REGISTRY, GENERIC_ERROR
from epplib.models import common, info
from epplib.responses import extensions
from epplib import responses
@@ -61,4 +61,6 @@ __all__ = [
"info",
"ErrorCode",
"RegistryError",
+ "CANNOT_CONTACT_REGISTRY",
+ "GENERIC_ERROR",
]
diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py
index d34ed5e91..dba5f328c 100644
--- a/src/epplibwrapper/errors.py
+++ b/src/epplibwrapper/errors.py
@@ -1,5 +1,8 @@
from enum import IntEnum
+CANNOT_CONTACT_REGISTRY = "Update failed. Cannot contact the registry."
+GENERIC_ERROR = "Value entered was wrong."
+
class ErrorCode(IntEnum):
"""
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index d6dd5e287..fa3ff443c 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -701,7 +701,7 @@ class Domain(TimeStampedModel, DomainHelper):
and errorCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
):
# TODO- ticket #433 look here for error handling
- raise Exception("Unable to add contact to registry")
+ raise RegistryError(code=errorCode)
# contact doesn't exist on the domain yet
logger.info("_set_singleton_contact()-> contact has been added to the registry")
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index b8fea7f93..803c2f069 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -32,6 +32,8 @@ from epplibwrapper import (
ErrorCode,
)
+from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
+
logger = logging.getLogger(__name__)
@@ -794,6 +796,20 @@ class MockEppLib(TestCase):
# use this for when a contact is being updated
# sets the second send() to fail
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
+ elif (
+ isinstance(_request, commands.CreateContact)
+ and getattr(_request, "email", None) == "test@failCreate.gov"
+ ):
+ # use this for when a contact is being updated
+ # mocks a registry error on creation
+ raise RegistryError(code=None)
+ elif (
+ isinstance(_request, commands.CreateContact)
+ and getattr(_request, "email", None) == "test@contactError.gov"
+ ):
+ # use this for when a contact is being updated
+ # mocks a registry error on creation
+ raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE)
elif isinstance(_request, commands.CreateHost):
return MagicMock(
res_data=[self.mockDataHostChange],
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index 2194b42db..bda23546b 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1490,6 +1490,66 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest, MockEppLib):
success_page, "The security email for this domain has been updated"
)
+ def test_security_email_form_messages(self):
+ """
+ Test against the success and error messages that are defined in the view
+ """
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+
+ form_data_registry_error = {
+ "security_email": "test@failCreate.gov",
+ }
+
+ form_data_contact_error = {
+ "security_email": "test@contactError.gov",
+ }
+
+ form_data_success = {
+ "security_email": "test@something.gov",
+ }
+
+ test_cases = [
+ (
+ "RegistryError",
+ form_data_registry_error,
+ "Update failed. Cannot contact the registry.",
+ ),
+ ("ContactError", form_data_contact_error, "Value entered was wrong."),
+ (
+ "RegistrySuccess",
+ form_data_success,
+ "The security email for this domain has been updated.",
+ ),
+ # Add more test cases with different scenarios here
+ ]
+
+ for test_name, data, expected_message in test_cases:
+ response = self.client.post(
+ reverse("domain-security-email", kwargs={"pk": self.domain.id}),
+ data=data,
+ follow=True,
+ )
+
+ # Check the response status code, content, or any other relevant assertions
+ self.assertEqual(response.status_code, 200)
+
+ # Check if the expected message tag is set
+ if test_name == "RegistryError" or test_name == "ContactError":
+ message_tag = "error"
+ elif test_name == "RegistrySuccess":
+ message_tag = "success"
+ else:
+ # Handle other cases if needed
+ message_tag = "info" # Change to the appropriate default
+
+ # Check the message tag
+ messages = list(response.context["messages"])
+ self.assertEqual(len(messages), 1)
+ message = messages[0]
+ self.assertEqual(message.tags, message_tag)
+ self.assertEqual(message.message, expected_message)
+
def test_domain_overview_blocked_for_ineligible_user(self):
"""We could easily duplicate this test for all domain management
views, but a single url test should be solid enough since all domain
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 4ea3d2fbc..e993a7c1a 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -22,6 +22,7 @@ from registrar.models import (
UserDomainRole,
)
from registrar.models.public_contact import PublicContact
+from registrar.models.utility.contact_error import ContactError
from ..forms import (
ContactForm,
@@ -30,6 +31,13 @@ from ..forms import (
DomainSecurityEmailForm,
NameserverFormset,
)
+
+from epplibwrapper import (
+ RegistryError,
+ CANNOT_CONTACT_REGISTRY,
+ GENERIC_ERROR,
+)
+
from ..utility.email import send_templated_email, EmailSendingError
from .utility import DomainPermissionView, DomainInvitationPermissionDeleteView
@@ -310,15 +318,27 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
# If no default is created for security_contact,
# then we cannot connect to the registry.
if contact is None:
- messages.error(self.request, "Update failed. Cannot contact the registry.")
+ messages.error(self.request, CANNOT_CONTACT_REGISTRY)
return redirect(self.get_success_url())
contact.email = new_email
- contact.save()
- messages.success(
- self.request, "The security email for this domain has been updated."
- )
+ try:
+ contact.save()
+ except RegistryError as Err:
+ if Err.is_connection_error():
+ messages.error(self.request, CANNOT_CONTACT_REGISTRY)
+ logger.error(f"Registry connection error: {Err}")
+ else:
+ messages.error(self.request, GENERIC_ERROR)
+ logger.error(f"Registry error: {Err}")
+ except ContactError as Err:
+ messages.error(self.request, GENERIC_ERROR)
+ logger.error(f"Generic registry error: {Err}")
+ else:
+ messages.success(
+ self.request, "The security email for this domain has been updated."
+ )
# superclass has the redirect
return redirect(self.get_success_url())
From 7ed916a86489c295b50e6f1cda06ac4e7bc7c6e1 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Tue, 17 Oct 2023 14:56:50 -0400
Subject: [PATCH 75/79] lint
---
src/registrar/tests/common.py | 42 +++++++++++++++----------------
src/registrar/tests/test_views.py | 1 +
2 files changed, 21 insertions(+), 22 deletions(-)
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
index f73082ab4..a9f38db03 100644
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -795,28 +795,8 @@ class MockEppLib(TestCase):
return self.mockInfoContactCommands(_request, cleaned)
elif isinstance(_request, commands.UpdateDomain):
return self.mockUpdateDomainCommands(_request, cleaned)
- elif (
- isinstance(_request, commands.CreateContact)
- and getattr(_request, "id", None) == "fail"
- and self.mockedSendFunction.call_count == 3
- ):
- # use this for when a contact is being updated
- # sets the second send() to fail
- raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
- elif (
- isinstance(_request, commands.CreateContact)
- and getattr(_request, "email", None) == "test@failCreate.gov"
- ):
- # use this for when a contact is being updated
- # mocks a registry error on creation
- raise RegistryError(code=None)
- elif (
- isinstance(_request, commands.CreateContact)
- and getattr(_request, "email", None) == "test@contactError.gov"
- ):
- # use this for when a contact is being updated
- # mocks a registry error on creation
- raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE)
+ elif isinstance(_request, commands.CreateContact):
+ return self.mockCreateContactCommands(_request, cleaned)
elif isinstance(_request, commands.CreateHost):
return MagicMock(
res_data=[self.mockDataHostChange],
@@ -913,6 +893,24 @@ class MockEppLib(TestCase):
return MagicMock(res_data=[mocked_result])
+ def mockCreateContactCommands(self, _request, cleaned):
+ if (
+ getattr(_request, "id", None) == "fail"
+ and self.mockedSendFunction.call_count == 3
+ ):
+ # use this for when a contact is being updated
+ # sets the second send() to fail
+ raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
+ elif getattr(_request, "email", None) == "test@failCreate.gov":
+ # use this for when a contact is being updated
+ # mocks a registry error on creation
+ raise RegistryError(code=None)
+ elif getattr(_request, "email", None) == "test@contactError.gov":
+ # use this for when a contact is being updated
+ # mocks a contact error on creation
+ raise ContactError(code=ContactErrorCodes.CONTACT_TYPE_NONE)
+ return MagicMock(res_data=[self.mockDataInfoHosts])
+
def setUp(self):
"""mock epp send function as this will fail locally"""
self.mockSendPatch = patch("registrar.models.domain.registry.send")
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index ce901626b..0e8f895af 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1626,6 +1626,7 @@ class TestDomainSecurityEmail(TestDomainOverview):
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
self.assertEqual(response.status_code, 403)
+
class TestDomainDNSSEC(TestDomainOverview):
"""MockEPPLib is already inherited."""
From e248ae7760ca27adac0a8e735e70544df8a34021 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 17 Oct 2023 21:42:43 -0400
Subject: [PATCH 76/79] remoed CACHES from settings (unnecessary); refactored
_fetch_cache; added comments
---
src/registrar/config/settings.py | 5 -
src/registrar/models/domain.py | 154 +++++++++++--------------------
src/registrar/views/domain.py | 85 ++++++++---------
3 files changed, 99 insertions(+), 145 deletions(-)
diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py
index d5f2b53cf..7b96af5ee 100644
--- a/src/registrar/config/settings.py
+++ b/src/registrar/config/settings.py
@@ -169,11 +169,6 @@ WSGI_APPLICATION = "registrar.config.wsgi.application"
# "BACKEND": "django.core.cache.backends.db.DatabaseCache",
# }
# }
-CACHES = {
- "default": {
- "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
- }
-}
# Absolute path to the directory where `collectstatic`
# will place static files for deployment.
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 408721ae4..f32da2403 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1,6 +1,5 @@
from itertools import zip_longest
import logging
-import inspect
import ipaddress
import re
from datetime import date
@@ -62,35 +61,8 @@ class Domain(TimeStampedModel, DomainHelper):
def __init__(self, *args, **kwargs):
self._cache = {}
- self.print_calling_function()
- logger.info("__init__ being called on domain")
super(Domain, self).__init__(*args, **kwargs)
- def print_calling_function(self):
- # Get the current frame in the call stack
- current_frame = inspect.currentframe()
-
- i = 1
- while True:
- try:
- # Get the calling frame
- calling_frame = inspect.getouterframes(current_frame, 2)[i]
-
- # Extract information about the calling function
- calling_function_name = calling_frame.function
- calling_module_name = calling_frame[0].f_globals["__name__"]
- calling_line_number = calling_frame[2]
-
- # Print information about the calling function
- print(
- f"Calling function: {calling_function_name} in module {calling_module_name} at line {calling_line_number}"
- )
-
- i += 1
- except Exception as err:
- print("========================================================")
- break
-
class Status(models.TextChoices):
"""
The status codes we can receive from the registry.
@@ -183,12 +155,10 @@ class Domain(TimeStampedModel, DomainHelper):
def __get__(self, obj, objtype=None):
"""Called during get. Example: `r = domain.registrant`."""
- logger.info("domain __get__ is called: %s: %s", obj, objtype)
return super().__get__(obj, objtype)
def __set__(self, obj, value):
"""Called during set. Example: `domain.registrant = 'abc123'`."""
- logger.info("domain __set__ is called: %s", obj)
super().__set__(obj, value)
# always invalidate cache after sending updates to the registry
obj._invalidate_cache()
@@ -290,7 +260,6 @@ class Domain(TimeStampedModel, DomainHelper):
"""Creates the host object in the registry
doesn't add the created host to the domain
returns ErrorCode (int)"""
- logger.info("Creating host")
if addrs is not None:
addresses = [epp.Ip(addr=addr) for addr in addrs]
request = commands.CreateHost(name=host, addrs=addresses)
@@ -1275,7 +1244,6 @@ class Domain(TimeStampedModel, DomainHelper):
count = 0
while not exitEarly and count < 3:
try:
- logger.info("Getting domain info from epp")
req = commands.InfoDomain(name=self.name)
domainInfoResponse = registry.send(req, cleaned=True)
exitEarly = True
@@ -1569,10 +1537,8 @@ class Domain(TimeStampedModel, DomainHelper):
def _fetch_hosts(self, host_data):
"""Fetch host info."""
- logger.info("calling _fetch_hosts on %s hosts", len(host_data))
hosts = []
for name in host_data:
- logger.info("calling InfoHost on %s", name)
req = commands.InfoHost(name=name)
data = registry.send(req, cleaned=True).res_data[0]
host = {
@@ -1584,10 +1550,6 @@ class Domain(TimeStampedModel, DomainHelper):
"up_date": getattr(data, "up_date", ...),
}
hosts.append({k: v for k, v in host.items() if v is not ...})
- logger.info(
- "successfully called InfoHost on host_data, and have %s hosts to set to cache",
- len(hosts),
- )
return hosts
def _convert_ips(self, ip_list: list[str]):
@@ -1717,87 +1679,85 @@ class Domain(TimeStampedModel, DomainHelper):
)
def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False):
- logger.info("fetch_cache called")
- logger.info("fetch_hosts = %s", fetch_hosts)
- logger.info("fetch_contacts = %s", fetch_contacts)
"""Contact registry for info about a domain."""
try:
# get info from registry
- dataResponse = self._get_or_create_domain()
- data = dataResponse.res_data[0]
- # extract properties from response
- # (Ellipsis is used to mean "null")
- cache = {
- "auth_info": getattr(data, "auth_info", ...),
- "_contacts": getattr(data, "contacts", ...),
- "cr_date": getattr(data, "cr_date", ...),
- "ex_date": getattr(data, "ex_date", ...),
- "_hosts": getattr(data, "hosts", ...),
- "name": getattr(data, "name", ...),
- "registrant": getattr(data, "registrant", ...),
- "statuses": getattr(data, "statuses", ...),
- "tr_date": getattr(data, "tr_date", ...),
- "up_date": getattr(data, "up_date", ...),
- }
- # remove null properties (to distinguish between "a value of None" and null)
- cleaned = {k: v for k, v in cache.items() if v is not ...}
+ data_response = self._get_or_create_domain()
+ cache = self._extract_data_from_response(data_response)
+
+ # remove null properties (to distinguish between "a value of None" and null)
+ cleaned = self._remove_null_properties(cache)
- # statuses can just be a list no need to keep the epp object
if "statuses" in cleaned:
cleaned["statuses"] = [status.state for status in cleaned["statuses"]]
- # get extensions info, if there is any
- # DNSSECExtension is one possible extension, make sure to handle
- # only DNSSECExtension and not other type extensions
- returned_extensions = dataResponse.extensions
- cleaned["dnssecdata"] = None
- for extension in returned_extensions:
- if isinstance(extension, extensions.DNSSECExtension):
- cleaned["dnssecdata"] = extension
+ cleaned["dnssecdata"] = self._get_dnssec_data(data_response.extensions)
+
# Capture and store old hosts and contacts from cache if they exist
old_cache_hosts = self._cache.get("hosts")
- logger.info("old_cache_hosts is %s", old_cache_hosts)
old_cache_contacts = self._cache.get("contacts")
- # get contact info, if there are any
- if (
- fetch_contacts
- and "_contacts" in cleaned
- and isinstance(cleaned["_contacts"], list)
- and len(cleaned["_contacts"]) > 0
- ):
- cleaned["contacts"] = self._fetch_contacts(cleaned["_contacts"])
- # We're only getting contacts, so retain the old
- # hosts that existed in cache (if they existed)
- # and pass them along.
+ if fetch_contacts:
+ cleaned["contacts"] = self._get_contacts(
+ cleaned.get("_contacts", [])
+ )
if old_cache_hosts is not None:
logger.debug("resetting cleaned['hosts'] to old_cache_hosts")
cleaned["hosts"] = old_cache_hosts
- # get nameserver info, if there are any
if fetch_hosts:
- if (
- "_hosts" in cleaned
- and isinstance(cleaned["_hosts"], list)
- and len(cleaned["_hosts"])
- ):
- cleaned["hosts"] = self._fetch_hosts(cleaned["_hosts"])
- else:
- cleaned["hosts"] = []
- # We're only getting hosts, so retain the old
- # contacts that existed in cache (if they existed)
- # and pass them along.
- logger.info("set cleaned['hosts'] to %s", cleaned["hosts"])
+ cleaned["hosts"] = self._get_hosts(
+ cleaned.get("_hosts", [])
+ )
if old_cache_contacts is not None:
- logger.info("resetting cleaned['contacts'] to old_cache_contacts")
cleaned["contacts"] = old_cache_contacts
- # replace the prior cache with new data
- logger.info("replacing the prior cache with new data")
+
self._cache = cleaned
except RegistryError as e:
logger.error(e)
+ def _extract_data_from_response(self, data_response):
+ data = data_response.res_data[0]
+ cache = {
+ "auth_info": getattr(data, "auth_info", ...),
+ "_contacts": getattr(data, "contacts", ...),
+ "cr_date": getattr(data, "cr_date", ...),
+ "ex_date": getattr(data, "ex_date", ...),
+ "_hosts": getattr(data, "hosts", ...),
+ "name": getattr(data, "name", ...),
+ "registrant": getattr(data, "registrant", ...),
+ "statuses": getattr(data, "statuses", ...),
+ "tr_date": getattr(data, "tr_date", ...),
+ "up_date": getattr(data, "up_date", ...),
+ }
+ return {k: v for k, v in cache.items() if v is not ...}
+
+ def _remove_null_properties(self, cache):
+ return {k: v for k, v in cache.items() if v is not ...}
+
+ def _get_dnssec_data(self, response_extensions):
+ # get extensions info, if there is any
+ # DNSSECExtension is one possible extension, make sure to handle
+ # only DNSSECExtension and not other type extensions
+ dnssec_data = None
+ for extension in response_extensions:
+ if isinstance(extension, extensions.DNSSECExtension):
+ dnssec_data = extension
+ return dnssec_data
+
+ def _get_contacts(self, contacts):
+ cleaned_contacts = {}
+ if contacts and isinstance(contacts, list) and len(contacts) > 0:
+ cleaned_contacts = self._fetch_contacts(contacts)
+ return cleaned_contacts
+
+ def _get_hosts(self, hosts):
+ cleaned_hosts = []
+ if hosts and isinstance(hosts, list):
+ cleaned_hosts = self._fetch_hosts(hosts)
+ return cleaned_hosts
+
def _get_or_create_public_contact(self, public_contact: PublicContact):
"""Tries to find a PublicContact object in our DB.
If it can't, it'll create it. Returns PublicContact"""
@@ -1863,7 +1823,6 @@ class Domain(TimeStampedModel, DomainHelper):
def _get_property(self, property):
"""Get some piece of info about a domain."""
- logger.info("__get_property(%s)", property)
if property not in self._cache:
self._fetch_cache(
fetch_hosts=(property == "hosts"),
@@ -1871,7 +1830,6 @@ class Domain(TimeStampedModel, DomainHelper):
)
if property in self._cache:
- logger.info("writing %s to cache", property)
return self._cache[property]
else:
raise KeyError(
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 5e6894bb2..fbe6a3c20 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -51,40 +51,55 @@ logger = logging.getLogger(__name__)
class DomainBaseView(DomainPermissionView):
+ """
+ Base View for the Domain. Handles getting and setting the domain
+ in session cache on GETs. Also provides methods for getting
+ and setting the domain in cache
+ """
+
def get(self, request, *args, **kwargs):
- logger.info("DomainBaseView::get")
self._get_domain(request)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def _get_domain(self, request):
- # get domain from session cache or from db
- # and set to self.object
- # set session to self for downstream functions to
- # update session cache
+ """
+ get domain from session cache or from db and set
+ to self.object
+ set session to self for downstream functions to
+ update session cache
+ """
self.session = request.session
- pk = self.kwargs.get("pk")
- cached_domain = self.session.get(pk)
+ # domain:private_key is the session key to use for
+ # caching the domain in the session
+ domain_pk = "domain:" + str(self.kwargs.get("pk"))
+ cached_domain = self.session.get(domain_pk)
if cached_domain:
- logger.info("reading object from session cache")
self.object = cached_domain
else:
- logger.info("reading object from db")
self.object = self.get_object()
self._update_session_with_domain()
def _update_session_with_domain(self):
- pk = self.kwargs.get("pk")
- logger.info("writing object to session cache")
- self.session[pk] = self.object
+ """
+ update domain in the session cache
+ """
+ domain_pk = "domain:" + str(self.kwargs.get("pk"))
+ self.session[domain_pk] = self.object
class DomainFormBaseView(DomainBaseView, FormMixin):
+ """
+ Form Base View for the Domain. Handles getting and setting
+ domain in cache when dealing with domain forms. Provides
+ implementations of post, form_valid and form_invalid.
+ """
+
def post(self, request, *args, **kwargs):
"""Form submission posts to this view.
- This post method harmonizes using DetailView and FormMixin together.
+ This post method harmonizes using DomainBaseView and FormMixin
"""
self._get_domain(request)
form = self.get_form()
@@ -94,12 +109,14 @@ class DomainFormBaseView(DomainBaseView, FormMixin):
return self.form_invalid(form)
def form_valid(self, form):
+ # updates session cache with domain
self._update_session_with_domain()
# superclass has the redirect
return super().form_valid(form)
def form_invalid(self, form):
+ # updates session cache with domain
self._update_session_with_domain()
# superclass has the redirect
@@ -200,11 +217,7 @@ class DomainNameserversView(DomainFormBaseView):
def get_initial(self):
"""The initial value for the form (which is a formset here)."""
- logger.info("DomainNameserversView.get_initial()")
- domain = self.object
- logger.info("DomainNameserversView.get_initial:: after get_object")
- nameservers = domain.nameservers
- logger.info("DomainNameserversView.get_initial:: after set nameservers")
+ nameservers = self.object.nameservers
initial_data = []
if nameservers is not None:
@@ -252,8 +265,7 @@ class DomainNameserversView(DomainFormBaseView):
except KeyError:
# no server information in this field, skip it
pass
- domain = self.object
- domain.nameservers = nameservers
+ self.object.nameservers = nameservers
messages.success(
self.request, "The name servers for this domain have been updated."
@@ -273,9 +285,7 @@ class DomainDNSSECView(DomainFormBaseView):
"""The initial value for the form (which is a formset here)."""
context = super().get_context_data(**kwargs)
- self.domain = self.object
-
- has_dnssec_records = self.domain.dnssecdata is not None
+ has_dnssec_records = self.object.dnssecdata is not None
# Create HTML for the modal button
modal_button = (
@@ -292,17 +302,16 @@ class DomainDNSSECView(DomainFormBaseView):
def get_success_url(self):
"""Redirect to the DNSSEC page for the domain."""
- return reverse("domain-dns-dnssec", kwargs={"pk": self.domain.pk})
+ return reverse("domain-dns-dnssec", kwargs={"pk": self.object.pk})
def post(self, request, *args, **kwargs):
"""Form submission posts to this view."""
self._get_domain(request)
- self.domain = self.object
form = self.get_form()
if form.is_valid():
if "disable_dnssec" in request.POST:
try:
- self.domain.dnssecdata = {}
+ self.object.dnssecdata = {}
except RegistryError as err:
errmsg = "Error removing existing DNSSEC record(s)."
logger.error(errmsg + ": " + err)
@@ -326,8 +335,7 @@ class DomainDsDataView(DomainFormBaseView):
def get_initial(self):
"""The initial value for the form (which is a formset here)."""
- domain = self.object
- dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
+ dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
initial_data = []
if dnssecdata is not None:
@@ -368,8 +376,7 @@ class DomainDsDataView(DomainFormBaseView):
# 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.object
- dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
+ dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
if dnssecdata is not None and dnssecdata.dsData is not None:
self.request.session["dnssec_ds_confirmed"] = True
@@ -421,9 +428,8 @@ class DomainDsDataView(DomainFormBaseView):
# as valid; this can happen if form has been added but
# not been interacted with; in that case, want to ignore
pass
- domain = self.object
try:
- domain.dnssecdata = dnssecdata
+ self.object.dnssecdata = dnssecdata
except RegistryError as err:
errmsg = "Error updating DNSSEC data in the registry."
logger.error(errmsg)
@@ -447,8 +453,7 @@ class DomainKeyDataView(DomainFormBaseView):
def get_initial(self):
"""The initial value for the form (which is a formset here)."""
- domain = self.object
- dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
+ dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
initial_data = []
if dnssecdata is not None:
@@ -489,8 +494,7 @@ class DomainKeyDataView(DomainFormBaseView):
# 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.object
- dnssecdata: extensions.DNSSECExtension = domain.dnssecdata
+ dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
if dnssecdata is not None and dnssecdata.keyData is not None:
self.request.session["dnssec_key_confirmed"] = True
@@ -541,9 +545,8 @@ class DomainKeyDataView(DomainFormBaseView):
except KeyError:
# no server information in this field, skip it
pass
- domain = self.object
try:
- domain.dnssecdata = dnssecdata
+ self.object.dnssecdata = dnssecdata
except RegistryError as err:
errmsg = "Error updating DNSSEC data in the registry."
logger.error(errmsg)
@@ -596,9 +599,8 @@ class DomainSecurityEmailView(DomainFormBaseView):
def get_initial(self):
"""The initial value for the form."""
- domain = self.object
initial = super().get_initial()
- security_contact = domain.security_contact
+ security_contact = self.object.security_contact
if security_contact is None or security_contact.email == "dotgov@cisa.dhs.gov":
initial["security_email"] = None
return initial
@@ -619,8 +621,7 @@ class DomainSecurityEmailView(DomainFormBaseView):
if new_email is None or new_email.strip() == "":
new_email = PublicContact.get_default_security().email
- domain = self.object
- contact = domain.security_contact
+ contact = self.object.security_contact
# If no default is created for security_contact,
# then we cannot connect to the registry.
From a587920c4554e14c2a32a00db62d6f21419fb189 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Tue, 17 Oct 2023 21:59:33 -0400
Subject: [PATCH 77/79] bow down before the almighty linter
---
src/registrar/models/domain.py | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index f32da2403..e444f2a61 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1698,17 +1698,13 @@ class Domain(TimeStampedModel, DomainHelper):
old_cache_contacts = self._cache.get("contacts")
if fetch_contacts:
- cleaned["contacts"] = self._get_contacts(
- cleaned.get("_contacts", [])
- )
+ cleaned["contacts"] = self._get_contacts(cleaned.get("_contacts", []))
if old_cache_hosts is not None:
logger.debug("resetting cleaned['hosts'] to old_cache_hosts")
cleaned["hosts"] = old_cache_hosts
if fetch_hosts:
- cleaned["hosts"] = self._get_hosts(
- cleaned.get("_hosts", [])
- )
+ cleaned["hosts"] = self._get_hosts(cleaned.get("_hosts", []))
if old_cache_contacts is not None:
cleaned["contacts"] = old_cache_contacts
From 2b55e40d5793cdda88bd5991a08de2b5b10d7c5a Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Wed, 18 Oct 2023 11:04:05 -0400
Subject: [PATCH 78/79] refactored _get_contacts to return default dict when no
contacts exist rather then empty dict
---
src/registrar/models/domain.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index e444f2a61..a212480e7 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1743,7 +1743,14 @@ class Domain(TimeStampedModel, DomainHelper):
return dnssec_data
def _get_contacts(self, contacts):
- cleaned_contacts = {}
+ choices = PublicContact.ContactTypeChoices
+ # We expect that all these fields get populated,
+ # so we can create these early, rather than waiting.
+ cleaned_contacts = {
+ choices.ADMINISTRATIVE: None,
+ choices.SECURITY: None,
+ choices.TECHNICAL: None,
+ }
if contacts and isinstance(contacts, list) and len(contacts) > 0:
cleaned_contacts = self._fetch_contacts(contacts)
return cleaned_contacts
From 94ac0e703d7cb4b8c5b42aa23c03ca9950078486 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Wed, 18 Oct 2023 13:28:50 -0400
Subject: [PATCH 79/79] small refactor to remove redundant code
---
src/registrar/models/domain.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index a212480e7..d0e6f1edd 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1715,7 +1715,7 @@ class Domain(TimeStampedModel, DomainHelper):
def _extract_data_from_response(self, data_response):
data = data_response.res_data[0]
- cache = {
+ return {
"auth_info": getattr(data, "auth_info", ...),
"_contacts": getattr(data, "contacts", ...),
"cr_date": getattr(data, "cr_date", ...),
@@ -1727,7 +1727,6 @@ class Domain(TimeStampedModel, DomainHelper):
"tr_date": getattr(data, "tr_date", ...),
"up_date": getattr(data, "up_date", ...),
}
- return {k: v for k, v in cache.items() if v is not ...}
def _remove_null_properties(self, cache):
return {k: v for k, v in cache.items() if v is not ...}
@@ -1821,7 +1820,6 @@ class Domain(TimeStampedModel, DomainHelper):
def _invalidate_cache(self):
"""Remove cache data when updates are made."""
- logger.debug("_invalidate_cache called")
self._cache = {}
def _get_property(self, property):