Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to make Django's login_required the default

Tags:

python

django

I'm working on a large Django app, the vast majority of which requires a login to access. This means that all throughout our app we've sprinkled:

@login_required def view(...): 

That's fine, and it works great as long as we remember to add it everywhere! Sadly sometimes we forget, and the failure often isn't terribly evident. If the only link to a view is on a @login_required page then you're not likely to notice that you can actually reach that view without logging in. But the bad guys might notice, which is a problem.

My idea was to reverse the system. Instead of having to type @login_required everywhere, instead I'd have something like:

@public def public_view(...): 

Just for the public stuff. I tried to implement this with some middleware and I couldn't seem to get it to work. Everything I tried interacted badly with other middleware we're using, I think. Next up I tried writing something to traverse the URL patterns to check that everything that's not @public was marked @login_required - at least then we'd get a quick error if we forgot something. But then I couldn't figure out how to tell if @login_required had been applied to a view...

So, what's the right way to do this? Thanks for the help!

like image 600
samtregar Avatar asked Jan 29 '10 18:01

samtregar


People also ask

How to authenticate user login in Django?

from django.contrib.auth import authenticate, login def my_view(request): username = request.POST['username'] password = request.POST['password'] user = authenticate(request, username=username, password=password) if user is not None: login(request, user) # Redirect to a success page. ... else: # Return an 'invalid ...

How to set user in Django?

The correct way to create a user in Django is to use the create_user function. This will handle the hashing of the password, etc..


2 Answers

Middleware may be your best bet. I've used this piece of code in the past, modified from a snippet found elsewhere:

import re  from django.conf import settings from django.contrib.auth.decorators import login_required   class RequireLoginMiddleware(object):     """     Middleware component that wraps the login_required decorator around     matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and     define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your     settings.py. For example:     ------     LOGIN_REQUIRED_URLS = (         r'/topsecret/(.*)$',     )     LOGIN_REQUIRED_URLS_EXCEPTIONS = (         r'/topsecret/login(.*)$',         r'/topsecret/logout(.*)$',     )     ------     LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must     be a valid regex.      LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly     define any exceptions (like login and logout URLs).     """     def __init__(self):         self.required = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS)         self.exceptions = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)      def process_view(self, request, view_func, view_args, view_kwargs):         # No need to process URLs if user already logged in         if request.user.is_authenticated():             return None          # An exception match should immediately return None         for url in self.exceptions:             if url.match(request.path):                 return None          # Requests matching a restricted URL pattern are returned         # wrapped with the login_required decorator         for url in self.required:             if url.match(request.path):                 return login_required(view_func)(request, *view_args, **view_kwargs)          # Explicitly return None for all non-matching requests         return None 

Then in settings.py, list the base URLs you want to protect:

LOGIN_REQUIRED_URLS = (     r'/private_stuff/(.*)$',     r'/login_required/(.*)$', ) 

As long as your site follows URL conventions for the pages requiring authentication, this model will work. If this isn't a one-to-one fit, you may choose to modify the middleware to suit your circumstances more closely.

What I like about this approach - besides removing the necessity of littering the codebase with @login_required decorators - is that if the authentication scheme changes, you have one place to go to make global changes.

like image 110
Daniel Naab Avatar answered Nov 15 '22 17:11

Daniel Naab


There is an alternative to putting a decorator on each view function. You can also put the login_required() decorator in the urls.py file. While this is still a manual task, at least you have it all in one place, which makes it easier to audit.

e.g.,

     from my_views import home_view      urlpatterns = patterns('',         # "Home":         (r'^$', login_required(home_view), dict(template_name='my_site/home.html', items_per_page=20)),     ) 

Note that view functions are named and imported directly, not as strings.

Also note that this works with any callable view object, including classes.

like image 23
Ber Avatar answered Nov 15 '22 16:11

Ber