Merge pull request #1550 from cisagov/es/993-restrict-withdrawing-request

#993 Prevent restricted users from withdrawing domain application
This commit is contained in:
Erin Song 2023-12-21 09:56:14 -08:00 committed by GitHub
commit c22aff5531
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 8 deletions

View file

@ -114,7 +114,7 @@ class UserGroup(Group):
)
cisa_analysts_group.save()
logger.debug("CISA Analyt permissions added to group " + cisa_analysts_group.name)
logger.debug("CISA Analyst permissions added to group " + cisa_analysts_group.name)
except Exception as e:
logger.error(f"Error creating analyst permissions group: {e}")

View file

@ -2225,6 +2225,33 @@ class TestApplicationStatus(TestWithUser, WebTest):
home_page = self.app.get("/")
self.assertContains(home_page, "Withdrawn")
def test_application_withdraw_no_permissions(self):
"""Can't withdraw applications as a restricted user."""
self.user.status = User.RESTRICTED
self.user.save()
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)
application.save()
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Manage" link
detail_page = home_page.click("Manage", index=0)
self.assertContains(detail_page, "city.gov")
self.assertContains(detail_page, "city1.gov")
self.assertContains(detail_page, "Chief Tester")
self.assertContains(detail_page, "testy@town.com")
self.assertContains(detail_page, "Admin Tester")
self.assertContains(detail_page, "Status:")
# Restricted user trying to withdraw results in 403 error
with less_console_noise():
for url_name in [
"application-withdraw-confirmation",
"application-withdrawn",
]:
with self.subTest(url_name=url_name):
page = self.client.get(reverse(url_name, kwargs={"pk": application.pk}))
self.assertEqual(page.status_code, 403)
def test_application_status_no_permissions(self):
"""Can't access applications without being the creator."""
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user)

View file

@ -13,7 +13,11 @@ from registrar.models import DomainApplication
from registrar.utility import StrEnum
from registrar.views.utility import StepsHelper
from .utility import DomainApplicationPermissionView, ApplicationWizardPermissionView
from .utility import (
DomainApplicationPermissionView,
DomainApplicationPermissionWithdrawView,
ApplicationWizardPermissionView,
)
logger = logging.getLogger(__name__)
@ -544,7 +548,7 @@ class ApplicationStatus(DomainApplicationPermissionView):
template_name = "application_status.html"
class ApplicationWithdrawConfirmation(DomainApplicationPermissionView):
class ApplicationWithdrawConfirmation(DomainApplicationPermissionWithdrawView):
"""This page will ask user to confirm if they want to withdraw
The DomainApplicationPermissionView restricts access so that only the
@ -554,7 +558,7 @@ class ApplicationWithdrawConfirmation(DomainApplicationPermissionView):
template_name = "application_withdraw_confirmation.html"
class ApplicationWithdrawn(DomainApplicationPermissionView):
class ApplicationWithdrawn(DomainApplicationPermissionWithdrawView):
# this view renders no template
template_name = ""

View file

@ -4,6 +4,7 @@ from .always_404 import always_404
from .permission_views import (
DomainPermissionView,
DomainApplicationPermissionView,
DomainApplicationPermissionWithdrawView,
DomainInvitationPermissionDeleteView,
ApplicationWizardPermissionView,
)

View file

@ -26,7 +26,8 @@ class PermissionsLoginMixin(PermissionRequiredMixin):
class DomainPermission(PermissionsLoginMixin):
"""Does the logged-in user have access to this domain?"""
"""Permission mixin that redirects to domain if user has access,
otherwise 403"""
def has_permission(self):
"""Check if this user has access to this domain.
@ -134,7 +135,8 @@ class DomainPermission(PermissionsLoginMixin):
class DomainApplicationPermission(PermissionsLoginMixin):
"""Does the logged-in user have access to this domain application?"""
"""Permission mixin that redirects to domain application if user
has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to this domain application.
@ -154,9 +156,33 @@ class DomainApplicationPermission(PermissionsLoginMixin):
return True
class DomainApplicationPermissionWithdraw(PermissionsLoginMixin):
"""Permission mixin that redirects to withdraw action on domain application
if user has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to withdraw this domain application."""
if not self.request.user.is_authenticated:
return False
# user needs to be the creator of the application
# this query is empty if there isn't a domain application with this
# id and this user as creator
if not DomainApplication.objects.filter(creator=self.request.user, id=self.kwargs["pk"]).exists():
return False
# Restricted users should not be able to withdraw domain requests
if self.request.user.is_restricted():
return False
return True
class ApplicationWizardPermission(PermissionsLoginMixin):
"""Does the logged-in user have permission to start or edit an application?"""
"""Permission mixin that redirects to start or edit domain application if
user has access, otherwise 403"""
def has_permission(self):
"""Check if this user has permission to start or edit an application.
@ -173,7 +199,8 @@ class ApplicationWizardPermission(PermissionsLoginMixin):
class DomainInvitationPermission(PermissionsLoginMixin):
"""Does the logged-in user have access to this domain invitation?
"""Permission mixin that redirects to domain invitation if user has
access, otherwise 403"
A user has access to a domain invitation if they have a role on the
associated domain.

View file

@ -8,6 +8,7 @@ from registrar.models import Domain, DomainApplication, DomainInvitation
from .mixins import (
DomainPermission,
DomainApplicationPermission,
DomainApplicationPermissionWithdraw,
DomainInvitationPermission,
ApplicationWizardPermission,
)
@ -74,6 +75,26 @@ class DomainApplicationPermissionView(DomainApplicationPermission, DetailView, a
raise NotImplementedError
class DomainApplicationPermissionWithdrawView(DomainApplicationPermissionWithdraw, DetailView, abc.ABC):
"""Abstract base view for domain application withdraw function
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
# DetailView property for what model this is viewing
model = DomainApplication
# variable name in template context for the model object
context_object_name = "domainapplication"
# Abstract property enforces NotImplementedError on an attribute.
@property
@abc.abstractmethod
def template_name(self):
raise NotImplementedError
class ApplicationWizardPermissionView(ApplicationWizardPermission, TemplateView, abc.ABC):
"""Abstract base view for the application form that enforces permissions