]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Object-level permissions + filtering
authorMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Tue, 6 Dec 2022 06:56:03 +0000 (22:56 -0800)
committerMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Tue, 6 Dec 2022 06:56:03 +0000 (22:56 -0800)
src/documents/filters.py
src/documents/permissions.py
src/documents/views.py
src/paperless/settings.py
src/paperless/views.py

index d66849e0015701b2380633f35391e490df4bd922..4bcbda98d0d237c84c9e34565c00f76648709376 100644 (file)
@@ -2,6 +2,7 @@ from django.db.models import Q
 from django_filters.rest_framework import BooleanFilter
 from django_filters.rest_framework import Filter
 from django_filters.rest_framework import FilterSet
+from rest_framework_guardian.filters import ObjectPermissionsFilter
 
 from .models import Correspondent
 from .models import Document
@@ -134,3 +135,17 @@ class StoragePathFilterSet(FilterSet):
             "name": CHAR_KWARGS,
             "path": CHAR_KWARGS,
         }
+
+
+class ObjectOwnedOrGrandtedPermissionsFilter(ObjectPermissionsFilter):
+    """
+    A filter backend that limits results to those where the requesting user
+    has read object level permissions, owns the objects, or objects without
+    an owner (for backwards compat)
+    """
+
+    def filter_queryset(self, request, queryset, view):
+        objects_with_perms = super().filter_queryset(request, queryset, view)
+        objects_owned = queryset.filter(owner=request.user)
+        objects_unowned = queryset.filter(owner__isnull=True)
+        return objects_with_perms | objects_owned | objects_unowned
index 86cc66c185aabbd2fa0acf3f68147b3b4b6f0b6a..43b620b02c46a8dfee787d0ce84e6ae8d0744250 100644 (file)
@@ -1,18 +1,29 @@
 from rest_framework.permissions import BasePermission
