Search code examples
djangodjango-rest-frameworkdjango-permissions

How can I structure Django permissions to have multiple categories of permissions groups?


I'd like to have multiples layers of permissions for an API using Django Rest Framework, how can I best achieve this?

Specifically the three categories of authorization I have are:

  • Roles: Model level access, such as admin and different customer types.
  • GroupAccess: Per object, group access such as a team of users.
  • Sensitivities: Per object, additional tags for sensitive information.

The second two categories apply equally across all models and it would be nice to not need separate permissions for each model.

Idea 1:

Create a model for each category inheriting from the standard django auth group. Doing these as proxy groups, to be logically different in my code, but as consistent with standard authorization as possible. Then use django-guardian to enable the object level permissions.

Idea 2:

Use the standard groups for roles and assign model level permissions based on these groups. For the object level permissions write a custom permission classes in Django Rest Framework to check the object level permissions against the user.


Solution

  • I have recently designed such an architecture so the first thing came into my mind would be like this:

    1. Roles: You can override django's built-in AbstractUser class by adding role-level choices such as:
    # models.py
    from django.contrib.auth.models import AbstractUser
    
    class User(AbstractUser):
        """Custom user model with an extra type field"""
        SUPER_USER = 1
        OTHER_ROLE = 2
        SOME_OTHER_ROLE = 3
    
        USER_TYPE_CHOICES = (
            (SUPER_USER, 'Super user'),
            (OTHER_ROLE, 'Other role'),
            (SOME_OTHER_ROLE, 'Some other role'),
        )
    
        user_type = models.PositiveSmallIntegerField(choices=USER_TYPE_CHOICES)
    
    # -------------------------------------------------------------------------
    
    # Don't forget to set this User model as your default model
    # settings.py
    AUTH_USER_MODEL = 'my_app.User'
    
    
    1. You can get use of django's built-in Group model and put a ForeignKey to your Team models and do object-level permission manually.
    # models.py
    from django.contrib.auth.models import Group
    from django.db import models
    
    def Team(models.Model):
        group = models.ForeignKey(Group, on_delete=models.CASCADE)
    
    # -------------------------------------------------------------------------
    
    # You can do object-level permission per group by
    # ...
    if team.group in permitted_groups:
        # permission granted
        pass
    else:
        # permission not granted
        pass
    # ...
    
    1. You can define a Tag model and add as ManyToManyField to your sensitive information model. Similar to the second solution above, you can manually do object-level permission during runtime by relying on your current information's tags.