decorators on domain views

This commit is contained in:
David Kennedy 2025-02-11 11:38:45 -05:00
parent a334f7dc3e
commit 3f2ceb81e8
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
6 changed files with 90 additions and 19 deletions

View file

@ -297,7 +297,11 @@ urlpatterns = [
name="todo", name="todo",
), ),
path("domain/<int:domain_pk>", views.DomainView.as_view(), name="domain"), path("domain/<int:domain_pk>", views.DomainView.as_view(), name="domain"),
path("domain/<int:domain_pk>/prototype-dns", views.PrototypeDomainDNSRecordView.as_view(), name="prototype-domain-dns"), path(
"domain/<int:domain_pk>/prototype-dns",
views.PrototypeDomainDNSRecordView.as_view(),
name="prototype-domain-dns",
),
path("domain/<int:domain_pk>/users", views.DomainUsersView.as_view(), name="domain-users"), path("domain/<int:domain_pk>/users", views.DomainUsersView.as_view(), name="domain-users"),
path( path(
"domain/<int:domain_pk>/dns", "domain/<int:domain_pk>/dns",

View file

@ -1,7 +1,7 @@
import functools import functools
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from registrar.models import DomainInformation, DomainRequest, UserDomainRole from registrar.models import Domain, DomainInformation, DomainRequest, UserDomainRole
# Constants for clarity # Constants for clarity
ALL = "all" ALL = "all"
@ -9,6 +9,11 @@ IS_SUPERUSER = "is_superuser"
IS_STAFF = "is_staff" IS_STAFF = "is_staff"
IS_DOMAIN_MANAGER = "is_domain_manager" IS_DOMAIN_MANAGER = "is_domain_manager"
IS_STAFF_MANAGING_DOMAIN = "is_staff_managing_domain" IS_STAFF_MANAGING_DOMAIN = "is_staff_managing_domain"
IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER = "is_portfolio_member_and_domain_manager"
IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER = "is_domain_manager_and_not_portfolio_member"
HAS_PORTFOLIO_DOMAINS_VIEW_ALL = "has_portfolio_domains_view_all"
# HAS_PORTFOLIO_DOMAINS_VIEW_MANAGED = "has_portfolio_domains_view_managed"
def grant_access(*rules): def grant_access(*rules):
""" """
@ -75,19 +80,45 @@ def _user_has_permission(user, request, rules, **kwargs):
conditions_met.append(user.is_superuser) conditions_met.append(user.is_superuser)
if not any(conditions_met) and IS_DOMAIN_MANAGER in rules: if not any(conditions_met) and IS_DOMAIN_MANAGER in rules:
domain_id = kwargs.get('domain_pk') domain_id = kwargs.get("domain_pk")
# Check UserDomainRole directly instead of fetching Domain has_permission = _is_domain_manager(user, domain_id)
has_permission = UserDomainRole.objects.filter(user=user, domain_id=domain_id).exists()
conditions_met.append(has_permission) conditions_met.append(has_permission)
if not any(conditions_met) and IS_STAFF_MANAGING_DOMAIN in rules: if not any(conditions_met) and IS_STAFF_MANAGING_DOMAIN in rules:
domain_id = kwargs.get('domain_pk') domain_id = kwargs.get("domain_pk")
has_permission = _can_access_other_user_domains(request, domain_id) has_permission = _can_access_other_user_domains(request, domain_id)
conditions_met.append(has_permission) conditions_met.append(has_permission)
if not any(conditions_met) and HAS_PORTFOLIO_DOMAINS_VIEW_ALL in rules:
domain_id = kwargs.get("domain_pk")
has_permission = _can_access_domain_via_portfolio_view_all_domains(request, domain_id)
conditions_met.append(has_permission)
if not any(conditions_met) and IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER in rules:
domain_id = kwargs.get("domain_pk")
has_permission = _is_domain_manager(user, domain_id) and _is_portfolio_member(request)
conditions_met.append(has_permission)
if not any(conditions_met) and IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER in rules:
domain_id = kwargs.get("domain_pk")
has_permission = _is_domain_manager(user, domain_id) and not _is_portfolio_member(request)
conditions_met.append(has_permission)
return any(conditions_met) return any(conditions_met)
def _is_domain_manager(user, domain_pk):
"""Checks to see if the user is a domain manager of the
domain with domain_pk."""
return UserDomainRole.objects.filter(user=user, domain_id=domain_pk).exists()
def _is_portfolio_member(request):
"""Checks to see if the user in the request is a member of the
portfolio in the request's session."""
return request.user.is_org_user(request)
def _can_access_other_user_domains(request, domain_pk): def _can_access_other_user_domains(request, domain_pk):
"""Checks to see if an authorized user (staff or superuser) """Checks to see if an authorized user (staff or superuser)
can access a domain that they did not create or were invited to. can access a domain that they did not create or were invited to.
@ -144,3 +175,17 @@ def _can_access_other_user_domains(request, domain_pk):
# the user is permissioned, # the user is permissioned,
# and it is in a valid status # and it is in a valid status
return True return True
def _can_access_domain_via_portfolio_view_all_domains(request, domain_pk):
"""Returns whether the user in the request can access the domain
via portfolio view all domains permission."""
# NOTE: determine if in practice this ever needs to be called on its own
# or if it can be combined with view_managed_domains
portfolio = request.session.get("portfolio")
if request.user.has_view_all_domains_portfolio_permission(portfolio):
if Domain.objects.filter(id=domain_pk).exists():
domain = Domain.objects.get(id=domain_pk)
if domain.domain_info.portfolio == portfolio:
return True
return False

View file

@ -15,7 +15,14 @@ from django.shortcuts import redirect, render, get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django.conf import settings from django.conf import settings
from registrar.decorators import IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN, grant_access from registrar.decorators import (
HAS_PORTFOLIO_DOMAINS_VIEW_ALL,
IS_DOMAIN_MANAGER,
IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER,
IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER,
IS_STAFF_MANAGING_DOMAIN,
grant_access,
)
from registrar.forms.domain import DomainSuborganizationForm, DomainRenewalForm from registrar.forms.domain import DomainSuborganizationForm, DomainRenewalForm
from registrar.models import ( from registrar.models import (
Domain, Domain,
@ -257,7 +264,8 @@ class DomainFormBaseView(DomainBaseView, FormMixin):
exc_info=True, exc_info=True,
) )
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN, HAS_PORTFOLIO_DOMAINS_VIEW_ALL)
class DomainView(DomainBaseView): class DomainView(DomainBaseView):
"""Domain detail overview page.""" """Domain detail overview page."""
@ -311,6 +319,7 @@ class DomainView(DomainBaseView):
self.object = self.get_object() self.object = self.get_object()
self._update_session_with_domain() self._update_session_with_domain()
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN) @grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainRenewalView(DomainBaseView): class DomainRenewalView(DomainBaseView):
"""Domain detail overview page.""" """Domain detail overview page."""
@ -380,6 +389,7 @@ class DomainRenewalView(DomainBaseView):
}, },
) )
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN) @grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainOrgNameAddressView(DomainFormBaseView): class DomainOrgNameAddressView(DomainFormBaseView):
"""Organization view""" """Organization view"""
@ -422,6 +432,7 @@ class DomainOrgNameAddressView(DomainFormBaseView):
return super().has_permission() return super().has_permission()
@grant_access(IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER)
class DomainSubOrganizationView(DomainFormBaseView): class DomainSubOrganizationView(DomainFormBaseView):
"""Suborganization view""" """Suborganization view"""
@ -468,6 +479,7 @@ class DomainSubOrganizationView(DomainFormBaseView):
return super().form_valid(form) return super().form_valid(form)
@grant_access(IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER)
class DomainSeniorOfficialView(DomainFormBaseView): class DomainSeniorOfficialView(DomainFormBaseView):
"""Domain senior official editing view.""" """Domain senior official editing view."""
@ -525,6 +537,7 @@ class DomainSeniorOfficialView(DomainFormBaseView):
return super().has_permission() return super().has_permission()
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainDNSView(DomainBaseView): class DomainDNSView(DomainBaseView):
"""DNS Information View.""" """DNS Information View."""
@ -741,6 +754,7 @@ class PrototypeDomainDNSRecordView(DomainFormBaseView):
return super().post(request) return super().post(request)
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainNameserversView(DomainFormBaseView): class DomainNameserversView(DomainFormBaseView):
"""Domain nameserver editing view.""" """Domain nameserver editing view."""
@ -868,6 +882,7 @@ class DomainNameserversView(DomainFormBaseView):
return super().form_valid(formset) return super().form_valid(formset)
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainDNSSECView(DomainFormBaseView): class DomainDNSSECView(DomainFormBaseView):
"""Domain DNSSEC editing view.""" """Domain DNSSEC editing view."""
@ -905,6 +920,7 @@ class DomainDNSSECView(DomainFormBaseView):
return self.form_valid(form) return self.form_valid(form)
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainDsDataView(DomainFormBaseView): class DomainDsDataView(DomainFormBaseView):
"""Domain DNSSEC ds data editing view.""" """Domain DNSSEC ds data editing view."""
@ -1023,6 +1039,7 @@ class DomainDsDataView(DomainFormBaseView):
return super().form_valid(formset) return super().form_valid(formset)
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainSecurityEmailView(DomainFormBaseView): class DomainSecurityEmailView(DomainFormBaseView):
"""Domain security email editing view.""" """Domain security email editing view."""
@ -1094,6 +1111,7 @@ class DomainSecurityEmailView(DomainFormBaseView):
return redirect(self.get_success_url()) return redirect(self.get_success_url())
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainUsersView(DomainBaseView): class DomainUsersView(DomainBaseView):
"""Domain managers page in the domain details.""" """Domain managers page in the domain details."""
@ -1189,6 +1207,7 @@ class DomainUsersView(DomainBaseView):
return context return context
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainAddUserView(DomainFormBaseView): class DomainAddUserView(DomainFormBaseView):
"""Inside of a domain's user management, a form for adding users. """Inside of a domain's user management, a form for adding users.
@ -1286,6 +1305,7 @@ class DomainAddUserView(DomainFormBaseView):
messages.success(self.request, f"Added user {email}.") messages.success(self.request, f"Added user {email}.")
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainInvitationCancelView(SuccessMessageMixin, DomainInvitationPermissionCancelView): class DomainInvitationCancelView(SuccessMessageMixin, DomainInvitationPermissionCancelView):
object: DomainInvitation object: DomainInvitation
fields = [] fields = []
@ -1311,6 +1331,7 @@ class DomainInvitationCancelView(SuccessMessageMixin, DomainInvitationPermission
return f"Canceled invitation to {self.object.email}." return f"Canceled invitation to {self.object.email}."
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainDeleteUserView(UserDomainRolePermissionDeleteView): class DomainDeleteUserView(UserDomainRolePermissionDeleteView):
"""Inside of a domain's user management, a form for deleting users.""" """Inside of a domain's user management, a form for deleting users."""

View file

@ -2,6 +2,7 @@ from django.shortcuts import render
from registrar.decorators import grant_access, ALL from registrar.decorators import grant_access, ALL
@grant_access(ALL) @grant_access(ALL)
def index(request): def index(request):
"""This page is available to anyone without logging in.""" """This page is available to anyone without logging in."""