mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-21 10:16:13 +02:00
Merge branch 'main' into nmb/519-authorizing-official
This commit is contained in:
commit
4fdc1ae2cc
14 changed files with 198 additions and 24 deletions
|
@ -12,19 +12,34 @@ to get a `psql` shell on the sandbox environment's database.
|
||||||
|
|
||||||
## Running Migrations
|
## Running Migrations
|
||||||
|
|
||||||
When new code changes the database schema, we need to apply Django's migrations.
|
When new code changes the database schema (ie, you change a model or pull some
|
||||||
|
code that has), we need to apply Django's migrations.
|
||||||
|
|
||||||
|
### On Local
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-compose exec app bash
|
||||||
|
./manage.py makemigrations
|
||||||
|
```
|
||||||
|
|
||||||
|
Then perform docker-compose down & docker-compose up to run with the new migrations.
|
||||||
|
|
||||||
|
### On Cloud.gov
|
||||||
|
|
||||||
We can run these using CloudFoundry's tasks to run the `manage.py migrate`
|
We can run these using CloudFoundry's tasks to run the `manage.py migrate`
|
||||||
command in the correct environment. For any developer environment, developers
|
command in the correct environment. For any developer environment, developers
|
||||||
can manually run the task with
|
can manually run the task with
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cf run-task getgov-ENVIRONMENT --command 'python manage.py migrate' --name migrate
|
cf run-task getgov-ENVIRONMENT --wait --command 'python manage.py migrate' --name migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
|
(The optional 'wait' argument will wait until the environment is stable)
|
||||||
|
|
||||||
Optionally, load data from fixtures as well
|
Optionally, load data from fixtures as well
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cf run-task getgov-ENVIRONMENT --command 'python manage.py load' --name loaddata
|
cf run-task getgov-ENVIRONMENT --wait --command 'python manage.py load' --name loaddata
|
||||||
```
|
```
|
||||||
|
|
||||||
For the `stable` environment, developers don't have credentials so we need to
|
For the `stable` environment, developers don't have credentials so we need to
|
||||||
|
|
|
@ -88,6 +88,11 @@ urlpatterns = [
|
||||||
views.DomainAuthorizingOfficialView.as_view(),
|
views.DomainAuthorizingOfficialView.as_view(),
|
||||||
name="domain-authorizing-official",
|
name="domain-authorizing-official",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"domain/<int:pk>/security-email",
|
||||||
|
views.DomainSecurityEmailView.as_view(),
|
||||||
|
name="domain-security-email",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"domain/<int:pk>/users/add",
|
"domain/<int:pk>/users/add",
|
||||||
views.DomainAddUserView.as_view(),
|
views.DomainAddUserView.as_view(),
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
from .application_wizard import *
|
from .application_wizard import *
|
||||||
from .domain import DomainAddUserForm, NameserverFormset, ContactForm
|
from .domain import (
|
||||||
|
DomainAddUserForm,
|
||||||
|
NameserverFormset,
|
||||||
|
DomainSecurityEmailForm,
|
||||||
|
ContactForm,
|
||||||
|
)
|
||||||
|
|
|
@ -62,3 +62,10 @@ class ContactForm(forms.ModelForm):
|
||||||
|
|
||||||
for field_name in self.required:
|
for field_name in self.required:
|
||||||
self.fields[field_name].required = True
|
self.fields[field_name].required = True
|
||||||
|
|
||||||
|
|
||||||
|
class DomainSecurityEmailForm(forms.Form):
|
||||||
|
|
||||||
|
"""Form for adding or editing a security email to a domain."""
|
||||||
|
|
||||||
|
security_email = forms.EmailField(label="Security email")
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Django 4.2 on 2023-05-17 17:05
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0019_alter_domainapplication_organization_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="security_email",
|
||||||
|
),
|
||||||
|
]
|
|
@ -235,6 +235,19 @@ class Domain(TimeStampedModel):
|
||||||
# nothing.
|
# nothing.
|
||||||
logger.warn("TODO: Fake setting nameservers to %s", new_nameservers)
|
logger.warn("TODO: Fake setting nameservers to %s", new_nameservers)
|
||||||
|
|
||||||
|
def security_email(self) -> str:
|
||||||
|
"""Get the security email for this domain.
|
||||||
|
|
||||||
|
TODO: call EPP to get this info instead of returning fake data.
|
||||||
|
"""
|
||||||
|
return "mayor@igorville.gov"
|
||||||
|
|
||||||
|
def set_security_email(self, new_security_email: str):
|
||||||
|
"""Set the security email for this domain."""
|
||||||
|
# TODO: call EPP to set these values in the registry instead of doing
|
||||||
|
# nothing.
|
||||||
|
logger.warn("TODO: Fake setting security email to %s", new_security_email)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def roid(self):
|
def roid(self):
|
||||||
return self._get_property("roid")
|
return self._get_property("roid")
|
||||||
|
|
|
@ -200,12 +200,6 @@ class DomainInformation(TimeStampedModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Acknowledged .gov acceptable use policy",
|
help_text="Acknowledged .gov acceptable use policy",
|
||||||
)
|
)
|
||||||
security_email = models.EmailField(
|
|
||||||
max_length=320,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text="Security email for public use",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -61,18 +61,15 @@
|
||||||
<a class="usa-skipnav" href="#main-content">Skip to main content</a>
|
<a class="usa-skipnav" href="#main-content">Skip to main content</a>
|
||||||
|
|
||||||
{% if IS_DEMO_SITE %}
|
{% if IS_DEMO_SITE %}
|
||||||
<section
|
<section aria-label="Alert" >
|
||||||
class="usa-site-alert usa-site-alert--emergency usa-site-alert--no-icon"
|
<div class="usa-alert usa-alert--warning usa-alert--no-icon">
|
||||||
aria-label="Site alert"
|
<div class="usa-alert__body">
|
||||||
>
|
<p class="usa-alert__text">
|
||||||
<div class="usa-alert">
|
<strong>BETA SITE:</strong> We’re building a new way to get a .gov. Take a look around, but don’t rely on this site yet. This site is for testing purposes only. Don’t enter real data into any form on this site. To learn about requesting a .gov domain, visit <a href="https://get.gov" class="usa-link">get.gov</a>
|
||||||
<div class="usa-alert__body">
|
</p>
|
||||||
<p class="usa-alert__text">
|
|
||||||
<strong>TEST SITE</strong> - Do not use real personal information. Demo purposes only.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<section class="usa-banner" aria-label="Official website of the United States government">
|
<section class="usa-banner" aria-label="Official website of the United States government">
|
||||||
|
|
27
src/registrar/templates/domain_security_email.html
Normal file
27
src/registrar/templates/domain_security_email.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends "domain_base.html" %}
|
||||||
|
{% load static field_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Domain security email | {{ domain.name }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block domain_content %}
|
||||||
|
|
||||||
|
<h1>Domain security email</h1>
|
||||||
|
|
||||||
|
<p>We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. Security emails are made public and included in the <a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/about/data/">.gov domain data</a> we provide.</p>
|
||||||
|
|
||||||
|
<p>A security contact should be capable of evaluating or triaging security reports for your entire domain. Use a team email address, not an individual’s email. We recommend using an alias, like security@domain.gov.</p>
|
||||||
|
|
||||||
|
{% include "includes/required_fields.html" %}
|
||||||
|
|
||||||
|
<form class="usa-form usa-form--large" method="post" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% input_with_errors form.security_email %}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="usa-button"
|
||||||
|
>Add security email</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %} {# domain_content #}
|
|
@ -49,7 +49,7 @@
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="usa-sidenav__item">
|
<li class="usa-sidenav__item">
|
||||||
{% url 'todo' as url %}
|
{% url 'domain-security-email' pk=domain.id as url %}
|
||||||
<a href="{{ url }}"
|
<a href="{{ url }}"
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
{% if request.path == url %}class="usa-current"{% endif %}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1063,6 +1063,11 @@ class TestDomainPermissions(TestWithDomainPermissions):
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("domain-security-email", kwargs={"pk": self.domain.id})
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
def test_no_domain_role(self):
|
def test_no_domain_role(self):
|
||||||
"""Logged in but no role gets 403 Forbidden."""
|
"""Logged in but no role gets 403 Forbidden."""
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
@ -1082,6 +1087,12 @@ class TestDomainPermissions(TestWithDomainPermissions):
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("domain-security-email", kwargs={"pk": self.domain.id})
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
|
||||||
class TestDomainDetail(TestWithDomainPermissions, WebTest):
|
class TestDomainDetail(TestWithDomainPermissions, WebTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -1292,6 +1303,38 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest):
|
||||||
)
|
)
|
||||||
self.assertContains(page, "Testy")
|
self.assertContains(page, "Testy")
|
||||||
|
|
||||||
|
def test_domain_security_email(self):
|
||||||
|
"""Can load domain's security email page."""
|
||||||
|
page = self.client.get(
|
||||||
|
reverse("domain-security-email", kwargs={"pk": self.domain.id})
|
||||||
|
)
|
||||||
|
self.assertContains(page, "Domain security email")
|
||||||
|
|
||||||
|
def test_domain_security_email_form(self):
|
||||||
|
"""Adding a security email works.
|
||||||
|
|
||||||
|
Uses self.app WebTest because we need to interact with forms.
|
||||||
|
"""
|
||||||
|
security_email_page = self.app.get(
|
||||||
|
reverse("domain-security-email", kwargs={"pk": self.domain.id})
|
||||||
|
)
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
security_email_page.form["security_email"] = "mayor@igorville.gov"
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
with less_console_noise(): # swallow log warning message
|
||||||
|
result = security_email_page.form.submit()
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(
|
||||||
|
result["Location"],
|
||||||
|
reverse("domain-security-email", kwargs={"pk": self.domain.id}),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
success_page = result.follow()
|
||||||
|
self.assertContains(
|
||||||
|
success_page, "The security email for this domain have been updated"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestApplicationStatus(TestWithUser, WebTest):
|
class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -3,6 +3,7 @@ from .domain import (
|
||||||
DomainView,
|
DomainView,
|
||||||
DomainAuthorizingOfficialView,
|
DomainAuthorizingOfficialView,
|
||||||
DomainNameserversView,
|
DomainNameserversView,
|
||||||
|
DomainSecurityEmailView,
|
||||||
DomainUsersView,
|
DomainUsersView,
|
||||||
DomainAddUserView,
|
DomainAddUserView,
|
||||||
DomainInvitationDeleteView,
|
DomainInvitationDeleteView,
|
||||||
|
|
|
@ -12,7 +12,12 @@ from django.views.generic.edit import DeleteView, FormMixin
|
||||||
|
|
||||||
from registrar.models import Domain, DomainInvitation, User, UserDomainRole
|
from registrar.models import Domain, DomainInvitation, User, UserDomainRole
|
||||||
|
|
||||||
from ..forms import DomainAddUserForm, NameserverFormset, ContactForm
|
from ..forms import (
|
||||||
|
DomainAddUserForm,
|
||||||
|
NameserverFormset,
|
||||||
|
DomainSecurityEmailForm,
|
||||||
|
ContactForm,
|
||||||
|
)
|
||||||
from ..utility.email import send_templated_email, EmailSendingError
|
from ..utility.email import send_templated_email, EmailSendingError
|
||||||
from .utility import DomainPermission
|
from .utility import DomainPermission
|
||||||
|
|
||||||
|
@ -133,12 +138,57 @@ class DomainNameserversView(DomainPermission, FormMixin, DetailView):
|
||||||
domain.set_nameservers(nameservers)
|
domain.set_nameservers(nameservers)
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request, "The name servers for this domain have been updated"
|
self.request, "The name servers for this domain have been updated."
|
||||||
)
|
)
|
||||||
# superclass has the redirect
|
# superclass has the redirect
|
||||||
return super().form_valid(formset)
|
return super().form_valid(formset)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainSecurityEmailView(DomainPermission, FormMixin, DetailView):
|
||||||
|
|
||||||
|
"""Domain security email editing view."""
|
||||||
|
|
||||||
|
model = Domain
|
||||||
|
template_name = "domain_security_email.html"
|
||||||
|
context_object_name = "domain"
|
||||||
|
form_class = DomainSecurityEmailForm
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
"""The initial value for the form."""
|
||||||
|
domain = self.get_object()
|
||||||
|
initial = super().get_initial()
|
||||||
|
initial["security_email"] = domain.security_email()
|
||||||
|
return initial
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
"""Redirect to the overview page for the domain."""
|
||||||
|
return reverse("domain-security-email", kwargs={"pk": self.object.pk})
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
"""Formset 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."""
|
||||||
|
|
||||||
|
# Set the security email from the form
|
||||||
|
new_email = form.cleaned_data.get("security_email", "")
|
||||||
|
domain = self.get_object()
|
||||||
|
domain.set_security_email(new_email)
|
||||||
|
|
||||||
|
messages.success(
|
||||||
|
self.request, "The security email for this domain have been updated."
|
||||||
|
)
|
||||||
|
# superclass has the redirect
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
class DomainUsersView(DomainPermission, DetailView):
|
class DomainUsersView(DomainPermission, DetailView):
|
||||||
|
|
||||||
"""User management page in the domain details."""
|
"""User management page in the domain details."""
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
10038 OUTOFSCOPE http://app:8080/users
|
10038 OUTOFSCOPE http://app:8080/users
|
||||||
10038 OUTOFSCOPE http://app:8080/users/add
|
10038 OUTOFSCOPE http://app:8080/users/add
|
||||||
10038 OUTOFSCOPE http://app:8080/nameservers
|
10038 OUTOFSCOPE http://app:8080/nameservers
|
||||||
|
10038 OUTOFSCOPE http://app:8080/security-email
|
||||||
10038 OUTOFSCOPE http://app:8080/delete
|
10038 OUTOFSCOPE http://app:8080/delete
|
||||||
10038 OUTOFSCOPE http://app:8080/withdraw
|
10038 OUTOFSCOPE http://app:8080/withdraw
|
||||||
10038 OUTOFSCOPE http://app:8080/withdrawconfirmed
|
10038 OUTOFSCOPE http://app:8080/withdrawconfirmed
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue