Re: Default Authorization BackEnd Denying Permissions if Object Provided
Here is the text of linked stuff for convenience:
For authorization backends checking object level permissions (like guardian) usually requires calling the django's default authorization backend as a fallback to the more general set of permissions:
if user.has_perm('foo.change_bar', obj=bar) or user.has_perm('foo.change_bar'):
However, this not only looks ugly, but also requires polling of all the backends twice, and thus, is a performance loss.
First, and possibly the best, solution to this is that, django does not deny permission if obj argument is provided, but just ignores it. This is also very logical, one who has a permission for the entire model/table, would also have it for an instance/row. This way by properly ordering backends in the settings, it could be a fallback solution for the lower level checkers. This might be the move in the right direction, although it is backwards incompatible.
A second solution is a keyword argument, such as fallback_to_model=None, that will allow lower-level checkers mimic the model level permissions that django does. Obviously, this is not DRY. But is needed if the first solution is not accepted to get the necessary permissions with one round of polling, and without cluttering the code. If it was accepted, it would still be a useful addition since it would allow backends to prefer to handle the fallback by themselves. Or, it would allow users who fallback by default override that behavior and not fallback (via a value of False), i.e., when object level permissions are definitive.
Here is what I propose in terms of working around the backward compatibility that seems to have kept it from being solved for so long.
1) define a global setting, say: OBJECT_PERMISSION_FALLBACK_TO_MODEL=False. This is to help maintain the default behavior (unless the setting is changed of course).
2) (as mentioned in the above comment) define a keyword argument at the method level for occasional override, say: fallback_to_model=None. Default value of None means it will be ignored in favor of the global setting, otherwise, it will take precedence.
# default: check all, model checker expected to NOT disown her child
user.has_perm('foo.change_bar', obj, backends=('object', 'role', ))
# just check backends specified; 'role' for *.RoleBackend
One will not have to use package specific API for checking just the object permissions, for example.
Cleaner and generic: allows backends re-use each other. For example, I am writing a RoleBackend and can easily make calls to model or object permission backends, in order not to reinvent the wheel.