|
|
|
# DjangoLDP Permissions
|
|
|
|
|
|
|
|
DjangoLDP has a permissions system which differs slightly from Django's inbuilt permissions, although it is built on it
|
|
|
|
|
|
|
|
DjangoLDP permissions classes perform 3 roles:
|
|
|
|
* Control: permit or deny actions based on the user permission
|
|
|
|
* Filter: remove objects from a container which a user shouldn't be able to see
|
|
|
|
* Output: return a list of permissions which the user has on a given resource
|
|
|
|
|
|
|
|
The important function for all of these roles is `user_permissions`, defined in the base class `LDPPermissions`. This function returns a list of permissions which the user has on the resource, and is used by `has_permission` and `has_object_permission` when granting or denying an action
|
|
|
|
|
|
|
|
The built-in permissions are defined in the DjangoLDP Model.Meta:
|
|
|
|
```python
|
|
|
|
default_permissions = ('add', 'change', 'delete', 'view', 'control')
|
|
|
|
```
|
|
|
|
If you define your own Meta class, these permissions will not be inherited by default, [so you should inherit them explicitly](https://docs.djangoproject.com/fr/2.2/topics/db/models/#meta-inheritance)
|
|
|
|
|
|
|
|
## Why?
|
|
|
|
|
|
|
|
Traditionally, Django provides methods such as `has_perms`, returning a boolean which returns `True` or `False` depending on whether a user has permission to perform an action. Whilst this is useful for the Filter and Control roles of our permissions class, it doesn't provide the Output role, returning which permissions are available to the user
|
|
|
|
|
|
|
|
# Extending Permissions for Your Own Models
|
|
|
|
|
|
|
|
## Anonymous, Authenticated and Owner Permissions
|
|
|
|
|
|
|
|
As a shortcut, you can define extend your model's Meta to include a list of permissions which should be given to anonymous users, authenticated users and the owner of a resource (usually the creator)
|
|
|
|
```python
|
|
|
|
class Circle(Model):
|
|
|
|
...
|
|
|
|
|
|
|
|
class Meta(Model.Meta):
|
|
|
|
anonymous_perms = []
|
|
|
|
authenticated_perms = []
|
|
|
|
owner_perms = []
|
|
|
|
```
|
|
|
|
|
|
|
|
By default the `owner` field is a field on the model named `owner`, but you can set this to something else with the following setting:
|
|
|
|
```python
|
|
|
|
class Meta(model.Meta):
|
|
|
|
owner_field = 'user'
|
|
|
|
```
|
|
|
|
|
|
|
|
## Object-Level Permissions
|
|
|
|
|
|
|
|
DjangoLDP comes packaged with [Django-Guardian](https://django-guardian.readthedocs.io/en/stable/overview.html), which is used for supporting object-level permissions
|
|
|
|
|
|
|
|
DjangoLDP Models are registered with the Admin site using Django Guardian by default, but if you have created a custom Admin class for your model you will need to extend `GuardedModelAdmin` manually:
|
|
|
|
```python
|
|
|
|
from guardian.admin import GuardedModelAdmin
|
|
|
|
|
|
|
|
class CustomAdmin(GuardedModelAdmin):
|
|
|
|
pass
|
|
|
|
```
|
|
|
|
|
|
|
|
You can use Django-Guardian to add custom permissions to your models (as well as the default DjangoLDP permissions), and these will be returned by `user_permissions`
|
|
|
|
|
|
|
|
## Custom Permissions Classes
|
|
|
|
|
|
|
|
For anything which is a little more custom, you can provide a custom permissions class, extending `LDPPermissions`
|
|
|
|
|
|
|
|
```python
|
|
|
|
class CirclePermissions(LDPPermissions):
|
|
|
|
def user_permissions(self, user, obj_or_model, obj=None):
|
|
|
|
# user_permissions may be working with an instance, or the model base!
|
|
|
|
if not isinstance(obj_or_model, ModelBase):
|
|
|
|
obj = obj_or_model
|
|
|
|
|
|
|
|
# start with the permissions set on the object and model
|
|
|
|
# important as this will return anon_perms, auth_perms, owner_perms,
|
|
|
|
# and custom object-level permissions
|
|
|
|
# conversion to a set is useful because it will prevent duplicates
|
|
|
|
perms = set(super().user_permissions(user, obj_or_model, obj))
|
|
|
|
|
|
|
|
if not user.is_anonymous:
|
|
|
|
# object-level permissions
|
|
|
|
if obj:
|
|
|
|
# permissions gained by being a circle-member, and admin
|
|
|
|
if obj.members.filter(user=user).exists():
|
|
|
|
perms.add('view')
|
|
|
|
|
|
|
|
if obj.members.filter(user=user).get().is_admin:
|
|
|
|
perms = perms.union({'add', 'change', 'delete'})
|
|
|
|
|
|
|
|
# permissions gained by the circle being public
|
|
|
|
if obj.status == 'Public':
|
|
|
|
perms = perms.union({'view', 'add'})
|
|
|
|
|
|
|
|
# model-level permissions
|
|
|
|
else:
|
|
|
|
perms = perms.union({'view', 'add'})
|
|
|
|
|
|
|
|
return list(perms)
|
|
|
|
```
|
|
|
|
|
|
|
|
Finally, in your model's Meta, attach the permission class:
|
|
|
|
```python
|
|
|
|
class Circle(Model):
|
|
|
|
...
|
|
|
|
|
|
|
|
class Meta(Model.Meta):
|
|
|
|
permission_classes = [CirclePermissions]
|
|
|
|
``` |
|
|
|
\ No newline at end of file |