From 269985927e14f4429960d2a7bd42f30c7d64489e Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sat, 17 Dec 2022 14:00:26 +0100
Subject: [PATCH 1/3] Validate permissions in Queries

---
 aleksis/core/schema/__init__.py        | 28 +++++++++++++++++---------
 aleksis/core/schema/base.py            | 14 +++++++++++++
 aleksis/core/schema/celery_progress.py |  8 ++++++--
 aleksis/core/schema/group.py           |  9 ++++++---
 aleksis/core/schema/notification.py    |  8 ++++++--
 aleksis/core/schema/person.py          |  8 ++++++--
 6 files changed, 57 insertions(+), 18 deletions(-)
 create mode 100644 aleksis/core/schema/base.py

diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py
index 1f5a2fa8e..69a3bbf82 100644
--- a/aleksis/core/schema/__init__.py
+++ b/aleksis/core/schema/__init__.py
@@ -8,7 +8,11 @@ from haystack.utils.loading import UnifiedIndex
 from ..models import Notification, Person, TaskUserAssignment
 from ..util.apps import AppConfig
 from ..util.core_helpers import get_allowed_object_ids, get_app_module, get_app_packages, has_person
-from .celery_progress import CeleryProgressFetchedMutation, CeleryProgressType
+from .celery_progress import (
+    CeleryProgressFetchedMutation,
+    CeleryProgressMetaType,
+    CeleryProgressType,
+)
 from .group import GroupType  # noqa
 from .installed_apps import AppType
 from .notification import MarkNotificationReadMutation, NotificationType
@@ -37,15 +41,18 @@ class Query(graphene.ObjectType):
     )
 
     def resolve_notifications(root, info, **kwargs):
-        # FIXME do permission stuff
-        return Notification.objects.all()
+        return NotificationType.get_queryset(
+            Notification.objects.all().order_by("-created"),
+            info,
+        )
 
     def resolve_persons(root, info, **kwargs):
-        # FIXME do permission stuff
-        return Person.objects.all()
+        return PersonType.get_queryset(Person.objects.all(), info).all()
 
     def resolve_person_by_id(root, info, id):  # noqa
-        return Person.objects.get(pk=id)
+        return PersonType.get_queryset(
+            Person.objects.filter(pk=id), info, "core.view_person_rule"
+        ).first()
 
     def resolve_who_am_i(root, info, **kwargs):
         if has_person(info.context.user):
@@ -60,10 +67,13 @@ class Query(graphene.ObjectType):
         return [app for app in apps.get_app_configs() if isinstance(app, AppConfig)]
 
     def resolve_celery_progress_by_task_id(root, info, task_id, **kwargs):
-        task = TaskUserAssignment.objects.get(task_result__task_id=task_id)
+        task = CeleryProgressMetaType.get_queryset(
+            TaskUserAssignment.objects.filter(task_result__task_id=task_id),
+            info,
+        ).first()
 
-        if not info.context.user.has_perm("core.view_progress_rule", task):
-            return None
+        if not task:
+            raise PermissionDenied()
         progress = task.get_progress_with_meta()
         return progress
 
diff --git a/aleksis/core/schema/base.py b/aleksis/core/schema/base.py
new file mode 100644
index 000000000..363c6b8ac
--- /dev/null
+++ b/aleksis/core/schema/base.py
@@ -0,0 +1,14 @@
+from graphene_django import DjangoObjectType
+
+from ..util.core_helpers import queryset_rules_filter
+
+
+class RulesObjectType(DjangoObjectType):
+    class Meta:
+        abstract = True
+
+    @classmethod
+    def get_queryset(cls, queryset, info, perm):
+        q = super().get_queryset(queryset, info)
+
+        return queryset_rules_filter(info.context, q, perm)
diff --git a/aleksis/core/schema/celery_progress.py b/aleksis/core/schema/celery_progress.py
index 941d18f1d..c6fe93dd0 100644
--- a/aleksis/core/schema/celery_progress.py
+++ b/aleksis/core/schema/celery_progress.py
@@ -2,9 +2,9 @@ from django.contrib.messages.constants import DEFAULT_TAGS
 
 import graphene
 from graphene import ObjectType
