9 BitBlog

This is where I blog about apps I built and technical subjects.

View Based Security for Django

March 26, 2012

Securing a view in Django can be done easily. Just add a few if statements to the code to check for conditions (like permissions). Unfortunately splattering if statements throughout the code has some downsides. One of which is the weakest link problem.

Security within web-apps depends on all available views in the system. That means that if only one view is vulnerable the whole system might be compromised. Since each and every view can be accessed directly the burden on software developers to enforce security increases with the amount views that are added to the system.

To get declarative security for Django we start out by writing a generic view mixin. This mixin can be used with all other generic views.

from django.contrib.auth.views import redirect_to_login

class SecureMixin(object):
    context = None
    permission = None

    def dispatch(request, *args, **kwargs):
        self.context = self.context(*args, **kwargs)
        if not self.context.has_permission(request.user, self.permission):
            return redirect_to_login(request.get_full_path())
        # Security cleared, go ahead and execute the normal view code
        return super(SecureMixin, self).dispatch(request, *args, **kwargs)

The dispatch method is the first point of entry for Django's generic views. This class will be used as the first mixin and therefore override the default behavior. As you can see the main security assertion will happen in the has_permission method of the context.

A context should represent a logical place within your application. Say we have an address like /blogs/john/posts/12/ where john would be a user that has his own blog and 12 the id of his latest post. In this case the context of the view would be user: john and post: 12.

Let's look at an example implementation of this context:

class BlogPostContext(object):

    def __init__(self, username, post_id, *args, **kwargs):
        self.user = get_object_or_404(User.objects, username=username)
        self.post = get_object_or_404(self.user.blogpost_set, id=post_id)
        
    def has_permission(user, permission):
        if self.post.author.id == user.id:
            return True
        if permission == 'view':
            return self.post.state == 'published'
        return False

The first thing this class does is to load both the user and the post objects from the database. It also makes sure that the post must be authored by the proper user by fetching it from the user instead of going directly to the BlogPost model.

The BlogPostContext also has a simple has_permission implementation. This will anything as long as the current user is the author. It only allows other people to view the context when the blog post has been published.

Let's write a simple detail view to tie it all together:

class BlogPostView(SecureMixin, generic.DetailView):
    context_class = BlogPostContext
    permission = 'view'
    
    def get_object(self):
        return self.context.post

The SecureMixin is placed before DetailView to make sure it's version of dispatch is called. Next is the context_class and permission assignment. These just setup the proper values for this view. Finally there's the get_object method. This is overridden to reuse the object that has been loaded already. It's a small efficiency booster since it saves a query.

It is easy to create many more views in the same context now that the context has been created. Each view can be easily secured by subclassing it from the SecureMixin and setting the proper class attributes. The result is that each view has a simple declarative security declaration.

This solution does not offer security for the code that runs within the view itself. So care has to be taken as always with this as well. It does offer a very generic and easy to verify base level of security to all views that use it.

In this example permissions are used as a verification system. This could easily be replaced with a role based policy or some other form. Another nice trick is to create different contexts and use subclassing to do general assertions in one context and more specific checks in it's subclass.

You can take a look at the Pyramid framework to find out more about how this concept can be applied. The solution presented here is conceptually based on the security system in Pyramid.