diff --git a/djangoldp/__init__.py b/djangoldp/__init__.py index ed2171212bcb88a02d02fa1822197a68c7a13df9..790f02aa4e153fc4e9fbf41a833590d4bba23a5e 100644 --- a/djangoldp/__init__.py +++ b/djangoldp/__init__.py @@ -1,3 +1,4 @@ from django.db.models import options +__version__ = '0.0.0' options.DEFAULT_NAMES += ('rdf_type', 'auto_author') diff --git a/djangoldp/factories.py b/djangoldp/factories.py new file mode 100644 index 0000000000000000000000000000000000000000..172e289b5b49acfaf4a80e711ba249cd77c2d3af --- /dev/null +++ b/djangoldp/factories.py @@ -0,0 +1,14 @@ +import factory +from django.contrib.auth.models import User +from django.db.models.signals import post_save + +@factory.django.mute_signals(post_save) +class UserFactory(factory.django.DjangoModelFactory): + class Meta: + model = User + + username = factory.Faker('user_name') + first_name = factory.Faker('first_name') + last_name = factory.Faker('last_name') + email = factory.Faker('email') + password = factory.PostGenerationMethodCall('set_password', 'totototo') diff --git a/djangoldp/management/__init__.py b/djangoldp/management/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/djangoldp/management/commands/__init__.py b/djangoldp/management/commands/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/djangoldp/management/commands/mock_user.py b/djangoldp/management/commands/mock_user.py new file mode 100644 index 0000000000000000000000000000000000000000..30b4373e8dcd7d0bed9c4a8277bac1a3fbd49c4b --- /dev/null +++ b/djangoldp/management/commands/mock_user.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand, CommandError +from djangoldp.factories import UserFactory + +class Command(BaseCommand): + help = 'Mock data' + + def add_arguments(self, parser): + parser.add_argument('--size', type=int, default=0, help='Number of user to create') + + def handle(self, *args, **options): + UserFactory.create_batch(size=options['size']); + + self.stdout.write(self.style.SUCCESS('Successful data mock install')) diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py index aed5ce3bc8c8d6f2ccc5c4cafa687920d6e4857c..90862a84ba142799f9bc1216eb1b09dbc9625e4b 100644 --- a/djangoldp/permissions.py +++ b/djangoldp/permissions.py @@ -1,4 +1,6 @@ from rest_framework import permissions +from rest_framework import filters +from guardian.shortcuts import get_objects_for_user, get_user_perms """ Liste des actions passées dans views selon le protocole REST : @@ -9,105 +11,83 @@ Liste des actions passées dans views selon le protocole REST : destroy Pour chacune de ces actions, on va définir si on accepte la requête (True) ou non (False) """ +""" + The instance-level has_object_permission method will only be called if the view-level has_permission + checks have already passed +""" + +class WACPermissions(permissions.DjangoObjectPermissions): + perms_map = { + 'GET': ['%(app_label)s.view_%(model_name)s'], + 'OPTIONS': [], + 'HEAD': ['%(app_label)s.view_%(model_name)s'], + 'POST': ['%(app_label)s.add_%(model_name)s'], + 'PUT': ['%(app_label)s.change_%(model_name)s'], + 'PATCH': ['%(app_label)s.change_%(model_name)s'], + 'DELETE': ['%(app_label)s.delete_%(model_name)s'], + } + def has_permission(self, request, view): + if request.method == 'OPTIONS': + return True + return super().has_permission(request, view) + +class ObjectFilter(filters.BaseFilterBackend): + def filter_queryset(self, request, queryset, view): + """ + Ensure that queryset only contains objects visible by current user + """ + perm = "view_{}".format(queryset.model._meta.model_name.lower()) + objects = get_objects_for_user(request.user, perm, klass=queryset) + return objects -class PublicPostPermissions(permissions.BasePermission): +class ObjectPermission(permissions.DjangoObjectPermissions): + filter_class = ObjectFilter + +class AnonymousReadOnly(permissions.DjangoObjectPermissions): """ 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 """ def has_permission(self, request, view): - - if view.action == "list": - return True - - if not request.user.is_authenticated(): - return False - elif view.action == 'create': - return True - elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']: + if view.action in ['list', 'retrieve']: return True else: - return False + return super().has_permission(request, view) def has_object_permission(self, request, view, obj): - - if view.action == "create": + if view.action == "create" and request.user.is_authenticated(): return True - - elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']: + elif view.action == "retrieve": + return True + elif view.action in ['update', 'partial_update', 'destroy']: if hasattr(obj._meta, 'auto_author'): - auth = getattr(obj, obj._meta.auto_author) - if auth == request.user: + author = getattr(obj, obj._meta.auto_author) + if author == request.user: return True else: - return False - - -class PrivateProjectPermissions(permissions.BasePermission): - """ - 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 - """ - - def has_permission(self, request, view): - if not request.user.is_authenticated(): - return False - if view.action == "list": - return True - elif view.action == 'create': - return True - elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']: - return True - else: - return False + return super().has_object_permission(request, view) - def has_object_permission(self, request, view, obj): - - if view.action in ['retrieve']: - # Is user in the team ? - for t in obj.team.all(): - if request.user == t: - return True - - elif view.action in ['update', 'partial_update', 'destroy']: - if request.user.groups.filter(name='Partners').exists(): - return True - return False - - -class NotificationsPermissions(permissions.BasePermission): +class InboxPermissions(permissions.DjangoObjectPermissions): """ 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 """ - + filter_class = ObjectFilter def has_permission(self, request, view): - - if view.action == "list": - return False - elif view.action == 'create': - return True - elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']: + if view.action in ['create', 'retrieve', 'update', 'partial_update', 'destroy']: return True else: - return False + return super().has_permission(request, view) def has_object_permission(self, request, view, obj): - - if view.action in ["retrieve", 'update', 'partial_update', 'destroy']: - if hasattr(obj._meta, 'auto_author'): - auth = getattr(obj, obj._meta.auto_author) - if auth == request.user: - return True - else: - return False - if view.action == "create": - if request.user == "AnonymousUser" or request.user.is_authenticated(): + return True + if hasattr(obj._meta, 'auto_author'): + if request.user == getattr(obj, obj._meta.auto_author): return True + return super().has_object_permission(request, view) diff --git a/djangoldp/views.py b/djangoldp/views.py index 213d53dd2c6c806ff6ce587f55f0b02b1acd1bd3..bf72306cd9ed9a3b80df362e6529a4cd897a29f4 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -16,6 +16,7 @@ from rest_framework.viewsets import ModelViewSet from .models import LDPSource from .serializers import LDPSerializer from guardian.shortcuts import get_objects_for_user +from djangoldp.permissions import ObjectFilter class JSONLDRenderer(JSONRenderer): @@ -34,33 +35,6 @@ class NoCSRFAuthentication(SessionAuthentication): def enforce_csrf(self, request): return -class WACPermissions(DjangoObjectPermissions): - perms_map = { - 'GET': ['%(app_label)s.view_%(model_name)s'], - 'OPTIONS': [], - 'HEAD': ['%(app_label)s.view_%(model_name)s'], - 'POST': ['%(app_label)s.add_%(model_name)s'], - 'PUT': ['%(app_label)s.change_%(model_name)s'], - 'PATCH': ['%(app_label)s.change_%(model_name)s'], - 'DELETE': ['%(app_label)s.delete_%(model_name)s'], - } - def has_permission(self, request, view): - if request.method == 'OPTIONS': - return True - return super().has_permission(request, view) - -class AnnonReadOnly(WACPermissions): - authenticated_users_only = False - -class DjangoObjectPermissionsFilter(BaseFilterBackend): - def filter_queryset(self, request, queryset, view): - """ - Ensure that queryset only contains objects visible by current user - """ - perm="view_{}".format(queryset.model._meta.model_name.lower()) - objects = get_objects_for_user(request.user, perm, klass=queryset) - return objects - class LDPViewSetGenerator(ModelViewSet): """An extension of ModelViewSet that generates automatically URLs for the model""" model = None @@ -113,9 +87,11 @@ class LDPViewSet(LDPViewSetGenerator): renderer_classes = (JSONLDRenderer, ) parser_classes = (JSONLDParser, ) authentication_classes = (NoCSRFAuthentication,) - + def __init__(self, **kwargs): super().__init__(**kwargs) + if self.permission_classes and self.permission_classes.filter_class: + self.filter_backends = (self.permission_classes.filter_class,) self.serializer_class = self.build_serializer()