Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Designing Django Rest Framework role based authorization

I'm designing the authorization of a DRF application. I need to use roles, not just permissions.

I have a model (e.g. project) in which I have some information (e.g. name, description) that can be modified by some roles (e.g. admin). But at the same time there are other roles (e.g. worker) that should not be able to modify that information inside that model but can still modify some other information (e.g. initial and final dates).

I've thought two solutions for this issue. The first one is reading the HTTP request sent and defining the actions to be taken depending on what the request is. This means that each time that a new field is added to the model, I will have to modify this logic. This sounds awfully hard to maintain, is error prone and can introduce vulnerabilities.

On the other hand, I've thought that I could divide the model into two different models. One of them containing the data that just one role (admin) can modify and the other defining the other data that can be modified by both roles (admin, worker). This way, I will not have to parse the HTTP request, because if I get a POST/PUT request affecting the first model and the user has a worker role I can directly reject it.

This situation happens with more than one model.

I would like to know if there's a default way to go or if I'm reinventing the wheel. I think that this situation must be really common. For example, I can think of a git project in which some users have access to do one thing inside a project but not others.

Complementary notes (feedback will be really appreciated):

  • I most probably will use the django-role-permissions module to implement roles and permissions. I cannot use django built-in groups because, although you can add permissions to them, I will use them to group users (without having anything to do with roles).

  • I will create a relation between roles and permissions (string based permission, such as create_project, modify_project_description) in a permissions file.

  • When receiving each request I will check which roles have privileges to perform that action and check if the user is any of those roles (activity based authorization, what means that in the endpoint I'll check the activity/action instead of the role).

like image 415
newlog Avatar asked Aug 18 '15 03:08

newlog


2 Answers

Sounds like you want some field level security. You might look into using proxy models to give write access to a limited set of fields for the restricted users.

Another option might be to use a custom serializer class which applies readonly to some fields. get_serializer on a ViewSet subclass could be a good place to do the pivot, you should find the current user in self.request.user.

like image 73
Aaron McMillin Avatar answered Oct 22 '22 13:10

Aaron McMillin


After giving some thought I found two solutions.

The first solution was (instead of dividing the models as I mentioned in the question) declaring different endpoints (URLs) for each type of action that has to happen. Then in each endpoint serializer defining the valid parameters through the fields class variable.

This means that if one role can only update certain fields, I would create a ModelViewSet for this action and in the serializer class related to this ModelViewSet only allow certain parameters.

This solution has many problems and is not quite RESTful. You might end up with dozens of different URLs. In my case, given that the API is not big and the actions were limited that might not have been a problem in the short term, but sure it would have been a problem when the requirements of the application changed.

The second (and chosen) solution was to define a permission/role table in which you define the endpoint where the action has to happen (if you have built your application in the correct way it can just be the ModelViewSet class name), the action to be executed (list, retrieve, create...) the fields of the related model that can be interacted on and the role that can interact with those fields.

I would suggest using dictionaries as the used data structure for the permission table so each permission check is O(1).

Now each time you get a request to that endpoint, a permission check using that table should be done.

I implemented the permission check by overriding the check_permissions() method in the ModelSetView class. Be careful and keep the original check_permissions() functionality in there too.

like image 28
newlog Avatar answered Oct 22 '22 13:10

newlog