auth.py 6.31 KB
Newer Older
1 2 3 4 5 6
"""
The application interface to roles which checks whether any user trying to change
authorization has authorization to do so, which infers authorization via role hierarchy
(GlobalStaff is superset of auths of course instructor, ...), which consults the config
to decide whether to check course creator role, and other such functions.
"""
7
from ccx_keys.locator import CCXBlockUsageLocator, CCXLocator
8
from django.conf import settings
9
from django.core.exceptions import PermissionDenied
10
from opaque_keys.edx.locator import LibraryLocator
11

12 13 14 15 16 17 18 19 20 21 22 23
from student.roles import (
    CourseBetaTesterRole,
    CourseCreatorRole,
    CourseInstructorRole,
    CourseRole,
    CourseStaffRole,
    GlobalStaff,
    LibraryUserRole,
    OrgInstructorRole,
    OrgLibraryUserRole,
    OrgStaffRole
)
24

25 26 27 28 29
# Studio permissions:
STUDIO_EDIT_ROLES = 8
STUDIO_VIEW_USERS = 4
STUDIO_EDIT_CONTENT = 2
STUDIO_VIEW_CONTENT = 1
Jesse Shapiro committed
30
STUDIO_NO_PERMISSIONS = 0
E. Kolpakov committed
31
# In addition to the above, one is always allowed to "demote" oneself to a lower role within a course, or remove oneself
32 33


34 35 36 37 38
def is_ccx_course(course_key):
    """
    Check whether the course locator maps to a CCX course; this is important
    because we don't allow access to CCX courses in Studio.
    """
Jesse Shapiro committed
39
    return isinstance(course_key, CCXLocator) or isinstance(course_key, CCXBlockUsageLocator)
40 41


42
def user_has_role(user, role):
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
    """
    Check whether this user has access to this role (either direct or implied)
    :param user:
    :param role: an AccessRole
    """
    if not user.is_active:
        return False
    # do cheapest check first even tho it's not the direct one
    if GlobalStaff().has_user(user):
        return True
    # CourseCreator is odd b/c it can be disabled via config
    if isinstance(role, CourseCreatorRole):
        # completely shut down course creation setting
        if settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
            return False
        # wide open course creation setting
        if not settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
            return True

    if role.has_user(user):
        return True
    # if not, then check inferred permissions
    if (isinstance(role, (CourseStaffRole, CourseBetaTesterRole)) and
66
            CourseInstructorRole(role.course_key).has_user(user)):
67 68 69 70
        return True
    return False


71 72 73 74 75 76 77 78 79 80 81
def get_user_permissions(user, course_key, org=None):
    """
    Get the bitmask of permissions that this user has in the given course context.
    Can also set course_key=None and pass in an org to get the user's
    permissions for that organization as a whole.
    """
    if org is None:
        org = course_key.org
        course_key = course_key.for_branch(None)
    else:
        assert course_key is None
82 83
    # No one has studio permissions for CCX courses
    if is_ccx_course(course_key):
Jesse Shapiro committed
84 85
        return STUDIO_NO_PERMISSIONS
    all_perms = STUDIO_EDIT_ROLES | STUDIO_VIEW_USERS | STUDIO_EDIT_CONTENT | STUDIO_VIEW_CONTENT
86 87 88
    # global staff, org instructors, and course instructors have all permissions:
    if GlobalStaff().has_user(user) or OrgInstructorRole(org=org).has_user(user):
        return all_perms
89
    if course_key and user_has_role(user, CourseInstructorRole(course_key)):
90 91
        return all_perms
    # Staff have all permissions except EDIT_ROLES:
92
    if OrgStaffRole(org=org).has_user(user) or (course_key and user_has_role(user, CourseStaffRole(course_key))):
93 94
        return STUDIO_VIEW_USERS | STUDIO_EDIT_CONTENT | STUDIO_VIEW_CONTENT
    # Otherwise, for libraries, users can view only:
E. Kolpakov committed
95
    if course_key and isinstance(course_key, LibraryLocator):
96
        if OrgLibraryUserRole(org=org).has_user(user) or user_has_role(user, LibraryUserRole(course_key)):
97
            return STUDIO_VIEW_USERS | STUDIO_VIEW_CONTENT
Jesse Shapiro committed
98
    return STUDIO_NO_PERMISSIONS
99 100 101


def has_studio_write_access(user, course_key):
102
    """
103
    Return True if user has studio write access to the given course.
104 105
    Note that the CMS permissions model is with respect to courses.
    There is a super-admin permissions if user.is_staff is set.
106 107 108 109 110 111
    Also, since we're unifying the user database between LMS and CAS,
    I'm presuming that the course instructor (formally known as admin)
    will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our
    queries here as INSTRUCTOR has all the rights that STAFF do.

    :param user:
112
    :param course_key: a CourseKey
113
    """
114
    return bool(STUDIO_EDIT_CONTENT & get_user_permissions(user, course_key))
115 116


117
def has_course_author_access(user, course_key):
118
    """
119
    Old name for has_studio_write_access
120
    """
121
    return has_studio_write_access(user, course_key)
122 123 124 125 126 127 128 129 130 131


def has_studio_read_access(user, course_key):
    """
    Return True iff user is allowed to view this course/library in studio.
    Will also return True if user has write access in studio (has_course_author_access)

    There is currently no such thing as read-only course access in studio, but
    there is read-only access to content libraries.
    """
132
    return bool(STUDIO_VIEW_CONTENT & get_user_permissions(user, course_key))
133 134


135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
def add_users(caller, role, *users):
    """
    The caller requests adding the given users to the role. Checks that the caller
    has sufficient authority.

    :param caller: a user
    :param role: an AccessRole
    """
    _check_caller_authority(caller, role)
    role.add_users(*users)


def remove_users(caller, role, *users):
    """
    The caller requests removing the given users from the role. Checks that the caller
    has sufficient authority.

    :param caller: a user
    :param role: an AccessRole
    """
    # can always remove self (at this layer)
    if not(len(users) == 1 and caller == users[0]):
        _check_caller_authority(caller, role)
    role.remove_users(*users)


def _check_caller_authority(caller, role):
    """
    Internal function to check whether the caller has authority to manipulate this role
    :param caller: a user
    :param role: an AccessRole
    """
167
    if not (caller.is_authenticated() and caller.is_active):
168 169 170 171 172 173 174 175
        raise PermissionDenied
    # superuser
    if GlobalStaff().has_user(caller):
        return

    if isinstance(role, (GlobalStaff, CourseCreatorRole)):
        raise PermissionDenied
    elif isinstance(role, CourseRole):  # instructors can change the roles w/in their course
176
        if not user_has_role(caller, CourseInstructorRole(role.course_key)):
177
            raise PermissionDenied