|
|
# DjangoLDP Permissions
|
|
# Introduction
|
|
|
|
|
|
|
|
DjangoLDP provides some permissions classes built on top of [Rest Framework permissions classes](https://www.django-rest-framework.org/api-guide/permissions/)
|
|
DjangoLDP uses Permissions classes, which is something we've inherited from [Django Rest Framework](https://www.django-rest-framework.org/api-guide/permissions/)
|
|
|
|
|
|
|
|
> Permissions in REST framework are always defined as a list of permission classes
|
|
|
|
|
|
|
|
## Custom Permissions
|
|
|
|
|
|
|
|
In DRF you write a class which inherits two methods:
|
|
|
|
|
|
|
|
* `has_permission` returns `True` if the `request.user` has access to the `view`
|
|
|
|
* `has_object_permission` returns `True` if the `request.user` has access to the `obj`
|
|
|
|
|
|
|
|
it's possible to extend `LDPBasePermissions` in the same way without any extras, but in fact in most situations in `LDPBasePermissions` was designed so that you will not need to override `has_`
|
|
|
|
|
|
|
|
### The Extras
|
|
|
|
|
|
|
|
`LDPBasePermissions` (the base class for all custom permissions) extends the Rest Framework in the following ways:
|
|
|
|
|
|
|
|
#### Model Permissions
|
|
|
|
|
|
|
|
we add `has_model_permission(request, view)`. This is useful if you are using `LDPViewSet` (or writing your own) because it returns `True` if the `request.user` has access to the `view.model`. Why ?
|
|
|
|
* It is checked before permitting `LDPViewSet.create` (POST)
|
|
|
|
* It is used by `LDPPermissionsFilterBackend` (see below). If it's possible that you can grant users permissions from the model-level, there are performance benefits
|
|
|
|
|
|
|
|
#### Serializing Permissions
|
|
|
|
|
|
|
|
the `LDPSerializer` is written to serialize permissions on all objects, to provide Web-ACLs on LDP resources. For this we change the working of permissions classes, and hence how you extend them. We add three new functions:
|
|
|
|
* `get_model_permissions(request, view, obj=None)`. Returns a set of permissions granted on the model. Rarely need to override this
|
|
|
|
* `get_object_permissions(request, view, obj)`. Returns a set of permissions granted on the object. Rarely need to override this
|
|
|
|
* `get_user_permissions(request, view, obj)`. Returns a set of all permissions on the object and model. This is the function you will typically override
|
|
|
|
|
|
|
|
Always start your permissions classes with `perms = super()`, e.g.
|
|
|
|
|
|
|
|
```
|
|
|
|
def get_user_permissions(self, request, view, obj=None):
|
|
|
|
'''returns a set of all model permissions and object permissions for given parameters'''
|
|
|
|
perms = self.get_model_permissions(request, view, obj)
|
|
|
|
if obj is not None:
|
|
|
|
return perms.union(self.get_object_permissions(request, view, obj))
|
|
|
|
return perms
|
|
|
|
```
|
|
|
|
|
|
|
|
What are the set of permissions which can be returned ?
|
|
|
|
|
|
|
|
The built-in permissions are defined in the DjangoLDP Model.Meta:
|
|
The built-in permissions are defined in the DjangoLDP Model.Meta:
|
|
|
```python
|
|
```python
|
| ... | @@ -8,28 +49,36 @@ default_permissions = ('add', 'change', 'delete', 'view', 'control') |
... | @@ -8,28 +49,36 @@ 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)
|
|
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)
|
|
|
|
|
|
|
|
# DjangoLDP >= 2.1
|
|
You can also extend this to include custom permissions. To allow for object-level permissions with custom any custom permissions, please refer to [Django Guardian](https://django-guardian.readthedocs.io/en/stable/userguide/assign.html#prepare-permissions)
|
|
|
|
|
|
|
|
## LDPBasePermissions
|
|
#### Filter Backends
|
|
|
|
|
|
|
|
Built on Rest Framework's `DjangoObjectPermissions`, this is the class which any custom permissions should extend, as it defines the custom logic necessary for DjangoLDP
|
|
In Django Rest Framework, an extra step is required that you manually extend `filter_queryset` in your custom views to remove any unwanted objects, or assign a `FilterBackend` to them. This is not completed automatically for performance reasons
|
|
|
|
|
|
|
|
Very generally in Django Rest Framework you have two key functions to override:
|
|
In DjangoLDP you can do this, and extending `LDPBasePermissions` in your custom permissions class won't include any of the automated filter backend functionality
|
|
|
* `has_permission` returns `True` or `False` depending on whether the passed request should be given access to the passed _view_. It is therefore the _view-level_ permission
|
|
|
|
|
* `has_object_permission` is similar, but passing an object it returns a boolean based on the _object_ level permissions
|
|
|
|
|
|
|
|
|
|
LDPPermissions do a little more than the Rest Framework:
|
|
However offering a way to make things as automated as possible is a stated objective of DjangoLDP, hence why we allow the use of the default `LDPViewSet` to automatically expose views and urls on your `Model` subclasses
|
|
|
* we extend a new concept of `has_model_permission`, which defines whether a given request has permission to access the object based on _model_ permissions
|
|
|
|
|
* we _output_ lists of permissions granted, which we use to serialize the permissions on each object/container, as part of the LDP standards
|
|
|
|
|
|
|
|
|
|
In order to provide the output functionality, there are three key functions provided by `LDPBasePermissions`:
|
|
If you want to offer a similar feature for your own permissions classes, you'll need to define a custom `FilterBackend`. Please refer to the [Django Rest Framework](https://www.django-rest-framework.org/api-guide/filtering/#custom-generic-filtering) documentation for how to do this, but note that we recommend extending `rest_framework_guardian.filters.ObjectPermissionsFilter` (bundled with DjangoLDP) because it allows Guardian object permissions to work (see below example)
|
|
|
* `get_model_permissions(self, request, view, obj=None)`
|
|
|
|
|
* `get_object_permissions(self, request, view, obj)`
|
|
|
|
|
* `get_user_permissions(self, request, view, obj=None)`
|
|
|
|
|
|
|
|
|
|
All 3 return a `set` of permissions. Overriding these functions allows the permissions to be serialized on the models, but if this is for whatever reason disabled in your app then you can fallback onto the `has_permission` and `has_object_permission` provided by DRF
|
|
the FilterBackend can be attached to your permissions class, and therefore any `Model` or `LDPViewSet` which attaches to it:
|
|
|
|
```
|
|
|
|
# filters.py
|
|
|
|
class MyFilterBackend(ObjectPermissionsFilter):
|
|
|
|
def filter_queryset(self, request, queryset, view):
|
|
|
|
object_perms = super().filter_queryset(request, queryset, view)
|
|
|
|
# ...
|
|
|
|
|
|
|
|
# permissions.py
|
|
|
|
class MyPermissionsClass(LDPBasePermissions):
|
|
|
|
filter_backends = [MyFilterBackend]
|
|
|
|
```
|
|
|
|
|
|
|
|
In fact it's very rare that you should need to override `has_object_permission` or `has_model_permission` at all, as these by default analyse and return the results of their counterparts
|
|
# Detail
|
|
|
|
|
|
|
|
## LDPBasePermissions
|
|
|
|
|
|
|
|
Built on Rest Framework's `DjangoObjectPermissions`, this is the class which any custom permissions should extend, as it defines the custom logic necessary for DjangoLDP
|
|
|
|
|
|
|
|
To provide custom permissions, simply extend the `LDPBasePermissions` or one or more of the subclasses below, and then set the `permission_classes` to your custom models and/or views:
|
|
To provide custom permissions, simply extend the `LDPBasePermissions` or one or more of the subclasses below, and then set the `permission_classes` to your custom models and/or views:
|
|
|
|
|
|
| ... | @@ -89,10 +138,6 @@ Combines `OwnerAuthAnonPermissions` and `LDPObjectLevelPermissions` with a custo |
... | @@ -89,10 +138,6 @@ Combines `OwnerAuthAnonPermissions` and `LDPObjectLevelPermissions` with a custo |
|
|
|
|
|
|
|
## FilterBackends and LDPPermissions
|
|
## FilterBackends and LDPPermissions
|
|
|
|
|
|
|
|
One key divergence from Django Rest Framework's design is in the ability to attach `filter_backends` to custom permissions classes
|
|
|
|
|
|
|
|
|
|
A key design feature of DjangoLDP is to allow ViewSet and serializer content to be automatically configured and serialized as much as possible for the general use case
|
|
|
|
|
|
|
|
|
|
By default `LDPViewSet` will iterate all of the permissions classes [configured on a DjangoLDP Model](https://git.startinblox.com/djangoldp-packages/djangoldp#permissions_classes), will search for any `filter_backends` which are set on these and apply them:
|
|
By default `LDPViewSet` will iterate all of the permissions classes [configured on a DjangoLDP Model](https://git.startinblox.com/djangoldp-packages/djangoldp#permissions_classes), will search for any `filter_backends` which are set on these and apply them:
|
|
|
```python
|
|
```python
|
|
|
# this is the whole of the LDPPermissions class
|
|
# this is the whole of the LDPPermissions class
|
| ... | @@ -104,7 +149,7 @@ class LDPPermissions(LDPObjectLevelPermissions, OwnerAuthAnonPermissions, Cached |
... | @@ -104,7 +149,7 @@ class LDPPermissions(LDPObjectLevelPermissions, OwnerAuthAnonPermissions, Cached |
|
|
|
|
|
|
|
Note that the default behaviour of `LDPBasePermissions` is to define `filter_backends = []`. Extending this class will prevent `LDPPermissionsFilterBackend` being applied to your custom permissions classes without explicitly requesting this, but you can still add your own `FilterBackend` to the permissions class for working with `LDPViewSet` or for use in custom views
|
|
Note that the default behaviour of `LDPBasePermissions` is to define `filter_backends = []`. Extending this class will prevent `LDPPermissionsFilterBackend` being applied to your custom permissions classes without explicitly requesting this, but you can still add your own `FilterBackend` to the permissions class for working with `LDPViewSet` or for use in custom views
|
|
|
|
|
|
|
|
# DjangoLDP v1
|
|
# DjangoLDP v1 - Legacy versions of DjangoLDP
|
|
|
|
|
|
|
|
DjangoLDP permissions classes perform 3 roles:
|
|
DjangoLDP permissions classes perform 3 roles:
|
|
|
* Control: permit or deny actions based on the user permission
|
|
* Control: permit or deny actions based on the user permission
|
| ... | |
... | |
| ... | | ... | |