mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-06-04 19:47:22 +02:00
Merge branch 'main' of github.com:cisagov/manage.get.gov into es/1378-availability-bugfix
This commit is contained in:
commit
69dd932320
12 changed files with 119 additions and 12 deletions
1
.github/workflows/deploy-sandbox.yaml
vendored
1
.github/workflows/deploy-sandbox.yaml
vendored
|
@ -21,6 +21,7 @@ jobs:
|
||||||
|| startsWith(github.head_ref, 'dk/')
|
|| startsWith(github.head_ref, 'dk/')
|
||||||
|| startsWith(github.head_ref, 'es/')
|
|| startsWith(github.head_ref, 'es/')
|
||||||
|| startsWith(github.head_ref, 'ky/')
|
|| startsWith(github.head_ref, 'ky/')
|
||||||
|
|| startsWith(github.head_ref, 'backup/')
|
||||||
outputs:
|
outputs:
|
||||||
environment: ${{ steps.var.outputs.environment}}
|
environment: ${{ steps.var.outputs.environment}}
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
|
|
1
.github/workflows/migrate.yaml
vendored
1
.github/workflows/migrate.yaml
vendored
|
@ -16,6 +16,7 @@ on:
|
||||||
- stable
|
- stable
|
||||||
- staging
|
- staging
|
||||||
- development
|
- development
|
||||||
|
- backup
|
||||||
- ky
|
- ky
|
||||||
- es
|
- es
|
||||||
- nl
|
- nl
|
||||||
|
|
1
.github/workflows/reset-db.yaml
vendored
1
.github/workflows/reset-db.yaml
vendored
|
@ -16,6 +16,7 @@ on:
|
||||||
options:
|
options:
|
||||||
- staging
|
- staging
|
||||||
- development
|
- development
|
||||||
|
- backup
|
||||||
- ky
|
- ky
|
||||||
- es
|
- es
|
||||||
- nl
|
- nl
|
||||||
|
|
32
ops/manifests/manifest-backup.yaml
Normal file
32
ops/manifests/manifest-backup.yaml
Normal 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
|
|
@ -2,6 +2,9 @@
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
from registrar.templatetags.url_helpers import public_site_url
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -18,8 +21,13 @@ DOMAIN_API_MESSAGES = {
|
||||||
" For example, if you want www.city.gov, you would enter “city”"
|
" For example, if you want www.city.gov, you would enter “city”"
|
||||||
" (without the quotes).",
|
" (without the quotes).",
|
||||||
"extra_dots": "Enter the .gov domain you want without any periods.",
|
"extra_dots": "Enter the .gov domain you want without any periods.",
|
||||||
"unavailable": "That domain isn’t available. Try entering another one."
|
# message below is considered safe; no user input can be inserted into the message
|
||||||
" Contact us if you need help coming up with a domain.",
|
# body; public_site_url() function reads from local app settings and therefore safe
|
||||||
|
"unavailable": mark_safe( # nosec
|
||||||
|
"That domain isn’t 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).",
|
"invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).",
|
||||||
"success": "That domain is available!",
|
"success": "That domain is available!",
|
||||||
"error": "Error finding domain availability. Please wait a few minutes and try again. If you continue \
|
"error": "Error finding domain availability. Please wait a few minutes and try again. If you continue \
|
||||||
|
|
|
@ -322,7 +322,7 @@ class WebsiteAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
|
|
||||||
class UserDomainRoleAdmin(ListHeaderAdmin):
|
class UserDomainRoleAdmin(ListHeaderAdmin):
|
||||||
"""Custom domain role admin class."""
|
"""Custom user domain role admin class."""
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
|
@ -340,6 +340,8 @@ class UserDomainRoleAdmin(ListHeaderAdmin):
|
||||||
]
|
]
|
||||||
search_help_text = "Search by user, domain, or role."
|
search_help_text = "Search by user, domain, or role."
|
||||||
|
|
||||||
|
autocomplete_fields = ["user", "domain"]
|
||||||
|
|
||||||
|
|
||||||
class DomainInvitationAdmin(ListHeaderAdmin):
|
class DomainInvitationAdmin(ListHeaderAdmin):
|
||||||
"""Custom domain invitation admin class."""
|
"""Custom domain invitation admin class."""
|
||||||
|
|
|
@ -115,14 +115,14 @@ function inlineToast(el, id, style, msg) {
|
||||||
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
|
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
|
||||||
toastBody.classList.add("usa-alert__body");
|
toastBody.classList.add("usa-alert__body");
|
||||||
p.classList.add("usa-alert__text");
|
p.classList.add("usa-alert__text");
|
||||||
p.innerText = msg;
|
p.innerHTML = msg;
|
||||||
toastBody.appendChild(p);
|
toastBody.appendChild(p);
|
||||||
toast.appendChild(toastBody);
|
toast.appendChild(toastBody);
|
||||||
el.parentNode.insertBefore(toast, el.nextSibling);
|
el.parentNode.insertBefore(toast, el.nextSibling);
|
||||||
} else {
|
} else {
|
||||||
// update and show the existing message div
|
// update and show the existing message div
|
||||||
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
|
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
|
||||||
toast.querySelector("div p").innerText = msg;
|
toast.querySelector("div p").innerHTML = msg;
|
||||||
makeVisible(toast);
|
makeVisible(toast);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -245,3 +245,9 @@ h1, h2, h3 {
|
||||||
padding-left: 90px;
|
padding-left: 90px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Combo box
|
||||||
|
#select2-id_domain-results,
|
||||||
|
#select2-id_user-results {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -627,6 +627,7 @@ ALLOWED_HOSTS = [
|
||||||
"getgov-stable.app.cloud.gov",
|
"getgov-stable.app.cloud.gov",
|
||||||
"getgov-staging.app.cloud.gov",
|
"getgov-staging.app.cloud.gov",
|
||||||
"getgov-development.app.cloud.gov",
|
"getgov-development.app.cloud.gov",
|
||||||
|
"getgov-backup.app.cloud.gov",
|
||||||
"getgov-ky.app.cloud.gov",
|
"getgov-ky.app.cloud.gov",
|
||||||
"getgov-es.app.cloud.gov",
|
"getgov-es.app.cloud.gov",
|
||||||
"getgov-nl.app.cloud.gov",
|
"getgov-nl.app.cloud.gov",
|
||||||
|
|
|
@ -118,8 +118,34 @@ class DomainNameserverForm(forms.Form):
|
||||||
self.add_error("ip", str(e))
|
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(
|
NameserverFormset = formset_factory(
|
||||||
DomainNameserverForm,
|
DomainNameserverForm,
|
||||||
|
formset=BaseNameserverFormset,
|
||||||
extra=1,
|
extra=1,
|
||||||
max_num=13,
|
max_num=13,
|
||||||
validate_max=True,
|
validate_max=True,
|
||||||
|
|
|
@ -1219,6 +1219,8 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
||||||
self.app.set_user(self.user.username)
|
self.app.set_user(self.user.username)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainDetail(TestDomainOverview):
|
||||||
def test_domain_detail_link_works(self):
|
def test_domain_detail_link_works(self):
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "igorville.gov")
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
@ -1227,7 +1229,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
||||||
self.assertContains(detail_page, "igorville.gov")
|
self.assertContains(detail_page, "igorville.gov")
|
||||||
self.assertContains(detail_page, "Status")
|
self.assertContains(detail_page, "Status")
|
||||||
|
|
||||||
def test_domain_overview_blocked_for_ineligible_user(self):
|
def test_domain_detail_blocked_for_ineligible_user(self):
|
||||||
"""We could easily duplicate this test for all domain management
|
"""We could easily duplicate this test for all domain management
|
||||||
views, but a single url test should be solid enough since all domain
|
views, but a single url test should be solid enough since all domain
|
||||||
management pages share the same permissions class"""
|
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}))
|
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
||||||
self.assertEqual(response.status_code, 403)
|
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"""
|
"""Test that the domain overview page displays for on hold domain"""
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "on-hold.gov")
|
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}))
|
detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_on_hold.id}))
|
||||||
self.assertNotContains(detail_page, "Edit")
|
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("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "justnameserver.com")
|
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, "ns1.justnameserver.com")
|
||||||
self.assertContains(detail_page, "ns2.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("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "nameserverwithip.gov")
|
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, "(1.2.3.4,")
|
||||||
self.assertContains(detail_page, "2.3.4.5)")
|
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
|
"""Test that domain management page returns 200 and displays error
|
||||||
when no domain information or domain application exist"""
|
when no domain information or domain application exist"""
|
||||||
# have to use staff user for this test
|
# have to use staff user for this test
|
||||||
|
@ -1506,6 +1508,30 @@ class TestDomainNameservers(TestDomainOverview):
|
||||||
status_code=200,
|
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):
|
def test_domain_nameservers_form_submit_whitespace(self):
|
||||||
"""Nameserver form removes whitespace from ip.
|
"""Nameserver form removes whitespace from ip.
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,8 @@ class NameserverErrorCodes(IntEnum):
|
||||||
- 4 TOO_MANY_HOSTS more than the max allowed host values
|
- 4 TOO_MANY_HOSTS more than the max allowed host values
|
||||||
- 5 MISSING_HOST host is missing for a nameserver
|
- 5 MISSING_HOST host is missing for a nameserver
|
||||||
- 6 INVALID_HOST host is invalid 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
|
MISSING_IP = 1
|
||||||
|
@ -77,7 +78,8 @@ class NameserverErrorCodes(IntEnum):
|
||||||
TOO_MANY_HOSTS = 4
|
TOO_MANY_HOSTS = 4
|
||||||
MISSING_HOST = 5
|
MISSING_HOST = 5
|
||||||
INVALID_HOST = 6
|
INVALID_HOST = 6
|
||||||
BAD_DATA = 7
|
DUPLICATE_HOST = 7
|
||||||
|
BAD_DATA = 8
|
||||||
|
|
||||||
|
|
||||||
class NameserverError(Exception):
|
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.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.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.INVALID_HOST: ("Enter a name server in the required format, like ns1.example.com"),
|
||||||
|
NameserverErrorCodes.DUPLICATE_HOST: ("Remove duplicate entry"),
|
||||||
NameserverErrorCodes.BAD_DATA: (
|
NameserverErrorCodes.BAD_DATA: (
|
||||||
"There’s something wrong with the name server information you provided. "
|
"There’s something wrong with the name server information you provided. "
|
||||||
"If you need help email us at help@get.gov."
|
"If you need help email us at help@get.gov."
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue