diff --git a/src/registrar/templates/portfolio_requests.html b/src/registrar/templates/portfolio_requests.html index 868c9bd2a7..70d17feaeb 100644 --- a/src/registrar/templates/portfolio_requests.html +++ b/src/registrar/templates/portfolio_requests.html @@ -12,10 +12,11 @@

Domain requests

-
-

Domain requests can only be modified by the person who created the request.

-
+ {% if has_edit_request_portfolio_permission %} +
+

Domain requests can only be modified by the person who created the request.

+
{% comment %} IMPORTANT: @@ -29,6 +30,8 @@

Domain requests

+ {% else %} +

Domain requests can only be modified by the person who created the request.

{% endif %}
diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py index aaafa32629..b8392a3709 100644 --- a/src/registrar/tests/test_views_portfolio.py +++ b/src/registrar/tests/test_views_portfolio.py @@ -837,3 +837,106 @@ def test_portfolio_cache_updates_when_flag_disabled_while_logged_in(self): response = self.client.get(reverse("home")) self.assertIsNone(self.client.session.get("portfolio")) self.assertNotContains(response, "Hotel California") + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + def test_org_user_can_delete_own_domain_request_with_permission(self): + """Test that an org user with edit permission can delete their own DomainRequest with a deletable status.""" + + # Assign the user to a portfolio with edit permission + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS], + ) + + # Create a domain request with status WITHDRAWN + domain_request = completed_domain_request( + name="test-domain.gov", + status=DomainRequest.DomainRequestStatus.WITHDRAWN, + portfolio=self.portfolio, + ) + domain_request.creator = self.user + domain_request.save() + + self.client.force_login(self.user) + # Perform delete + response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) + + # Check that the response is 200 + self.assertEqual(response.status_code, 200) + + # Check that the domain request no longer exists + self.assertFalse(DomainRequest.objects.filter(pk=domain_request.pk).exists()) + domain_request.delete() + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + def test_delete_domain_request_as_org_user_without_permission_with_deletable_status(self): + """Test that an org user without edit permission cant delete their DomainRequest even if status is deletable.""" + + # Assign the user to a portfolio without edit permission + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + additional_permissions=[], + ) + + # Create a domain request with status STARTED + domain_request = completed_domain_request( + name="test-domain.gov", + status=DomainRequest.DomainRequestStatus.STARTED, + portfolio=self.portfolio, + ) + domain_request.creator = self.user + domain_request.save() + + self.client.force_login(self.user) + # Attempt to delete + response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) + + # Check response is 403 Forbidden + self.assertEqual(response.status_code, 403) + + # Check that the domain request still exists + self.assertTrue(DomainRequest.objects.filter(pk=domain_request.pk).exists()) + domain_request.delete() + + @less_console_noise_decorator + @override_flag("organization_feature", active=True) + @override_flag("organization_requests", active=True) + def test_org_user_cannot_delete_others_domain_requests(self): + """Test that an org user with edit permission cannot delete DomainRequests they did not create.""" + + # Assign the user to a portfolio with edit permission + UserPortfolioPermission.objects.get_or_create( + user=self.user, + portfolio=self.portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER], + additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS], + ) + + # Create another user and a domain request + other_user = User.objects.create(username="other_user") + domain_request = completed_domain_request( + name="test-domain.gov", + status=DomainRequest.DomainRequestStatus.STARTED, + portfolio=self.portfolio, + ) + domain_request.creator = other_user + domain_request.save() + + self.client.force_login(self.user) + # Perform delete as self.user + response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) + + # Check response is 403 Forbidden + self.assertEqual(response.status_code, 403) + + # Check that the domain request still exists + self.assertTrue(DomainRequest.objects.filter(pk=domain_request.pk).exists()) + domain_request.delete() diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 0abe6d69a1..5fed892156 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -796,6 +796,12 @@ def has_permission(self): if status not in valid_statuses: return False + # Portfolio users cannot delete their requests if they aren't permissioned to do so + if self.request.user.is_org_user(self.request): + portfolio = self.request.session.get("portfolio") + if not self.request.user.has_edit_request_portfolio_permission(portfolio): + return False + return True def get_success_url(self): diff --git a/src/registrar/views/domain_requests_json.py b/src/registrar/views/domain_requests_json.py index 7b86cd9ef5..bc880cdafb 100644 --- a/src/registrar/views/domain_requests_json.py +++ b/src/registrar/views/domain_requests_json.py @@ -25,9 +25,8 @@ def get_domain_requests_json(request): paginator = Paginator(objects, 10) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) - domain_requests = [ - serialize_domain_request(domain_request, request.user) for domain_request in page_obj.object_list + serialize_domain_request(request, domain_request, request.user) for domain_request in page_obj.object_list ] return JsonResponse( @@ -90,13 +89,22 @@ def apply_sorting(queryset, request): return queryset.order_by(sort_by) -def serialize_domain_request(domain_request, user): - # Determine if the request is deletable - is_deletable = domain_request.status in [ +def serialize_domain_request(request, domain_request, user): + + deletable_statuses = [ DomainRequest.DomainRequestStatus.STARTED, DomainRequest.DomainRequestStatus.WITHDRAWN, ] + # Determine if the request is deletable + if not user.is_org_user(request): + is_deletable = domain_request.status in deletable_statuses + else: + portfolio = request.session.get("portfolio") + is_deletable = ( + domain_request.status in deletable_statuses and user.has_edit_request_portfolio_permission(portfolio) + ) and domain_request.creator == user + # Determine action label based on user permissions and request status editable_statuses = [ DomainRequest.DomainRequestStatus.STARTED,