The Django web framework which I work with only has a simple permission system and from time to time, that is pretty annoying. But more fine-grained permissions are not trivial and one can discuss for ages on how one should implement these features. For a website I work with, I really wanted a layer of protection so the application server could not access certain parts of data when the user logged in does not have to know stuff.
So I started "hacking" against Django's ORM and soon enough I had a elegant solution with a slightly hackish implementation. It all starts with extending the default
Model and wrapping the fields in properties which control access. This access is configured by a
Access innerclass (just like the
Meta innerclass). The reason it is in the model definition itself is because I see access to that model as directly related. It is not an Admin page definition. The statements in the
Access class describe features users must have using the ORM itself.
This control structure lets you limit access to a number of things, namely: reading and updating on attributes, selecting instances from the object's
Manager, creating, deleting and saving instances. These restrictions are formulated as functions on the
Access class and get the relevant data (user, instance or set, etc). The following is an example for a
Person-model; certain fields are readable for everyone, but the rest is pretty restricted:
read = lambda user, person, field: not (field.attname in
['address', 'phonenumber', 'email', 'banknumber']
and person.get_user() != user)
update = lambda user, person, field: person.get_user() == user
commit = lambda user, person: person.get_user() == user
Using the system is easy, just use the restricted
Model as superclass, which loads the permission system for that model. By default superusers get full access, but this can be turned off. The system also includes a custom
ModelAdmin and a custom
ModelForm, which ensure that unreadable attributes get hidden by using the
exclude mechanism of Django's
You might ask yourself how the ORM can possibly get hold of the current user? Well, that is part of the hack; it uses thread-local storage for this. This means you have to install a tiny piece of middleware to make the whole thread aware of the current user. Mind you, the user must be present if access is restricted; if no user is specified, it will deny access. This way, if your authentication middleware fails in some way, access is denied. Denying access if there is no user causes a minor problem: using shell commands like
loaddata. There is a special
ENV-variable to tell the system not to impose any restrictions, but you (I) have to remember to set it when running data-operations O:)
This system has been up and running on the site since Django 1.0-beta and has survived until now (1.2-alpha). I explicitly ignored private functionality in the ORM so it would not break easily. It is not completely finished; there are some minor
TODOs left, which are mainly to make things even more robust. I can make a fair guess that it will cost you some performance, but the major part of that will be your own fault: keep your access rules simple.
Also, this is no fail-safe security system, this is just another layer of protection from users in the default environment: doing http requests. If the rest of the code is secure, it will not divulge information you restricted from them, but it will not hold in case of code-injections.
The whole thing comes in around 250-300 lines of code, including
ModelForm and decorators. E-mail me if you are interested or leave a comment, I might patch it so it survives daylight :-) But I would recommend people thinking about such things, it really helps you grasp the ORM and thus helps with solving problems you might encounter in the future.