Tobias Bengfort
2018-10-27 16:10:37 UTC
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
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
--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+***@googlegroups.com.
To post to this group, send email to django-***@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/c50b1518-9f7c-d090-a40b-70995bf2f731%40posteo.de.
For more options, visit https://groups.google.com/d/optout.
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+***@googlegroups.com.
To post to this group, send email to django-***@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/c50b1518-9f7c-d090-a40b-70995bf2f731%40posteo.de.
For more options, visit https://groups.google.com/d/optout.