Merge branch 'main' into dk/1220-check-availability-error-message

This commit is contained in:
David Kennedy 2023-11-27 20:56:23 -05:00
commit d9dca1c20a
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
12 changed files with 173 additions and 14 deletions

View file

@ -344,6 +344,12 @@ class UserDomainRoleAdmin(ListHeaderAdmin):
class DomainInvitationAdmin(ListHeaderAdmin):
"""Custom domain invitation admin class."""
class Meta:
model = models.DomainInvitation
fields = "__all__"
_meta = Meta()
# Columns
list_display = [
"email",
@ -356,6 +362,10 @@ class DomainInvitationAdmin(ListHeaderAdmin):
"email",
"domain__name",
]
# Filters
list_filter = ("status",)
search_help_text = "Search by email or domain."
# Mark the FSM field 'status' as readonly

View file

@ -212,11 +212,9 @@ class Domain(TimeStampedModel, DomainHelper):
@Cache
def registry_expiration_date(self) -> date:
"""Get or set the `ex_date` element from the registry.
Additionally, update the expiration date in the registrar"""
Additionally, _get_property updates the expiration date in the registrar"""
try:
self.expiration_date = self._get_property("ex_date")
self.save()
return self.expiration_date
return self._get_property("ex_date")
except Exception as e:
# exception raised during the save to registrar
logger.error(f"error updating expiration date in registrar: {e}")
@ -880,6 +878,14 @@ class Domain(TimeStampedModel, DomainHelper):
"""
return self.state == self.State.READY
def is_editable(self) -> bool:
"""domain is editable unless state is on hold or deleted"""
return self.state in [
self.State.UNKNOWN,
self.State.DNS_NEEDED,
self.State.READY,
]
def transfer(self):
"""Going somewhere. Not implemented."""
raise NotImplementedError()
@ -1188,7 +1194,7 @@ class Domain(TimeStampedModel, DomainHelper):
logger.error(e)
logger.error(e.code)
raise e
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST and self.state != Domain.State.DELETED:
# avoid infinite loop
already_tried_to_create = True
self.dns_needed_from_unknown()
@ -1602,6 +1608,12 @@ class Domain(TimeStampedModel, DomainHelper):
if old_cache_contacts is not None:
cleaned["contacts"] = old_cache_contacts
# if expiration date from registry does not match what is in db,
# update the db
if "ex_date" in cleaned and cleaned["ex_date"] != self.expiration_date:
self.expiration_date = cleaned["ex_date"]
self.save()
self._cache = cleaned
except RegistryError as e:

View file

