From 9cf2bc21371bb6308a1162313bfdda90674f6dba Mon Sep 17 00:00:00 2001 From: Thibaud Duquennoy <thibaud@duquennoy.fr> Date: Wed, 16 Jan 2019 14:52:10 +0100 Subject: [PATCH] permissions upgrade upgrade for permission management --- djangoldp/__init__.py | 1 + djangoldp/factories.py | 14 +++ djangoldp/management/__init__.py | 0 djangoldp/management/commands/__init__.py | 0 djangoldp/management/commands/mock_user.py | 13 +++ djangoldp/permissions.py | 122 +++++++++------------ djangoldp/views.py | 32 +----- 7 files changed, 83 insertions(+), 99 deletions(-) create mode 100644 djangoldp/factories.py create mode 100644 djangoldp/management/__init__.py create mode 100644 djangoldp/management/commands/__init__.py create mode 100644 djangoldp/management/commands/mock_user.py diff --git a/djangoldp/__init__.py b/djangoldp/__init__.py index ed217121..790f02aa 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 00000000..172e289b --- /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 00000000..e69de29b diff --git a/djangoldp/management/commands/__init__.py b/djangoldp/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/djangoldp/management/commands/mock_user.py b/djangoldp/management/commands/mock_user.py new file mode 100644 index 00000000..30b4373e --- /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 aed5ce3b..90862a84 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 213d53dd..bf72306c 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() -- GitLab