#31405 closed New feature (fixed)
LoginRequiredAuthenticationMiddleware force all views to require authentication by default.
Reported by: | Mehmet İnce | Owned by: | Hisham Mahmood |
---|---|---|---|
Component: | contrib.auth | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Ready for checkin | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
This is discussed in the mailing list https://groups.google.com/forum/#!topic/django-developers/PUQQUHIxEXQ.
Change History (14)
comment:1 by , 5 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:2 by , 5 years ago
Has patch: | set |
---|---|
Owner: | changed from | to
Status: | new → assigned |
follow-up: 4 comment:3 by , 4 years ago
Needs documentation: | set |
---|---|
Needs tests: | set |
Owner: | changed from | to
Patch needs improvement: | set |
Thanks Mehmet. Comments on PR — Please uncheck flags when address to put it back in the review queue.
comment:4 by , 4 years ago
Has patch: | unset |
---|---|
Needs tests: | unset |
Patch needs improvement: | unset |
Replying to Carlton Gibson:
Thanks Mehmet. Comments on PR — Please uncheck flags when address to put it back in the review queue.
Thansk for the review Carlton. I believe that I solved the issues you pointed.
There were nice people from the mailing list who are willing to help out with docs. Once we are finished everything, I'll ping them for the docs :)
comment:6 by , 3 years ago
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
comment:7 by , 10 months ago
Owner: | changed from | to
---|
comment:9 by , 8 months ago
Patch needs improvement: | set |
---|
comment:10 by , 6 months ago
Patch needs improvement: | unset |
---|
comment:11 by , 6 months ago
Triage Stage: | Accepted → Ready for checkin |
---|
I'll Accept based on the mailing list discussion. Thanks.