ocadotechnology/rapid-router

View on GitHub
game/permissions.py

Summary

Maintainability
C
1 day
Test Coverage
import logging

from rest_framework import permissions

LOGGER = logging.getLogger(__name__)


def _get_userprofile_school(userprofile):
    if hasattr(userprofile, "teacher"):
        return userprofile.teacher.school
    elif hasattr(userprofile, "student"):
        return userprofile.student.class_field.teacher.school
    else:
        LOGGER.error(f"Userprofile ID {userprofile.id} has no teacher or student attribute")
        return None


#########################
# Workspace permissions #
#########################


def can_create_workspace(user):
    return not user.is_anonymous


def can_load_workspace(user, workspace):
    return not user.is_anonymous and workspace.owner == user.userprofile


def can_save_workspace(user, workspace):
    return not user.is_anonymous and workspace.owner == user.userprofile


def can_delete_workspace(user, workspace):
    return not user.is_anonymous and workspace.owner == user.userprofile


#####################
# Level permissions #
#####################


def can_create_level(user):
    return not user.is_anonymous


def can_play_or_delete_level(user, level):
    # If the teacher is an admin, they can play any student's level in the school, otherwise only student levels
    # from their own classes
    if user.userprofile.teacher.is_admin and hasattr(level.owner, "student"):
        return user.userprofile.teacher.school == level.owner.student.class_field.teacher.school
    else:
        return user.userprofile.teacher.teaches(level.owner)


def can_play_level(user, level, early_access):
    if not user.is_anonymous and hasattr(user.userprofile, "student") and user.userprofile.student.class_field:
        # If the user is a student, check that the level isn't locked for their class
        return user.userprofile.student.class_field not in level.locked_for_class.all()
    elif level.default and not level.episode.in_development:
        return True
    elif level.anonymous:
        return False
    elif level.default and level.episode.in_development and early_access:
        return True
    elif user.is_anonymous:
        return level.default and not level.episode.in_development
    elif user.userprofile == level.owner:
        return True
    elif level.shared_with.filter(id=user.id):
        user_school = _get_userprofile_school(user.userprofile)
        owner_school = _get_userprofile_school(level.owner)
        return user_school is not None and user_school == owner_school
    else:
        return can_play_or_delete_level(user, level)


def can_load_level(user, level):
    if user.is_anonymous:
        return False
    elif user.userprofile == level.owner:
        return True
    elif level.shared_with.filter(id=user.id):
        user_school = _get_userprofile_school(user.userprofile)
        owner_school = _get_userprofile_school(level.owner)
        return user_school is not None and user_school == owner_school
    else:
        return hasattr(user.userprofile, "teacher") and user.userprofile.teacher.teaches(level.owner)


def can_save_level(user, level):
    if level.anonymous:
        return True
    elif user.is_anonymous:
        return False
    else:
        return user.userprofile == level.owner


def can_delete_level(user, level):
    if user.is_anonymous:
        return False
    elif level.owner == user.userprofile:
        return True
    elif hasattr(user.userprofile, "teacher"):
        return can_play_or_delete_level(user, level)

    return False


class CanShareLevel(permissions.BasePermission):
    """
    Used to verify that an incoming request is made by a user who is authorised to share
    the level - that is, that they are the owner of the level as a student, or if they're a teacher that the level was
    shared with them.
    """

    def has_permission(self, request, view):
        return True

    def has_object_permission(self, request, view, obj):
        if request.user.is_anonymous:
            return False
        elif hasattr(request.user.userprofile, "student") and request.user.userprofile.student.is_independent():
            return False
        # if the user is a teacher and the level is shared with them
        elif hasattr(request.user.userprofile, "teacher") and obj.shared_with.filter(id=request.user.id).exists():
            return True
        else:
            return obj.owner == request.user.userprofile


class CanShareLevelWith(permissions.BasePermission):
    """
    Used to verify that the user who is requesting to share their level is authorised to share the level with a specific
    recipient.
    The user is authorised if:
    - neither they nor the recipient are anonymous,
    - neither they nor the recipient are independent students,
    - they are a student and the recipient is a student in the same class, or their teacher
    - they are a teacher and the recipient is a teacher in the same school, or their student
    - they are an admin teacher and the recipient is a student in the same school
    """

    def has_permission(self, request, view):
        return True

    def has_object_permission(self, request, view, obj):
        sharer = request.user
        return self.can_share_level_with(obj, sharer)

    def can_share_level_with(self, recipient, sharer):
        if recipient.is_anonymous or sharer.is_anonymous:
            return False

        recipient_profile = recipient.userprofile
        sharer_profile = sharer.userprofile

        if (
            hasattr(sharer_profile, "student")
            and not (sharer_profile.student.is_independent())
            and hasattr(recipient_profile, "student")
            and not (recipient_profile.student.is_independent())
        ):
            # Are they in the same class?
            return sharer_profile.student.class_field == recipient_profile.student.class_field
        elif hasattr(sharer_profile, "teacher") and sharer_profile.teacher.teaches(recipient_profile):
            # Is the recipient taught by the sharer?
            return True
        elif hasattr(recipient_profile, "teacher") and recipient_profile.teacher.teaches(sharer_profile):
            # Is the sharer taught by the recipient?
            return True
        elif hasattr(sharer_profile, "teacher") and hasattr(recipient_profile, "teacher"):
            # Are they in the same organisation?
            return recipient_profile.teacher.school == sharer_profile.teacher.school
        elif hasattr(sharer_profile, "teacher") and sharer_profile.teacher.is_admin:
            return recipient_profile.student.class_field.teacher.school == sharer_profile.teacher.school
        else:
            return False


#####################
# Other permissions #
#####################


def can_see_class(user, class_):
    if user.is_anonymous:
        return False
    elif hasattr(user.userprofile, "teacher"):
        return class_.teacher == user.userprofile.teacher


def can_see_level_moderation(user):
    if user.is_anonymous:
        return False
    else:
        return hasattr(user.userprofile, "teacher")


def can_see_scoreboard(user):
    return not user.is_anonymous