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

Reply via email to