From f2045a9be35ccad4e08037c92482d00ac654a120 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Tue, 21 May 2024 11:18:24 +0200
Subject: [PATCH] Fix permissions

---
 aleksis/apps/chronos/managers.py        |  9 +++++++++
 aleksis/apps/chronos/models.py          |  9 +++++++++
 aleksis/apps/chronos/rules.py           | 12 +++++++++++-
 aleksis/apps/chronos/schema/__init__.py |  4 ++--
 aleksis/apps/chronos/util/predicates.py | 25 ++++++++++++++++++++-----
 5 files changed, 51 insertions(+), 8 deletions(-)

diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py
index dd63807a..532e49cc 100644
--- a/aleksis/apps/chronos/managers.py
+++ b/aleksis/apps/chronos/managers.py
@@ -885,6 +885,15 @@ class LessonEventQuerySet(PolymorphicQuerySet):
             Q(groups__members=person) | Q(pk__in=amended)
         ).distinct()
 
+    def for_owner(self, person: Union[int, Person]) -> "LessonEventQuerySet":
+        """Get all lesson events the person owns any group of (including amends)."""
+        amended = self.filter(
+            Q(amended_by__isnull=False) | Q(groups__owners=person)
+        ).values_list("amended_by__pk", flat=True)
+        return self.filter(
+            Q(groups__owners=person) | Q(pk__in=amended)
+        ).distinct()
+
     def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet":
         """Get all lesson events for a certain group (including amends/as parent group)."""
         amended = self.filter(
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index d27e4c6a..815b312b 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -1567,6 +1567,8 @@ class LessonEvent(CalendarEvent):
                     return objs.for_room(obj_id)
                 elif type_ == "COURSE":
                     return objs.for_course(obj_id)
+                elif type_ == "OWNER":
+                    return objs.for_owner(obj_id)
 
             if "own" in params:
                 return objs
@@ -1601,6 +1603,13 @@ class LessonEvent(CalendarEvent):
                     "id": obj_id,
                 }
             )
+        else:
+            event_params.update(
+                {
+                    "type": "OWNER",
+                    "id": request.user.person.id,
+                }
+            )
 
         events = LessonEvent.get_single_events(
             date_start, date_end, request, event_params, with_reference_object=True
diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py
index 03ef88c2..65659458 100644
--- a/aleksis/apps/chronos/rules.py
+++ b/aleksis/apps/chronos/rules.py
@@ -12,6 +12,7 @@ from .util.predicates import (
     has_any_group_substitution_perm,
     has_any_timetable_object,
     has_group_substitution_perm,
+    has_substitution_perm_by_group,
     has_room_timetable_perm,
     has_timetable_perm,
 )
@@ -41,9 +42,18 @@ add_perm("chronos.view_substitution_overview_rule", view_substitution_overview_p
 manage_substitutions_for_group_predicate = has_person & has_group_substitution_perm
 add_perm("chronos.manage_substitutions_for_group_rule", manage_substitutions_for_group_predicate)
 
+# Add substitution
+add_substitution_predicate = has_person & (
+    has_substitution_perm_by_group
+    | has_global_perm("chronos.add_lessonsubstitution")
+    | has_object_perm("chronos.add_lessonsubstitution")
+)
+add_perm("chronos.add_substitution_rule", add_substitution_predicate)
+
 # Edit substition
 edit_substitution_predicate = has_person & (
-    has_global_perm("chronos.change_lessonsubstitution")
+    has_substitution_perm_by_group
+    | has_global_perm("chronos.change_lessonsubstitution")
     | has_object_perm("chronos.change_lessonsubstitution")
 )
 add_perm("chronos.edit_substitution_rule", edit_substitution_predicate)
diff --git a/aleksis/apps/chronos/schema/__init__.py b/aleksis/apps/chronos/schema/__init__.py
index f0beaf9d..d2f1c747 100644
--- a/aleksis/apps/chronos/schema/__init__.py
+++ b/aleksis/apps/chronos/schema/__init__.py
@@ -189,7 +189,7 @@ class SubstitutionBatchCreateOrUpdateMutation(graphene.Mutation):
                 amended_lesson_event.timezone
             )
 
-            if info.context.user.has_perm("chronos.change_lessonsubstitution"):
+            if info.context.user.has_perm("chronos.add_substitution_rule", amended_lesson_event):
                 obj = LessonEvent.objects.create(
                     datetime_start=datetime_start,
                     datetime_end=datetime_end,
@@ -208,7 +208,7 @@ class SubstitutionBatchCreateOrUpdateMutation(graphene.Mutation):
         else:
             obj = LessonEvent.objects.get(id=_id)
 
-            if not info.context.user.has_perm("chronos.edit_substitution_rule", obj):
+            if not info.context.user.has_perm("chronos.edit_substitution_rule", obj.amends):
                 raise PermissionDenied()
 
             if substitution.subject is not None:
diff --git a/aleksis/apps/chronos/util/predicates.py b/aleksis/apps/chronos/util/predicates.py
index 8549177f..f9103c12 100644
--- a/aleksis/apps/chronos/util/predicates.py
+++ b/aleksis/apps/chronos/util/predicates.py
@@ -4,9 +4,11 @@ from django.db.models import Model
 from rules import predicate
 
 from aleksis.core.models import Group, Person, Room
+from aleksis.core.util.core_helpers import queryset_rules_filter
 from aleksis.core.util.predicates import has_any_object, has_global_perm, has_object_perm
 
 from .chronos_helpers import get_groups, get_rooms, get_teachers
+from ..models import LessonEvent
 
 
 @predicate
@@ -44,13 +46,27 @@ def has_group_timetable_perm(user: User, obj: Group) -> bool:
     )
 
 
+@predicate
+def has_substitution_perm_by_group(user: User, obj: LessonEvent) -> bool:
+    """
+    Check if can create/edit substitution based on group.
+
+    Predicate which checks whether the user is allowed
+    to create/edit the requested substitution.
+    """
+    return (
+        obj.groups.filter(pk__in=user.person.owner_of.values_list("id", flat=True)).exists()
+        or queryset_rules_filter(user, obj.groups.all(), "core.manage_group_substitutions").exists()
+    )
+
+
 @predicate
 def has_group_substitution_perm(user: User, obj: Group) -> bool:
     """
-    Check if can access/edit group substitutions.
+    Check if can access/edit substitutions of given group.
 
     Predicate which checks whether the user is allowed
-    to access/edit the requested group substitutions.
+    to access/edit the substitutions of the given group.
     """
     return (
         obj in user.person.owner_of.all()
@@ -62,14 +78,13 @@ def has_group_substitution_perm(user: User, obj: Group) -> bool:
 @predicate
 def has_any_group_substitution_perm(user: User) -> bool:
     """
-    Check if can access/edit any group substitutions.
+    Check if can create/edit substitutions of any group.
 
     Predicate which checks whether the user is allowed
-    to access/edit any group substitutions.
+    to create/edit any substitutions of any group.
     """
     return (
         user.person.owner_of.exists()
-        or has_global_perm("chronos.view_lessonsubstitution")(user)
         or has_any_object("core.manage_group_substitutions", Group)(user)
     )
 
-- 
GitLab