-from graphene_django import DjangoObjectType
 
 from ..models import TaskUserAssignment
+from .base import RulesObjectType
 
 
 class CeleryProgressMessage(ObjectType):
@@ -28,7 +28,7 @@ class CeleryProgressAdditionalButtonType(ObjectType):
     icon = graphene.String()
 
 
-class CeleryProgressMetaType(DjangoObjectType):
+class CeleryProgressMetaType(RulesObjectType):
     additional_button = graphene.Field(CeleryProgressAdditionalButtonType, required=False)
     task_id = graphene.String(required=True)
 
@@ -47,6 +47,10 @@ class CeleryProgressMetaType(DjangoObjectType):
             "additional_button",
         )
 
+    @classmethod
+    def get_queryset(cls, queryset, info, perm="core.view_progress_rule"):
+        return super().get_queryset(queryset, info, perm)
+
     def resolve_additional_button(root, info, **kwargs):
         if not root.additional_button_title or not root.additional_button_url:
             return None
diff --git a/aleksis/core/schema/group.py b/aleksis/core/schema/group.py
index 327daff3d..70f00bd1d 100644
--- a/aleksis/core/schema/group.py
+++ b/aleksis/core/schema/group.py
@@ -1,8 +1,11 @@
-from graphene_django import DjangoObjectType
-
 from ..models import Group
+from .base import RulesObjectType
 
 
-class GroupType(DjangoObjectType):
+class GroupType(RulesObjectType):
     class Meta:
         model = Group
+
+    @classmethod
+    def get_queryset(cls, queryset, info, perm="core.view_groups_rule"):
+        return super().get_queryset(queryset, info, perm)
diff --git a/aleksis/core/schema/notification.py b/aleksis/core/schema/notification.py
index 114f92b32..105ed1366 100644
--- a/aleksis/core/schema/notification.py
+++ b/aleksis/core/schema/notification.py
@@ -1,13 +1,17 @@
 import graphene
-from graphene_django import DjangoObjectType
 
 from ..models import Notification
+from .base import RulesObjectType
 
 
-class NotificationType(DjangoObjectType):
+class NotificationType(RulesObjectType):
     class Meta:
         model = Notification
 
+    @classmethod
+    def get_queryset(cls, queryset, info, perm="core.view_notifications_rule"):
+        return super().get_queryset(queryset, info, perm)
+
 
 class MarkNotificationReadMutation(graphene.Mutation):
     class Arguments:
diff --git a/aleksis/core/schema/person.py b/aleksis/core/schema/person.py
index a48447273..c7b4fd6ff 100644
--- a/aleksis/core/schema/person.py
+++ b/aleksis/core/schema/person.py
@@ -1,14 +1,14 @@
 from django.utils import timezone
 
 import graphene
-from graphene_django import DjangoObjectType
 from graphene_django.forms.mutation import DjangoModelFormMutation
 
 from ..forms import PersonForm
 from ..models import Person
+from .base import RulesObjectType
 
 
-class PersonType(DjangoObjectType):
+class PersonType(RulesObjectType):
     class Meta:
         model = Person
 
@@ -21,6 +21,10 @@ class PersonType(DjangoObjectType):
     def resolve_notifications(root: Person, info, **kwargs):
         return root.notifications.filter(send_at__lte=timezone.now()).order_by("read", "-created")
 
+    @classmethod
+    def get_queryset(cls, queryset, info, perm="core.view_persons_rule"):
+        return super().get_queryset(queryset, info, perm)
+
 
 class PersonMutation(DjangoModelFormMutation):
     person = graphene.Field(PersonType)
-- 
GitLab


