I require to check authentication via token during execution of certain views, while some views can be accessed without authentication.
So, how do i make a middleware and exclude some views from it.
Any other idea to solve this is appreciated.
I would suggest taking inspiration from the csrf middleware that Django provides
from django.utils.deprecation import MiddlewareMixin
class MyAuthenticationMiddleware(MiddlewareMixin):
def process_view(self, request, callback, callback_args, callback_kwargs):
if getattr(callback, 'my_exempt_flag', False):
return None
# Authentication goes here
# Return None if authentication was successful
# Return a HttpResponse with some error status if not successful
And create a decorator to wrap your views
from functools import wraps
def exempt_from_my_authentication_middleware(view_func):
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.my_exempt_flag = True
return wraps(view_func)(wrapped_view)
Can be used like so
#exempt_from_my_authentication_middleware
def my_view(request):
# TODO
Related
I am creating an app in django where all the authentication like login signup is done by using an exernal rest api so there is no database involved in it. This is why I am saving logged in user data in request.session now the issue is I don't want to show some of the pages if the user is not inside the sessions, what we usually do back in django apps is use #login_required decorator but now as there is no database so what will be the procedure of not showing those pages to not logged in users. thanks
You can define a decorator that will look for a key in the request.sessions:
# app_name/decorators.py
from django.shortcuts import redirect
from functools import wraps
def session_login_required(function=None, session_key='user'):
def decorator(view_func):
#wraps(view_func)
def f(request, *args, **kwargs):
if session_key in request.session:
return view_func(request, *args, **kwargs)
return redirect('name-of-login-view')
return f
if function is not None:
return decorator(function)
return decorator
here it will thus check if 'user' is a key in the request.session. You can of course change this to a different key.
Then we use the decorator for these views:
# app_name/views.py
from app_name.decorators import session_login_required
#session_login_required
def my_view(request, some, parameters):
# …
Django is centered towards allow access to all the majority of the time, and restricting acces on the exception.
I am making a site whereby I would like access restricted the most of the time, and the rare case is open to all. Ensuring that one has added a #login_required decorate to all views is error prone.
I acheived this by creating a custom middleware like this. To keep track of open URLs, I defined a list, and then whenever I wished to open up a URL, I added it to the list, and the middleware checked the request path against this list, and allowed exceptions accordingly.
This method above works, but I often mess it up with changing urls, and other issues to do with code reuse on different sites.
Ideally I would like to create my own #login_not_requied decorator. How to get the class based view or function the request is going to call within the middleware, so I can check whether the view does not require login?
First create the decorator:
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
return obj
return decorator()
Then create your own custom middleware:
from contextlib import suppress
from django.conf import settings
from django.shortcuts import redirect
NONE_AUTH_ACCOUNT_PATHS = [
settings.STATIC_URL,
'/accounts/login/',
'/accounts/password_reset/',
'/accounts/reset/',
'/favicon.ico',
'/terminal/login/',
'/terminal/login_failed/',
]
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 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_none_auth_path(request.path)
):
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
Ensure you add your middleware to MIDDLEWARE bellow the sessions middleware, in settings.py!
If you have ways it can be improved, please comment below and we make it better for all.
I'm trying to write a mixin that will protect views by first checking if someone is logged in and then if they have been onboarded. It seems to work, by blocking views it's attached to, but it the URLjust goes to a 403 forbidden. Any ideas on how to get it to go to the named url?
from django.contrib.auth.mixins import UserPassesTestMixin
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
class OnboardedMixin(LoginRequiredMixin, UserPassesTestMixin):
"""
a custom mixin that checks to see if the user has been onboarded yet
"""
def test_func(self):
if self.request.user.onboarded and self.request.user.is_active:
return True
def get_login_url(self):
return redirect('onboarding',)
Rather than taking this approach, maybe its best to use a decorator instead. For example:
from django.contrib.auth.decorators import login_required
def my_login_required(function):
def wrapper(obj, request, *args, **kw):
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated:
return decorated_view_func(request) # restricts without login and sends to signin view
if request.user.onboarded and request.user.is_active:
return function(obj, request, *args, **kw)
return HttpResponseRedirect("/onboarding/")
return wrapper
And use this decorator in desired views:
class SomeView(DetailView):
...
#my_login_requried
def dispatch(self, *args, **kwargs):
return super(SomeView, self).dispatch(*args, **kwargs)
I want to redirect access of unauthenticated users to the login page, after which the logged-in user should be redirected to the originally requested page.
According to documentation, this is easily achieved using the #user_passes_test decorator. But it seems I'd have to decorate every view, which is crazy, there are too many and it's error-prone.
What is a good way to turn on this functionality globally (except for a small fixed set of views, such as login)? That is, default everything to logged-in-only + handle anonymous viewing explicitly, where needed.
from django.shortcuts import redirect
from django.conf import settings
class LoginRequiredMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.login_url = settings.LOGIN_URL
self.open_urls = [self.login_url] + \
getattr(settings, 'OPEN_URLS', [])
def __call__(self, request):
if not request.user.is_authenticated \
and not request.path_info in self.open_urls:
return redirect(self.login_url+'?next='+request.path)
return self.get_response(request)
have a look at middleware. these are functions run at various points in the request cycle, e.g. before each view is called.
since you may want to exclude certain views from this, i'd look at e.g. how the csrf middleware works, together with the csrf_exempt decorator.
see [SOURCE]/django/views/decorators/csrf.py and [SOURCE]/django/middleware/csrf.py
The way I solved this, was to have mixin class, with the decorator (or whatever code you need). Although you have to remember to call the super(Class, self).get(...) function, so I guess it's not so different after all.
On the other hand, having a set of mixins that does different things I found was quite good at getting a very simple view to do a lot without much code.
Edit
This is how I did in my last project:
class BaseAuthMixin(object):
def auth_check(self, user):
return True
def dispatch(self, request, *args, **kwargs):
if not self.auth_check(request.user):
from django.http import HttpResponseRedirect
from django.contrib.auth import logout
is_web = False
is_live = False
if hasattr(self, 'get_url_name'):
from django.core.urlresolvers import reverse
from django.core.urlresolvers import NoReverseMatch
try:
next = reverse(self.get_url_name(), kwargs=kwargs)
except NoReverseMatch:
next = ''
else:
next= '?next=' + next
logout(request)
redirect_url = settings.LOGIN_URL
redirect_url += next
return HttpResponseRedirect(redirect_url)
else:
return super(BaseAuthMixin, self).dispatch(request, *args, **kwargs)
class LoginRequiredMixin(BaseAuthMixin):
"""
Check if the view needs the user to be logged in.
"""
def auth_check(self, user):
if not super(LoginRequiredMixin, self).auth_check(user):
return False
else:
if hasattr(self, 'login_required'):
if self.login_required and not user.is_authenticated():
return False
return True
class MyDefaultMixin(LoginRequiredMixin):
"""
Mixin that inherits from all common view mixins.
"""
pass
The above is then used by the view-classes (I used Django 1.3 with class-based views):
from django.views.generic import TemplateView
class SomeViewClass(TemplateView, MyDefaultMixin):
# Used by 'LoginRequiredMixin' to check if a user has to be logged in
login_required = True
# Template for the Django TemplateView
template_name = "some_view_template.html"
You need a view to handle the login (with URL in settings.LOGIN_URL), containing a form with a hidden field called next. This field has to be set by a context variable to the page to go to after successful login.
If all views inherit from the base mixin (MyDefaultMixin in my code above), it will automatically check that the user is logged in iv the view contain an attribute called login_required and that is set to True.
There might be better ways to do this, but this is what I did and it worked very well.
If I want to make sure that a view is listed as having public access, is there a decorator equivalent to #public_access which would be the opposite of #login_required and make it clear that the view should be publicly accessible always?
One use case I have in mind is to automatically add "#csrf_exempt" to all public views in addition to making it clear in the code that the view should be publicly accessible.
Unfortunately, there's currently no built-in support for this in Django, leaving you at risk of exposing sensitive info when #login_required is accidentally forgotten.
Here's a solution from one of my projects:
middleware/security.py:
def public(function):
"""
Decorator for public views that do not require authentication
"""
orig_func = function
while isinstance(orig_func, partial): # if partial - use original function for authorization
orig_func = orig_func.func
orig_func.is_public_view = True
return function
def is_public(function):
try: # cache is found
return function.is_public_view
except AttributeError: # cache is not found
result = function.__module__.startswith('django.') and not function.__module__.startswith('django.views.generic') # Avoid modifying admin and other built-in views
try: # try to recreate cache
function.is_public_view = result
except AttributeError:
pass
return result
class NonpublicMiddleware(object):
def process_view_check_logged(self, request, view_func, view_args, view_kwargs):
return
def process_view(self, request, view_func, view_args, view_kwargs):
while isinstance(view_func, partial): # if partial - use original function for authorization
view_func = view_func.func
request.public = is_public(view_func)
if not is_public(view_func):
if request.user.is_authenticated(): # only extended checks are needed
return self.process_view_check_logged(request, view_func, view_args, view_kwargs)
return self.redirect_to_login(request.get_full_path()) # => login page
def redirect_to_login(self, original_target, login_url=settings.LOGIN_URL):
return HttpResponseRedirect("%s?%s=%s" % (login_url, REDIRECT_FIELD_NAME, urlquote(original_target)))
settings.py:
MIDDLEWARE_CLASSES = (
#...
'middleware.security.NonpublicProfilefullMiddleware',
#...
)
and, finally, view code:
from <projname>.middleware import publi
#public
def some_view(request):
#...
# Login required is added automatically
def some_private_view(request):
#...
Also, you may want to look at "Automatically decorating all views of a django project" blog post
As a previous poster mentioned, login not required is the default.
However, sometimes it's useful to block certain views from logged in users -- for instance, it makes no sense for a logged-in user to be able to use the site's signup page. In that case, you could do something like this, based off the existing login_required decorator
from django.contrib.auth.decorators import user_passes_test
from django.conf import settings
LOGGED_IN_HOME = settings.LOGGED_IN_HOME
def login_forbidden(function=None, redirect_field_name=None, redirect_to=LOGGED_IN_HOME):
"""
Decorator for views that checks that the user is NOT logged in, redirecting
to the homepage if necessary.
"""
actual_decorator = user_passes_test(
lambda u: not u.is_authenticated(),
login_url=redirect_to,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
A bit late, but another simple way to tackle this issue would be to rely on another decorator and add a lambda function:
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u: u.is_anonymous)
You can use user_passes_test and add a requirement, anonymous_required. This would work like the opposite to login_required and you can use on your register and login page - it is a bit irritating for users to still see the login page, after the are logged in.
This is how you would do it:
from django.contrib.auth.decorators import user_passes_test
#Anonymous required
def anonymous_required(function=None, redirect_url=None):
if not redirect_url:
redirect_url = settings.LOGIN_REDIRECT_URL
actual_decorator = user_passes_test(
lambda u: u.is_anonymous,
login_url=redirect_url
)
if function:
return actual_decorator(function)
return actual_decorator
#Application of the Decorator
#anonymous_required
def register(request):
#Your code goes here
"Login not required" is the default. If you want to annotate that a view should never be login-restricted then you should do so in the docstring.
I use django-decorator-include to use the login_required decorator to secure an entire app, rather than one view at a time. My app's main urls.py looks like this:
path('my_secret_app/', decorator_include(login_required, ('my_secret_app.urls', 'my_secret_app'))),
This works great, except for when one of my apps has one view which needs to be public.
To get around this, I wrote my own login_required and login_not_required. My login_required is based on django's django.contrib.auth.decorators.login_required, but is slightly modified to actually care when a view is marked as not requiring login.
My project is called mysite.
My app is called my_secret_app.
My public view within my_secret_app is called MyPublicView.
My entire solution looks like this:
mysite/lib.py
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test
# A copy of django.contrib.auth.decorators.login_required that looks for login_not_required attr
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
actual_decorator = user_passes_test(
lambda u: u.is_authenticated,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
login_req = getattr(function, "login_required", True)
if login_req:
return actual_decorator(function)
else:
return function
else:
return actual_decorator
# Decorator to mark a view as not requiring login to access
def login_not_required(f):
f.login_required = False
return f
mysite/urls.py
from .lib import login_required
path('my_secret_app/', decorator_include(login_required, ('my_secret_app.urls', 'my_secret_app'))),
my_secret_app/views.py:
from django.utils.decorators import method_decorator
from mysite.lib import login_not_required
class MyPublicView(View):
#method_decorator(login_not_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
...
def post(self, request, *args, **kwargs):
...
You should be able to do the same thing no matter if you're subclassing View, ListView, CreateView, UpdateView, TemplateView, or any of the other ones. It should also work if you're using a function as your view, though I haven't tried it:
#login_not_required
def MyPublicView(request):
...
#permission_classes([permissions.AllowAny])