@ -29,30 +29,34 @@
{% url 'domain-dns-nameservers' pk=domain.id as url %}
{% if domain.nameservers|length > 0 %}
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value=domain.nameservers list='true' edit_link=url %}
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value=domain.nameservers list='true' edit_link=url editable=domain.is_editable %}
{% else %}
{% if domain.is_editable %}
<h2 class="margin-top-neg-1"> DNS name servers </h2>
<p> No DNS name servers have been added yet. Before your domain can be used well need information about your domain name servers.</p>
<a class="usa-button margin-bottom-1" href="{{url}}"> Add DNS name servers </a>
{% else %}
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value='' edit_link=url editable=domain.is_editable %}
{% endif %}
{% endif %}
{% url 'domain-org-name-address' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domain.domain_info address='true' edit_link=url %}
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domain.domain_info address='true' edit_link=url editable=domain.is_editable %}
{% url 'domain-authorizing-official' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Authorizing official' value=domain.domain_info.authorizing_official contact='true' edit_link=url %}
{% include "includes/summary_item.html" with title='Authorizing official' value=domain.domain_info.authorizing_official contact='true' edit_link=url editable=domain.is_editable %}
{% url 'domain-your-contact-information' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url %}
{% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url editable=domain.is_editable %}
{% url 'domain-security-email' pk=domain.id as url %}
{% if security_email is not None and security_email != default_security_email%}
{% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url %}
{% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url editable=domain.is_editable %}
{% else %}
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %}
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url editable=domain.is_editable %}
{% endif %}
{% url 'domain-users' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url %}
{% include "includes/summary_item.html" with title='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url editable=domain.is_editable %}
</div>
{% endblock %} {# domain_content #}

View file

@ -12,6 +12,7 @@
</a>
</li>
{% if domain.is_editable %}
<li class="usa-sidenav__item">
{% url 'domain-dns' pk=domain.id as url %}
<a href="{{ url }}" {% if request.path|startswith:url %}class="usa-current"{% endif %}>
@ -98,6 +99,7 @@
Domain managers
</a>
</li>
{% endif %}
</ul>
</nav>
</div>

View file

@ -85,7 +85,7 @@
{% endif %}
</div>
{% if edit_link %}
{% if editable and edit_link %}
<div class="text-right">
<a
href="{{ edit_link }}"

View file

@ -617,6 +617,7 @@ class MockEppLib(TestCase):
common.Status(state="serverTransferProhibited", description="", lang="en"),
common.Status(state="inactive", description="", lang="en"),
],
ex_date=datetime.date(2023, 5, 25),
)
mockDataInfoContact = mockDataInfoDomain.dummyInfoContactResultData(
"123", "123@mail.gov", datetime.datetime(2023, 5, 25, 19, 45, 35), "lastPw"

View file

@ -8,6 +8,7 @@ from registrar.admin import (
DomainAdmin,
DomainApplicationAdmin,
DomainApplicationAdminForm,
DomainInvitationAdmin,
ListHeaderAdmin,
MyUserAdmin,
AuditedAdmin,
@ -847,6 +848,44 @@ class TestDomainApplicationAdmin(MockEppLib):
User.objects.all().delete()
class DomainInvitationAdminTest(TestCase):
"""Tests for the DomainInvitation page"""
def setUp(self):
"""Create a client object"""
self.client = Client(HTTP_HOST="localhost:8080")
self.factory = RequestFactory()
self.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite())
self.superuser = create_superuser()
def tearDown(self):
"""Delete all DomainInvitation objects"""
DomainInvitation.objects.all().delete()
def test_get_filters(self):
"""Ensures that our filters are displaying correctly"""
# Have to get creative to get past linter
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/domaininvitation/",
{},
follow=True,
)
# Assert that the filters are added
self.assertContains(response, "invited", count=4)
self.assertContains(response, "retrieved", count=4)
# Check for the HTML context specificially
invited_html = '<a href="?status__exact=invited">invited</a>'
retrieved_html = '<a href="?status__exact=retrieved">retrieved</a>'
self.assertContains(response, invited_html, count=1)
self.assertContains(response, retrieved_html, count=1)
class ListHeaderAdminTest(TestCase):
def setUp(self):
self.site = AdminSite()

View file

@ -1987,6 +1987,13 @@ class TestExpirationDate(MockEppLib):
with self.assertRaises(RegistryError):
self.domain_w_error.renew_domain()
def test_expiration_date_updated_on_info_domain_call(self):
"""assert that expiration date in db is updated on info domain call"""
# force fetch_cache to be called
self.domain.statuses
test_date = datetime.date(2023, 5, 25)
self.assertEquals(self.domain.expiration_date, test_date)
class TestAnalystClientHold(MockEppLib):
"""Rule: Analysts may suspend or restore a domain by using client hold"""

View file

@ -50,6 +50,7 @@ class ExportDataTest(TestCase):
)
def tearDown(self):
# Dummy push - will remove
Domain.objects.all().delete()
DomainInformation.objects.all().delete()
User.objects.all().delete()

View file

