This commit is contained in:
David Kennedy 2025-02-11 23:47:55 -05:00
parent 6269cc56e3
commit 40737cbcf7
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
8 changed files with 49 additions and 56 deletions

View file

@ -10,8 +10,10 @@ IS_STAFF = "is_staff"
IS_DOMAIN_MANAGER = "is_domain_manager"
IS_DOMAIN_REQUEST_CREATOR = "is_domain_request_creator"
IS_STAFF_MANAGING_DOMAIN = "is_staff_managing_domain"
IS_PORTFOLIO_MEMBER = "is_portfolio_member"
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_ANY_PERM = "has_portfolio_domains_any_perm"
HAS_PORTFOLIO_DOMAINS_VIEW_ALL = "has_portfolio_domains_view_all"
HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM = "has_portfolio_domain_requests_any_perm"
HAS_PORTFOLIO_DOMAIN_REQUESTS_VIEW_ALL = "has_portfolio_domain_requests_view_all"
@ -97,11 +99,21 @@ def _user_has_permission(user, request, rules, **kwargs):
has_permission = _can_access_other_user_domains(request, domain_id)
conditions_met.append(has_permission)
if not any(conditions_met) and IS_PORTFOLIO_MEMBER in rules:
has_permission = user.is_org_user(request)
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 HAS_PORTFOLIO_DOMAINS_ANY_PERM in rules:
has_permission = user.is_org_user(request) and user.has_any_domains_portfolio_permission(
request.session.get("portfolio")
)
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)

View file

@ -1,6 +1,6 @@
<li class="usa-sidenav__item">
{% if url_name %}
{% url url_name pk=domain.id as url %}
{% url url_name domain_pk=domain.id as url %}
{% endif %}
<a href="{{ url }}"
{% if request.path == url %}class="usa-current"{% endif %}

View file

