mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-06-29 07:43:32 +02:00
Merge branch 'main' into dk/1016-nameservers-ui
This commit is contained in:
commit
f772c40efe
22 changed files with 379 additions and 698 deletions
|
@ -406,3 +406,30 @@ function prepareDeleteButtons(formLabel) {
|
|||
}
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* An IIFE that triggers a modal on the DS Data Form under certain conditions
|
||||
*
|
||||
*/
|
||||
(function triggerModalOnDsDataForm() {
|
||||
let saveButon = document.querySelector("#save-ds-data");
|
||||
|
||||
// The view context will cause a hitherto hidden modal trigger to
|
||||
// show up. On save, we'll test for that modal trigger appearing. We'll
|
||||
// run that test once every 100 ms for 5 secs, which should balance performance
|
||||
// while accounting for network or lag issues.
|
||||
if (saveButon) {
|
||||
let i = 0;
|
||||
var tryToTriggerModal = setInterval(function() {
|
||||
i++;
|
||||
if (i > 100) {
|
||||
clearInterval(tryToTriggerModal);
|
||||
}
|
||||
let modalTrigger = document.querySelector("#ds-toggle-dnssec-alert");
|
||||
if (modalTrigger) {
|
||||
modalTrigger.click()
|
||||
clearInterval(tryToTriggerModal);
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -22,8 +22,11 @@ a.breadcrumb__back {
|
|||
}
|
||||
}
|
||||
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
|
||||
a.usa-button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
|
||||
color: color('white');
|
||||
}
|
||||
|
||||
|
@ -111,15 +114,3 @@ a.usa-button--unstyled:visited {
|
|||
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;
|
||||
left: 88px;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
margin-bottom: units(1.5);
|
||||
}
|
||||
|
||||
.usa-form .usa-button.margin-top-1 {
|
||||
margin-top: units(1);
|
||||
}
|
||||
|
||||
.usa-form--extra-large {
|
||||
max-width: none;
|
||||
}
|
||||
|
|
|
@ -100,11 +100,6 @@ urlpatterns = [
|
|||
views.DomainDsDataView.as_view(),
|
||||
name="domain-dns-dnssec-dsdata",
|
||||
),
|
||||
path(
|
||||
"domain/<int:pk>/dns/dnssec/keydata",
|
||||
views.DomainKeyDataView.as_view(),
|
||||
name="domain-dns-dnssec-keydata",
|
||||
),
|
||||
path(
|
||||
"domain/<int:pk>/your-contact-information",
|
||||
views.DomainYourContactInformationView.as_view(),
|
||||
|
|
|
@ -86,6 +86,12 @@ class UserFixture:
|
|||
"first_name": "Kristina",
|
||||
"last_name": "Yin",
|
||||
},
|
||||
{
|
||||
"username": "ac49d7c1-368a-4e6b-8f1d-60250e20a16f",
|
||||
"first_name": "Vicky",
|
||||
"last_name": "Chin",
|
||||
"email": "szu.chin@associates.cisa.dhs.gov",
|
||||
},
|
||||
]
|
||||
|
||||
STAFF = [
|
||||
|
@ -150,6 +156,12 @@ class UserFixture:
|
|||
"last_name": "Yin-Analyst",
|
||||
"email": "kristina.yin+1@gsa.gov",
|
||||
},
|
||||
{
|
||||
"username": "8f42302e-b83a-4c9e-8764-fc19e2cea576",
|
||||
"first_name": "Vickster-Analyst",
|
||||
"last_name": "Chin-Analyst",
|
||||
"email": "szu.chin@ecstech.com",
|
||||
},
|
||||
]
|
||||
|
||||
def load_users(cls, users, group_name):
|
||||
|
|
|
@ -8,6 +8,4 @@ from .domain import (
|
|||
DomainDnssecForm,
|
||||
DomainDsdataFormset,
|
||||
DomainDsdataForm,
|
||||
DomainKeydataFormset,
|
||||
DomainKeydataForm,
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# common.py
|
||||
#
|
||||
# ALGORITHM_CHOICES are options for alg attribute in DS Data and Key Data
|
||||
# ALGORITHM_CHOICES are options for alg attribute in DS Data
|
||||
# reference:
|
||||
# https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
|
||||
ALGORITHM_CHOICES = [
|
||||
|
@ -24,15 +24,3 @@ DIGEST_TYPE_CHOICES = [
|
|||
(0, "(0) Reserved"),
|
||||
(1, "(1) SHA-256"),
|
||||
]
|
||||
# PROTOCOL_CHOICES are options for protocol attribute in Key Data
|
||||
# reference: https://datatracker.ietf.org/doc/html/rfc4034#section-2.1.2
|
||||
PROTOCOL_CHOICES = [
|
||||
(3, "(3) DNSSEC"),
|
||||
]
|
||||
# FLAG_CHOICES are options for flags attribute in Key Data
|
||||
# reference: https://datatracker.ietf.org/doc/html/rfc4034#section-2.1.1
|
||||
FLAG_CHOICES = [
|
||||
(0, "(0)"),
|
||||
(256, "(256) ZSK"),
|
||||
(257, "(257) KSK"),
|
||||
]
|
||||
|
|
|
@ -14,8 +14,6 @@ from ..models import Contact, DomainInformation, Domain
|
|||
from .common import (
|
||||
ALGORITHM_CHOICES,
|
||||
DIGEST_TYPE_CHOICES,
|
||||
FLAG_CHOICES,
|
||||
PROTOCOL_CHOICES,
|
||||
)
|
||||
|
||||
|
||||
|
@ -252,44 +250,3 @@ DomainDsdataFormset = formset_factory(
|
|||
extra=0,
|
||||
can_delete=True,
|
||||
)
|
||||
|
||||
|
||||
class DomainKeydataForm(forms.Form):
|
||||
"""Form for adding or editing DNSSEC Key Data to a domain."""
|
||||
|
||||
flag = forms.TypedChoiceField(
|
||||
required=True,
|
||||
label="Flag",
|
||||
coerce=int,
|
||||
choices=FLAG_CHOICES,
|
||||
error_messages={"required": ("Flag is required.")},
|
||||
)
|
||||
|
||||
protocol = forms.TypedChoiceField(
|
||||
required=True,
|
||||
label="Protocol",
|
||||
coerce=int,
|
||||
choices=PROTOCOL_CHOICES,
|
||||
error_messages={"required": ("Protocol is required.")},
|
||||
)
|
||||
|
||||
algorithm = forms.TypedChoiceField(
|
||||
required=True,
|
||||
label="Algorithm",
|
||||
coerce=int,
|
||||
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.")},
|
||||
)
|
||||
|
||||
|
||||
DomainKeydataFormset = formset_factory(
|
||||
DomainKeydataForm,
|
||||
extra=0,
|
||||
can_delete=True,
|
||||
)
|
||||
|
|
|
@ -492,12 +492,11 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
addExtension: dict
|
||||
remExtension: dict
|
||||
|
||||
addExtension includes all dsData or keyData to be added
|
||||
remExtension includes all dsData or keyData to be removed
|
||||
addExtension includes all dsData to be added
|
||||
remExtension includes all dsData 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
|
||||
method operates on dsData;
|
||||
if dsData is not present, addExtension will be empty dict, and
|
||||
remExtension will be all existing dnssecdata to be deleted
|
||||
"""
|
||||
|
||||
|
@ -530,32 +529,9 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
else:
|
||||
addDnssecdata["dsData"] = None
|
||||
|
||||
elif _dnssecdata and _dnssecdata.keyData is not None:
|
||||
# initialize addDnssecdata and remDnssecdata for keyData
|
||||
addDnssecdata["keyData"] = _dnssecdata.keyData
|
||||
|
||||
if oldDnssecdata and len(oldDnssecdata.keyData) > 0:
|
||||
# if existing keyData not in new keyData, mark for removal
|
||||
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
|
||||
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
|
||||
# there are no new dsData, remove all dsData from existing
|
||||
remDnssecdata["dsData"] = getattr(oldDnssecdata, "dsData", None)
|
||||
remDnssecdata["keyData"] = getattr(oldDnssecdata, "keyData", None)
|
||||
|
||||
return addDnssecdata, remDnssecdata
|
||||
|
||||
|
@ -565,12 +541,10 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
addParams = {
|
||||
"maxSigLife": _addDnssecdata.get("maxSigLife", None),
|
||||
"dsData": _addDnssecdata.get("dsData", None),
|
||||
"keyData": _addDnssecdata.get("keyData", None),
|
||||
}
|
||||
remParams = {
|
||||
"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)
|
||||
|
@ -579,19 +553,9 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
remExtension = commands.UpdateDomainDNSSECExtension(**remParams)
|
||||
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
|
||||
):
|
||||
if "dsData" in _addDnssecdata and _addDnssecdata["dsData"] 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
|
||||
):
|
||||
if "dsData" in _remDnssecdata and _remDnssecdata["dsData"] is not None:
|
||||
registry.send(remRequest, cleaned=True)
|
||||
except RegistryError as e:
|
||||
logger.error(
|
||||
|
|
|
@ -4,6 +4,9 @@ from django.contrib.auth.models import AbstractUser
|
|||
from django.db import models
|
||||
|
||||
from .domain_invitation import DomainInvitation
|
||||
from .transition_domain import TransitionDomain
|
||||
from .domain_information import DomainInformation
|
||||
from .domain import Domain
|
||||
|
||||
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
||||
|
||||
|
@ -62,12 +65,9 @@ class User(AbstractUser):
|
|||
def is_restricted(self):
|
||||
return self.status == self.RESTRICTED
|
||||
|
||||
def first_login(self):
|
||||
"""Callback when the user is authenticated for the very first time.
|
||||
|
||||
When a user first arrives on the site, we need to retrieve any domain
|
||||
invitations that match their email address.
|
||||
"""
|
||||
def check_domain_invitations_on_login(self):
|
||||
"""When a user first arrives on the site, we need to retrieve any domain
|
||||
invitations that match their email address."""
|
||||
for invitation in DomainInvitation.objects.filter(
|
||||
email=self.email, status=DomainInvitation.INVITED
|
||||
):
|
||||
|
@ -82,6 +82,101 @@ class User(AbstractUser):
|
|||
"Failed to retrieve invitation %s", invitation, exc_info=True
|
||||
)
|
||||
|
||||
def create_domain_and_invite(self, transition_domain: TransitionDomain):
|
||||
transition_domain_name = transition_domain.domain_name
|
||||
transition_domain_status = transition_domain.status
|
||||
transition_domain_email = transition_domain.username
|
||||
|
||||
# type safety check. name should never be none
|
||||
if transition_domain_name is not None:
|
||||
new_domain = Domain(
|
||||
name=transition_domain_name, state=transition_domain_status
|
||||
)
|
||||
new_domain.save()
|
||||
# check that a domain invitation doesn't already
|
||||
# exist for this e-mail / Domain pair
|
||||
domain_email_already_in_domain_invites = DomainInvitation.objects.filter(
|
||||
email=transition_domain_email.lower(), domain=new_domain
|
||||
).exists()
|
||||
if not domain_email_already_in_domain_invites:
|
||||
# Create new domain invitation
|
||||
new_domain_invitation = DomainInvitation(
|
||||
email=transition_domain_email.lower(), domain=new_domain
|
||||
)
|
||||
new_domain_invitation.save()
|
||||
|
||||
def check_transition_domains_on_login(self):
|
||||
"""When a user first arrives on the site, we need to check
|
||||
if they are logging in with the same e-mail as a
|
||||
transition domain and update our database accordingly."""
|
||||
|
||||
for transition_domain in TransitionDomain.objects.filter(username=self.email):
|
||||
# Looks like the user logged in with the same e-mail as
|
||||
# one or more corresponding transition domains.
|
||||
# Create corresponding DomainInformation objects.
|
||||
|
||||
# NOTE: adding an ADMIN user role for this user
|
||||
# for each domain should already be done
|
||||
# in the invitation.retrieve() method.
|
||||
# However, if the migration scripts for transition
|
||||
# domain objects were not executed correctly,
|
||||
# there could be transition domains without
|
||||
# any corresponding Domain & DomainInvitation objects,
|
||||
# which means the invitation.retrieve() method might
|
||||
# not execute.
|
||||
# Check that there is a corresponding domain object
|
||||
# for this transition domain. If not, we have an error
|
||||
# with our data and migrations need to be run again.
|
||||
|
||||
# Get the domain that corresponds with this transition domain
|
||||
domain_exists = Domain.objects.filter(
|
||||
name=transition_domain.domain_name
|
||||
).exists()
|
||||
if not domain_exists:
|
||||
logger.warn(
|
||||
"""There are transition domains without
|
||||
corresponding domain objects!
|
||||
Please run migration scripts for transition domains
|
||||
(See data_migration.md)"""
|
||||
)
|
||||
# No need to throw an exception...just create a domain
|
||||
# and domain invite, then proceed as normal
|
||||
self.create_domain_and_invite(transition_domain)
|
||||
|
||||
domain = Domain.objects.get(name=transition_domain.domain_name)
|
||||
|
||||
# Create a domain information object, if one doesn't
|
||||
# already exist
|
||||
domain_info_exists = DomainInformation.objects.filter(
|
||||
domain=domain
|
||||
).exists()
|
||||
if not domain_info_exists:
|
||||
new_domain_info = DomainInformation(creator=self, domain=domain)
|
||||
new_domain_info.save()
|
||||
|
||||
def first_login(self):
|
||||
"""Callback when the user is authenticated for the very first time.
|
||||
|
||||
When a user first arrives on the site, we need to retrieve any domain
|
||||
invitations that match their email address.
|
||||
|
||||
We also need to check if they are logging in with the same e-mail
|
||||
as a transition domain and update our domainInfo objects accordingly.
|
||||
"""
|
||||
|
||||
# PART 1: TRANSITION DOMAINS
|
||||
#
|
||||
# NOTE: THIS MUST RUN FIRST
|
||||
# (If we have an issue where transition domains were
|
||||
# not fully converted into Domain and DomainInvitation
|
||||
# objects, this method will fill in the gaps.
|
||||
# This will ensure the Domain Invitations method
|
||||
# runs correctly (no missing invites))
|
||||
self.check_transition_domains_on_login()
|
||||
|
||||
# PART 2: DOMAIN INVITATIONS
|
||||
self.check_domain_invitations_on_login()
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
("analyst_access_permission", "Analyst Access Permission"),
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{% url 'domain-dns-nameservers' pk=domain.id as url %}
|
||||
<p><a href="{{ url }}">DNS name servers</a></p>
|
||||
|
||||
{% url 'domain-dnssec' pk=domain.id as url %}
|
||||
{% url 'domain-dns-dnssec' pk=domain.id as url %}
|
||||
<p><a href="{{ url }}">DNSSEC</a></p>
|
||||
|
||||
{% endblock %} {# domain_content #}
|
||||
|
|
|
@ -7,38 +7,37 @@
|
|||
|
||||
<h1>DNSSEC</h1>
|
||||
|
||||
<p>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.</p>
|
||||
<p>DNSSEC, or DNS Security Extensions, is additional security layer to protect your website. Enabling DNSSEC ensures that when someone visits your website, they can be certain that it’s connecting to the correct server, preventing potential hijacking or tampering with your domain's records.</p>
|
||||
|
||||
<form class="usa-form usa-form--text-width" method="post">
|
||||
{% csrf_token %}
|
||||
{% if has_dnssec_records %}
|
||||
<div class="usa-alert usa-alert--info usa-alert--slim">
|
||||
<div class="usa-alert__body">
|
||||
In order to fully disable DNSSEC on your domain, you will need to work with your DNS provider to remove your DNSSEC-related records from your zone.
|
||||
<div
|
||||
class="usa-summary-box dotgov-status-box padding-top-0"
|
||||
role="region"
|
||||
aria-labelledby="Important notes on disabling DNSSEC"
|
||||
>
|
||||
<div class="usa-summary-box__body">
|
||||
<p class="usa-summary-box__heading font-sans-md margin-bottom-0"
|
||||
id="summary-box-key-information"
|
||||
>
|
||||
<h2>To fully disable DNSSEC </h2>
|
||||
<ul class="usa-list">
|
||||
<li>Click “Disable DNSSEC” below.</li>
|
||||
<li>Wait until the Time to Live (TTL) expires on your DNSSEC records managed by your DNS hosting provider. This is often less than 24 hours, but confirm with your provider.</li>
|
||||
<li>After the TTL expiration, disable DNSSEC at your DNS hosting provider. </li>
|
||||
</ul>
|
||||
<p><strong>Warning:</strong> If you disable DNSSEC at your DNS hosting provider before TTL expiration, this may cause your domain to appear offline.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h2>DNSSEC is enabled on your domain</h2>
|
||||
<a
|
||||
href="#toggle-dnssec-alert"
|
||||
class="usa-button"
|
||||
class="usa-button usa-button--outline margin-top-1"
|
||||
aria-controls="toggle-dnssec-alert"
|
||||
data-open-modal
|
||||
>Disable DNSSEC</a
|
||||
>
|
||||
{% elif dnssec_enabled %}
|
||||
<div id="add-records">
|
||||
<h2> Add DS Records </h2>
|
||||
<p>In order to enable DNSSEC and add Delegation Signer (DS) records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
||||
<p>
|
||||
<a href="{% url 'domain-dns-dnssec-dsdata' pk=domain.id %}" class="usa-button usa-button--outline">Add DS Data</a>
|
||||
<a href="{% url 'domain-dns-dnssec-keydata' pk=domain.id %}" class="usa-button usa-button--outline">Add Key Data</a>
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button usa-button--unstyled usa-button--cancel"
|
||||
name="cancel_dnssec"
|
||||
id="cancel_dnssec"
|
||||
>Cancel</button>
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="enable-dnssec">
|
||||
<div class="usa-alert usa-alert--info usa-alert--slim">
|
||||
|
@ -46,12 +45,7 @@
|
|||
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.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button"
|
||||
name="enable_dnssec"
|
||||
id="enable_dnssec"
|
||||
>Enable DNSSEC</button>
|
||||
<a href="{% url 'domain-dns-dnssec-dsdata' pk=domain.id %}" class="usa-button">Enable DNSSEC</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
@ -62,7 +56,7 @@
|
|||
aria-labelledby="Are you sure you want to continue?"
|
||||
aria-describedby="Your DNSSEC records will be deleted from the registry."
|
||||
>
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to continue?" modal_description="Your DNSSEC records will be deleted from the registry." modal_button=modal_button|safe %}
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to disable DNSSEC?" modal_button=modal_button|safe %}
|
||||
</div>
|
||||
|
||||
{% endblock %} {# domain_content #}
|
||||
|
|
|
@ -4,47 +4,24 @@
|
|||
{% block title %}DS Data | {{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block domain_content %}
|
||||
{% for form in formset %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
{% endfor %}
|
||||
|
||||
{% if domain.dnssecdata is None and not dnssec_ds_confirmed %}
|
||||
{% if domain.dnssecdata is None %}
|
||||
<div class="usa-alert usa-alert--info usa-alert--slim margin-bottom-3">
|
||||
<div class="usa-alert__body">
|
||||
You have no DS Data added. Enable DNSSEC by adding DS Data or return to the DNSSEC page and click 'enable.'
|
||||
You have no DS Data added. Enable DNSSEC by adding DS Data.
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for form in formset %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
{% endfor %}
|
||||
|
||||
<h1>DS Data</h1>
|
||||
|
||||
{% if domain.dnssecdata is not None and domain.dnssecdata.keyData is not None %}
|
||||
<div class="usa-alert usa-alert--warning usa-alert--slim margin-bottom-3">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Warning, you cannot add DS Data</h4>
|
||||
<p class="usa-alert__text">
|
||||
You cannot add DS Data because you have already added Key Data. Delete your Key Data records in order to add DS Data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% elif not dnssec_ds_confirmed %}
|
||||
<p>In order to enable DNSSEC, you must first configure it with your DNS hosting service.</p>
|
||||
<p>Enter the values given by your DNS provider for DS Data.</p>
|
||||
<p>Required fields are marked with an asterisk (<abbr
|
||||
title="required"
|
||||
class="usa-hint usa-hint--required"
|
||||
>*</abbr>).</p>
|
||||
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="usa-button usa-button--unstyled display-block" name="confirm-ds">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
|
||||
</svg><span class="margin-left-05">Add new record</span>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
|
||||
<p>Enter the values given by your DNS provider for DS Data.</p>
|
||||
|
||||
{% include "includes/required_fields.html" %}
|
||||
|
||||
<form class="usa-form usa-form--extra-large" method="post" novalidate id="form-container">
|
||||
|
@ -104,20 +81,40 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
id="save-ds-data"
|
||||
type="submit"
|
||||
class="usa-button"
|
||||
>Save
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form aria-label="form to undo changes to the DS records">
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button usa-button--outline btn-cancel"
|
||||
class="usa-button usa-button--outline"
|
||||
name="btn-cancel-click"
|
||||
aria-label="Reset the data in the DS records to the registry state (undo changes)"
|
||||
>Cancel
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if trigger_modal %}
|
||||
<a
|
||||
id="ds-toggle-dnssec-alert"
|
||||
href="#toggle-dnssec-alert"
|
||||
class="usa-button usa-button--outline margin-top-1 display-none"
|
||||
aria-controls="toggle-dnssec-alert"
|
||||
data-open-modal
|
||||
>Trigger Disable DNSSEC Modal</a
|
||||
>
|
||||
{% endif %}
|
||||
{# Use data-force-action to take esc out of the equation and pass cancel_button_resets_ds_form to effectuate a reset in the view #}
|
||||
<div
|
||||
class="usa-modal"
|
||||
id="toggle-dnssec-alert"
|
||||
aria-labelledby="Are you sure you want to continue?"
|
||||
aria-describedby="Your DNSSEC records will be deleted from the registry."
|
||||
data-force-action
|
||||
>
|
||||
{% include 'includes/modal.html' with cancel_button_resets_ds_form=True modal_heading="Warning: You are about to delete all DS records on your domain" modal_description="To fully disable DNSSEC: In addition to deleting your DS records here you’ll also need to delete the DS records at your DNS host. To avoid causing your domain to appear offline you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button=modal_button|safe %}
|
||||
</div>
|
||||
|
||||
{% endblock %} {# domain_content #}
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
{% extends "domain_base.html" %}
|
||||
{% load static field_helpers url_helpers %}
|
||||
|
||||
{% block title %}Key Data | {{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block domain_content %}
|
||||
{% for form in formset %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
{% endfor %}
|
||||
|
||||
<h1>Key Data</h1>
|
||||
|
||||
{% if domain.dnssecdata is not None and domain.dnssecdata.dsData is not None %}
|
||||
<div class="usa-alert usa-alert--warning usa-alert--slim margin-bottom-3">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Warning, you cannot add Key Data</h4>
|
||||
<p class="usa-alert__text">
|
||||
You cannot add Key Data because you have already added DS Data. Delete your DS Data records in order to add Key Data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% elif not dnssec_key_confirmed %}
|
||||
<p>In order to enable DNSSEC and add DS records, you must first configure it with your DNS hosting service. Your configuration will determine whether you need to add DS Data or Key Data. Contact your DNS hosting provider if you are unsure which record type to add.</p>
|
||||
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button"
|
||||
name="confirm-key"
|
||||
>Add DS Key record</button>
|
||||
</form>
|
||||
{% else %}
|
||||
|
||||
<p>Enter the values given by your DNS provider for DS Key Data.</p>
|
||||
{% include "includes/required_fields.html" %}
|
||||
|
||||
<form class="usa-form usa-form--extra-large" method="post" novalidate id="form-container">
|
||||
{% csrf_token %}
|
||||
{{ formset.management_form }}
|
||||
|
||||
{% for form in formset %}
|
||||
<fieldset class="repeatable-form">
|
||||
|
||||
<legend class="sr-only">DS Data record {{forloop.counter}}</legend>
|
||||
|
||||
<h2 class="margin-top-0">DS Data record {{forloop.counter}}</h2>
|
||||
|
||||
<div class="grid-row grid-gap-2 flex-end">
|
||||
<div class="tablet:grid-col-4">
|
||||
{% with attr_required=True add_group_class="usa-form-group--unstyled-error" %}
|
||||
{% input_with_errors form.flag %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="tablet:grid-col-4">
|
||||
{% with attr_required=True add_group_class="usa-form-group--unstyled-error" %}
|
||||
{% input_with_errors form.protocol %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="tablet:grid-col-4">
|
||||
{% with attr_required=True add_group_class="usa-form-group--unstyled-error" %}
|
||||
{% input_with_errors form.algorithm %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="grid-col">
|
||||
{% with attr_required=True add_group_class="usa-form-group--unstyled-error" %}
|
||||
{% input_with_errors form.pub_key %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-row margin-top-2">
|
||||
<div class="grid-col">
|
||||
<button type="button" class="usa-button usa-button--unstyled display-block float-right-tablet delete-record">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
||||
</svg><span class="margin-left-05">Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
{% endfor %}
|
||||
|
||||
<button type="button" class="usa-button usa-button--unstyled display-block margin-bottom-2" id="add-form">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
|
||||
</svg><span class="margin-left-05">Add new record</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button"
|
||||
>Save
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form aria-label="form to undo changes to the DS records">
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button usa-button--outline btn-cancel"
|
||||
name="btn-cancel-click"
|
||||
aria-label="Reset the data in the DS records to the registry state (undo changes)"
|
||||
>Cancel
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %} {# domain_content #}
|
|
@ -34,7 +34,7 @@
|
|||
>
|
||||
DNSSEC
|
||||
</a>
|
||||
{% if domain.dnssecdata is not None or request.path|startswith:url and request.path|endswith:'data' %}
|
||||
{% if domain.dnssecdata is not None or request.path|startswith:url and request.path|endswith:'dsdata' %}
|
||||
<ul class="usa-sidenav__sublist">
|
||||
<li class="usa-sidenav__item">
|
||||
{% url 'domain-dns-dnssec-dsdata' pk=domain.id as url %}
|
||||
|
@ -44,15 +44,6 @@
|
|||
DS Data
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="usa-sidenav__item">
|
||||
{% url 'domain-dns-dnssec-keydata' pk=domain.id as url %}
|
||||
<a href="{{ url }}"
|
||||
{% if request.path == url %}class="usa-current"{% endif %}
|
||||
>
|
||||
DS Key Data
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{% load static form_helpers url_helpers %}
|
||||
|
||||
<div class="usa-modal__content">
|
||||
<div class="usa-modal__main">
|
||||
<h2 class="usa-modal__heading" id="modal-1-heading">
|
||||
|
@ -18,6 +20,21 @@
|
|||
</form>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
{% comment %} The cancel button the DS form actually triggers a context change in the view,
|
||||
in addition to being a close modal hook {% endcomment %}
|
||||
{% if cancel_button_resets_ds_form %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||
name="btn-cancel-click"
|
||||
data-close-modal
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<button
|
||||
type="button"
|
||||
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||
|
@ -25,10 +42,29 @@
|
|||
>
|
||||
Cancel
|
||||
</button>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} The cancel button the DS form actually triggers a context change in the view,
|
||||
in addition to being a close modal hook {% endcomment %}
|
||||
{% if cancel_button_resets_ds_form %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button usa-modal__close"
|
||||
aria-label="Close this window"
|
||||
name="btn-cancel-click"
|
||||
data-close-modal
|
||||
>
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<button
|
||||
type="button"
|
||||
class="usa-button usa-modal__close"
|
||||
|
@ -36,7 +72,8 @@
|
|||
data-close-modal
|
||||
>
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="/assets/img/sprite.svg#close"></use>
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -773,12 +773,6 @@ class MockEppLib(TestCase):
|
|||
"digestType": 1,
|
||||
"digest": "ec0bdd990b39feead889f0ba613db4adecb4adec",
|
||||
}
|
||||
keyDataDict = {
|
||||
"flags": 257,
|
||||
"protocol": 3,
|
||||
"alg": 1,
|
||||
"pubKey": "AQPJ////4Q==",
|
||||
}
|
||||
dnssecExtensionWithDsData = extensions.DNSSECExtension(
|
||||
**{
|
||||
"dsData": [
|
||||
|
@ -794,11 +788,6 @@ class MockEppLib(TestCase):
|
|||
], # type: ignore
|
||||
}
|
||||
)
|
||||
dnssecExtensionWithKeyData = extensions.DNSSECExtension(
|
||||
**{
|
||||
"keyData": [common.DNSSECKeyData(**keyDataDict)], # type: ignore
|
||||
}
|
||||
)
|
||||
dnssecExtensionRemovingDsData = extensions.DNSSECExtension()
|
||||
|
||||
infoDomainHasIP = fakedEppObject(
|
||||
|
@ -914,10 +903,6 @@ class MockEppLib(TestCase):
|
|||
self.mockDataInfoDomain,
|
||||
self.dnssecExtensionWithMultDsData,
|
||||
),
|
||||
"dnssec-keydata.gov": (
|
||||
self.mockDataInfoDomain,
|
||||
self.dnssecExtensionWithKeyData,
|
||||
),
|
||||
"dnssec-none.gov": (self.mockDataInfoDomain, None),
|
||||
"my-nameserver.gov": (
|
||||
self.infoDomainTwoHosts
|
||||
|
|
|
@ -14,7 +14,8 @@ from registrar.models import (
|
|||
UserDomainRole,
|
||||
)
|
||||
|
||||
import boto3_mocking # type: ignore
|
||||
import boto3_mocking
|
||||
from registrar.models.transition_domain import TransitionDomain # type: ignore
|
||||
from .common import MockSESClient, less_console_noise, completed_application
|
||||
from django_fsm import TransitionNotAllowed
|
||||
|
||||
|
@ -612,3 +613,48 @@ class TestInvitations(TestCase):
|
|||
"""A new user's first_login callback retrieves their invitations."""
|
||||
self.user.first_login()
|
||||
self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain))
|
||||
|
||||
|
||||
class TestUser(TestCase):
|
||||
"""For now, just test actions that
|
||||
occur on user login."""
|
||||
|
||||
def setUp(self):
|
||||
self.email = "mayor@igorville.gov"
|
||||
self.domain_name = "igorvilleInTransition.gov"
|
||||
self.user, _ = User.objects.get_or_create(email=self.email)
|
||||
|
||||
# clean out the roles each time
|
||||
UserDomainRole.objects.all().delete()
|
||||
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="mayor@igorville.gov", domain_name=self.domain_name
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
Domain.objects.all().delete()
|
||||
DomainInvitation.objects.all().delete()
|
||||
DomainInformation.objects.all().delete()
|
||||
TransitionDomain.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
|
||||
def test_check_transition_domains_on_login(self):
|
||||
"""A new user's first_login callback checks transition domains.
|
||||
Makes DomainInformation object."""
|
||||
self.domain, _ = Domain.objects.get_or_create(name=self.domain_name)
|
||||
|
||||
self.user.first_login()
|
||||
self.assertTrue(DomainInformation.objects.get(domain=self.domain))
|
||||
|
||||
def test_check_transition_domains_without_domains_on_login(self):
|
||||
"""A new user's first_login callback checks transition domains.
|
||||
This test makes sure that in the event a domain does not exist
|
||||
for a given transition domain, both a domain and domain invitation
|
||||
are created."""
|
||||
self.user.first_login()
|
||||
self.assertTrue(Domain.objects.get(name=self.domain_name))
|
||||
|
||||
domain = Domain.objects.get(name=self.domain_name)
|
||||
self.assertTrue(DomainInvitation.objects.get(email=self.email, domain=domain))
|
||||
self.assertTrue(DomainInformation.objects.get(domain=domain))
|
||||
|
|
|
@ -1991,79 +1991,6 @@ class TestRegistrantDNSSEC(MockEppLib):
|
|||
|
||||
patcher.stop()
|
||||
|
||||
def test_user_adds_dnssec_keydata(self):
|
||||
"""
|
||||
Scenario: Registrant adds DNSSEC key 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
|
||||
|
||||
"""
|
||||
|
||||
# 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, _ = mocked_send.call_args
|
||||
# assert that the extension matches
|
||||
self.assertEquals(
|
||||
args[0].extensions[0],
|
||||
self.createUpdateExtension(self.dnssecExtensionWithKeyData),
|
||||
)
|
||||
# test that the dnssecdata getter is functioning properly
|
||||
dnssecdata_get = domain.dnssecdata
|
||||
mocked_send.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
commands.UpdateDomain(
|
||||
name="dnssec-keydata.gov",
|
||||
nsset=None,
|
||||
keyset=None,
|
||||
registrant=None,
|
||||
auth_info=None,
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
call(
|
||||
commands.InfoDomain(
|
||||
name="dnssec-keydata.gov",
|
||||
),
|
||||
cleaned=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEquals(
|
||||
dnssecdata_get.keyData, self.dnssecExtensionWithKeyData.keyData
|
||||
)
|
||||
|
||||
patcher.stop()
|
||||
|
||||
def test_update_is_unsuccessful(self):
|
||||
"""
|
||||
Scenario: An update to the dns data is unsuccessful
|
||||
|
|
|
@ -1103,7 +1103,6 @@ class TestWithDomainPermissions(TestWithUser):
|
|||
self.domain_multdsdata, _ = Domain.objects.get_or_create(
|
||||
name="dnssec-multdsdata.gov"
|
||||
)
|
||||
self.domain_keydata, _ = Domain.objects.get_or_create(name="dnssec-keydata.gov")
|
||||
# We could simply use domain (igorville) but this will be more readable in tests
|
||||
# that inherit this setUp
|
||||
self.domain_dnssec_none, _ = Domain.objects.get_or_create(
|
||||
|
@ -1118,9 +1117,6 @@ class TestWithDomainPermissions(TestWithUser):
|
|||
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
|
||||
)
|
||||
|
@ -1135,11 +1131,6 @@ class TestWithDomainPermissions(TestWithUser):
|
|||
domain=self.domain_multdsdata,
|
||||
role=UserDomainRole.Roles.MANAGER,
|
||||
)
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user,
|
||||
domain=self.domain_keydata,
|
||||
role=UserDomainRole.Roles.MANAGER,
|
||||
)
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user,
|
||||
domain=self.domain_dnssec_none,
|
||||
|
@ -1806,38 +1797,13 @@ class TestDomainDNSSEC(TestDomainOverview):
|
|||
|
||||
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("/")
|
||||
and shows a 'Enable DNSSEC' button."""
|
||||
|
||||
page = self.client.get(
|
||||
reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})
|
||||
)
|
||||
self.assertContains(page, "Enable DNSSEC")
|
||||
|
||||
# Prepare the data for the POST request
|
||||
post_data = {
|
||||
"enable_dnssec": "Enable DNSSEC",
|
||||
}
|
||||
updated_page = self.client.post(
|
||||
reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}),
|
||||
post_data,
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertEqual(updated_page.status_code, 200)
|
||||
|
||||
self.assertContains(updated_page, "Add DS Data")
|
||||
self.assertContains(updated_page, "Add Key Data")
|
||||
|
||||
self.app.get("/")
|
||||
|
||||
back_to_page = self.client.get(
|
||||
reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})
|
||||
)
|
||||
self.assertContains(back_to_page, "Enable DNSSEC")
|
||||
|
||||
def test_dnssec_page_loads_with_data_in_domain(self):
|
||||
"""DNSSEC overview page loads when domain has DNSSEC data
|
||||
and the template contains a button to disable DNSSEC."""
|
||||
|
@ -1882,43 +1848,25 @@ class TestDomainDNSSEC(TestDomainOverview):
|
|||
)
|
||||
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})
|
||||
def test_ds_data_form_modal(self):
|
||||
"""When user clicks on save, a modal pops up."""
|
||||
add_data_page = self.app.get(
|
||||
reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.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}
|
||||
# Assert that a hidden trigger for the modal does not exist.
|
||||
# This hidden trigger will pop on the page when certain condition are met:
|
||||
# 1) Initial form contained DS data, 2) All data is deleted and form is
|
||||
# submitted.
|
||||
self.assertNotContains(add_data_page, "Trigger Disable DNSSEC Modal")
|
||||
# Simulate a delete all data
|
||||
form_data = {}
|
||||
response = self.client.post(
|
||||
reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}),
|
||||
data=form_data,
|
||||
)
|
||||
)
|
||||
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")
|
||||
self.assertEqual(response.status_code, 200) # Adjust status code as needed
|
||||
# Now check to see whether the JS trigger for the modal is present on the page
|
||||
self.assertContains(response, "Trigger Disable DNSSEC Modal")
|
||||
|
||||
def test_ds_data_form_submits(self):
|
||||
"""DS Data form submits successfully
|
||||
|
@ -1964,50 +1912,6 @@ 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):
|
||||
|
|
|
@ -7,7 +7,6 @@ from .domain import (
|
|||
DomainNameserversView,
|
||||
DomainDNSSECView,
|
||||
DomainDsDataView,
|
||||
DomainKeyDataView,
|
||||
DomainYourContactInformationView,
|
||||
DomainSecurityEmailView,
|
||||
DomainUsersView,
|
||||
|
|
|
@ -12,7 +12,6 @@ from django.contrib.messages.views import SuccessMessageMixin
|
|||
from django.db import IntegrityError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.template import RequestContext
|
||||
from django.urls import reverse
|
||||
from django.views.generic.edit import FormMixin
|
||||
|
||||
|
@ -41,8 +40,6 @@ from ..forms import (
|
|||
DomainDnssecForm,
|
||||
DomainDsdataFormset,
|
||||
DomainDsdataForm,
|
||||
DomainKeydataFormset,
|
||||
DomainKeydataForm,
|
||||
)
|
||||
|
||||
from epplibwrapper import (
|
||||
|
@ -362,8 +359,8 @@ class DomainDNSSECView(DomainFormBaseView):
|
|||
# Create HTML for the modal button
|
||||
modal_button = (
|
||||
'<button type="submit" '
|
||||
'class="usa-button" '
|
||||
'name="disable_dnssec">Disable DNSSEC</button>'
|
||||
'class="usa-button usa-button--secondary" '
|
||||
'name="disable_dnssec">Confirm</button>'
|
||||
)
|
||||
|
||||
context["modal_button"] = modal_button
|
||||
|
@ -388,12 +385,6 @@ class DomainDNSSECView(DomainFormBaseView):
|
|||
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
|
||||
|
||||
return self.form_valid(form)
|
||||
|
||||
|
@ -410,14 +401,7 @@ class DomainDsDataView(DomainFormBaseView):
|
|||
dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
|
||||
initial_data = []
|
||||
|
||||
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:
|
||||
if dnssecdata is not None and dnssecdata.dsData is not None:
|
||||
# Add existing nameservers as initial data
|
||||
initial_data.extend(
|
||||
{
|
||||
|
@ -445,38 +429,49 @@ class DomainDsDataView(DomainFormBaseView):
|
|||
# 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
|
||||
dnssecdata: extensions.DNSSECExtension = self.object.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):
|
||||
"""Formset submission posts to this view."""
|
||||
self._get_domain(request)
|
||||
formset = self.get_form()
|
||||
override = False
|
||||
|
||||
if "confirm-ds" in request.POST:
|
||||
request.session["dnssec_ds_confirmed"] = True
|
||||
request.session["dnssec_key_confirmed"] = False
|
||||
return super().form_valid(formset)
|
||||
|
||||
# This is called by the form cancel button,
|
||||
# and also by the modal's X and cancel buttons
|
||||
if "btn-cancel-click" in request.POST:
|
||||
return redirect("/", {"formset": formset}, RequestContext(request))
|
||||
url = self.get_success_url()
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
if formset.is_valid():
|
||||
# This is called by the Disable DNSSEC modal to override
|
||||
if "disable-override-click" in request.POST:
|
||||
override = True
|
||||
|
||||
# This is called when all DNSSEC data has been deleted and the
|
||||
# Save button is pressed
|
||||
if len(formset) == 0 and formset.initial != [{}] and override is False:
|
||||
# trigger the modal
|
||||
# get context data from super() rather than self
|
||||
# to preserve the context["form"]
|
||||
context = super().get_context_data(form=formset)
|
||||
context["trigger_modal"] = True
|
||||
# Create HTML for the modal button
|
||||
modal_button = (
|
||||
'<button type="submit" '
|
||||
'class="usa-button usa-button--secondary" '
|
||||
'name="disable-override-click">Delete all records</button>'
|
||||
)
|
||||
|
||||
# context to back out of a broken form on all fields delete
|
||||
context["modal_button"] = modal_button
|
||||
return self.render_to_response(context)
|
||||
|
||||
if formset.is_valid() or override:
|
||||
return self.form_valid(formset)
|
||||
else:
|
||||
return self.form_invalid(formset)
|
||||
|
||||
def form_valid(self, formset):
|
||||
def form_valid(self, formset, **kwargs):
|
||||
"""The formset is valid, perform something with it."""
|
||||
|
||||
# Set the dnssecdata from the formset
|
||||
|
@ -503,10 +498,12 @@ class DomainDsDataView(DomainFormBaseView):
|
|||
try:
|
||||
self.object.dnssecdata = dnssecdata
|
||||
except RegistryError as err:
|
||||
errmsg = "Error updating DNSSEC data in the registry."
|
||||
logger.error(errmsg)
|
||||
logger.error(err)
|
||||
messages.error(self.request, errmsg)
|
||||
if err.is_connection_error():
|
||||
messages.error(self.request, GenericError(code=GenericErrorCodes.CANNOT_CONTACT_REGISTRY))
|
||||
logger.error(f"Registry connection error: {err}")
|
||||
else:
|
||||
messages.error(self.request, GenericError(code=GenericErrorCodes.GENERIC_ERROR))
|
||||
logger.error(f"Registry error: {err}")
|
||||
return self.form_invalid(formset)
|
||||
else:
|
||||
messages.success(
|
||||
|
@ -516,123 +513,6 @@ class DomainDsDataView(DomainFormBaseView):
|
|||
return super().form_valid(formset)
|
||||
|
||||
|
||||
class DomainKeyDataView(DomainFormBaseView):
|
||||
"""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)."""
|
||||
dnssecdata: extensions.DNSSECExtension = self.object.dnssecdata
|
||||
initial_data = []
|
||||
|
||||
if dnssecdata is not None:
|
||||
if dnssecdata.dsData is not None:
|
||||
# TODO: Throw an error?
|
||||
# Note: this is moot if we're
|
||||
# removing Key data
|
||||
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
|
||||
)
|
||||
|
||||
# Ensure at least 1 record, filled or empty
|
||||
while len(initial_data) == 0:
|
||||
initial_data.append({})
|
||||
|
||||
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")
|
||||
|
||||
# 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
|
||||
dnssecdata: extensions.DNSSECExtension = self.object.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):
|
||||
"""Formset submission posts to this view."""
|
||||
self._get_domain(request)
|
||||
formset = self.get_form()
|
||||
|
||||
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 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."""
|
||||
|
||||
# Set the nameservers from the formset
|
||||
dnssecdata = extensions.DNSSECExtension()
|
||||
|
||||
for form in formset:
|
||||
try:
|
||||
# if 'delete' not in form.cleaned_data
|
||||
# or form.cleaned_data['delete'] == False:
|
||||
keyrecord = {
|
||||
"flags": int(form.cleaned_data["flag"]),
|
||||
"protocol": int(form.cleaned_data["protocol"]),
|
||||
"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
|
||||
try:
|
||||
self.object.dnssecdata = dnssecdata
|
||||
except RegistryError as err:
|
||||
errmsg = "Error updating DNSSEC data in the registry."
|
||||
logger.error(errmsg)
|
||||
logger.error(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(DomainFormBaseView):
|
||||
"""Domain your contact information editing view."""
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue