#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.

Reply via email to