@ -13,6 +13,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render, get_object_or_404
from django.urls import reverse
from django.views.generic import DeleteView
from django.views.generic.edit import FormMixin
from django.conf import settings
from registrar.decorators import (
@ -1332,10 +1333,12 @@ class DomainInvitationCancelView(SuccessMessageMixin, DomainInvitationPermission
@grant_access(IS_DOMAIN_MANAGER, IS_STAFF_MANAGING_DOMAIN)
class DomainDeleteUserView(UserDomainRolePermissionDeleteView):
class DomainDeleteUserView(DeleteView):
"""Inside of a domain's user management, a form for deleting users."""
object: UserDomainRole # workaround for type mismatch in DeleteView
object: UserDomainRole
model = UserDomainRole
context_object_name = "userdomainrole"
def get_object(self, queryset=None):
"""Custom get_object definition to grab a UserDomainRole object from a domain_id and user_id"""

View file

@ -15,20 +15,20 @@ def get_domain_requests_json(request):
If we are on the portfolio requests page, limit the response to only those requests associated with
the given portfolio."""
domain_request_ids = get_domain_request_ids_from_request(request)
domain_request_ids = _get_domain_request_ids_from_request(request)
objects = DomainRequest.objects.filter(id__in=domain_request_ids)
unfiltered_total = objects.count()
objects = apply_search(objects, request)
objects = apply_status_filter(objects, request)
objects = apply_sorting(objects, request)
objects = _apply_search(objects, request)
objects = _apply_status_filter(objects, request)
objects = _apply_sorting(objects, request)
paginator = Paginator(objects, 10)
page_number = request.GET.get("page", 1)
page_obj = paginator.get_page(page_number)
domain_requests = [
serialize_domain_request(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(
@ -44,7 +44,7 @@ def get_domain_requests_json(request):
)
def get_domain_request_ids_from_request(request):
def _get_domain_request_ids_from_request(request):
"""Get domain request ids from request.
If portfolio specified, return domain request ids associated with portfolio.
@ -63,7 +63,7 @@ def get_domain_request_ids_from_request(request):
return domain_requests.values_list("id", flat=True)
def apply_search(queryset, request):
def _apply_search(queryset, request):
search_term = request.GET.get("search_term")
is_portfolio = request.GET.get("portfolio")
@ -91,7 +91,7 @@ def apply_search(queryset, request):
return queryset
def apply_status_filter(queryset, request):
def _apply_status_filter(queryset, request):
status_param = request.GET.get("status")
if status_param:
status_list = status_param.split(",")
@ -106,7 +106,7 @@ def apply_status_filter(queryset, request):
return queryset
def apply_sorting(queryset, request):
def _apply_sorting(queryset, request):
sort_by = request.GET.get("sort_by", "id") # Default to 'id'
order = request.GET.get("order", "asc") # Default to 'asc'
@ -119,7 +119,7 @@ def apply_sorting(queryset, request):
return queryset.order_by(sort_by)
def serialize_domain_request(request, domain_request, user):
def _serialize_domain_request(request, domain_request, user):
deletable_statuses = [
DomainRequest.DomainRequestStatus.STARTED,

View file

@ -6,7 +6,12 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.contrib import messages
from registrar.decorators import HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM, grant_access
from registrar.decorators import (
HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM,
HAS_PORTFOLIO_DOMAINS_ANY_PERM,
IS_PORTFOLIO_MEMBER,
grant_access,
)
from registrar.forms import portfolio as portfolioForms
from registrar.models import Portfolio, User
from registrar.models.domain import Domain
@ -26,9 +31,7 @@ from registrar.utility.errors import MissingEmailError
from registrar.utility.enums import DefaultUserValues
from registrar.views.utility.mixins import PortfolioMemberPermission
from registrar.views.utility.permission_views import (
PortfolioDomainsPermissionView,
PortfolioBasePermissionView,
NoPortfolioDomainsPermissionView,
PortfolioMemberDomainsPermissionView,
PortfolioMemberDomainsEditPermissionView,
PortfolioMemberEditPermissionView,
@ -45,7 +48,8 @@ from registrar.views.utility.invitation_helper import get_org_membership
logger = logging.getLogger(__name__)
class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
@grant_access(HAS_PORTFOLIO_DOMAINS_ANY_PERM)
class PortfolioDomainsView(View):
template_name = "portfolio_domains.html"
@ -685,7 +689,8 @@ class PortfolioInvitedMemberDomainsEditView(PortfolioMemberDomainsEditPermission
).update(status=DomainInvitation.DomainInvitationStatus.CANCELED)
class PortfolioNoDomainsView(NoPortfolioDomainsPermissionView, View):
@grant_access(IS_PORTFOLIO_MEMBER)
class PortfolioNoDomainsView(View):
"""Some users have access to the underlying portfolio, but not any domains.
This is a custom view which explains that to the user - and denotes who to contact.
"""
@ -714,7 +719,8 @@ class PortfolioNoDomainsView(NoPortfolioDomainsPermissionView, View):
return context
class PortfolioNoDomainRequestsView(NoPortfolioDomainsPermissionView, View):
@grant_access(IS_PORTFOLIO_MEMBER)
class PortfolioNoDomainRequestsView(View):
"""Some users have access to the underlying portfolio, but not any domain requests.
This is a custom view which explains that to the user - and denotes who to contact.
"""

View file

@ -373,23 +373,6 @@ class PortfolioBasePermission(PermissionsLoginMixin):
return self.request.user.is_org_user(self.request)
class PortfolioDomainsPermission(PortfolioBasePermission):
"""Permission mixin that allows access to portfolio domain pages if user
has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to domains for this portfolio.
The user is in self.request.user and the portfolio can be looked
up from the portfolio's primary key in self.kwargs["pk"]"""
portfolio = self.request.session.get("portfolio")
if not self.request.user.has_any_domains_portfolio_permission(portfolio):
return False
return super().has_permission()
class PortfolioMembersPermission(PortfolioBasePermission):
"""Permission mixin that allows access to portfolio members pages if user
has access, otherwise 403"""

View file

@ -10,7 +10,6 @@ from registrar.models.user_domain_role import UserDomainRole
from .mixins import (
DomainPermission,
DomainInvitationPermission,
PortfolioDomainsPermission,
PortfolioMemberDomainsPermission,
PortfolioMemberDomainsEditPermission,
PortfolioMemberEditPermission,
@ -74,6 +73,13 @@ class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
return False
def can_access_domain_via_portfolio(self, pk):
"""Most views should not allow permission to portfolio users.
If particular views allow access to the domain pages, they will need to override
this function.
"""
return False
# Abstract property enforces NotImplementedError on an attribute.
@property
@abc.abstractmethod
@ -142,23 +148,6 @@ class PortfolioBasePermissionView(PortfolioBasePermission, DetailView, abc.ABC):
raise NotImplementedError
class PortfolioDomainsPermissionView(PortfolioDomainsPermission, PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for portfolio domains views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
class NoPortfolioDomainsPermissionView(PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for a user without access to the
portfolio domains views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
class PortfolioMembersPermissionView(PortfolioMembersPermission, PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for portfolio members views that enforces permissions.