mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-27 04:58:42 +02:00
wip
This commit is contained in:
parent
303b74c458
commit
d84aa421d9
8 changed files with 121 additions and 2 deletions
|
@ -5,12 +5,14 @@ import logging
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import logout as auth_logout
|
from django.contrib.auth import logout as auth_logout
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
|
from login_required import login_not_required
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from urllib.parse import parse_qs, urlencode
|
from urllib.parse import parse_qs, urlencode
|
||||||
|
|
||||||
from djangooidc.oidc import Client
|
from djangooidc.oidc import Client
|
||||||
from djangooidc import exceptions as o_e
|
from djangooidc import exceptions as o_e
|
||||||
|
from registrar.decorators import grant_access
|
||||||
from registrar.models import User
|
from registrar.models import User
|
||||||
from registrar.views.utility.error_views import custom_500_error_view, custom_401_error_view
|
from registrar.views.utility.error_views import custom_500_error_view, custom_401_error_view
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,8 @@ MIDDLEWARE = [
|
||||||
"waffle.middleware.WaffleMiddleware",
|
"waffle.middleware.WaffleMiddleware",
|
||||||
"registrar.registrar_middleware.CheckUserProfileMiddleware",
|
"registrar.registrar_middleware.CheckUserProfileMiddleware",
|
||||||
"registrar.registrar_middleware.CheckPortfolioMiddleware",
|
"registrar.registrar_middleware.CheckPortfolioMiddleware",
|
||||||
|
# Restrict access using Opt-Out approach
|
||||||
|
"registrar.registrar_middleware.RestrictAccessMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
# application object used by Django's built-in servers (e.g. `runserver`)
|
# application object used by Django's built-in servers (e.g. `runserver`)
|
||||||
|
|
72
src/registrar/decorators.py
Normal file
72
src/registrar/decorators.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
from functools import wraps
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
from registrar.models.domain import Domain
|
||||||
|
from registrar.models.user_domain_role import UserDomainRole
|
||||||
|
|
||||||
|
# Constants for clarity
|
||||||
|
ALL = "all"
|
||||||
|
IS_SUPERUSER = "is_superuser"
|
||||||
|
IS_STAFF = "is_staff"
|
||||||
|
IS_DOMAIN_MANAGER = "is_domain_manager"
|
||||||
|
|
||||||
|
def grant_access(*rules):
|
||||||
|
"""
|
||||||
|
Allows multiple rules in a single decorator call:
|
||||||
|
@grant_access(IS_STAFF, IS_SUPERUSER, IS_DOMAIN_MANAGER)
|
||||||
|
or multiple stacked decorators:
|
||||||
|
@grant_access(IS_SUPERUSER)
|
||||||
|
@grant_access(IS_DOMAIN_MANAGER)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(view_func):
|
||||||
|
view_func.has_explicit_access = True # Mark as explicitly access-controlled
|
||||||
|
existing_rules = getattr(view_func, "_access_rules", set())
|
||||||
|
existing_rules.update(rules) # Support multiple rules in one call
|
||||||
|
view_func._access_rules = existing_rules # Store rules on the function
|
||||||
|
|
||||||
|
@wraps(view_func)
|
||||||
|
def wrapper(request, *args, **kwargs):
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
# Skip authentication if @login_not_required is applied
|
||||||
|
if getattr(view_func, "login_not_required", False):
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
|
||||||
|
# Allow everyone if `ALL` is in rules
|
||||||
|
if ALL in view_func._access_rules:
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
|
||||||
|
# Ensure user is authenticated
|
||||||
|
if not user.is_authenticated:
|
||||||
|
return JsonResponse({"error": "Authentication required"}, status=403)
|
||||||
|
|
||||||
|
conditions_met = []
|
||||||
|
|
||||||
|
if IS_STAFF in view_func._access_rules:
|
||||||
|
conditions_met.append(user.is_staff)
|
||||||
|
|
||||||
|
if not any(conditions_met) and IS_SUPERUSER in view_func._access_rules:
|
||||||
|
conditions_met.append(user.is_superuser)
|
||||||
|
|
||||||
|
if not any(conditions_met) and IS_DOMAIN_MANAGER in view_func._access_rules:
|
||||||
|
domain_id = kwargs.get('pk') or kwargs.get('domain_id')
|
||||||
|
if not domain_id:
|
||||||
|
return JsonResponse({"error": "Domain ID missing"}, status=400)
|
||||||
|
try:
|
||||||
|
domain = Domain.objects.get(pk=domain_id)
|
||||||
|
has_permission = UserDomainRole.objects.filter(
|
||||||
|
user=user, domain=domain
|
||||||
|
).exists()
|
||||||
|
conditions_met.append(has_permission)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return JsonResponse({"error": "Invalid Domain"}, status=404)
|
||||||
|
|
||||||
|
if not any(conditions_met):
|
||||||
|
return JsonResponse({"error": "Access Denied"}, status=403)
|
||||||
|
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
|
@ -3,9 +3,13 @@ Contains middleware used in settings.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.urls import resolve
|
||||||
from registrar.models import User
|
from registrar.models import User
|
||||||
from waffle.decorators import flag_is_active
|
from waffle.decorators import flag_is_active
|
||||||
|
|
||||||
|
@ -170,3 +174,38 @@ class CheckPortfolioMiddleware:
|
||||||
request.session["portfolio"] = request.user.get_first_portfolio()
|
request.session["portfolio"] = request.user.get_first_portfolio()
|
||||||
else:
|
else:
|
||||||
request.session["portfolio"] = request.user.get_first_portfolio()
|
request.session["portfolio"] = request.user.get_first_portfolio()
|
||||||
|
|
||||||
|
|
||||||
|
class RestrictAccessMiddleware:
|
||||||
|
""" Middleware that blocks all views unless explicitly permitted """
|
||||||
|
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
self.ignored_paths = [re.compile(pattern) for pattern in getattr(settings, "LOGIN_REQUIRED_IGNORE_PATHS", [])]
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
# Allow requests that match LOGIN_REQUIRED_IGNORE_PATHS
|
||||||
|
if any(pattern.match(request.path) for pattern in self.ignored_paths):
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
# Try to resolve the view function
|
||||||
|
try:
|
||||||
|
resolver_match = resolve(request.path_info)
|
||||||
|
view_func = resolver_match.func
|
||||||
|
app_name = resolver_match.app_name # Get app name of resolved view
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({"error": "Not Found"}, status=404)
|
||||||
|
|
||||||
|
# Auto-allow Django's built-in admin views (but NOT custom /admin/* views)
|
||||||
|
if app_name == "admin":
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
# Skip access restriction if the view explicitly allows unauthenticated access
|
||||||
|
if getattr(view_func, "login_required", True) is False:
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
# Enforce explicit access fules for other views
|
||||||
|
if not getattr(view_func, "has_explicit_access", False):
|
||||||
|
return JsonResponse({"error": "Access Denied"}, status=403)
|
||||||
|
|
||||||
|
return self.get_response(request)
|
0
src/registrar/utility/domain_cache_helper.py
Normal file
0
src/registrar/utility/domain_cache_helper.py
Normal file
|
@ -1,5 +1,6 @@
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from registrar.decorators import grant_access, ALL
|
||||||
from registrar.models import DomainRequest
|
from registrar.models import DomainRequest
|
||||||
from django.utils.dateformat import format
|
from django.utils.dateformat import format
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
@ -7,7 +8,7 @@ from django.urls import reverse
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@grant_access(ALL)
|
||||||
def get_domain_requests_json(request):
|
def get_domain_requests_json(request):
|
||||||
"""Given the current request,
|
"""Given the current request,
|
||||||
get all domain requests that are associated with the request user and exclude the APPROVED ones.
|
get all domain requests that are associated with the request user and exclude the APPROVED ones.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from registrar.decorators import grant_access, ALL
|
||||||
from registrar.models import UserDomainRole, Domain, DomainInformation, User
|
from registrar.models import UserDomainRole, Domain, DomainInformation, User
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -9,7 +10,7 @@ from django.db.models import Q
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@grant_access(ALL)
|
||||||
def get_domains_json(request):
|
def get_domains_json(request):
|
||||||
"""Given the current request,
|
"""Given the current request,
|
||||||
get all domains that are associated with the UserDomainRole object"""
|
get all domains that are associated with the UserDomainRole object"""
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from registrar.decorators import 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."""
|
||||||
context = {}
|
context = {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue