Skip to content
Snippets Groups Projects
Commit 47699bcb authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch '251-permissions-for-timetables-are-not-checked-properly' into 'master'

Resolve "Permissions for timetables are not checked properly"

Closes #251

See merge request !369
parents c583413b 1b76f3e1
No related branches found
No related tags found
1 merge request!369Resolve "Permissions for timetables are not checked properly"
Pipeline #193688 failed
Pipeline: AlekSIS

#193692

    ...@@ -15,7 +15,9 @@ class TimetableType(Enum): ...@@ -15,7 +15,9 @@ class TimetableType(Enum):
    GROUP = "group" GROUP = "group"
    TEACHER = "teacher" TEACHER = "teacher"
    PARTICIPANT = "participant"
    ROOM = "room" ROOM = "room"
    COURSE = "course"
    @classmethod @classmethod
    def from_string(cls, s: Optional[str]): def from_string(cls, s: Optional[str]):
    ......
    # Generated by Django 5.0.7 on 2024-09-13 21:20
    from django.db import migrations
    class Migration(migrations.Migration):
    dependencies = [
    ('chronos', '0019_remove_old_models'),
    ]
    operations = [
    migrations.AlterModelOptions(
    name='chronosglobalpermissions',
    options={'managed': False, 'permissions': (('view_all_room_timetables', 'Can view all room timetables'), ('view_all_group_timetables', 'Can view all group timetables'), ('view_all_person_timetables', 'Can view all person timetables'), ('view_all_course_timetables', 'Can view all course timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_substitutions', 'Can view substitutions table'), ('view_all_room_supervisions', 'Can view all room supervisions'), ('view_all_group_supervisions', 'Can view all group supervisions'), ('view_all_person_supervisions', 'Can view all person supervisions'))},
    ),
    ]
    from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
    from aleksis.core.models import Group, Person from aleksis.apps.cursus.models import Course
    from aleksis.core.models import Group, Person, Room
    # Dynamically add extra permissions to Group and Person models in core # Dynamically add permissions to Group, Person and Room models in core and Course model in cursus
    # Note: requires migrate afterwards # Note: requires migrate afterwards
    Group.add_permission( Group.add_permission(
    "view_group_timetable", "view_group_timetable",
    ...@@ -12,3 +13,19 @@ Person.add_permission( ...@@ -12,3 +13,19 @@ Person.add_permission(
    "view_person_timetable", "view_person_timetable",
    _("Can view person timetable"), _("Can view person timetable"),
    ) )
    Group.add_permission(
    "view_group_supervisions",
    _("Can view group supervisions"),
    )
    Person.add_permission(
    "view_person_supervisions",
    _("Can view person supervisions"),
    )
    Room.add_permission(
    "view_room_supervisions",
    _("Can view room supervisions"),
    )
    Course.add_permission(
    "view_course_timetable",
    _("Can view course timetable"),
    )
    ...@@ -7,6 +7,7 @@ from datetime import date ...@@ -7,6 +7,7 @@ from datetime import date
    from typing import Any from typing import Any
    from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
    from django.core.exceptions import PermissionDenied
    from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
    from django.db import models from django.db import models
    from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet
    ...@@ -34,6 +35,7 @@ from aleksis.core.mixins import ( ...@@ -34,6 +35,7 @@ from aleksis.core.mixins import (
    ) )
    from aleksis.core.models import CalendarEvent, Group, Person, Room from aleksis.core.models import CalendarEvent, Group, Person, Room
    from aleksis.core.util.core_helpers import get_site_preferences, has_person from aleksis.core.util.core_helpers import get_site_preferences, has_person
    from aleksis.core.util.predicates import check_global_permission
    class AutomaticPlan(LiveDocument): class AutomaticPlan(LiveDocument):
    ...@@ -146,8 +148,12 @@ class ChronosGlobalPermissions(GlobalPermissionModel): ...@@ -146,8 +148,12 @@ class ChronosGlobalPermissions(GlobalPermissionModel):
    ("view_all_room_timetables", _("Can view all room timetables")), ("view_all_room_timetables", _("Can view all room timetables")),
    ("view_all_group_timetables", _("Can view all group timetables")), ("view_all_group_timetables", _("Can view all group timetables")),
    ("view_all_person_timetables", _("Can view all person timetables")), ("view_all_person_timetables", _("Can view all person timetables")),
    ("view_all_course_timetables", _("Can view all course timetables")),
    ("view_timetable_overview", _("Can view timetable overview")), ("view_timetable_overview", _("Can view timetable overview")),
    ("view_substitutions", _("Can view substitutions table")), ("view_substitutions", _("Can view substitutions table")),
    ("view_all_room_supervisions", _("Can view all room supervisions")),
    ("view_all_group_supervisions", _("Can view all group supervisions")),
    ("view_all_person_supervisions", _("Can view all person supervisions")),
    ) )
    ...@@ -444,6 +450,39 @@ class LessonEvent(CalendarEvent): ...@@ -444,6 +450,39 @@ class LessonEvent(CalendarEvent):
    q = q & LessonEventQuerySet.related_to_person_q(request.user.person) q = q & LessonEventQuerySet.related_to_person_q(request.user.person)
    if type_ and obj_id: if type_ and obj_id:
    if request and not (
    (
    type_ == "GROUP"
    and check_global_permission(
    request.user, "chronos.view_all_group_timetables"
    )
    )
    or (
    type_ == "TEACHER"
    or type_ == "PARTICIPANT"
    and check_global_permission(
    request.user, "chronos.view_all_person_timetables"
    )
    )
    or (
    type_ == "ROOM"
    and check_global_permission(
    request.user, "chronos.view_all_room_timetables"
    )
    )
    or (
    type_ == "COURSE"
    and check_global_permission(
    request.user, "chronos.view_all_course_timetables"
    )
    )
    ):
    # inline import needed to avoid circular import
    from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
    obj = get_el_by_pk(request, type_.lower(), obj_id)
    if not request.user.has_perm("chronos.view_timetable_rule", obj):
    raise PermissionDenied()
    if type_ == "TEACHER": if type_ == "TEACHER":
    q = q & LessonEventQuerySet.for_teacher_q(obj_id) q = q & LessonEventQuerySet.for_teacher_q(obj_id)
    elif type_ == "PARTICIPANT": elif type_ == "PARTICIPANT":
    ...@@ -523,6 +562,32 @@ class SupervisionEvent(LessonEvent): ...@@ -523,6 +562,32 @@ class SupervisionEvent(LessonEvent):
    q = q & SupervisionEventQuerySet.amending_q() q = q & SupervisionEventQuerySet.amending_q()
    if type_ and obj_id: if type_ and obj_id:
    if request and not (
    (
    type_ == "GROUP"
    and check_global_permission(
    request.user, "chronos.view_all_group_supervisions"
    )
    )
    or (
    type_ == "TEACHER"
    and check_global_permission(
    request.user, "chronos.view_all_person_supervisions"
    )
    )
    or (
    type_ == "ROOM"
    and check_global_permission(
    request.user, "chronos.view_all_room_supervisions"
    )
    )
    ):
    # inline import needed to avoid circular import
    from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
    obj = get_el_by_pk(request, type_.lower(), obj_id)
    if not request.user.has_perm("chronos.view_supervisions_rule", obj):
    raise PermissionDenied()
    if type_ == "TEACHER": if type_ == "TEACHER":
    q = q & SupervisionEventQuerySet.for_teacher_q(obj_id) q = q & SupervisionEventQuerySet.for_teacher_q(obj_id)
    elif type_ == "GROUP": elif type_ == "GROUP":
    ......
    ...@@ -6,7 +6,7 @@ from aleksis.core.util.predicates import ( ...@@ -6,7 +6,7 @@ from aleksis.core.util.predicates import (
    has_person, has_person,
    ) )
    from .util.predicates import has_any_timetable_object, has_timetable_perm from .util.predicates import has_any_timetable_object, has_supervisions_perm, has_timetable_perm
    # View timetable overview # View timetable overview
    view_timetable_overview_predicate = has_person & ( view_timetable_overview_predicate = has_person & (
    ...@@ -18,6 +18,9 @@ add_perm("chronos.view_timetable_overview_rule", view_timetable_overview_predica ...@@ -18,6 +18,9 @@ add_perm("chronos.view_timetable_overview_rule", view_timetable_overview_predica
    view_timetable_predicate = has_person & has_timetable_perm view_timetable_predicate = has_person & has_timetable_perm
    add_perm("chronos.view_timetable_rule", view_timetable_predicate) add_perm("chronos.view_timetable_rule", view_timetable_predicate)
    # View supervisions for group, person or room
    view_supervisions_predicate = has_person & has_supervisions_perm
    add_perm("chronos.view_supervisions_rule", view_supervisions_predicate)
    # Edit substition # Edit substition
    edit_substitution_predicate = has_person & ( edit_substitution_predicate = has_person & (
    ......
    ...@@ -2,12 +2,16 @@ from datetime import date, datetime, timedelta ...@@ -2,12 +2,16 @@ from datetime import date, datetime, timedelta
    from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
    from django.db.models import Count, Q from django.db.models import Count, Q
    from django.http import HttpRequest, HttpResponseNotFound
    from django.shortcuts import get_object_or_404
    from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
    from aleksis.apps.cursus.models import Course
    from aleksis.core.models import Announcement, Group, Person, Room from aleksis.core.models import Announcement, Group, Person, Room
    from aleksis.core.util.core_helpers import get_site_preferences from aleksis.core.util.core_helpers import get_site_preferences
    from ..managers import TimetableType
    from .build import build_substitutions_list from .build import build_substitutions_list
    if TYPE_CHECKING: if TYPE_CHECKING:
    ...@@ -16,6 +20,29 @@ if TYPE_CHECKING: ...@@ -16,6 +20,29 @@ if TYPE_CHECKING:
    User = get_user_model() # noqa User = get_user_model() # noqa
    def get_el_by_pk(
    request: HttpRequest,
    type_: str,
    pk: int,
    prefetch: bool = False,
    *args,
    **kwargs,
    ):
    if type_ == TimetableType.GROUP.value:
    return get_object_or_404(
    Group.objects.prefetch_related("owners", "parent_groups") if prefetch else Group,
    pk=pk,
    )
    elif type_ == TimetableType.TEACHER.value or type_ == TimetableType.PARTICIPANT.value:
    return get_object_or_404(Person, pk=pk)
    elif type_ == TimetableType.ROOM.value:
    return get_object_or_404(Room, pk=pk)
    elif type_ == TimetableType.COURSE.value:
    return get_object_or_404(Course, pk=pk)
    else:
    return HttpResponseNotFound()
    def get_teachers(user: "User"): def get_teachers(user: "User"):
    """Get the teachers whose timetables are allowed to be seen by current user.""" """Get the teachers whose timetables are allowed to be seen by current user."""
    ......
    ...@@ -3,6 +3,7 @@ from django.db.models import Model ...@@ -3,6 +3,7 @@ from django.db.models import Model
    from rules import predicate from rules import predicate
    from aleksis.apps.cursus.models import Course
    from aleksis.core.models import Group, Person, Room from aleksis.core.models import Group, Person, Room
    from aleksis.core.util.predicates import has_global_perm, has_object_perm from aleksis.core.util.predicates import has_global_perm, has_object_perm
    ...@@ -23,6 +24,8 @@ def has_timetable_perm(user: User, obj: Model) -> bool: ...@@ -23,6 +24,8 @@ def has_timetable_perm(user: User, obj: Model) -> bool:
    return has_person_timetable_perm(user, obj) return has_person_timetable_perm(user, obj)
    elif isinstance(obj, Room): elif isinstance(obj, Room):
    return has_room_timetable_perm(user, obj) return has_room_timetable_perm(user, obj)
    elif isinstance(obj, Course):
    return has_course_timetable_perm(user, obj)
    else: else:
    return False return False
    ...@@ -72,6 +75,88 @@ def has_room_timetable_perm(user: User, obj: Room) -> bool: ...@@ -72,6 +75,88 @@ def has_room_timetable_perm(user: User, obj: Room) -> bool:
    )(user, obj) )(user, obj)
    @predicate
    def has_course_timetable_perm(user: User, obj: Course) -> bool:
    """
    Check if can access course timetable.
    Predicate which checks whether the user is allowed
    to access the requested course timetable.
    """
    return (
    user.person in obj.teachers.all()
    or obj.groups.all().intersection(user.person.member_of.all()).exists()
    or user.person.primary_group in obj.groups.all()
    or obj.groups.all().intersection(user.person.owner_of.all()).exists()
    or has_global_perm("chronos.view_all_course_timetables")(user)
    or has_object_perm("cursus.view_course_timetable")(user, obj)
    )
    @predicate
    def has_supervisions_perm(user: User, obj: Model) -> bool:
    """
    Check if can access supervisions of object.
    Predicate which checks whether the user is allowed
    to access the requested supervisions of the given
    group, person or room.
    """
    if isinstance(obj, Group):
    return has_group_supervisions_perm(user, obj)
    elif isinstance(obj, Person):
    return has_person_supervisions_perm(user, obj)
    elif isinstance(obj, Room):
    return has_room_supervisions_perm(user, obj)
    else:
    return False
    @predicate
    def has_group_supervisions_perm(user: User, obj: Group) -> bool:
    """
    Check if can access group supervisions.
    Predicate which checks whether the user is allowed
    to access the requested group supervisions.
    """
    return (
    obj in user.person.member_of.all()
    or user.person.primary_group == obj
    or obj in user.person.owner_of.all()
    or has_global_perm("chronos.view_all_group_supervisions")(user)
    or has_object_perm("core.view_group_supervisions")(user, obj)
    )
    @predicate
    def has_person_supervisions_perm(user: User, obj: Person) -> bool:
    """
    Check if can access person supervisions.
    Predicate which checks whether the user is allowed
    to access the requested person supervisions.
    """
    return (
    user.person == obj
    or has_global_perm("chronos.view_all_person_supervisions")(user)
    or has_object_perm("core.view_person_supervisions")(user, obj)
    )
    @predicate
    def has_room_supervisions_perm(user: User, obj: Room) -> bool:
    """
    Check if can access room supervisions.
    Predicate which checks whether the user is allowed
    to access the requested room supervisions.
    """
    return has_global_perm("chronos.view_all_room_supervisions")(user) or has_object_perm(
    "core.view_room_supervisions"
    )(user, obj)
    @predicate @predicate
    def has_any_timetable_object(user: User) -> bool: def has_any_timetable_object(user: User) -> bool:
    """Predicate which checks whether there are any timetables the user is allowed to access.""" """Predicate which checks whether there are any timetables the user is allowed to access."""
    ......
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment