permissions.py 6.15 KB
Newer Older
1 2 3 4
"""
API library for Django REST Framework permissions-oriented workflows
"""

5
from django.conf import settings
6
from django.http import Http404
7 8
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
9
from rest_framework import permissions
10

11
from openedx.core.lib.log_utils import audit_log
12
from student.roles import CourseInstructorRole, CourseStaffRole
13

14 15

class ApiKeyHeaderPermission(permissions.BasePermission):
16 17 18
    """
    Django REST Framework permissions class used to manage API Key integrations
    """
19

20 21 22 23 24 25 26 27 28 29
    def has_permission(self, request, view):
        """
        Check for permissions by matching the configured API key and header

        If settings.DEBUG is True and settings.EDX_API_KEY is not set or None,
        then allow the request. Otherwise, allow the request if and only if
        settings.EDX_API_KEY is set and the X-Edx-Api-Key HTTP header is
        present in the request and matches the setting.
        """
        api_key = getattr(settings, "EDX_API_KEY", None)
30 31 32 33 34 35 36 37 38 39 40

        if settings.DEBUG and api_key is None:
            return True

        elif api_key is not None and request.META.get("HTTP_X_EDX_API_KEY") == api_key:
            audit_log("ApiKeyHeaderPermission used",
                      path=request.path,
                      ip=request.META.get("REMOTE_ADDR"))
            return True

        return False
41 42


43 44 45 46 47 48
class ApiKeyHeaderPermissionIsAuthenticated(ApiKeyHeaderPermission, permissions.IsAuthenticated):
    """
    Allow someone to access the view if they have the API key OR they are authenticated.

    See ApiKeyHeaderPermission for more information how the API key portion is implemented.
    """
49

50
    def has_permission(self, request, view):
51
        # TODO We can optimize this later on when we know which of these methods is used more often.
52 53 54 55 56
        api_permissions = ApiKeyHeaderPermission.has_permission(self, request, view)
        is_authenticated_permissions = permissions.IsAuthenticated.has_permission(self, request, view)
        return api_permissions or is_authenticated_permissions


57 58 59 60
class IsUserInUrl(permissions.BasePermission):
    """
    Permission that checks to see if the request user matches the user in the URL.
    """
61

62
    def has_permission(self, request, view):
63 64 65 66 67 68
        """
        Returns true if the current request is by the user themselves.

        Note: a 404 is returned for non-staff instead of a 403. This is to prevent
        users from being able to detect the existence of accounts.
        """
69 70
        url_username = request.parser_context.get('kwargs', {}).get('username', '')
        if request.user.username.lower() != url_username.lower():
71 72
            if request.user.is_staff:
                return False  # staff gets 403
73 74
            raise Http404()
        return True
75 76


77
class IsCourseStaffInstructor(permissions.BasePermission):
78
    """
79 80 81
    Permission to check that user is a course instructor or staff of
    a master course given a course object or the user is a coach of
    the course itself.
82 83 84
    """

    def has_object_permission(self, request, view, obj):
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        return (hasattr(request, 'user') and
                # either the user is a staff or instructor of the master course
                (hasattr(obj, 'course_id') and
                 (CourseInstructorRole(obj.course_id).has_user(request.user) or
                  CourseStaffRole(obj.course_id).has_user(request.user))) or
                # or it is a safe method and the user is a coach on the course object
                (request.method in permissions.SAFE_METHODS
                 and hasattr(obj, 'coach') and obj.coach == request.user))


class IsMasterCourseStaffInstructor(permissions.BasePermission):
    """
    Permission to check that user is instructor or staff of the master course.
    """
    def has_permission(self, request, view):
        """
        This method is assuming that a `master_course_id` parameter
        is available in the request as a GET parameter, a POST parameter
        or it is in the JSON payload included in the request.
        The reason is because this permission class is going
        to check if the user making the request is an instructor
        for the specified course.
        """
        master_course_id = (request.GET.get('master_course_id')
                            or request.POST.get('master_course_id')
                            or request.data.get('master_course_id'))
        if master_course_id is not None:
            try:
                course_key = CourseKey.from_string(master_course_id)
            except InvalidKeyError:
                raise Http404()
            return (hasattr(request, 'user') and
                    (CourseInstructorRole(course_key).has_user(request.user) or
                     CourseStaffRole(course_key).has_user(request.user)))
        return False
120 121


122 123 124 125
class IsUserInUrlOrStaff(IsUserInUrl):
    """
    Permission that checks to see if the request user matches the user in the URL or has is_staff access.
    """
126

127 128 129 130 131
    def has_permission(self, request, view):
        if request.user.is_staff:
            return True

        return super(IsUserInUrlOrStaff, self).has_permission(request, view)
132 133 134 135 136 137


class IsStaffOrReadOnly(permissions.BasePermission):
    """Permission that checks to see if the user is global or course
    staff, permitting only read-only access if they are not.
    """
138

139 140 141 142
    def has_object_permission(self, request, view, obj):
        return (request.user.is_staff or
                CourseStaffRole(obj.course_id).has_user(request.user) or
                request.method in permissions.SAFE_METHODS)
Clinton Blackburn committed
143 144 145 146 147 148 149


class IsStaffOrOwner(permissions.BasePermission):
    """
    Permission that allows access to admin users or the owner of an object.
    The owner is considered the User object represented by obj.user.
    """
150

Clinton Blackburn committed
151 152 153 154 155
    def has_object_permission(self, request, view, obj):
        return request.user.is_staff or obj.user == request.user

    def has_permission(self, request, view):
        user = request.user
156 157
        return user.is_staff \
            or (user.username == request.GET.get('username')) \
158 159
            or (user.username == getattr(request, 'data', {}).get('username')) \
            or (user.username == getattr(view, 'kwargs', {}).get('username'))