Skip to content

Commit

Permalink
Merge pull request #1633 from cisagov/za/1484-domain-manager-delete
Browse files Browse the repository at this point in the history
(On getgov-za) Ticket #1484: Domain manager delete
  • Loading branch information
zandercymatics authored Jan 31, 2024
2 parents 5d128bb + e72c6a5 commit fb03b6e
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 22 deletions.
16 changes: 16 additions & 0 deletions src/registrar/assets/sass/_theme/_buttons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ a.usa-button.disabled-link:focus {
color: #454545 !important
}

a.usa-button--unstyled.disabled-link,
a.usa-button--unstyled.disabled-link:hover,
a.usa-button--unstyled.disabled-link:focus {
cursor: not-allowed !important;
outline: none !important;
text-decoration: none !important;
}

.usa-button--unstyled.disabled-button,
.usa-button--unstyled.disabled-link:hover,
.usa-button--unstyled.disabled-link:focus {
cursor: not-allowed !important;
outline: none !important;
text-decoration: none !important;
}

a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
color: color('white');
}
Expand Down
5 changes: 5 additions & 0 deletions src/registrar/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@
views.DomainApplicationDeleteView.as_view(http_method_names=["post"]),
name="application-delete",
),
path(
"domain/<int:pk>/users/<int:user_pk>/delete",
views.DomainDeleteUserView.as_view(http_method_names=["post"]),
name="domain-user-delete",
),
]

# we normally would guard these with `if settings.DEBUG` but tests run with
Expand Down
26 changes: 13 additions & 13 deletions src/registrar/templates/dashboard_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
{% block wrapper %}

<div id="wrapper" class="dashboard">
{% block messages %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}" {% endif %}>
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

{% block section_nav %}{% endblock %}

{% block hero %}{% endblock %}
{% block content %}{% endblock %}
{% block content %}
{% block messages %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li {% if message.tags %} class="{{ message.tags }}" {% endif %}>
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}
{% endblock %}

<div role="complementary">{% block complementary %}{% endblock %}</div>

Expand Down
71 changes: 63 additions & 8 deletions src/registrar/templates/domain_users.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ <h1>Domain managers</h1>
<li>There is no limit to the number of domain managers you can add.</li>
<li>After adding a domain manager, an email invitation will be sent to that user with
instructions on how to set up an account.</li>
<li>To remove a domain manager, <a href="{% public_site_url 'contact/' %}"
target="_blank" rel="noopener noreferrer" class="usa-link">contact us</a> for
assistance.</li>
<li>All domain managers must keep their contact information updated and be responsive if contacted by the .gov team.</li>
<li>Domains must have at least one domain manager. You can’t remove yourself as a domain manager if you’re the only one assigned to this domain. Add another domain manager before you remove yourself from this domain.</li>
</ul>

