From fbf1d1bca9313b6da5b09e8038e970193ea777fa Mon Sep 17 00:00:00 2001 From: Jean-Baptiste <bleme@pm.me> Date: Thu, 21 Mar 2019 15:37:28 +0100 Subject: [PATCH] update: add permissions to containers --- djangoldp/models.py | 44 +++++++++++++++++++++++++----------- djangoldp/permissions.py | 20 ++++++++-------- djangoldp/serializers.py | 29 ++++++++---------------- djangoldp/tests/models.py | 2 ++ djangoldp/tests/tests_get.py | 21 +++++++++++++---- djangoldp/urls.py | 12 ++++------ 6 files changed, 74 insertions(+), 54 deletions(-) diff --git a/djangoldp/models.py b/djangoldp/models.py index c1d0c1a9..e66c1170 100644 --- a/djangoldp/models.py +++ b/djangoldp/models.py @@ -1,6 +1,9 @@ from django.conf import settings +from django.contrib.auth.models import AnonymousUser from django.db import models from django.urls import get_resolver +from django.utils.decorators import classonlymethod +from guardian.shortcuts import get_perms class Model(models.Model): @@ -27,12 +30,12 @@ class Model(models.Model): def get_container_id(self): return Model.container_id(self) - @classmethod + @classonlymethod def resource_id(cls, instance): r_id = "{}{}".format(cls.container_id(instance), getattr(instance, cls.slug_field(instance))) return cls.__clean_path(r_id) - @classmethod + @classonlymethod def slug_field(cls, instance): view_name = '{}-detail'.format(instance._meta.object_name.lower()) slug_field = '/{}'.format(get_resolver().reverse_dict[view_name][0][0][1][0]) @@ -40,7 +43,7 @@ class Model(models.Model): slug_field = slug_field[1:] return slug_field - @classmethod + @classonlymethod def container_id(cls, instance): if isinstance(instance, cls): path = instance.get_container_path() @@ -58,19 +61,19 @@ class Model(models.Model): depth = 1 many_depth = 0 - @classmethod + @classonlymethod def resolve_id(cls, id): id = cls.__clean_path(id) view, args, kwargs = get_resolver().resolve(id) return view.initkwargs['model'].objects.get(**kwargs) - @classmethod + @classonlymethod def resolve_container(cls, path): path = cls.__clean_path(path) view, args, kwargs = get_resolver().resolve(path) return view.initkwargs['model'] - @classmethod + @classonlymethod def resolve(cls, path): container = cls.resolve_container(path) try: @@ -79,7 +82,7 @@ class Model(models.Model): resolve_id = None return container, resolve_id - @classmethod + @classonlymethod def __clean_path(cls, path): if not path.startswith("/"): path = "/{}".format(path) @@ -87,13 +90,28 @@ class Model(models.Model): path = "{}/".format(path) return path - @classmethod + @classonlymethod def get_permission_classes(cls, related_model, default_permissions_classes): - try: - return getattr(related_model._meta, 'permission_classes', - getattr(related_model.Meta, 'permission_classes', default_permissions_classes)) - except AttributeError: - return default_permissions_classes + return cls.get_meta(related_model, 'permission_classes', default_permissions_classes) + + @classonlymethod + def get_meta(cls, model_class, meta_name, default=None): + if hasattr(model_class, 'Meta'): + meta = getattr(model_class.Meta, meta_name, default) + else: + meta = default + return getattr(model_class._meta, meta_name, meta) + + @staticmethod + def get_permissions(obj_or_model, user_or_group, filter): + permissions = filter + for permission_class in Model.get_permission_classes(obj_or_model, []): + permissions = permission_class().filter_user_perms(user_or_group, obj_or_model, permissions) + + if not isinstance(user_or_group, AnonymousUser): + permissions += get_perms(user_or_group, obj_or_model) + return [{'mode': {'@type': name.split('_')[0]}} for name in permissions] + class LDPSource(models.Model): diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py index 7b5c1345..9de91cd7 100644 --- a/djangoldp/permissions.py +++ b/djangoldp/permissions.py @@ -2,6 +2,8 @@ from guardian.shortcuts import get_objects_for_user from rest_framework import filters from rest_framework import permissions +from djangoldp.models import Model + """ Liste des actions passées dans views selon le protocole REST : list @@ -35,11 +37,11 @@ class WACPermissions(permissions.DjangoObjectPermissions): return super().has_permission(request, view) # This method should be overriden by other permission classes - def user_permissions(self, request, obj): + def user_permissions(self, user, obj): return [] - def filter_user_perms(self, request, obj, permissions): - return [perm for perm in permissions if perm in self.user_permissions(request, obj)] + def filter_user_perms(self, user_or_group, obj, permissions): + return [perm for perm in permissions if perm in self.user_permissions(user_or_group, obj)] class ObjectFilter(filters.BaseFilterBackend): @@ -77,11 +79,11 @@ class InboxPermissions(WACPermissions): else: return super().has_object_permission(request, view) - def user_permissions(self, request, obj): - if request.user.is_anonymous: + def user_permissions(self, user, obj): + if user.is_anonymous: return self.anonymous_perms else: - if hasattr(obj._meta, 'auto_author') and getattr(obj, obj._meta.auto_author) == request.user: + if Model.get_meta(obj, 'auto_author') == user: return self.author_perms else: return self.authenticated_perms @@ -119,11 +121,11 @@ class AnonymousReadOnly(WACPermissions): else: return super().has_object_permission(request, view, obj) - def user_permissions(self, request, obj): - if request.user.is_anonymous: + def user_permissions(self, user, obj): + if user.is_anonymous: return self.anonymous_perms else: - if hasattr(obj._meta, 'auto_author') and getattr(obj, obj._meta.auto_author) == request.user: + if Model.get_meta(obj, 'auto_author') == user: return self.author_perms else: return self.authenticated_perms diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index 92457ab4..a7474d37 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -1,13 +1,11 @@ from collections import OrderedDict, Mapping from urllib import parse -from django.contrib.auth.models import AnonymousUser from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError from django.core.urlresolvers import get_resolver, resolve, get_script_prefix, Resolver404 from django.utils.datastructures import MultiValueDictKeyError from django.utils.encoding import uri_to_iri -from guardian.shortcuts import get_perms from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty from rest_framework.fields import get_error_detail, set_value @@ -35,7 +33,11 @@ class LDListMixin: return [self.child.to_internal_value(item) for item in data] def to_representation(self, value): - return {'@id': self.id, '@type': 'ldp:Container', 'ldp:contains': super().to_representation(value)} + return {'@id': self.id, + '@type': 'ldp:Container', + 'ldp:contains': super().to_representation(value), + 'permissions': Model.get_permissions(value.model, self.context['request'].user, ['view', 'add']) + } def get_attribute(self, instance): parent_id_field = self.parent.fields[self.parent.url_field_name] @@ -205,26 +207,13 @@ class LDPSerializer(HyperlinkedModelSerializer): pass return fields + list(getattr(self.Meta, 'extra_fields', [])) - def get_permissions(self, obj): - permissions = ['view', 'add', 'change', 'control', 'delete'] - - if hasattr(obj._meta, 'permission_classes'): - for permission_class in obj._meta.permission_classes: - permissions = permission_class().filter_user_perms(self.context['request'], obj, permissions) - - if not isinstance(self.context['request'].user, AnonymousUser): - permissions += get_perms(self.context['request'].user, obj) - return [{'mode': {'@type': name.split('_')[0]}} for name in permissions] - def to_representation(self, obj): data = super().to_representation(obj) - if hasattr(obj._meta, 'rdf_type'): - data['@type'] = obj._meta.rdf_type - if hasattr(obj._meta, 'rdf_context'): - data['@context'] = obj._meta.rdf_context - - data['permissions'] = self.get_permissions(obj) + data['@type'] = Model.get_meta(obj, 'rdf_type', None) + data['@context'] = Model.get_meta(obj, 'rdf_context', None) + data['permissions'] = Model.get_permissions(obj, self.context['request'].user, + ['view', 'change', 'control', 'delete']) return data diff --git a/djangoldp/tests/models.py b/djangoldp/tests/models.py index 502e3cdc..9f828667 100644 --- a/djangoldp/tests/models.py +++ b/djangoldp/tests/models.py @@ -2,6 +2,7 @@ from django.conf import settings from django.db import models from djangoldp.models import Model +from djangoldp.permissions import AnonymousReadOnly class Skill(Model): @@ -50,6 +51,7 @@ class Invoice(Model): class Meta: depth = 2 + permission_classes = [AnonymousReadOnly] nested_fields = ["batches"] diff --git a/djangoldp/tests/tests_get.py b/djangoldp/tests/tests_get.py index c626b430..abedc23f 100644 --- a/djangoldp/tests/tests_get.py +++ b/djangoldp/tests/tests_get.py @@ -3,7 +3,7 @@ import json from django.contrib.auth.models import User from rest_framework.test import APIRequestFactory, APIClient, APITestCase -from djangoldp.tests.models import Post +from djangoldp.tests.models import Post, Task, Invoice class TestGET(APITestCase): @@ -11,14 +11,27 @@ class TestGET(APITestCase): def setUp(self): self.factory = APIRequestFactory() self.client = APIClient() - self.user = User.objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion') def tearDown(self): - self.user.delete() + pass - def test_get(self): + def test_get_resource(self): post = Post.objects.create(content="content") response = self.client.get('/posts/{}/'.format(post.pk), content_type='application/ld+json') self.assertEqual(response.status_code, 200) self.assertEquals(response.data['content'], "content") self.assertIn('author', response.data) + + def test_get_container(self): + Post.objects.create(content="content") + response = self.client.get('/posts/', content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertIn('permissions', response.data) + self.assertEquals(2, len(response.data['permissions'])) # read and add + + Invoice.objects.create(title="content") + response = self.client.get('/invoices/', content_type='application/ld+json') + self.assertEqual(response.status_code, 200) + self.assertIn('permissions', response.data) + self.assertEquals(1, len(response.data['permissions'])) # read only + diff --git a/djangoldp/urls.py b/djangoldp/urls.py index 4f404355..3d7647a5 100644 --- a/djangoldp/urls.py +++ b/djangoldp/urls.py @@ -30,11 +30,7 @@ for class_name in model_classes: urls_fct = model_class.get_view_set().urls urlpatterns.append(url(r'^' + path, include( urls_fct(model=model_class, - lookup_field=getattr(model_class._meta, 'lookup_field', - getattr(model_class.Meta, 'lookup_field', 'pk')), - permission_classes=getattr(model_class._meta, 'permission_classes', - getattr(model_class.Meta, 'permission_classes', [])), - fields=getattr(model_class._meta, 'serializer_fields', - getattr(model_class.Meta, 'serializer_fields', [])), - nested_fields=getattr(model_class._meta, 'nested_fields', - getattr(model_class.Meta, 'nested_fields', [])))))) + lookup_field=Model.get_meta(model_class, 'lookup_field', 'pk'), + permission_classes=Model.get_meta(model_class, 'permission_classes', []), + fields=Model.get_meta(model_class, 'serializer_fields', []), + nested_fields=Model.get_meta(model_class, 'nested_fields', []))))) -- GitLab