You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Handle composition of unimplemented permission methods correctly
Previously has_object_permission defaulted to returning True when
unimplemented because it was assumed that has_permission had already returned
true, but when composed with OR, this might not be the case. Take for example
the case where you want an object to be accessible by the user that created it
or any admin. If you have a permission IsOwner that implements
has_object_permission and IsAdminUser which doesn't, then if you OR them
together an have a user that is neither the owner nor the admin they should
get denied but instead IsOwner will pass has_permission and IsAdminUser will
pass has_object_permission even though it didn't pass has_permission.
One solution would be to default has_object_permission to calling
has_permission but that would potentially cause redundant database lookups.
Alternatively this could be done only in the OR class, but the redundant
calls will still happen.
Also, incorrect behavior can still occur using more complicated compositions
including NOT. For example, the IsOwner permission above only implemented
has_object_permission and not has_permission, but ~IsOwner will always fail,
even on objects that the authenticated user doesn't own, because the default
True from BasePermission.has_permission() will also be inverted.
My solution is to be more explicit about when a permission subclass doesn't
implement has_permission or has_object_permission by returning NotImplemented.
NotImplemented is truthy in a boolean context, so by default everything will
continue to work the same as long as code is not expecting the result to
literally be True or False. I modified AND and OR so that if one side returns
NotImplemented, it defers to the other. If both sides return NotImplemented,
NotImplmented will be returned to propogate upwards. NOT will also propagate
NotImplmented instead of inverting it. If NotImplemented propagates
all the way up to APIView.check_permissions or
APIView.check_object_permissions, it is considered a pass (truthy).
0 commit comments