#31405: LoginRequiredAuthenticationMiddleware force all views to require
authentication by default.
------------------------------+---------------------------------------
Reporter: Mehmet İnce | Owner: Mehmet INCE
Type: New feature | Status: assigned
Component: contrib.auth | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+---------------------------------------
Comment (by Michael):
I am very interested in this new feature. Will it have a way to mark
function and class based views as no login requied?
Probably too late but heres some code from my solution:
A decorator to mark a view/function as no longer required:
{{{
from functools import wraps
def login_not_required(obj):
"""Adds the attrbiute login_not_required = True to the object
(func/class).
Use it as follows:
@login_not_required
class FooView(generic.View):
...
@login_not_required
def bar_view(request):
...
"""
@wraps(obj)
def decorator():
obj.login_not_required = True # For general pages
obj.permission_classes = [] # For REST framework
return obj
return decorator()
}}}
Middleware:
{{{
# settings.py
NONE_AUTH_ACCOUNT_PATHS = [
....
'/accounts/password_reset/',
'/accounts/reset/',
]
# middleware.py
class RequireLoginCheck:
"""Middleware to require authentication on all views by default,
except when allowed.
URLS can be opened by adding them to NONE_AUTH_ACCOUNT_PATHS, or by
adding
the @login_not_required decorator.
Must appear below the sessions middleware because the sessions
middleware
adds the user to the request, which is used by this middleware.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def _is_none_auth_path(self, path):
for none_auth_path in NONE_AUTH_ACCOUNT_PATHS:
if path.startswith(none_auth_path):
return True
return False
def _is_login_not_required(self, view_func):
with suppress(AttributeError):
# If a class with the @login_not_required decorator, will
return True
return view_func.view_class.login_not_required
with suppress(AttributeError):
# If a function with the @login_not_required decorator, will
return True
return view_func.login_not_required
return False
def _is_open_rest_view(self, view_func):
try:
klass = view_func.view_class
except AttributeError:
return False
if not issubclass(view_func.view_class, APIView):
return False
else:
auth_classes = getattr(klass, 'authentication_classes', None)
perm_classes = getattr(klass, 'permission_classes', None)
# if auth_classes and perm_classes are empty list/tuples, then
don't require login checks
no_login_required = (
auth_classes is not None
and not auth_classes
and perm_classes is not None
and not perm_classes
)
return no_login_required
def log_unauthorised_request(self, request, view_func, view_args,
view_kwargs):
get_response = lambda: HTTP_NO_RESPONSE
reason = CsrfViewMiddleware(get_response).process_view(request,
None, (), {})
s = ["base.auth.middleware.RequireLoginCheck"]
s.append(f"User: {request.user}")
s.append(f"Method: {request.method}")
s.append(f"URL: {request.path}")
s.append(f"IP: {get_ip(request)}")
s.append(f"Reason: {reason}")
s.append(f"Open URL (is_login_not_required):
{self._is_login_not_required(view_func)}")
s.append(f"is_none_auth_path:
{self._is_none_auth_path(request.path)}")
s.append(f"HEADERS: {request.headers}")
s.append(f"GET: {request.GET}")
s.append(f"POST: {request.POST}")
if LOGGING:
log_info(', '.join(s))
if settings.DEBUG and not request.path.startswith('static'):
print(', '.join(s))
def process_view(self, request, view_func, view_args, view_kwargs):
"""https://docs.djangoproject.com/en/stable/topics/http/middleware
/#other-middleware-hooks"""
if not (
request.user.is_authenticated
or self._is_login_not_required(view_func)
or self._is_open_rest_view(view_func)
or self._is_none_auth_path(request.path)
):
self.log_unauthorised_request(request, view_func, view_args,
view_kwargs)
if settings.LOGIN_URL != request.path:
# if next URL after login is the same login URL, then
cyclic loop
return redirect('%s?next=%s' % (settings.LOGIN_URL,
request.path))
else:
return redirect('%s?next=%s' % (settings.LOGIN_URL, '/'))
return None
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:6>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/066.81e1842a796c00c93964edac25575d7d%40djangoproject.com.