1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
from functools import wraps
from urllib.parse import urlsplit
from asgiref.sync import async_to_sync, iscoroutinefunction, sync_to_async
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import PermissionDenied
from django.shortcuts import resolve_url
def user_passes_test(
test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME
):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
that takes the user object and returns True if the user passes.
"""
def decorator(view_func):
def _redirect_to_login(request):
path = request.build_absolute_uri()
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
current_scheme, current_netloc = urlsplit(path)[:2]
if (not login_scheme or login_scheme == current_scheme) and (
not login_netloc or login_netloc == current_netloc
):
path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(path, resolved_login_url, redirect_field_name)
if iscoroutinefunction(view_func):
async def _view_wrapper(request, *args, **kwargs):
auser = await request.auser()
if iscoroutinefunction(test_func):
test_pass = await test_func(auser)
else:
test_pass = await sync_to_async(test_func)(auser)
if test_pass:
return await view_func(request, *args, **kwargs)
return _redirect_to_login(request)
else:
def _view_wrapper(request, *args, **kwargs):
if iscoroutinefunction(test_func):
test_pass = async_to_sync(test_func)(request.user)
else:
test_pass = test_func(request.user)
if test_pass:
return view_func(request, *args, **kwargs)
return _redirect_to_login(request)
# Attributes used by LoginRequiredMiddleware.
_view_wrapper.login_url = login_url
_view_wrapper.redirect_field_name = redirect_field_name
return wraps(view_func)(_view_wrapper)
return decorator
def login_required(
function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None
):
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_authenticated,
login_url=login_url,
redirect_field_name=redirect_field_name,
)
if function:
return actual_decorator(function)
return actual_decorator
def login_not_required(view_func):
"""
Decorator for views that allows access to unauthenticated requests.
"""
view_func.login_required = False
return view_func
def permission_required(perm, login_url=None, raise_exception=False):
"""
Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page if necessary.
If the raise_exception parameter is given the PermissionDenied exception
is raised.
"""
if isinstance(perm, str):
perms = (perm,)
else:
perms = perm
def decorator(view_func):
if iscoroutinefunction(view_func):
async def check_perms(user):
# First check if the user has the permission (even anon users).
if await user.ahas_perms(perms):
return True
# In case the 403 handler should be called raise the exception.
if raise_exception:
raise PermissionDenied
# As the last resort, show the login form.
return False
else:
def check_perms(user):
# First check if the user has the permission (even anon users).
if user.has_perms(perms):
return True
# In case the 403 handler should be called raise the exception.
if raise_exception:
raise PermissionDenied
# As the last resort, show the login form.
return False
return user_passes_test(check_perms, login_url=login_url)(view_func)
return decorator
|