{% if domain.permissions %}
Expand All @@ -30,7 +28,8 @@ <h2 class> Domain managers </h2>
<thead>
<tr>
<th data-sortable scope="col" role="columnheader">Email</th>
<th data-sortable scope="col" role="columnheader">Role</th>
<th class="grid-col-2" data-sortable scope="col" role="columnheader">Role</th>
<th class="grid-col-1" scope="col" role="columnheader"><span class="sr-only">Action</span></th>
</tr>
</thead>
<tbody>
Expand All @@ -40,6 +39,61 @@ <h2 class> Domain managers </h2>
{{ permission.user.email }}
</th>
<td data-label="Role">{{ permission.role|title }}</td>
<td>
{% if can_delete_users %}
<a
id="button-toggle-user-alert-{{ forloop.counter }}"
href="#toggle-user-alert-{{ forloop.counter }}"
class="usa-button--unstyled text-no-underline"
aria-controls="toggle-user-alert-{{ forloop.counter }}"
data-open-modal
aria-disabled="false"
>
Remove
</a>
{# Display a custom message if the user is trying to delete themselves #}
{% if permission.user.email == current_user_email %}
<div
class="usa-modal"
id="toggle-user-alert-{{ forloop.counter }}"
aria-labelledby="Are you sure you want to continue?"
aria-describedby="You will be removed from this domain"
data-force-action
>
<form method="POST" action="{% url "domain-user-delete" pk=domain.id user_pk=permission.user.id %}">
{% with domain_name=domain.name|force_escape %}
{% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager?" modal_description="You will no longer be able to manage the domain <strong>"|add:domain_name|add:"</strong>."|safe modal_button=modal_button_self|safe %}
{% endwith %}
</form>
</div>
{% else %}
<div
class="usa-modal"
id="toggle-user-alert-{{ forloop.counter }}"
aria-labelledby="Are you sure you want to continue?"
aria-describedby="{{ permission.user.email }} will be removed"
data-force-action
>
<form method="POST" action="{% url "domain-user-delete" pk=domain.id user_pk=permission.user.id %}">
{% with email=permission.user.email|default:permission.user|force_escape domain_name=domain.name|force_escape %}
{% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value=email|add:"?" modal_description="<strong>"|add:email|add:"</strong> will no longer be able to manage the domain <strong>"|add:domain_name|add:"</strong>."|safe modal_button=modal_button|safe %}
{% endwith %}
</form>
</div>
{% endif %}
{% else %}
<input
type="submit"
class="usa-button--unstyled disabled-button usa-tooltip"
value="Remove"
data-position="bottom"
title="Domains must have at least one domain manager"
data-tooltip="true"
aria-disabled="true"
role="button"
>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
Expand All @@ -66,8 +120,8 @@ <h2>Invitations</h2>
<tr>
<th data-sortable scope="col" role="columnheader">Email</th>
<th data-sortable scope="col" role="columnheader">Date created</th>
<th data-sortable scope="col" role="columnheader">Status</th>
<th scope="col" role="columnheader"><span class="sr-only">Action</span></th>
<th class="grid-col-2" data-sortable scope="col" role="columnheader">Status</th>
<th class="grid-col-1" scope="col" role="columnheader"><span class="sr-only">Action</span></th>
</tr>
</thead>
<tbody>
Expand All @@ -78,8 +132,9 @@ <h2>Invitations</h2>
</th>
<td data-sort-value="{{ invitation.created_at|date:"U" }}" data-label="Date created">{{ invitation.created_at|date }} </td>
<td data-label="Status">{{ invitation.status|title }}</td>
<td><form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
{% csrf_token %}<input type="submit" class="usa-button--unstyled" value="Cancel">
<td>
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline" value="Cancel">
</form>
</td>
</tr>
Expand Down
3 changes: 3 additions & 0 deletions src/registrar/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
{# the entire logged in page goes here #}

<div class="tablet:grid-col-11 desktop:grid-col-10 tablet:grid-offset-1">
{% block messages %}
{% include "includes/form_messages.html" %}
{% endblock %}
<h1>Manage your domains</h2>

<p class="margin-top-4">
Expand Down
2 changes: 1 addition & 1 deletion src/registrar/templates/includes/form_messages.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% for message in messages %}
<div class="usa-alert usa-alert--{{ message.tags }} usa-alert--slim margin-bottom-2">
<div class="usa-alert__body">
{{ message }}
{{ message }}
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/registrar/tests/test_url_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"content_type_id": "2",
"object_id": "3",
"domain": "whitehouse.gov",
"user_pk": "1",
}

# Our test suite will ignore some namespaces.
Expand Down
178 changes: 178 additions & 0 deletions src/registrar/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2662,6 +2662,7 @@ def tearDown(self):
super().tearDown()
self.user.is_staff = False
self.user.save()
User.objects.all().delete()

def test_domain_managers(self):
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
Expand All @@ -2677,6 +2678,183 @@ def test_domain_user_add(self):
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
self.assertContains(response, "Add a domain manager")

def test_domain_user_delete(self):
"""Tests if deleting a domain manager works"""

# Add additional users
dummy_user_1 = User.objects.create(
username="macncheese",
email="[email protected]",
)
dummy_user_2 = User.objects.create(
username="pastapizza",
email="[email protected]",
)

role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER)

response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))

# Make sure we're on the right page
self.assertContains(response, "Domain managers")

# Make sure the desired user exists
self.assertContains(response, "[email protected]")

# Delete dummy_user_1
response = self.client.post(
reverse("domain-user-delete", kwargs={"pk": self.domain.id, "user_pk": dummy_user_1.id}), follow=True
)

# Grab the displayed messages
messages = list(response.context["messages"])
self.assertEqual(len(messages), 1)

# Ensure the error we recieve is in line with what we expect
message = messages[0]
self.assertEqual(message.message, "Removed [email protected] as a manager for this domain.")
self.assertEqual(message.tags, "success")

# Check that role_1 deleted in the DB after the post
deleted_user_exists = UserDomainRole.objects.filter(id=role_1.id).exists()
self.assertFalse(deleted_user_exists)

# Ensure that the current user wasn't deleted
current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists()
self.assertTrue(current_user_exists)

# Ensure that the other userdomainrole was not deleted
role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists()
self.assertTrue(role_2_exists)

def test_domain_user_delete_denied_if_no_permission(self):
"""Deleting a domain manager is denied if the user has no permission to do so"""

# Create a domain object
vip_domain = Domain.objects.create(name="freeman.gov")

# Add users
dummy_user_1 = User.objects.create(
username="bagel",
email="[email protected]",
)
dummy_user_2 = User.objects.create(
username="pastapizza",
email="[email protected]",
)

role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=vip_domain, role=UserDomainRole.Roles.MANAGER)
role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=vip_domain, role=UserDomainRole.Roles.MANAGER)

response = self.client.get(reverse("domain-users", kwargs={"pk": vip_domain.id}))

# Make sure that we can't access the domain manager page normally
self.assertEqual(response.status_code, 403)

# Try to delete dummy_user_1
response = self.client.post(
reverse("domain-user-delete", kwargs={"pk": vip_domain.id, "user_pk": dummy_user_1.id}), follow=True
)

# Ensure that we are denied access
self.assertEqual(response.status_code, 403)

# Ensure that the user wasn't deleted
role_1_exists = UserDomainRole.objects.filter(id=role_1.id).exists()
self.assertTrue(role_1_exists)

# Ensure that the other userdomainrole was not deleted
role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists()
self.assertTrue(role_2_exists)

# Make sure that the current user wasn't deleted for some reason
current_user_exists = UserDomainRole.objects.filter(user=dummy_user_1.id, domain=vip_domain.id).exists()
self.assertTrue(current_user_exists)

def test_domain_user_delete_denied_if_last_man_standing(self):
"""Deleting a domain manager is denied if the user is the only manager"""

# Create a domain object
vip_domain = Domain.objects.create(name="olive-oil.gov")

# Add the requesting user as the only manager on the domain
UserDomainRole.objects.create(user=self.user, domain=vip_domain, role=UserDomainRole.Roles.MANAGER)

response = self.client.get(reverse("domain-users", kwargs={"pk": vip_domain.id}))

# Make sure that we can still access the domain manager page normally
self.assertContains(response, "Domain managers")

# Make sure that the logged in user exists
self.assertContains(response, "[email protected]")

# Try to delete the current user
response = self.client.post(
reverse("domain-user-delete", kwargs={"pk": vip_domain.id, "user_pk": self.user.id}), follow=True
)

# Ensure that we are denied access
self.assertEqual(response.status_code, 403)

# Make sure that the current user wasn't deleted
current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=vip_domain.id).exists()
self.assertTrue(current_user_exists)

def test_domain_user_delete_self_redirects_home(self):
"""Tests if deleting yourself redirects to home"""
# Add additional users
dummy_user_1 = User.objects.create(
username="macncheese",
email="[email protected]",
)
dummy_user_2 = User.objects.create(
username="pastapizza",
email="[email protected]",
)

role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER)

response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))

# Make sure we're on the right page
self.assertContains(response, "Domain managers")

# Make sure the desired user exists
self.assertContains(response, "[email protected]")

# Make sure more than one UserDomainRole exists on this object
self.assertContains(response, "[email protected]")

# Delete the current user
response = self.client.post(
reverse("domain-user-delete", kwargs={"pk": self.domain.id, "user_pk": self.user.id}), follow=True
)

# Check if we've been redirected to the home page
self.assertContains(response, "Manage your domains")

# Grab the displayed messages
messages = list(response.context["messages"])
self.assertEqual(len(messages), 1)

# Ensure the error we recieve is in line with what we expect
message = messages[0]
self.assertEqual(message.message, "You are no longer managing the domain igorville.gov.")
self.assertEqual(message.tags, "success")

# Ensure that the current user was deleted
current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists()
self.assertFalse(current_user_exists)

# Ensure that the other userdomainroles are not deleted
role_1_exists = UserDomainRole.objects.filter(id=role_1.id).exists()
self.assertTrue(role_1_exists)

role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists()
self.assertTrue(role_2_exists)

@boto3_mocking.patching
def test_domain_user_add_form(self):
"""Adding an existing user works."""
Expand Down
Loading

0 comments on commit fb03b6e

Please sign in to comment.