#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 django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/066.81e1842a796c00c93964edac25575d7d%40djangoproject.com.