@ -1082,6 +1082,8 @@ class TestWithDomainPermissions(TestWithUser):
self.domain_with_ip, _ = Domain.objects.get_or_create(name="nameserverwithip.gov")
self.domain_just_nameserver, _ = Domain.objects.get_or_create(name="justnameserver.com")
self.domain_no_information, _ = Domain.objects.get_or_create(name="noinformation.gov")
self.domain_on_hold, _ = Domain.objects.get_or_create(name="on-hold.gov", state=Domain.State.ON_HOLD)
self.domain_deleted, _ = Domain.objects.get_or_create(name="deleted.gov", state=Domain.State.DELETED)
self.domain_dsdata, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
self.domain_multdsdata, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
@ -1096,6 +1098,8 @@ class TestWithDomainPermissions(TestWithUser):
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dnssec_none)
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_ip)
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_just_nameserver)
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_on_hold)
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_deleted)
self.role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
@ -1124,6 +1128,12 @@ class TestWithDomainPermissions(TestWithUser):
domain=self.domain_just_nameserver,
role=UserDomainRole.Roles.MANAGER,
)
UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain_on_hold, role=UserDomainRole.Roles.MANAGER
)
UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain_deleted, role=UserDomainRole.Roles.MANAGER
)
def tearDown(self):
try:
@ -1177,6 +1187,31 @@ class TestDomainPermissions(TestWithDomainPermissions):
response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id}))
self.assertEqual(response.status_code, 403)
def test_domain_pages_blocked_for_on_hold_and_deleted(self):
"""Test that the domain pages are blocked for on hold and deleted domains"""
self.client.force_login(self.user)
for view_name in [
"domain-users",
"domain-users-add",
"domain-dns",
"domain-dns-nameservers",
"domain-dns-dnssec",
"domain-dns-dnssec-dsdata",
"domain-org-name-address",
"domain-authorizing-official",
"domain-your-contact-information",
"domain-security-email",
]:
for domain in [
self.domain_on_hold,
self.domain_deleted,
]:
with self.subTest(view_name=view_name, domain=domain):
with less_console_noise():
response = self.client.get(reverse(view_name, kwargs={"pk": domain.id}))
self.assertEqual(response.status_code, 403)
class TestDomainOverview(TestWithDomainPermissions, WebTest):
def setUp(self):
@ -1204,6 +1239,15 @@ 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):
"""Test that the domain overview page displays for on hold domain"""
home_page = self.app.get("/")
self.assertContains(home_page, "on-hold.gov")
# View domain overview page
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):
home_page = self.app.get("/")
self.assertContains(home_page, "justnameserver.com")

View file

@ -152,6 +152,28 @@ class DomainView(DomainBaseView):
context["security_email"] = security_email
return context
def in_editable_state(self, pk):
"""Override in_editable_state from DomainPermission
Allow detail page to be viewable"""
requested_domain = None
if Domain.objects.filter(id=pk).exists():
requested_domain = Domain.objects.get(id=pk)
# return true if the domain exists, this will allow the detail page to load
if requested_domain:
return True
return False
def _get_domain(self, request):
"""
override get_domain for this view so that domain overview
always resets the cache for the domain object
"""
self.session = request.session
self.object = self.get_object()
self._update_session_with_domain()
class DomainOrgNameAddressView(DomainFormBaseView):
"""Organization name and mailing address view"""

View file

@ -3,6 +3,7 @@
from django.contrib.auth.mixins import PermissionRequiredMixin
from registrar.models import (
Domain,
DomainApplication,
DomainInvitation,
DomainInformation,
@ -45,6 +46,10 @@ class DomainPermission(PermissionsLoginMixin):
if pk is None:
raise ValueError("Primary key is None")
# test if domain in editable state
if not self.in_editable_state(pk):
return False
if self.can_access_other_user_domains(pk):
return True
@ -55,6 +60,18 @@ class DomainPermission(PermissionsLoginMixin):
# if we need to check more about the nature of role, do it here.
return True
def in_editable_state(self, pk):
"""Is the domain in an editable state"""
requested_domain = None
if Domain.objects.filter(id=pk).exists():
requested_domain = Domain.objects.get(id=pk)
# if domain is editable return true
if requested_domain and requested_domain.is_editable():
return True
return False
def can_access_other_user_domains(self, pk):
"""Checks to see if an authorized user (staff or superuser)
can access a domain that they did not create or was invited to.