mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-13 21:19:42 +02:00
updated comments
This commit is contained in:
parent
ed683baaab
commit
37c6731001
2 changed files with 56 additions and 30 deletions
|
@ -24,37 +24,43 @@ HAS_PORTFOLIO_MEMBERS_VIEW = "has_portfolio_members_view"
|
||||||
|
|
||||||
def grant_access(*rules):
|
def grant_access(*rules):
|
||||||
"""
|
"""
|
||||||
Allows multiple rules in a single decorator call:
|
A decorator that enforces access control based on specified rules.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
- Multiple rules in a single decorator:
|
||||||
@grant_access(IS_STAFF, IS_SUPERUSER, IS_DOMAIN_MANAGER)
|
@grant_access(IS_STAFF, IS_SUPERUSER, IS_DOMAIN_MANAGER)
|
||||||
or multiple stacked decorators:
|
|
||||||
|
- Stacked decorators for separate rules:
|
||||||
@grant_access(IS_SUPERUSER)
|
@grant_access(IS_SUPERUSER)
|
||||||
@grant_access(IS_DOMAIN_MANAGER)
|
@grant_access(IS_DOMAIN_MANAGER)
|
||||||
|
|
||||||
|
The decorator supports both function-based views (FBVs) and class-based views (CBVs).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(view):
|
def decorator(view):
|
||||||
if isinstance(view, type): # If decorating a class-based view (CBV)
|
if isinstance(view, type): # Check if decorating a class-based view (CBV)
|
||||||
original_dispatch = view.dispatch # save original dispatch method
|
original_dispatch = view.dispatch # Store the original dispatch method
|
||||||
|
|
||||||
@method_decorator(grant_access(*rules)) # apply the decorator to dispatch
|
@method_decorator(grant_access(*rules)) # Apply the decorator to dispatch
|
||||||
def wrapped_dispatch(self, request, *args, **kwargs):
|
def wrapped_dispatch(self, request, *args, **kwargs):
|
||||||
if not _user_has_permission(request.user, request, rules, **kwargs):
|
if not _user_has_permission(request.user, request, rules, **kwargs):
|
||||||
raise PermissionDenied
|
raise PermissionDenied # Deny access if the user lacks permission
|
||||||
return original_dispatch(self, request, *args, **kwargs)
|
return original_dispatch(self, request, *args, **kwargs)
|
||||||
|
|
||||||
view.dispatch = wrapped_dispatch # replace dispatch with wrapped version
|
view.dispatch = wrapped_dispatch # Replace the dispatch method
|
||||||
return view
|
return view
|
||||||
|
|
||||||
else: # If decorating a function-based view (FBV)
|
else: # If decorating a function-based view (FBV)
|
||||||
view.has_explicit_access = True
|
view.has_explicit_access = True # Mark the view as having explicit access control
|
||||||
existing_rules = getattr(view, "_access_rules", set())
|
existing_rules = getattr(view, "_access_rules", set()) # Retrieve existing rules
|
||||||
existing_rules.update(rules)
|
existing_rules.update(rules) # Merge with new rules
|
||||||
view._access_rules = existing_rules
|
view._access_rules = existing_rules # Store updated rules
|
||||||
|
|
||||||
@functools.wraps(view)
|
@functools.wraps(view)
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
if not _user_has_permission(request.user, request, rules, **kwargs):
|
if not _user_has_permission(request.user, request, rules, **kwargs):
|
||||||
raise PermissionDenied
|
raise PermissionDenied # Deny access if the user lacks permission
|
||||||
return view(request, *args, **kwargs)
|
return view(request, *args, **kwargs) # Proceed with the original view
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
@ -63,7 +69,21 @@ def grant_access(*rules):
|
||||||
|
|
||||||
def _user_has_permission(user, request, rules, **kwargs):
|
def _user_has_permission(user, request, rules, **kwargs):
|
||||||
"""
|
"""
|
||||||
Checks if the user meets the permission requirements.
|
Determines if the user meets the required permission rules.
|
||||||
|
|
||||||
|
This function evaluates a set of predefined permission rules to check whether a user has access
|
||||||
|
to a specific view. It supports various access control conditions, including staff status,
|
||||||
|
domain management roles, and portfolio-related permissions.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- user: The user requesting access.
|
||||||
|
- request: The HTTP request object.
|
||||||
|
- rules: A set of access control rules to evaluate.
|
||||||
|
- **kwargs: Additional keyword arguments used in specific permission checks.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- True if the user satisfies any of the specified rules.
|
||||||
|
- False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Skip authentication if @login_not_required is applied
|
# Skip authentication if @login_not_required is applied
|
||||||
|
@ -86,7 +106,7 @@ def _user_has_permission(user, request, rules, **kwargs):
|
||||||
(IS_PORTFOLIO_MEMBER, lambda: user.is_org_user(request)),
|
(IS_PORTFOLIO_MEMBER, lambda: user.is_org_user(request)),
|
||||||
(
|
(
|
||||||
HAS_PORTFOLIO_DOMAINS_VIEW_ALL,
|
HAS_PORTFOLIO_DOMAINS_VIEW_ALL,
|
||||||
lambda: _can_access_domain_via_portfolio_view_all_domains(request, kwargs.get("domain_pk")),
|
lambda: _has_portfolio_view_all_domains(request, kwargs.get("domain_pk")),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
HAS_PORTFOLIO_DOMAINS_ANY_PERM,
|
HAS_PORTFOLIO_DOMAINS_ANY_PERM,
|
||||||
|
@ -268,11 +288,9 @@ def _is_staff_managing_domain(request, **kwargs):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _can_access_domain_via_portfolio_view_all_domains(request, domain_pk):
|
def _has_portfolio_view_all_domains(request, domain_pk):
|
||||||
"""Returns whether the user in the request can access the domain
|
"""Returns whether the user in the request can access the domain
|
||||||
via portfolio view all domains permission."""
|
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")
|
portfolio = request.session.get("portfolio")
|
||||||
if request.user.has_view_all_domains_portfolio_permission(portfolio):
|
if request.user.has_view_all_domains_portfolio_permission(portfolio):
|
||||||
if Domain.objects.filter(id=domain_pk).exists():
|
if Domain.objects.filter(id=domain_pk).exists():
|
||||||
|
|
|
@ -177,40 +177,48 @@ class CheckPortfolioMiddleware:
|
||||||
|
|
||||||
|
|
||||||
class RestrictAccessMiddleware:
|
class RestrictAccessMiddleware:
|
||||||
"""Middleware that blocks all views unless explicitly permitted"""
|
"""
|
||||||
|
Middleware that blocks access to all views unless explicitly permitted.
|
||||||
|
|
||||||
|
This middleware enforces authentication by default. Views must explicitly allow access
|
||||||
|
using access control mechanisms such as the `@grant_access` decorator. Exceptions are made
|
||||||
|
for Django admin views, explicitly ignored paths, and views that opt out of login requirements.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
# Compile regex patterns from settings to identify paths that bypass login requirements
|
||||||
self.ignored_paths = [re.compile(pattern) for pattern in getattr(settings, "LOGIN_REQUIRED_IGNORE_PATHS", [])]
|
self.ignored_paths = [re.compile(pattern) for pattern in getattr(settings, "LOGIN_REQUIRED_IGNORE_PATHS", [])]
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
|
|
||||||
# Allow Django Debug Toolbar requests
|
# Allow requests to Django Debug Toolbar
|
||||||
if request.path.startswith("/__debug__/"):
|
if request.path.startswith("/__debug__/"):
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
# Allow requests that match LOGIN_REQUIRED_IGNORE_PATHS
|
# Allow requests matching configured ignored paths
|
||||||
if any(pattern.match(request.path) for pattern in self.ignored_paths):
|
if any(pattern.match(request.path) for pattern in self.ignored_paths):
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
# Try to resolve the view function
|
# Attempt to resolve the request path to a view function
|
||||||
try:
|
try:
|
||||||
resolver_match = resolve(request.path_info)
|
resolver_match = resolve(request.path_info)
|
||||||
view_func = resolver_match.func
|
view_func = resolver_match.func
|
||||||
app_name = resolver_match.app_name # Get app name of resolved view
|
app_name = resolver_match.app_name # Get the app name of the resolved view
|
||||||
except Exception:
|
except Exception:
|
||||||
|
# If resolution fails, allow the request to proceed (avoid blocking non-view routes)
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
# Auto-allow Django's built-in admin views (but NOT custom /admin/* views)
|
# Automatically allow access to Django's built-in admin views (excluding custom /admin/* views)
|
||||||
if app_name == "admin":
|
if app_name == "admin":
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
# Skip access restriction if the view explicitly allows unauthenticated access
|
# Allow access if the view explicitly opts out of login requirements
|
||||||
if getattr(view_func, "login_required", True) is False:
|
if getattr(view_func, "login_required", True) is False:
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
# Enforce explicit access fules for other views
|
# Restrict access to views that do not explicitly declare access rules
|
||||||
if not getattr(view_func, "has_explicit_access", False):
|
if not getattr(view_func, "has_explicit_access", False):
|
||||||
raise PermissionDenied
|
raise PermissionDenied # Deny access if the view lacks explicit permission handling
|
||||||
|
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue