Files
old-saburly-wagtail-web/env/lib/python3.10/site-packages/wagtail/admin/auth.py
2024-08-27 20:33:44 +02:00

189 lines
6.6 KiB
Python

import types
from functools import wraps
import l18n
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.timezone import override as override_tz
from django.utils.translation import gettext as _
from django.utils.translation import override
from wagtail.admin import messages
from wagtail.log_actions import LogContext
from wagtail.permissions import page_permission_policy
def permission_denied(request):
"""Return a standard 'permission denied' response"""
if request.headers.get("x-requested-with") == "XMLHttpRequest":
raise PermissionDenied
from wagtail.admin import messages
messages.error(request, _("Sorry, you do not have permission to access this area."))
return redirect("wagtailadmin_home")
def user_passes_test(test):
"""
Given a test function that takes a user object and returns a boolean,
return a view decorator that denies access to the user if the test returns false.
"""
def decorator(view_func):
# decorator takes the view function, and returns the view wrapped in
# a permission check
@wraps(view_func)
def wrapped_view_func(request, *args, **kwargs):
if test(request.user):
# permission check succeeds; run the view function as normal
return view_func(request, *args, **kwargs)
else:
# permission check failed
return permission_denied(request)
return wrapped_view_func
return decorator
def permission_required(permission_name):
"""
Replacement for django.contrib.auth.decorators.permission_required which returns a
more meaningful 'permission denied' response than just redirecting to the login page.
(The latter doesn't work anyway because Wagtail doesn't define LOGIN_URL...)
"""
def test(user):
return user.has_perm(permission_name)
# user_passes_test constructs a decorator function specific to the above test function
return user_passes_test(test)
def any_permission_required(*perms):
"""
Decorator that accepts a list of permission names, and allows the user
to pass if they have *any* of the permissions in the list
"""
def test(user):
for perm in perms:
if user.has_perm(perm):
return True
return False
return user_passes_test(test)
class PermissionPolicyChecker:
"""
Provides a view decorator that enforces the given permission policy,
returning the wagtailadmin 'permission denied' response if permission not granted
"""
def __init__(self, policy):
self.policy = policy
def require(self, action):
def test(user):
return self.policy.user_has_permission(user, action)
return user_passes_test(test)
def require_any(self, *actions):
def test(user):
return self.policy.user_has_any_permission(user, actions)
return user_passes_test(test)
def user_has_any_page_permission(user):
"""
Check if a user has any permission to add, edit, or otherwise manage any
page.
"""
return page_permission_policy.user_has_any_permission(
user, {"add", "change", "publish", "bulk_delete", "lock", "unlock"}
)
def reject_request(request):
if request.headers.get("x-requested-with") == "XMLHttpRequest":
raise PermissionDenied
# import redirect_to_login here to avoid circular imports on model files that import
# wagtail.admin.auth, specifically where custom user models are involved
from django.contrib.auth.views import redirect_to_login as auth_redirect_to_login
login_url = getattr(
settings, "WAGTAILADMIN_LOGIN_URL", reverse("wagtailadmin_login")
)
return auth_redirect_to_login(request.get_full_path(), login_url=login_url)
def require_admin_access(view_func):
def decorated_view(request, *args, **kwargs):
user = request.user
if user.is_anonymous:
return reject_request(request)
if user.has_perms(["wagtailadmin.access_admin"]):
try:
preferred_language = None
if hasattr(user, "wagtail_userprofile"):
preferred_language = (
user.wagtail_userprofile.get_preferred_language()
)
l18n.set_language(preferred_language)
time_zone = user.wagtail_userprofile.get_current_time_zone()
else:
time_zone = settings.TIME_ZONE
with override_tz(time_zone), LogContext(user=user):
if preferred_language:
with override(preferred_language):
response = view_func(request, *args, **kwargs)
else:
response = view_func(request, *args, **kwargs)
if hasattr(response, "render"):
# If the response has a render() method, Django treats it
# like a TemplateResponse, so we should do the same
# In this case, we need to guarantee that when the TemplateResponse
# is rendered, it is done within the override context manager
# or the user preferred_language/timezone will not be used
# (this could be replaced with simply rendering the TemplateResponse
# for simplicity but this does remove some of its middleware modification
# potential)
render = response.render
def overridden_render(response):
with override_tz(time_zone):
if preferred_language:
with override(preferred_language):
return render()
return render()
response.render = types.MethodType(overridden_render, response)
# decorate the response render method with the override context manager
return response
except PermissionDenied:
if request.headers.get("x-requested-with") == "XMLHttpRequest":
raise
return permission_denied(request)
if not request.headers.get("x-requested-with") == "XMLHttpRequest":
messages.error(request, _("You do not have permission to access the admin"))
return reject_request(request)
return decorated_view