# Role-Based Access Control with Django Rest Framework

##### Robert C Jensen - Aug 17, 2016 in[Notes](/content/categories/Notes/index.html)

> “I should talk now about Phaedrus’ knife. It’ll help you understand some of the things we talked about. The application of this knife, the division of the world into parts and the building of this structure, is something everybody does.”
> 
> _Zen and the Art of Motorcycle Maintenance - Robert M. Pirsig_

### Background

Business applications almost always feature some hierarchy of roles. A user’s position in this hierarchy determines their experience of the application - the things they can see, the places they can go and the actions they can take. One might say that the structure of user roles is the key abstraction that gives an application its essential character. Consider Unix’s plain distinction between user and superuser, Twitter’s public sea of content creators against its hidden mass of advertisers and [Active Directory’s labyrinthine table of security groups](https://technet.microsoft.com/en-us/library/dn579255.aspx).

[REST](https://en.wikipedia.org/wiki/Representational_state_transfer) is today’s prevailing approach to API design and the basis of the design of the internet itself. With REST, user roles are _not_ the key abstraction in a business application. Instead, REST promotes an architecture centered around _resources_ delivered in a particular _representation_.

### Designing your API

Let’s say your boss - a gorilla named Ishmael - has given you the following specification for an API:

> There are three types of users: **Takers**, **Leavers** and **Gods**. There is one resource: **Land**. The resource is read-only. Each piece of land has a _name_ and a flag representing its _arability_. **Takers** can only see _arable_ land. **Leavers** can only see _non-arable_ land. **Gods**, being gods, can see everything. Further, **Takers** and **Leavers** should not be aware that the land has been divided amongst them by arability. That is, they should not be able to see the _arability_ flag.

You set off to work on your API. Since you have been fully indoctrinated in the [Cult of the Design Recipe](http://www.ccs.neu.edu/course/csg107/design-recipe.html) you begin by designing a data structure for a **Land** and a **User** in something not-quite-like-but-close-enough-to JSON Schema:

|     |     |
| --- | --- |
| ```<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>``` | ```<br>Land = {<br>    "name": "string",<br>    "arable": "boolean"<br>}<br>User = {<br>    "user_type": "string"<br>}<br>``` |

Now you begin to consider the structure of your REST API. Your first instinct is that the _structure of user roles drives the behavior of the API_. You draw out a spec for your resources that looks like this:

|     |     |
| --- | --- |
| ```<br>1<br>2<br>3<br>4<br>5<br>6<br>``` | ```<br>Resources = {<br>    "land-for-takers": "/api/takers/land",<br>    "land-for-leavers": "/api/leavers/land",<br>    "land-for-gods": "/api/gods/land"<br>}<br>``` |

Suddenly you feel very anxious. **Takers**, **Leavers** and **Gods** are not _resources_. They are just _resource metadata_ about a **User**. The **Land** is the resource in question, not the **User** who access the resource. You rewrite the API in earnest:

|     |     |
| --- | --- |
| ```<br>1<br>2<br>3<br>``` | ```<br>Resources = {<br>    "land": "/api/land",<br>}<br>``` |

Much better. You begin to write your API with [Django REST Framework](http://www.django-rest-framework.org/) - your preferred python API framework. You make a ViewSet for **Land** like this:

|     |     |
| --- | --- |
| ```<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>``` | ```<br>class LandViewSet(ReadOnlyViewSet):<br>    # ...<br>    def get_queryset(self):<br>        if is_taker_user(self.request.user):<br>            return Land.objects.filter(arable=True)<br>        elif is_leaver_user(self.request.user):<br>            return Land.objects.filter(arable=False)<br>        elif is_god_user(self.request.user):<br>            return Land.objects.all()<br>    def get_serializer_class(self):<br>        if is_taker_user(self.request.user) or is_leaver_user(self.request.user):<br>            return LandSerializerHidingArability<br>        elif is_god_user(self.request.user):<br>            return LandSerializerWithAllFields<br>    # ...<br>``` |

You sit back - satisfied but still feeling a little anxious. Your code will meet Ishmael’s requirements, but you feel uneasy about the _future_ of the code.

- If another type of **User** is added you will have to change a lot of code. You will have to add a `is_new_type_of_user()` predicate and update all of the corresponding methods with the new user.
- These methods will become harder to read as you add more parameterization (e.g: over the HTTP verb)
- It is dull to repeatedly type out the same parameterization over **User**.

### Adding Roles to Django REST Framework

We had this exact problem at [Computer Lab](/content/site-root.html). We found that we could leverage two simple techniques to ease the pain of multiple user types:

- Use Django’s **Groups** to organize your **Users** into roles.
- Automatically dispatch REST calls to _role-specific methods_ based on the current **User** and their **Group** membership.

When we package these techniques into a mixin, the code above becomes:

|     |     |
| --- | --- |
| ```<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>``` | ```<br>class LandViewSet(RoleViewSetMixin, ReadOnlyViewSet):<br>    # ...<br>    def get_queryset_for_takers(self):<br>        return Land.objects.filter(arable=True)<br>    def get_queryset_for_leavers(self):<br>        return Land.objects.filter(arable=False)<br>    def get_queryset_for_gods(self):<br>        return Land.objects.all()<br>    def get_serializer_class_for_takers(self):<br>        return LandSerializerHidingArability<br>    def get_serializer_class_for_leavers(self):<br>        return LandSerializerHidingArability<br>    def get_serializer_class_for_gods(self):<br>        return LandSerializerWithAllFields<br>    # ...<br>``` |

It might not seem like much of a change, but we have accomplished a lot:

- We can think in terms of the **business logic** on the _inside_ of our API while still delivering **resources** on the _outside_.
- Our methods are well-scoped and easy to read.
- We don’t have to manage predicates for each type of **User**.

### Our implementation

We made [django-rest-framework-roles](https://github.com/computer-lab/django-rest-framework-roles) to re-use this technique in our client work. It includes more features beyond those described in this post, including fallback-defaults for unimplemented role-scoped methods and a configurable whitelist of methods to parameterize. Let us know if you find it useful in your work (or play)!

##### Robert C Jensen
