diff --git a/README.md b/README.md index cd9b4226dfdeb7796a7f36adafa3dc7a3a609fb1..e8525f1c41aa65fa6f64b780ded4280a4012a706 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,64 @@ class MyModel(models.Model): class Meta: auto_author = 'author_user' ``` +## permissions +This allows you to add permissions for AnonymousUser, logged in user, author ... in the url: +Currently, there are 3 choices : +* PublicPostPermissions +* PrivateProjectPermissions +* NotificationsPermissions +Specific permissin classes can be developed to fit special needs. + +PublicPostPermissions 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 + +``` +from django.conf.urls import url +from djangoldp.views import LDPViewSet +from djangoldp.permissions import PublicPostPermissions + +urlpatterns = [ + url(r'^projects/', ProjectViewSet.urls(permission_classes=(PublicPostPermissions,))), + 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: +* 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 + +``` +from django.conf.urls import url +from djangoldp.views import LDPViewSet +from djangoldp.permissions import NotificationsPermissions + +urlpatterns = [ + url(r'^projects/', ProjectViewSet.urls(permission_classes=(NotificationsPermissions,))), + url(r'^customers/', LDPViewSet.urls(model=Customer)), +] +``` + +Important note: +If you need to give permissions to owner's object, don't forget to add auto_author in model's meta + ## License diff --git a/djangoldp/permissions.py b/djangoldp/permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..90862a84ba142799f9bc1216eb1b09dbc9625e4b --- /dev/null +++ b/djangoldp/permissions.py @@ -0,0 +1,93 @@ +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 : + list + create + retrieve + update, partial update + 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 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 in ['list', 'retrieve']: + return True + else: + return super().has_permission(request, view) + + def has_object_permission(self, request, view, obj): + if view.action == "create" and request.user.is_authenticated(): + return True + elif view.action == "retrieve": + return True + elif view.action in ['update', 'partial_update', 'destroy']: + if hasattr(obj._meta, 'auto_author'): + author = getattr(obj, obj._meta.auto_author) + if author == request.user: + return True + else: + return super().has_object_permission(request, view) + + +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 in ['create', 'retrieve', 'update', 'partial_update', 'destroy']: + return True + else: + return super().has_permission(request, view) + + def has_object_permission(self, request, view, obj): + if view.action == "create": + 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..26a84d1ea3dfaa29069bc94134c9e5fdee8c115b 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -34,33 +34,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 +86,14 @@ class LDPViewSet(LDPViewSetGenerator): renderer_classes = (JSONLDRenderer, ) parser_classes = (JSONLDParser, ) authentication_classes = (NoCSRFAuthentication,) - + def __init__(self, **kwargs): super().__init__(**kwargs) + if self.permission_classes: + for p in self.permission_classes: + if hasattr(p, 'filter_class') and p.filter_class: + self.filter_backends = p.filter_class + self.serializer_class = self.build_serializer() @@ -138,8 +116,8 @@ class LDPViewSet(LDPViewSetGenerator): def get_queryset(self, *args, **kwargs): if self.model: return self.model.objects.all() -# perm="view_{}".format(self.model._meta.model_name.lower()) -# return get_objects_for_user(self.request.user, perm, klass=self.model) + perm="view_{}".format(self.model._meta.model_name.lower()) + return get_objects_for_user(self.request.user, perm, klass=self.model) else: return super(LDPView, self).get_queryset(*args, **kwargs)