-from rest_framework.permissions import DjangoModelPermissions
+from rest_framework.permissions import DjangoObjectPermissions
 
 
-class PaperlessModelPermissions(DjangoModelPermissions):
+class PaperlessObjectPermissions(DjangoObjectPermissions):
+    """
+    A permissions backend that checks for object-level permissions
+    or for ownership.
+    """
+
     perms_map = {
         "GET": ["%(app_label)s.view_%(model_name)s"],
-        "OPTIONS": [],
-        "HEAD": [],
+        "OPTIONS": ["%(app_label)s.view_%(model_name)s"],
+        "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_object_permission(self, request, view, obj):
+        if hasattr(obj, "owner") and request.user == obj.owner:
+            return True
+        else:
+            return super().has_object_permission(request, view, obj)
+
 
 class PaperlessAdminPermissions(BasePermission):
     def has_permission(self, request, view):
index 0657ae93afce12fcdfcf64b8f34a42e4563b1798..2a8881376a2c99f9e2020cf59b356466c4a4032b 100644 (file)
@@ -28,8 +28,9 @@ from django.utils.translation import get_language
 from django.views.decorators.cache import cache_control
 from django.views.generic import TemplateView
 from django_filters.rest_framework import DjangoFilterBackend
+from documents.filters import ObjectOwnedOrGrandtedPermissionsFilter
 from documents.permissions import PaperlessAdminPermissions
-from documents.permissions import PaperlessModelPermissions
+from documents.permissions import PaperlessObjectPermissions
 from documents.tasks import consume_file
 from packaging import version as packaging_version
 from paperless import version
@@ -146,8 +147,12 @@ class CorrespondentViewSet(ModelViewSet):
 
     serializer_class = CorrespondentSerializer
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
-    filter_backends = (DjangoFilterBackend, OrderingFilter)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
+    filter_backends = (
+        DjangoFilterBackend,
+        OrderingFilter,
+        ObjectOwnedOrGrandtedPermissionsFilter,
+    )
     filterset_class = CorrespondentFilterSet
     ordering_fields = (
         "name",
@@ -172,7 +177,7 @@ class TagViewSet(ModelViewSet):
             return TagSerializer
 
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
     filter_backends = (DjangoFilterBackend, OrderingFilter)
     filterset_class = TagFilterSet
     ordering_fields = ("name", "matching_algorithm", "match", "document_count")
@@ -187,7 +192,7 @@ class DocumentTypeViewSet(ModelViewSet):
 
     serializer_class = DocumentTypeSerializer
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
     filter_backends = (DjangoFilterBackend, OrderingFilter)
     filterset_class = DocumentTypeFilterSet
     ordering_fields = ("name", "matching_algorithm", "match", "document_count")
@@ -204,7 +209,7 @@ class DocumentViewSet(
     queryset = Document.objects.all()
     serializer_class = DocumentSerializer
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
     filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
     filterset_class = DocumentFilterSet
     search_fields = ("title", "correspondent__name", "content")
@@ -552,7 +557,7 @@ class SavedViewViewSet(ModelViewSet):
     queryset = SavedView.objects.all()
     serializer_class = SavedViewSerializer
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
 
     def get_queryset(self):
         user = self.request.user
@@ -828,7 +833,7 @@ class StoragePathViewSet(ModelViewSet):
 
     serializer_class = StoragePathSerializer
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
     filter_backends = (DjangoFilterBackend, OrderingFilter)
     filterset_class = StoragePathFilterSet
     ordering_fields = ("name", "path", "matching_algorithm", "match", "document_count")
index dd078f0f9215d4e3134b066627d141f4e323b26b..966f8cc3fa722385985f18faed250e2fcaffc318 100644 (file)
@@ -258,6 +258,11 @@ CHANNEL_LAYERS = {
 # Security                                                                    #
 ###############################################################################
 
+AUTHENTICATION_BACKENDS = [
+    "guardian.backends.ObjectPermissionBackend",
+    "django.contrib.auth.backends.ModelBackend",
+]
+
 AUTO_LOGIN_USERNAME = os.getenv("PAPERLESS_AUTO_LOGIN_USERNAME")
 
 if AUTO_LOGIN_USERNAME:
@@ -274,11 +279,7 @@ HTTP_REMOTE_USER_HEADER_NAME = os.getenv(
 
 if ENABLE_HTTP_REMOTE_USER:
     MIDDLEWARE.append("paperless.auth.HttpRemoteUserMiddleware")
-    AUTHENTICATION_BACKENDS = [
-        "django.contrib.auth.backends.RemoteUserBackend",
-        "django.contrib.auth.backends.ModelBackend",
-        "guardian.backends.ObjectPermissionBackend",
-    ]
+    AUTHENTICATION_BACKENDS.insert(0, "django.contrib.auth.backends.RemoteUserBackend")
     REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].append(
         "rest_framework.authentication.RemoteUserAuthentication",
     )
index 431bbfd8164b92b3b01df5a78060bd6fc5d8db7f..7ff1462d3a0632ebfc1fbd1b8074fc02ba7ef2bb 100644 (file)
@@ -6,7 +6,7 @@ from django.db.models.functions import Lower
 from django.http import HttpResponse
 from django.views.generic import View
 from django_filters.rest_framework import DjangoFilterBackend
-from documents.permissions import PaperlessModelPermissions
+from documents.permissions import PaperlessObjectPermissions
 from paperless.filters import GroupFilterSet
 from paperless.filters import UserFilterSet
 from paperless.serialisers import GroupSerializer
@@ -43,7 +43,7 @@ class UserViewSet(ModelViewSet):
 
     serializer_class = UserSerializer
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
     filter_backends = (DjangoFilterBackend, OrderingFilter)
     filterset_class = UserFilterSet
     ordering_fields = ("username",)
@@ -56,7 +56,7 @@ class GroupViewSet(ModelViewSet):
 
     serializer_class = GroupSerializer
     pagination_class = StandardPagination
-    permission_classes = (IsAuthenticated, PaperlessModelPermissions)
+    permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
     filter_backends = (DjangoFilterBackend, OrderingFilter)
     filterset_class = GroupFilterSet
     ordering_fields = ("name",)