Search code examples
pythondjangodjango-rest-frameworkpermissions

Is there a difference between permission_classes = [A, B], permission_classes = [A & B]?


What is the difference between

permission_classes = [A, B] 
permission_classes = [A & B]

??

I understand that the first one processes permission sequentially, and the second one does it at once.

Is there a difference in the result?

Also, I wonder which method is preferred and why.


Solution

  • In short: two or more elements in a list or tuple is advisable.

    Semantically the two are nearly the same. Indeed, Django checks the permissions in the .check_permissions(…) method [GitHub]:

    def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None),
                )

    It thus enumerates over the items in the list and when one of the permissions fails, it invokes the .permission_denied(…) method with the message and code of the permission that failed.

    If you use the & operator on the other hand, it will construct a new permission, with the .__and__(…) method [GitHub]:

    class OperationHolderMixin:
        def __and__(self, other):
            return OperandHolder(AND, self, other)
    
    
    # …
    
    
    class OperandHolder(OperationHolderMixin):
        def __init__(self, operator_class, op1_class, op2_class):
            self.operator_class = operator_class
            self.op1_class = op1_class
            self.op2_class = op2_class
    
        def __call__(self, *args, **kwargs):
            op1 = self.op1_class(*args, **kwargs)
            op2 = self.op2_class(*args, **kwargs)
            return self.operator_class(op1, op2)
    
        # …
    
    
    class AND:
        def __init__(self, op1, op2):
            self.op1 = op1
            self.op2 = op2
    
        def has_permission(self, request, view):
            return self.op1.has_permission(request, view) and self.op2.has_permission(
                request, view
            )
    
        def has_object_permission(self, request, view, obj):
            return self.op1.has_object_permission(
                request, view, obj
            ) and self.op2.has_object_permission(request, view, obj)

    This is thus essentially just some meta-programming logic to first run the .has_permission(…) on the first operand and if that succeeds, on the second operand.

    So it seems that the two are equivalent? Well not exactly, by using an &, the .message and .code are gone, so one can not trace back which permission check exactly has been denied. While most (all) builtin permissions have no message or code, if you thus specify one yourself for a custom permission check, and that permission check is used in an operator, the result of that expression loses the code and message.

    So while this is likely, a small detail, there is a small advantage in using [perm1, perm2] over [perm1 & perm2]