From 64b57e89516df031144d632ee189abb630b35ed4 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sat, 17 Dec 2022 17:17:30 +0100
Subject: [PATCH 2/3] Validate permissions in Mutations

---
 aleksis/core/schema/notification.py |  6 ++++--
 aleksis/core/schema/person.py       | 10 ++++++++++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/aleksis/core/schema/notification.py b/aleksis/core/schema/notification.py
index 105ed1366..fe1fc19a4 100644
--- a/aleksis/core/schema/notification.py
+++ b/aleksis/core/schema/notification.py
@@ -22,8 +22,10 @@ class MarkNotificationReadMutation(graphene.Mutation):
     @classmethod
     def mutate(cls, root, info, id):  # noqa
         notification = Notification.objects.get(pk=id)
-        # FIXME permissions
+
+        if not info.context.user.has_perm("core.mark_notification_as_read_rule", notification):
+            raise PermissionDenied()
         notification.read = True
         notification.save()
 
-        return notification
+        return MarkNotificationReadMutation(notification=notification)
diff --git a/aleksis/core/schema/person.py b/aleksis/core/schema/person.py
index c7b4fd6ff..d329235ad 100644
--- a/aleksis/core/schema/person.py
+++ b/aleksis/core/schema/person.py
@@ -31,3 +31,13 @@ class PersonMutation(DjangoModelFormMutation):
 
     class Meta:
         form_class = PersonForm
+
+    @classmethod
+    def perform_mutate(cls, form, info):
+        if form.initial:
+            if not info.context.user.has_perm("core.create_person.rule"):
+                raise PermissionDenied()
+        else:
+            if not info.context.user.has_perm("core.edit_person.rule", form.instance):
+                raise PermissionDenied()
+        return super().perform_mutate(form, info)
-- 
GitLab


From 1408500e5cd707a0e2d71a0a23289e226b9fe8a5 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sat, 17 Dec 2022 17:17:51 +0100
Subject: [PATCH 3/3] Unprotect GraphQL endpoint

---
 aleksis/core/urls.py  | 3 ++-
 aleksis/core/views.py | 5 -----
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index e2e9d0c15..fd36dba48 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -8,6 +8,7 @@ from django.views.i18n import JavaScriptCatalog
 
 import calendarweek.django
 from ckeditor_uploader import views as ckeditor_uploader_views
+from graphene_django.views import GraphQLView
 from health_check.urls import urlpatterns as health_urls
 from oauth2_provider.views import ConnectDiscoveryInfoView
 from rules.contrib.views import permission_required
@@ -140,7 +141,7 @@ urlpatterns = [
         name="oauth2_provider:authorize",
     ),
     path("oauth/", include("oauth2_provider.urls", namespace="oauth2_provider")),
-    path("graphql/", csrf_exempt(views.PrivateGraphQLView.as_view(graphiql=True)), name="graphql"),
+    path("graphql/", csrf_exempt(GraphQLView.as_view(graphiql=True)), name="graphql"),
     path("__i18n__/", include("django.conf.urls.i18n")),
     path(
         "ckeditor/upload/",
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index 886ae4e3b..5a1739a47 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -44,7 +44,6 @@ from django_celery_results.models import TaskResult
 from django_filters.views import FilterView
 from django_tables2 import RequestConfig, SingleTableMixin, SingleTableView
 from dynamic_preferences.forms import preference_form_builder
-from graphene_django.views import GraphQLView
 from guardian.shortcuts import GroupObjectPermission, UserObjectPermission, get_objects_for_user
 from haystack.generic_views import SearchView
 from haystack.inputs import AutoQuery
@@ -1585,7 +1584,3 @@ class ICalFeedCreateView(PermissionRequiredMixin, AdvancedCreateView):
         obj.person = self.request.user.person
         obj.save()
         return super().form_valid(form)
-
-
-class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
-    """GraphQL view that requires a valid user session."""
-- 
GitLab