diff --git a/README.md b/README.md index 17b3fabed37bd158d1522e0a7949e594db04828d..07ac1cf2a49059c4e42967fae86544e5decf6264 100644 --- a/README.md +++ b/README.md @@ -147,12 +147,14 @@ class MyModel(models.Model): ## permissions This allows you to add permissions for AnonymousUser, logged in user, author ... in the url: Currently, there are 3 choices : -* PublicPostPermissions -* PrivateProjectPermissions -* NotificationsPermissions +* ObjectPermission +* AnonymousReadOnly +* InboxPermissions Specific permissin classes can be developed to fit special needs. -PublicPostPermissions gives these permissions: +ObjectPermission give permissions assign in the administration + +AnonymousReadOnly gives these permissions: * Anonymous users: can read all posts * Logged in users: can read all posts + create new posts * Author: can read all posts + create new posts + update their own @@ -160,30 +162,15 @@ PublicPostPermissions gives these permissions: ``` from django.conf.urls import url from djangoldp.views import LDPViewSet -from djangoldp.permissions import PublicPostPermissions +from djangoldp.permissions import AnonymousReadOnly urlpatterns = [ - url(r'^projects/', ProjectViewSet.urls(permission_classes=(PublicPostPermissions,))), + url(r'^projects/', ProjectViewSet.urls(permission_classes=(AnonymousReadOnly,))), url(r'^customers/', LDPViewSet.urls(model=Customer)), ] ``` -PrivateProjectPermissions provides the following -* Anonymous users: no permissions -* Logged in users: can read projects if they're in the team -* Users of group Partners: can see all projects + update all projects - -``` -from django.conf.urls import url -from djangoldp.views import LDPViewSet -from djangoldp.permissions import PrivateProjectPermissions - -urlpatterns = [ - url(r'^projects/', ProjectViewSet.urls(permission_classes=(PrivateProjectPermissions,))), - url(r'^customers/', LDPViewSet.urls(model=Customer)), -] -``` -NotificationsPermissions is used for, well, notifications: +InboxPermissions is used for, well, notifications: * Anonymous users: can create notifications but can't read * Logged in users: can create notifications but can't read * Inbox owners: can read + update all notifications @@ -194,7 +181,7 @@ from djangoldp.views import LDPViewSet from djangoldp.permissions import NotificationsPermissions urlpatterns = [ - url(r'^projects/', ProjectViewSet.urls(permission_classes=(NotificationsPermissions,))), + url(r'^projects/', ProjectViewSet.urls(permission_classes=(InboxPermissions,))), url(r'^customers/', LDPViewSet.urls(model=Customer)), ] ``` diff --git a/djangoldp/fields.py b/djangoldp/fields.py new file mode 100644 index 0000000000000000000000000000000000000000..fb4ba855b788f71f216984e8e1154a38d6879757 --- /dev/null +++ b/djangoldp/fields.py @@ -0,0 +1,10 @@ +from django.db import models +from rest_framework import fields + +class IdURLField (fields.URLField): + def to_representation(self, value): + str = super(IdURLField, self).to_representation(value) + return {'@id': str} + +class LDPUrlField (models.URLField): + pass diff --git a/djangoldp/models.py b/djangoldp/models.py index 0bef4dcf8424df51df836fd9c599de616252063f..151cb7d82b1f0a1765686ec48a24fb42a4744e29 100644 --- a/djangoldp/models.py +++ b/djangoldp/models.py @@ -69,7 +69,6 @@ class Model(models.Model): path = "{}/".format(path) return path - class LDPSource(models.Model): container = models.URLField() federation = models.CharField(max_length=255) diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py index 90862a84ba142799f9bc1216eb1b09dbc9625e4b..49ef6a968cbbced48ec4b78b1fbcb901795f6b87 100644 --- a/djangoldp/permissions.py +++ b/djangoldp/permissions.py @@ -67,7 +67,7 @@ class AnonymousReadOnly(permissions.DjangoObjectPermissions): if author == request.user: return True else: - return super().has_object_permission(request, view) + return super().has_object_permission(request, view, obj) class InboxPermissions(permissions.DjangoObjectPermissions): diff --git a/djangoldp/serializers.py b/djangoldp/serializers.py index 9bb033b6e5090df3df30cd53b5af654239cc7feb..da4632611caaa5efb7bb040a461bba7cc8153733 100644 --- a/djangoldp/serializers.py +++ b/djangoldp/serializers.py @@ -11,7 +11,7 @@ from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty from rest_framework.fields import get_error_detail, set_value from rest_framework.relations import HyperlinkedRelatedField, ManyRelatedField, MANY_RELATION_KWARGS -from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer +from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer, ModelSerializer from rest_framework.settings import api_settings from rest_framework.utils import model_meta from rest_framework.utils.field_mapping import get_nested_relation_kwargs @@ -19,6 +19,11 @@ from rest_framework.utils.serializer_helpers import ReturnDict from djangoldp.models import Model +from rest_framework.serializers import HyperlinkedModelSerializer, ListSerializer, ModelSerializer +from rest_framework.utils.field_mapping import get_nested_relation_kwargs +from rest_framework.utils.serializer_helpers import ReturnDict + +from djangoldp.fields import LDPUrlField, IdURLField class LDListMixin: def to_internal_value(self, data): @@ -164,6 +169,8 @@ class LDPSerializer(HyperlinkedModelSerializer): url_field_name = "@id" serializer_related_field = JsonLdRelatedField serializer_url_field = JsonLdIdentityField + ModelSerializer.serializer_field_mapping [LDPUrlField] = IdURLField + @property def data(self): @@ -184,6 +191,7 @@ class LDPSerializer(HyperlinkedModelSerializer): def to_representation(self, obj): data = super().to_representation(obj) + if hasattr(obj._meta, 'rdf_type'): data['@type'] = obj._meta.rdf_type data['permissions'] = [{'mode': {'@type': name.split('_')[0]}} for name in diff --git a/djangoldp/tests/runner.py b/djangoldp/tests/runner.py index 4e3bcd0417a8ea9e8588f2ec0ae9539615278bb8..70074c5b66755ae9ffe7715611f1e2c3335c0945 100644 --- a/djangoldp/tests/runner.py +++ b/djangoldp/tests/runner.py @@ -27,6 +27,8 @@ test_runner = DiscoverRunner(verbosity=1) failures = test_runner.run_tests([ 'djangoldp.tests.tests_ldp_model', 'djangoldp.tests.tests_save', + 'djangoldp.tests.tests_user_permissions', + 'djangoldp.tests.tests_anonymous_permissions', 'djangoldp.tests.tests_update']) if failures: sys.exit(failures) diff --git a/djangoldp/tests/tests_anonymous_permissions.py b/djangoldp/tests/tests_anonymous_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..a8f4ee3f471f3d245c8f92fbd00ecfbd73d08fd8 --- /dev/null +++ b/djangoldp/tests/tests_anonymous_permissions.py @@ -0,0 +1,43 @@ +from django.contrib.auth.models import AnonymousUser +from django.test import TestCase, RequestFactory + +from djangoldp.permissions import AnonymousReadOnly +from djangoldp.tests.models import JobOffer +from djangoldp.views import LDPViewSet + + +class TestAnonymousUserPermissions(TestCase): + def setUp(self): + self.factory = RequestFactory() + # self.c = Client() + self.user = AnonymousUser + + def test_get_request_with_anonymousUser(self): + request = self.factory.get("/job-offers/") + request.user = self.user + my_view = LDPViewSet.as_view({'get': 'list'}, + model=JobOffer, + nested_fields=["skills"], + permission_classes=[AnonymousReadOnly]) + response = my_view(request) + self.assertEqual(response.status_code, 200) + + def test_request_options_create_with_anonymousUser(self): + request = self.factory.options("/job-offers/") + request.user = self.user + my_view = LDPViewSet.as_view({'options': 'create'}, + model=JobOffer, + nested_fields=["skills"], + permission_classes=[AnonymousReadOnly]) + response = my_view(request) + self.assertEqual(response.status_code, 403) + + def test_request_options_update_with_anonymousUser(self): + request = self.factory.options("/job-offers/") + request.user = self.user + my_view = LDPViewSet.as_view({'options': 'update'}, + model=JobOffer, + nested_fields=["skills"], + permission_classes=[AnonymousReadOnly]) + response = my_view(request) + self.assertEqual(response.status_code, 403) diff --git a/djangoldp/tests/tests_user_permissions.py b/djangoldp/tests/tests_user_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..93d2da07ff5d034f21a7f9d9a5c623933028ab6b --- /dev/null +++ b/djangoldp/tests/tests_user_permissions.py @@ -0,0 +1,48 @@ +from django.contrib.auth.models import User +from django.test import TestCase, RequestFactory + +from djangoldp.permissions import AnonymousReadOnly +from djangoldp.tests.models import JobOffer +from djangoldp.views import LDPViewSet + + +class TestUserPermissions(TestCase): + def setUp(self): + self.factory = RequestFactory() + # self.c = Client() + self.user = User.objects.create_user(username='john', email='jlennon@beatles.com', password='glass onion') + self.job = JobOffer.objects.create(title="job") + + def tearDown(self): + self.user.delete() + + def test_get_with_user(self): + request = self.factory.get('/job-offers/') + request.user = self.user + my_view = LDPViewSet.as_view({'get': 'list'}, model=JobOffer, nested_fields=["skills"], + permission_classes=[AnonymousReadOnly]) + response = my_view(request) + self.assertEqual(response.status_code, 200) + + def test_request_options_create_with_user(self): + request = self.factory.options('/job-offers/') + request.user = self.user + my_view = LDPViewSet.as_view({'options': 'create'}, model=JobOffer, nested_fields=["skills"], + permission_classes=[AnonymousReadOnly]) + response = my_view(request) + self.assertEqual(response.status_code, 201) + + def test_request_options_update_with_user(self): + request = self.factory.options('/job-offers/' + str(self.job.pk) + "/") + request.user = self.user + my_view = LDPViewSet.as_view({'options': 'update'}, model=JobOffer, nested_fields=["skills"], + permission_classes=[AnonymousReadOnly]) + response = my_view(request, pk=self.job.pk) + self.assertEqual(response.status_code, 200) + + def test_request_patch_with_user(self): + request = self.factory.patch('/job-offers/' + str(self.job.pk) + "/") + request.user = self.user + my_view = LDPViewSet.as_view({'patch': 'partial_update'}, model=JobOffer, nested_fields=["skills"]) + response = my_view(request, pk=self.job.pk) + self.assertEqual(response.status_code, 200) \ No newline at end of file diff --git a/djangoldp/views.py b/djangoldp/views.py index f4b2fb59f72c56b898174c6329c7b8f8ee606393..3ccfdf7390087596257aea6ba06969384cf9a911 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -134,7 +134,7 @@ class LDPViewSet(LDPViewSetGenerator): def dispatch(self, request, *args, **kwargs): response = super(LDPViewSet, self).dispatch(request, *args, **kwargs) response["Access-Control-Allow-Origin"] = request.META.get('HTTP_ORIGIN') - response["Access-Control-Allow-Methods"] = "POST,PUT" + response["Access-Control-Allow-Methods"] = "POST,PUT, PATCH" response["Access-Control-Allow-Headers"] = "Content-Type, if-match" response["Access-Control-Allow-Credentials"] = 'true' response["Accept-Post"] = "application/ld+json"