Permission control logic of Django and DRF

The module responsible for permission control in Django project is contrib auth. Sometimes, in order to extend the row level permission function, a package named Guardian is introduced. The description of this article is based on the combination of Django + DRF + Guardian.

model

Django does not strictly follow the RBAC model. His "permissions" can be assigned to both people and groups. The same is true for Guardian. Model is defined as follows:

class Permission(models.Model):
    name = models.CharField(_('name'), max_length=255)
    content_type = models.ForeignKey(ContentType, models.CASCADE)
    codename = models.CharField(_('codename'), max_length=100)


class Group(models.Model):
    name = models.CharField(_('name'), max_length=80, unique=True)
    permissions = models.ManyToManyField(Permission)


class UserPermission(models.Model):
    user = models.ForeignKey(User)
    permission = models.ForeignKey(Permission)


class UserObjectPermission():
    user = models.ForeignKey(user_model_label, on_delete=models.CASCADE)
    permission = models.ForeignKey(Permission, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_pk = models.CharField(_('object ID'), max_length=255)
    content_object = GenericForeignKey(fk_field='object_pk')
    
class GroupObjectPermission()
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    permission = models.ForeignKey(Permission, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_pk = models.CharField(_('object ID'), max_length=255)
    content_object = GenericForeignKey(fk_field='object_pk')

The codename field of Permission table is an verb object structure, similar to view_ In this way, User means that he has read Permission to the User table. The UserObjectPermission table adds object to the foreign key of a Permission table_ PK information. Therefore, this table can become very large, which should be paid attention to when using.

Backend

The functional positioning of Backend is to be frank:

Look up the table to determine whether someone has some authority.

Each kind of Backend is responsible for a specific table or table lookup rule.

class ObjectPermissionBackend(object):
    supports_object_permissions = True
    supports_anonymous_user = True
    supports_inactive_user = True

    def authenticate(self, username, password):
        return None

    def has_perm(self, user_obj, perm, obj=None):
        pass

    def get_all_permissions(self, user_obj, obj=None):
        pass


class ModelBackend:
    def get_all_permissions(self, user_obj, obj=None):
        if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
            return set()
        if not hasattr(user_obj, '_perm_cache'):
            user_obj._perm_cache = set()
            user_obj._perm_cache.update(self.get_user_permissions(user_obj))
            user_obj._perm_cache.update(self.get_group_permissions(user_obj))
        return user_obj._perm_cache

    def has_perm(self, user_obj, perm, obj=None):
        if not user_obj.is_active:
            return False
        return perm in self.get_all_permissions(user_obj, obj)

PermissionClass

Permission Class is an advanced permission definition method provided by DRF. The basic definition is:

class BasePermission(object):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

The DRF View will call get at the right time, for example_ Verify the user's permissions before object.

Configuration method

PermissionClass is configured on the View of DRF, and permission_classes = []. If there is no configuration, the default setting is used REST_ FRAMEWORK. DEFAULT_ AUTHENTICATION_ CLASSES.

When a user requests a view, the DRF checks in this way: PermissionClass is called in sequence, and the authorization is successful only when all of them return True.

The Backend is configured in settings AUTHENTICATION_ In the backends list, it means to enable these backends. Backends in this list can be used by Django contrib. auth. auth. get_ Obtained by the backends() function. The most commonly used built-in backends caller is user has_perm() method, which will iterate over each Backend. If any Backend returns True, the verification passes, but if any Backend raise PermissionDenied, it has_perm returns False directly. That is, for a single Backend:

  • Return True must pass
  • Returning False does not necessarily mean rejecting
  • raise PermissionDenied must refuse

To sum up, the core of configuring DRF permission control is to handle the relationship between Backend and PermissionClass. They belong to the back end and the front end. Generally, it can be divided according to the following rules:

  • Those that need to check the database belong to the category of Backend
  • The logical connection of multiple permissions, such as IsStaffOrReadonly, belongs to the category of PermissionClass
  • Can affect user has_ The result returned by perm() is Backend, which is used by user has_ Perm() affects PermissionClass
  • The configuration of backmissionclass is global and the configuration of backmissionclass takes effect objectively.

give an example:

Case1 logical permission

It refers to the permission defined by a "rule" rather than stored in the database. For example, to realize such a requirement:

If the user has Model1 of A Permission, it is automatically owned Model2 of B jurisdiction. Otherwise, there is no permission.

Our analysis of this requirement needs to directly affect the user has_ The return result of perm (model2, b), so it should be implemented with Backend.

Tags: def CASCADE

Posted by Svoboda on Wed, 25 May 2022 11:16:26 +0300