Commit 0b630a1d authored by Jean-Baptiste Pasquier's avatar Jean-Baptiste Pasquier

Merge branch '161-filter-container-by-permission' into 'master'

Resolve "Filter every container by permission on each object contained"

Closes #161

See merge request !88
parents 54fdc333 8ab9cd8f
Pipeline #4927 passed with stage
in 1 minute and 21 seconds
...@@ -74,6 +74,12 @@ class Model(models.Model): ...@@ -74,6 +74,12 @@ class Model(models.Model):
view, args, kwargs = get_resolver().resolve(id) view, args, kwargs = get_resolver().resolve(id)
return view.initkwargs['model'].objects.get(**kwargs) return view.initkwargs['model'].objects.get(**kwargs)
@classonlymethod
def resolve_parent(cls, path):
split = path.strip('/').split('/')
parent_path = "/".join(split[0:len(split) - 1])
return Model.resolve_id(parent_path)
@classonlymethod @classonlymethod
def resolve_container(cls, path): def resolve_container(cls, path):
path = cls.__clean_path(path) path = cls.__clean_path(path)
......
from rest_framework.permissions import BasePermission
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.urls import Resolver404
from rest_framework.permissions import BasePermission
class LDPPermissions(BasePermission): class LDPPermissions(BasePermission):
...@@ -14,10 +15,18 @@ class LDPPermissions(BasePermission): ...@@ -14,10 +15,18 @@ class LDPPermissions(BasePermission):
authenticated_perms = ['inherit'] authenticated_perms = ['inherit']
owner_perms = ['inherit'] owner_perms = ['inherit']
def user_permissions(self, user, model, obj=None): def user_permissions(self, user, obj_or_model, obj=None):
""" """
Filter user permissions for a model class Filter user permissions for a model class
""" """
# sorted out param mess
if isinstance(obj_or_model, ModelBase):
model = obj_or_model
else:
obj = obj_or_model
model = obj_or_model.__class__
# Get Anonymous permissions from Model's Meta. If not found use default # Get Anonymous permissions from Model's Meta. If not found use default
anonymous_perms = getattr(model._meta, 'anonymous_perms', self.anonymous_perms) anonymous_perms = getattr(model._meta, 'anonymous_perms', self.anonymous_perms)
...@@ -37,7 +46,9 @@ class LDPPermissions(BasePermission): ...@@ -37,7 +46,9 @@ class LDPPermissions(BasePermission):
return anonymous_perms return anonymous_perms
else: else:
if obj and hasattr(model._meta, 'owner_field') and (getattr(obj, getattr(model._meta, 'owner_field')) == user or getattr(obj, getattr(model._meta, 'owner_field')) == user.id): if obj and hasattr(model._meta, 'owner_field') and (
getattr(obj, getattr(model._meta, 'owner_field')) == user or getattr(obj, getattr(model._meta,
'owner_field')) == user.id):
return owner_perms return owner_perms
else: else:
...@@ -45,14 +56,7 @@ class LDPPermissions(BasePermission): ...@@ -45,14 +56,7 @@ class LDPPermissions(BasePermission):
def filter_user_perms(self, user, obj_or_model, permissions): def filter_user_perms(self, user, obj_or_model, permissions):
# Only used on Model.get_permissions to translate permissions to LDP # Only used on Model.get_permissions to translate permissions to LDP
if isinstance(obj_or_model, ModelBase): return [perm for perm in permissions if perm in self.user_permissions(user, obj_or_model)]
model = obj_or_model
obj = None
else:
obj = obj_or_model
model = obj_or_model.__class__
return [perm for perm in permissions if perm in self.user_permissions(user, model, obj)]
perms_map = { perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'], 'GET': ['%(app_label)s.view_%(model_name)s'],
...@@ -83,14 +87,20 @@ class LDPPermissions(BasePermission): ...@@ -83,14 +87,20 @@ class LDPPermissions(BasePermission):
""" """
Access to containers Access to containers
""" """
from djangoldp.models import Model
model = view.model
perms = self.get_permissions(request.method, model) if self.is_a_container(request._request.path):
try:
obj = Model.resolve_parent(request.path)
model = view.parent_model
except Resolver404:
obj = None
model = view.model
else:
obj = Model.resolve_id(request._request.path)
model = view.model
try: perms = self.get_permissions(request.method, model)
obj = view.model.resolve_id(request._request.path)
except:
obj = None
for perm in perms: for perm in perms:
if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj): if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj):
...@@ -98,6 +108,11 @@ class LDPPermissions(BasePermission): ...@@ -98,6 +108,11 @@ class LDPPermissions(BasePermission):
return True return True
def is_a_container(self, path):
from djangoldp.models import Model
container, id = Model.resolve(path)
return id is None
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
""" """
Access to objects Access to objects
...@@ -111,4 +126,4 @@ class LDPPermissions(BasePermission): ...@@ -111,4 +126,4 @@ class LDPPermissions(BasePermission):
if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj): if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj):
return False return False
return True return True
\ No newline at end of file
from collections import OrderedDict, Mapping from collections import OrderedDict, Mapping, Iterable
from typing import Any from typing import Any
from urllib import parse from urllib import parse
...@@ -40,14 +40,39 @@ class LDListMixin: ...@@ -40,14 +40,39 @@ class LDListMixin:
return [getattr(self, self.child_attr).to_internal_value(item) for item in data] return [getattr(self, self.child_attr).to_internal_value(item) for item in data]
def to_representation(self, value): def to_representation(self, value):
'''
Permission on container :
- Can Add if add permission on contained object's type
- Can view the container is view permission on container model : container obj are filtered by view permission
'''
try: try:
model = getattr(self, self.child_attr).Meta.model child_model = getattr(self, self.child_attr).Meta.model
except AttributeError: except AttributeError:
model = value.model child_model = value.model
parent_model = None
if isinstance(value, QuerySet):
value = list(value)
if not isinstance(value, Iterable):
filtered_values = value
container_permissions = Model.get_permissions(child_model, self.context['request'].user, ['view', 'add'])
else:
try:
parent_model = Model.resolve_parent(self.context['request'].path)
except:
parent_model = child_model
filtered_values = list(
filter(lambda v: Model.get_permission_classes(v, [LDPPermissions])[0]().has_object_permission(
self.context['request'], self.context['view'], v), value))
container_permissions = Model.get_permissions(child_model, self.context['request'].user, ['add'])
container_permissions.extend(
Model.get_permissions(parent_model, self.context['request'].user,
['view']))
return {'@id': self.id, return {'@id': self.id,
'@type': 'ldp:Container', '@type': 'ldp:Container',
'ldp:contains': super().to_representation(value), 'ldp:contains': super().to_representation(filtered_values),
'permissions': Model.get_permissions(model, self.context['request'].user, ['view', 'add']) 'permissions': container_permissions
} }
def get_attribute(self, instance): def get_attribute(self, instance):
...@@ -225,7 +250,7 @@ class LDPSerializer(HyperlinkedModelSerializer): ...@@ -225,7 +250,7 @@ class LDPSerializer(HyperlinkedModelSerializer):
data = super().to_representation(obj) data = super().to_representation(obj)
slug_field = Model.slug_field(obj) slug_field = Model.slug_field(obj)
for field in data: for field in data:
if isinstance(data[field], dict) and'@id' in data[field]: if isinstance(data[field], dict) and '@id' in data[field]:
data[field]['@id'] = data[field]['@id'].format(Model.container_id(obj), str(getattr(obj, slug_field))) data[field]['@id'] = data[field]['@id'].format(Model.container_id(obj), str(getattr(obj, slug_field)))
rdf_type = Model.get_meta(obj, 'rdf_type', None) rdf_type = Model.get_meta(obj, 'rdf_type', None)
rdf_context = Model.get_meta(obj, 'rdf_context', None) rdf_context = Model.get_meta(obj, 'rdf_context', None)
...@@ -252,7 +277,8 @@ class LDPSerializer(HyperlinkedModelSerializer): ...@@ -252,7 +277,8 @@ class LDPSerializer(HyperlinkedModelSerializer):
serializer_generator = LDPViewSet(model=model_class, serializer_generator = LDPViewSet(model=model_class,
lookup_field=Model.get_meta(model_class, 'lookup_field', 'pk'), lookup_field=Model.get_meta(model_class, 'lookup_field', 'pk'),
permission_classes=Model.get_meta(model_class, permission_classes=Model.get_meta(model_class,
'permission_classes', [LDPPermissions]), 'permission_classes',
[LDPPermissions]),
fields=Model.get_meta(model_class, 'serializer_fields', []), fields=Model.get_meta(model_class, 'serializer_fields', []),
nested_fields=Model.get_meta(model_class, 'nested_fields', [])) nested_fields=Model.get_meta(model_class, 'nested_fields', []))
parent_depth = max(getattr(self.parent.Meta, "depth", 0) - 1, 0) parent_depth = max(getattr(self.parent.Meta, "depth", 0) - 1, 0)
...@@ -388,7 +414,6 @@ class LDPSerializer(HyperlinkedModelSerializer): ...@@ -388,7 +414,6 @@ class LDPSerializer(HyperlinkedModelSerializer):
kwargs['required'] = False kwargs['required'] = False
return NestedLDPSerializer, kwargs return NestedLDPSerializer, kwargs
@classmethod @classmethod
def many_init(cls, *args, **kwargs): def many_init(cls, *args, **kwargs):
kwargs['child'] = cls(**kwargs) kwargs['child'] = cls(**kwargs)
......
...@@ -4,7 +4,7 @@ from django.contrib.auth.models import User ...@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from rest_framework.test import APIRequestFactory, APIClient from rest_framework.test import APIRequestFactory, APIClient
from djangoldp.tests.models import Skill, JobOffer from djangoldp.tests.models import Skill, JobOffer, Post
class TestTemp(TestCase): class TestTemp(TestCase):
...@@ -18,3 +18,4 @@ class TestTemp(TestCase): ...@@ -18,3 +18,4 @@ class TestTemp(TestCase):
pass pass
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment