Permissions – after the divorce
Written by Robert de Vries on 29th June 2018
Permissions form one of the fundamental parts of the security of our products. A permission’s purpose seems simple enough: determine whether a request should be granted or denied access. However, when working on complex projects, permissions can become complex as well. Especially when you add in a divorce. That’s where I came into the story. During my internship at Spindle, I researched ways of implementing complex permissions. In this article, I will lead you through the results.
Planning a divorce
At Spindle, we use Django for most of our development needs. It’s fast, it’s secure and it lets us develop and improve on features quickly. Instead of basing your backend and frontend off of the same tech, it’s sometimes better to use technologies specific to their components. That’s why we’re currently in the process of separating the backend and frontend of our telephony platform VoIPGRID.
Sandwich by Xkcd
Separating your backend and frontend into two standalone packages comes with lots of awesomeness. Firstly, we get to use more modern tech. After all, technology improves by the minute. Secondly, by using more modern tech, we can work much more efficiently. Our developers can focus on their own stack, making our teams more efficient at the jobs that they do. And lastly, of course, this will lead to more happy customers. Everyone wants to use their devices to the fullest of their potential and by using more modern technologies we can actually make that happen.
What could go wrong?
Currently, both our VoIPGRID backend and frontend are written in Django. That means that they’re part of the same codebase and that they can easily share data. Our permissions are based on Django’s default auth framework. You can read more about that framework here.
Django’s default auth framework is awesome, but it’s rather simple. It only allows for creating permissions for an entire model. By granting the user a permission, it will grant the user that permission on all and every single piece of data associated with that model. So, granting a user the permission to change passwords will enable them to not only change their own, but every single user’s password. No thanks!
Straight forward as it seems, you can’t implement very complex permissions with Django’s default auth framework. So we use a workaround. We not only check whether the user has a certain permission, but we also do some additional checks, such as if they’re changing their own password or someone else’s. And most of those additional checks actually get carried out in the frontend. A place where they don’t really belong.
“Divorce is awesome. But it’s not without its own challenges.”
Now that we’re in the process of doing a divorce, this problem is becoming even more serious. We can’t enforce permissions on the frontend if we separate it from the backend. Divorce is awesome, but it doesn’t come without its own challenges. We need a way to keep enforcing our existing permissions in the backend instead of the frontend.
Searching for a solution
We didn’t want to abandon Django or its default auth framework. It may have some shortcomings, but we’d rather improve upon it than having to abandon it. Let’s not reinvent the wheel here.
So first, I went out on an adventure. I went looking for other permission modules that claimed to solve the problem we had. Naturally, as we love open source at Spindle, I looked at GitHub and had a search for some awesome frameworks. These are some interesting ones that I found.
Unfortunately, none of these alternatives really fit the requirements, so I went on to write our own extension to the default Django auth framework.
Writing our own solution
In short, we basically wanted to “add an if-statement” to our permissions. Defining such permissions can basically be done in two ways. You can either define and save them in the database, or you can do it in code.
Django not only has a built-in auth framework, but it also comes with a content types framework. The content types framework exposes a GenericForeignKey. That’s a piece of technology that lets us connect a model to any other model in the database. So, by having a Permission model that uses a GenericForeignKey, we could not only give permission labels to users but also reference objects at the same time. This basically enables object-based permissions.
After doing lots of testing, it seemed that a solution with GenericForeignKey could work. However, there were still a few obstacles. It’s not very fast… It would also not be very scalable. To give users permission to change their own password, we’d need to add a new permission for every user. And what if permissions change? That would mean having to migrate millions of database rows. On our scale, that would simply not be worth it.
Just keep ‘em in code
That’s when I started experimenting with having our permissions simply reside in code. The idea behind the new permissions module is that one can simply define a function or a class, that will act as the permission. This enables developers to write any arbitrary code to evaluate permissions. And boy is it fast! We can evaluate up to 200.000 of such permissions per second with a low-end laptop CPU.
We also wanted these new permissions to work with Django’s existing auth framework. Luckily, Django exposes functionality to plug in your own permission module. This enables compatibility with loads of other frameworks that depend on the Django auth framework.
But that’s not where it stopped. I actually implemented lots of other nice goodies to either speed up permissions or to give our developers more control over them:
- Combining permissions
This is awesome. It really is. By keeping our permissions small and combining them, we can keep our code DRY. We don’t have to write the same permission over and over again: we can just combine them!
Essentially, permissions aren’t the core functionality of our program and are therefore overhead which we want to keep at a minimum. To do so, I implemented caching on top of the permissions. This also works with combined permissions and can really speed up permission evaluation.
- Object-based permissions
Our permissions module will pass the user and an optional object to the permission handler. That way we can check whether the user is allowed to execute an action on the specified object.
- Field-based permissions
None of the alternative permission modules implement support for having field-based permissions. We saw a need for them. So we implemented field-based permissions, using a simple config class to specify which permissions should be enforced on which fields. Together with the API framework integrations, this allows us to easily hide sensitive data or make fields read-only.
- Lots of integrations
The permission module integrates with Django’s default auth framework, Django REST framework and Tastypie. All out of the box. It’s also easy to add extra integrations with other frameworks!
It’s open source!
As mentioned above, we love open source. And we think that we’re not the only ones coming across this challenge. So if you’re looking at simplifying the way your team implements permissions, do have a look at our new open-source permissions module. Maybe even consider contributing? 🙂
It’s on GitHub: https://github.com/wearespindle/django-logical-perms
And there’s a presentation!!1!
This blog post is only getting more exciting, isn’t it? I told the whole story about this new permission module to my colleagues during our last pizza session. You can watch it online. It’s great.
Watch it on YouTube: https://youtu.be/tVN2uXFWHK8
No comments so far