Merge branch 'main' of github.com:cisagov/manage.get.gov into es/1378-availability-bugfix

This commit is contained in:
Erin 2023-11-29 15:53:26 -08:00
commit 69dd932320
No known key found for this signature in database
GPG key ID: 1CAD275313C62460
12 changed files with 119 additions and 12 deletions

View file

@ -21,6 +21,7 @@ jobs:
|| startsWith(github.head_ref, 'dk/')
|| startsWith(github.head_ref, 'es/')
|| startsWith(github.head_ref, 'ky/')
|| startsWith(github.head_ref, 'backup/')
outputs:
environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest"

View file

@ -16,6 +16,7 @@ on:
- stable
- staging
- development
- backup
- ky
- es
- nl

View file

@ -16,6 +16,7 @@ on:
options:
- staging
- development
- backup
- ky
- es
- nl

View file

@ -0,0 +1,32 @@
---
applications:
- name: getgov-backup
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
health-check-invocation-timeout: 40
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-backup.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
# default public site location
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
# Flag to disable/enable features in prod environments
IS_PRODUCTION: False
routes:
- route: getgov-backup.app.cloud.gov
services:
- getgov-credentials
- getgov-backup-database

View file

@ -2,6 +2,9 @@
from django.apps import apps
from django.views.decorators.http import require_http_methods
from django.http import JsonResponse
from django.utils.safestring import mark_safe
from registrar.templatetags.url_helpers import public_site_url
import requests
@ -18,8 +21,13 @@ DOMAIN_API_MESSAGES = {
" For example, if you want www.city.gov, you would enter “city”"
" (without the quotes).",
"extra_dots": "Enter the .gov domain you want without any periods.",
"unavailable": "That domain isnt available. Try entering another one."
" Contact us if you need help coming up with a domain.",
# message below is considered safe; no user input can be inserted into the message
# body; public_site_url() function reads from local app settings and therefore safe
"unavailable": mark_safe( # nosec
"That domain isnt available. "
"<a class='usa-link' href='{}' target='_blank'>"
"Read more about choosing your .gov domain.</a>".format(public_site_url("domains/choosing"))
),
"invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).",
"success": "That domain is available!",
"error": "Error finding domain availability. Please wait a few minutes and try again. If you continue \

View file

@ -322,7 +322,7 @@ class WebsiteAdmin(ListHeaderAdmin):
class UserDomainRoleAdmin(ListHeaderAdmin):
"""Custom domain role admin class."""
"""Custom user domain role admin class."""
# Columns
list_display = [
@ -340,6 +340,8 @@ class UserDomainRoleAdmin(ListHeaderAdmin):
]
search_help_text = "Search by user, domain, or role."
autocomplete_fields = ["user", "domain"]
class DomainInvitationAdmin(ListHeaderAdmin):
"""Custom domain invitation admin class."""

View file

@ -115,14 +115,14 @@ function inlineToast(el, id, style, msg) {
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
toastBody.classList.add("usa-alert__body");
p.classList.add("usa-alert__text");
p.innerText = msg;
p.innerHTML = msg;
toastBody.appendChild(p);
toast.appendChild(toastBody);
el.parentNode.insertBefore(toast, el.nextSibling);
} else {
// update and show the existing message div
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
toast.querySelector("div p").innerText = msg;
toast.querySelector("div p").innerHTML = msg;
makeVisible(toast);
}
} else {

View file

@ -245,3 +245,9 @@ h1, h2, h3 {
padding-left: 90px;
}
}
// Combo box
#select2-id_domain-results,
#select2-id_user-results {
width: 100%;
}

View file

@ -627,6 +627,7 @@ ALLOWED_HOSTS = [
"getgov-stable.app.cloud.gov",
"getgov-staging.app.cloud.gov",
"getgov-development.app.cloud.gov",
"getgov-backup.app.cloud.gov",
"getgov-ky.app.cloud.gov",
"getgov-es.app.cloud.gov",
"getgov-nl.app.cloud.gov",

View file

@ -118,8 +118,34 @@ class DomainNameserverForm(forms.Form):
self.add_error("ip", str(e))
class BaseNameserverFormset(forms.BaseFormSet):
def clean(self):
"""
Check for duplicate entries in the formset.
"""
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
return
data = []
duplicates = []
for form in self.forms:
if form.cleaned_data:
value = form.cleaned_data["server"]
if value in data:
form.add_error(
"server",
NameserverError(code=nsErrorCodes.DUPLICATE_HOST, nameserver=value),
)
duplicates.append(value)
else:
data.append(value)
NameserverFormset = formset_factory(
DomainNameserverForm,
formset=BaseNameserverFormset,
extra=1,
max_num=13,
validate_max=True,

View file

@ -1219,6 +1219,8 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
self.app.set_user(self.user.username)
self.client.force_login(self.user)
class TestDomainDetail(TestDomainOverview):
def test_domain_detail_link_works(self):
home_page = self.app.get("/")
self.assertContains(home_page, "igorville.gov")
@ -1227,7 +1229,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
self.assertContains(detail_page, "igorville.gov")
self.assertContains(detail_page, "Status")
def test_domain_overview_blocked_for_ineligible_user(self):
def test_domain_detail_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"""
@ -1239,7 +1241,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
self.assertEqual(response.status_code, 403)
def test_domain_overview_allowed_for_on_hold(self):
def test_domain_detail_allowed_for_on_hold(self):
"""Test that the domain overview page displays for on hold domain"""
home_page = self.app.get("/")
self.assertContains(home_page, "on-hold.gov")
@ -1248,7 +1250,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_on_hold.id}))
self.assertNotContains(detail_page, "Edit")
def test_domain_see_just_nameserver(self):
def test_domain_detail_see_just_nameserver(self):
home_page = self.app.get("/")
self.assertContains(home_page, "justnameserver.com")
@ -1259,7 +1261,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
self.assertContains(detail_page, "ns1.justnameserver.com")
self.assertContains(detail_page, "ns2.justnameserver.com")
def test_domain_see_nameserver_and_ip(self):
def test_domain_detail_see_nameserver_and_ip(self):
home_page = self.app.get("/")
self.assertContains(home_page, "nameserverwithip.gov")
@ -1275,7 +1277,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
self.assertContains(detail_page, "(1.2.3.4,")
self.assertContains(detail_page, "2.3.4.5)")
def test_domain_with_no_information_or_application(self):
def test_domain_detail_with_no_information_or_application(self):
"""Test that domain management page returns 200 and displays error
when no domain information or domain application exist"""
# have to use staff user for this test
@ -1506,6 +1508,30 @@ class TestDomainNameservers(TestDomainOverview):
status_code=200,
)
def test_domain_nameservers_form_submit_duplicate_host(self):
"""Nameserver form catches error when host is duplicated.
Uses self.app WebTest because we need to interact with forms.
"""
# initial nameservers page has one server with two ips
nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# attempt to submit the form with duplicate host names of fake.host.com
nameservers_page.form["form-0-ip"] = ""
nameservers_page.form["form-1-server"] = "fake.host.com"
with less_console_noise(): # swallow log warning message
result = nameservers_page.form.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 required field. remove duplicate entry
self.assertContains(
result,
str(NameserverError(code=NameserverErrorCodes.DUPLICATE_HOST)),
count=2,
status_code=200,
)
def test_domain_nameservers_form_submit_whitespace(self):
"""Nameserver form removes whitespace from ip.

View file

@ -68,7 +68,8 @@ class NameserverErrorCodes(IntEnum):
- 4 TOO_MANY_HOSTS more than the max allowed host values
- 5 MISSING_HOST host is missing for a nameserver
- 6 INVALID_HOST host is invalid for a nameserver
- 7 BAD_DATA bad data input for nameserver
- 7 DUPLICATE_HOST host is a duplicate
- 8 BAD_DATA bad data input for nameserver
"""
MISSING_IP = 1
@ -77,7 +78,8 @@ class NameserverErrorCodes(IntEnum):
TOO_MANY_HOSTS = 4
MISSING_HOST = 5
INVALID_HOST = 6
BAD_DATA = 7
DUPLICATE_HOST = 7
BAD_DATA = 8
class NameserverError(Exception):
@ -93,6 +95,7 @@ class NameserverError(Exception):
NameserverErrorCodes.TOO_MANY_HOSTS: ("Too many hosts provided, you may not have more than 13 nameservers."),
NameserverErrorCodes.MISSING_HOST: ("Name server must be provided to enter IP address."),
NameserverErrorCodes.INVALID_HOST: ("Enter a name server in the required format, like ns1.example.com"),
NameserverErrorCodes.DUPLICATE_HOST: ("Remove duplicate entry"),
NameserverErrorCodes.BAD_DATA: (
"Theres something wrong with the name server information you provided. "
"If you need help email us at help@get.gov."