Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement roles in django rest framework

I am building an API that should have the following kind of users

super_user - create/manage admins

admin - manage events(model) and event participants

participants - participate in events, invited to events by admins

Additional i want to have each type of user to have phone number field

I tried

class SuperUser(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    phone_number = models.CharField(max_length=20)

class Admin(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    phone_number = models.CharField(max_length=20)


class Participant(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    phone_number = models.CharField(max_length=20)

But gut is telling me its a wrong way to handle this. Can someone please help.

like image 834
Emmanuel Mtali Avatar asked Dec 21 '16 22:12

Emmanuel Mtali


2 Answers

One possible solution is:

    1. Have only one User Model with role field, which defines what user role is.
    1. Create a User Group and add each group needed permissions.
    1. Add User to User Group
    1. Limit access using a Django REST Framework (later DRF) Permission Class.

Explanation:

  1. Using only one user model is a more simple and flexible solution. You can query all users, or filtered by feature (like user role). Standart Django auth system expects one UserModel.

  2. Read more about Django user groups. See "Django Permissions Docs #1" and "Django Groups Docs #2". Also useful is "User groups and permissions".

You need to create a group for each user role, and add needed permissions for each group. (Django has a default model permission, created automatically, look at the docs on the given links) or create the needed permission manually in the model definition.

  1. Manually or using a script, add User to the needed group by defining his role when a user is created or manually by Django Admin interface.

  2. Now everything should be ready for limited access by the user's role. You can easily limit access to the DRF View using a permission class. See more information in the "DRF Permission Docs".

Let's define our own:

from rest_framework.permissions import DjangoModelPermissions
# Using DjangoModelPermissions we can limit access by checking user permissions.

# Rights need only for CreateUpdateDelete actions.
class CUDModelPermissions(DjangoModelPermissions):
  perms_map = {
      'GET': [],
      'OPTIONS': [],
      'HEAD': ['%(app_label)s.read_%(model_name)s'],
      'POST': ['%(app_label)s.add_%(model_name)s'],
      'PUT': ['%(app_label)s.change_%(model_name)s'],
      'PATCH': ['%(app_label)s.change_%(model_name)s'],
      'DELETE': ['%(app_label)s.delete_%(model_name)s'],
  }

# Or you can inherit from BasePermission class and define your own rule for access
from rest_framework.permissions import BasePermission

class AdminsPermissions(BasePermission):
    allowed_user_roles = (User.SUPERVISOR, User.ADMINISTRATOR)

    def has_permission(self, request, view):
        is_allowed_user = request.user.role in self.allowed_user_roles
        return is_allowed_user

# ----
# on views.py

from rest_framework import generics
from .mypermissions import CUDModelPermissions, AdminsPermissions

class MyViewWithPermissions(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [CUDModelPermissions, ]
    queryset = SomeModel.objects.all()
    serializer_class = MyModelSerializer

You can add additional permission class to combine access limitation.

like image 113
Anton Manevskiy Avatar answered Nov 11 '22 23:11

Anton Manevskiy


So in Django any user has a flag is_superuser that corresponds to your 'superuser'. So just use that - e.g. User.objects.create(is_superuser=True).

For the rest you can simply use a field for a normal User model to differentiate between subroles of a normal user.

class User(AbstractBaseUser):
    can_participate_event = models.Boolean(default=False)
    can_create_event = models.Boolean(default=False)

Or

class User(AbstractBaseUser):
    permissions = models.CharField(default='')  # and populate with e.g. 'create_event,participate_event'

Still you will need to check all those fields in your view probably. The more you add to your application, the hairier this becomes so I would suggest using a 3rd party library like rest-framework-roles (I'm the author) or guardian.

like image 5
Pithikos Avatar answered Nov 11 '22 21:11

Pithikos