Hi,
as far as I understand the documentation[1], django's architecture
supports object permissions, but external apps like django-rules or
django-guardian are supposed to provide actual implementations.
I think this approach is great. However, I believe that there are some
key points where django core should provide more tooling so that the
external apps can concentrate more on building the actual authentication
backends.
# DefaultObjectBackend (#20218)
ModelBackend always returns `False` if an object is passed. Many
developers expect object permissions to be a superset of model
permissions. A DefaultObjectBackend could be added that does exactly
that. By implementing it as a separate backend it is fully optional and
does not break backwards compatibility.
```
class DefaultObjectBackend:
def authenticate(self, username, password):
return None
def get_user_permissions(self, user, obj=None):
if obj is None:
return set()
return user.get_user_permissions()
def get_group_permissions(self, user, obj=None):
if obj is None:
return set()
return user.get_group_permissions()
def get_all_permissions(self, user, obj=None):
if obj is None:
return set()
return user.get_all_permissions()
def has_perm(self, user, perm, obj=None):
if obj is not None:
return user.has_perm(perm)
```
# PermissionRequiredMixin.get_permission_object()
Currently, external apps have to ship their own mixin because the
default one does not support object permissions. A new method
`get_permission_object()` could be added. This is what django-guardian
does. However, in django core it should return None by default for
backwards compatibility (In django-guardian it falls back to
`get_object()`).
```
class PermissionRequiredMixin:
…
def get_permission_object(self):
return None
def has_permission(self):
perms = self.get_permission_required()
permission_object = self.get_permission_object()
return self.request.user.has_perms(perms, obj=permission_object)
```
# has_perm template tag
Just as with PermissionRequiredMixin, the current way to check for
permissions in templates does not support object permissions. A new
has_perm template tag could be added that simply calls
`user.has_perm()`. This approach is used by django-rules.
```
@register.simple_tag
def has_perm(perm, user, obj=None):
return user.has_perm(perm, obj)
```
# Add a BaseBackend
Currently, writing an authentication backend is not as straight-forward
as it could be. I propose to add a BaseBackend that implements some sane
defaults:
```
class BaseBackend:
def authenticate(self, username, password):
return None
def get_user_permissions(self, user, obj=None):
return set()
def get_group_permissions(self, user, obj=None):
return set()
def get_all_permissions(self, user, obj=None):
perms = set()
perms.update(self.get_user_permissions(user, obj=obj))
perms.update(self.get_group_permissions(user, obj=obj))
return perms
def has_perm(self, user, perm, obj=None):
perms = self.get_all_permissions(user, obj=obj)
return perm in perms
```
All of these changes can easily be implemented and have little to no
backwards incompatibility. However, they would provide a much stronger
base for object permission in core. I am not sure whether this is
desired from either the core team or the developers of external
authentication apps though.
What do you think? Should I proceed writing pull requests for the
proposed changes?
tobias
[1]:
https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#handling-object-permissions