Commit e0840d2d by tlindaliu

MA-849: Change has_access return type

New classes for the return type, and changes to the has_access function and tests to make them compatible.
parent 4b4ce5f1
...@@ -9,7 +9,7 @@ from django.core.exceptions import PermissionDenied ...@@ -9,7 +9,7 @@ from django.core.exceptions import PermissionDenied
from student.roles import CourseInstructorRole, CourseStaffRole, CourseCreatorRole from student.roles import CourseInstructorRole, CourseStaffRole, CourseCreatorRole
from student.tests.factories import AdminFactory from student.tests.factories import AdminFactory
from student.auth import has_access, add_users, remove_users from student.auth import has_role, add_users, remove_users
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
...@@ -30,30 +30,30 @@ class CreatorGroupTest(TestCase): ...@@ -30,30 +30,30 @@ class CreatorGroupTest(TestCase):
Tests that CourseCreatorRole().has_user always returns True if ENABLE_CREATOR_GROUP Tests that CourseCreatorRole().has_user always returns True if ENABLE_CREATOR_GROUP
and DISABLE_COURSE_CREATION are both not turned on. and DISABLE_COURSE_CREATION are both not turned on.
""" """
self.assertTrue(has_access(self.user, CourseCreatorRole())) self.assertTrue(has_role(self.user, CourseCreatorRole()))
def test_creator_group_enabled_but_empty(self): def test_creator_group_enabled_but_empty(self):
""" Tests creator group feature on, but group empty. """ """ Tests creator group feature on, but group empty. """
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}): with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
self.assertFalse(has_access(self.user, CourseCreatorRole())) self.assertFalse(has_role(self.user, CourseCreatorRole()))
# Make user staff. This will cause CourseCreatorRole().has_user to return True. # Make user staff. This will cause CourseCreatorRole().has_user to return True.
self.user.is_staff = True self.user.is_staff = True
self.assertTrue(has_access(self.user, CourseCreatorRole())) self.assertTrue(has_role(self.user, CourseCreatorRole()))
def test_creator_group_enabled_nonempty(self): def test_creator_group_enabled_nonempty(self):
""" Tests creator group feature on, user added. """ """ Tests creator group feature on, user added. """
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}): with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
add_users(self.admin, CourseCreatorRole(), self.user) add_users(self.admin, CourseCreatorRole(), self.user)
self.assertTrue(has_access(self.user, CourseCreatorRole())) self.assertTrue(has_role(self.user, CourseCreatorRole()))
# check that a user who has not been added to the group still returns false # check that a user who has not been added to the group still returns false
user_not_added = User.objects.create_user('testuser2', 'test+courses2@edx.org', 'foo2') user_not_added = User.objects.create_user('testuser2', 'test+courses2@edx.org', 'foo2')
self.assertFalse(has_access(user_not_added, CourseCreatorRole())) self.assertFalse(has_role(user_not_added, CourseCreatorRole()))
# remove first user from the group and verify that CourseCreatorRole().has_user now returns false # remove first user from the group and verify that CourseCreatorRole().has_user now returns false
remove_users(self.admin, CourseCreatorRole(), self.user) remove_users(self.admin, CourseCreatorRole(), self.user)
self.assertFalse(has_access(self.user, CourseCreatorRole())) self.assertFalse(has_role(self.user, CourseCreatorRole()))
def test_course_creation_disabled(self): def test_course_creation_disabled(self):
""" Tests that the COURSE_CREATION_DISABLED flag overrides course creator group settings. """ """ Tests that the COURSE_CREATION_DISABLED flag overrides course creator group settings. """
...@@ -63,15 +63,15 @@ class CreatorGroupTest(TestCase): ...@@ -63,15 +63,15 @@ class CreatorGroupTest(TestCase):
add_users(self.admin, CourseCreatorRole(), self.user) add_users(self.admin, CourseCreatorRole(), self.user)
# DISABLE_COURSE_CREATION overrides (user is not marked as staff). # DISABLE_COURSE_CREATION overrides (user is not marked as staff).
self.assertFalse(has_access(self.user, CourseCreatorRole())) self.assertFalse(has_role(self.user, CourseCreatorRole()))
# Mark as staff. Now CourseCreatorRole().has_user returns true. # Mark as staff. Now CourseCreatorRole().has_user returns true.
self.user.is_staff = True self.user.is_staff = True
self.assertTrue(has_access(self.user, CourseCreatorRole())) self.assertTrue(has_role(self.user, CourseCreatorRole()))
# Remove user from creator group. CourseCreatorRole().has_user still returns true because is_staff=True # Remove user from creator group. CourseCreatorRole().has_user still returns true because is_staff=True
remove_users(self.admin, CourseCreatorRole(), self.user) remove_users(self.admin, CourseCreatorRole(), self.user)
self.assertTrue(has_access(self.user, CourseCreatorRole())) self.assertTrue(has_role(self.user, CourseCreatorRole()))
def test_add_user_not_authenticated(self): def test_add_user_not_authenticated(self):
""" """
...@@ -84,7 +84,7 @@ class CreatorGroupTest(TestCase): ...@@ -84,7 +84,7 @@ class CreatorGroupTest(TestCase):
anonymous_user = AnonymousUser() anonymous_user = AnonymousUser()
role = CourseCreatorRole() role = CourseCreatorRole()
add_users(self.admin, role, anonymous_user) add_users(self.admin, role, anonymous_user)
self.assertFalse(has_access(anonymous_user, role)) self.assertFalse(has_role(anonymous_user, role))
def test_add_user_not_active(self): def test_add_user_not_active(self):
""" """
...@@ -96,7 +96,7 @@ class CreatorGroupTest(TestCase): ...@@ -96,7 +96,7 @@ class CreatorGroupTest(TestCase):
): ):
self.user.is_active = False self.user.is_active = False
add_users(self.admin, CourseCreatorRole(), self.user) add_users(self.admin, CourseCreatorRole(), self.user)
self.assertFalse(has_access(self.user, CourseCreatorRole())) self.assertFalse(has_role(self.user, CourseCreatorRole()))
def test_add_user_to_group_requires_staff_access(self): def test_add_user_to_group_requires_staff_access(self):
with self.assertRaises(PermissionDenied): with self.assertRaises(PermissionDenied):
...@@ -150,15 +150,15 @@ class CourseGroupTest(TestCase): ...@@ -150,15 +150,15 @@ class CourseGroupTest(TestCase):
Tests adding user to course group (happy path). Tests adding user to course group (happy path).
""" """
# Create groups for a new course (and assign instructor role to the creator). # Create groups for a new course (and assign instructor role to the creator).
self.assertFalse(has_access(self.creator, CourseInstructorRole(self.course_key))) self.assertFalse(has_role(self.creator, CourseInstructorRole(self.course_key)))
add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator) add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator) add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)
self.assertTrue(has_access(self.creator, CourseInstructorRole(self.course_key))) self.assertTrue(has_role(self.creator, CourseInstructorRole(self.course_key)))
# Add another user to the staff role. # Add another user to the staff role.
self.assertFalse(has_access(self.staff, CourseStaffRole(self.course_key))) self.assertFalse(has_role(self.staff, CourseStaffRole(self.course_key)))
add_users(self.creator, CourseStaffRole(self.course_key), self.staff) add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
self.assertTrue(has_access(self.staff, CourseStaffRole(self.course_key))) self.assertTrue(has_role(self.staff, CourseStaffRole(self.course_key)))
def test_add_user_to_course_group_permission_denied(self): def test_add_user_to_course_group_permission_denied(self):
""" """
...@@ -177,13 +177,13 @@ class CourseGroupTest(TestCase): ...@@ -177,13 +177,13 @@ class CourseGroupTest(TestCase):
add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator) add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)
add_users(self.creator, CourseStaffRole(self.course_key), self.staff) add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
self.assertTrue(has_access(self.staff, CourseStaffRole(self.course_key))) self.assertTrue(has_role(self.staff, CourseStaffRole(self.course_key)))
remove_users(self.creator, CourseStaffRole(self.course_key), self.staff) remove_users(self.creator, CourseStaffRole(self.course_key), self.staff)
self.assertFalse(has_access(self.staff, CourseStaffRole(self.course_key))) self.assertFalse(has_role(self.staff, CourseStaffRole(self.course_key)))
remove_users(self.creator, CourseInstructorRole(self.course_key), self.creator) remove_users(self.creator, CourseInstructorRole(self.course_key), self.creator)
self.assertFalse(has_access(self.creator, CourseInstructorRole(self.course_key))) self.assertFalse(has_role(self.creator, CourseInstructorRole(self.course_key)))
def test_remove_user_from_course_group_permission_denied(self): def test_remove_user_from_course_group_permission_denied(self):
""" """
......
...@@ -316,5 +316,5 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase): ...@@ -316,5 +316,5 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
""" """
Test for instructor access Test for instructor access
""" """
ret_val = has_instructor_access_for_class(self.instructor, self.course.id) ret_val = bool(has_instructor_access_for_class(self.instructor, self.course.id))
self.assertEquals(ret_val, True) self.assertEquals(ret_val, True)
...@@ -22,7 +22,7 @@ def has_instructor_access_for_class(user, course_id): ...@@ -22,7 +22,7 @@ def has_instructor_access_for_class(user, course_id):
""" """
course = get_course_with_access(user, 'staff', course_id, depth=None) course = get_course_with_access(user, 'staff', course_id, depth=None)
return has_access(user, 'staff', course) return bool(has_access(user, 'staff', course))
def all_sequential_open_distrib(request, course_id): def all_sequential_open_distrib(request, course_id):
......
...@@ -83,9 +83,11 @@ class CourseViewMixin(object): ...@@ -83,9 +83,11 @@ class CourseViewMixin(object):
Determines if the user is staff or an instructor for the course. Determines if the user is staff or an instructor for the course.
Always returns True if DEBUG mode is enabled. Always returns True if DEBUG mode is enabled.
""" """
return (settings.DEBUG return bool(
or has_access(user, CourseStaffRole.ROLE, course) settings.DEBUG
or has_access(user, CourseInstructorRole.ROLE, course)) or has_access(user, CourseStaffRole.ROLE, course)
or has_access(user, CourseInstructorRole.ROLE, course)
)
def check_course_permissions(self, user, course): def check_course_permissions(self, user, course):
""" """
......
...@@ -23,8 +23,10 @@ from opaque_keys.edx.keys import CourseKey, UsageKey ...@@ -23,8 +23,10 @@ from opaque_keys.edx.keys import CourseKey, UsageKey
from xblock.core import XBlock from xblock.core import XBlock
from xmodule.course_module import ( from xmodule.course_module import (
CourseDescriptor, CATALOG_VISIBILITY_CATALOG_AND_ABOUT, CourseDescriptor,
CATALOG_VISIBILITY_ABOUT) CATALOG_VISIBILITY_CATALOG_AND_ABOUT,
CATALOG_VISIBILITY_ABOUT,
)
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT
from xmodule.split_test_module import get_split_user_partitions from xmodule.split_test_module import get_split_user_partitions
...@@ -37,8 +39,12 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi ...@@ -37,8 +39,12 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi
from student import auth from student import auth
from student.models import CourseEnrollmentAllowed from student.models import CourseEnrollmentAllowed
from student.roles import ( from student.roles import (
GlobalStaff, CourseStaffRole, CourseInstructorRole, CourseBetaTesterRole,
OrgStaffRole, OrgInstructorRole, CourseBetaTesterRole CourseInstructorRole,
CourseStaffRole,
GlobalStaff,
OrgInstructorRole,
OrgStaffRole,
) )
from util.milestones_helpers import ( from util.milestones_helpers import (
get_pre_requisite_courses_not_completed, get_pre_requisite_courses_not_completed,
...@@ -48,7 +54,17 @@ from ccx_keys.locator import CCXLocator ...@@ -48,7 +54,17 @@ from ccx_keys.locator import CCXLocator
import dogstats_wrapper as dog_stats_api import dogstats_wrapper as dog_stats_api
from courseware.access_response import (
AccessResponse,
MilestoneError,
MobileAvailabilityError,
StartDateError,
VisibilityError,
)
DEBUG_ACCESS = False DEBUG_ACCESS = False
ACCESS_GRANTED = AccessResponse(True)
ACCESS_DENIED = AccessResponse(False)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -86,8 +102,8 @@ def has_access(user, action, obj, course_key=None): ...@@ -86,8 +102,8 @@ def has_access(user, action, obj, course_key=None):
Required when accessing anything other than a CourseDescriptor, 'global', Required when accessing anything other than a CourseDescriptor, 'global',
or a location with category 'course' or a location with category 'course'
Returns a bool. It is up to the caller to actually deny access in a way Returns an AccessResponse object. It is up to the caller to actually
that makes sense in context. deny access in a way that makes sense in context.
""" """
# Just in case user is passed in as None, make them anonymous # Just in case user is passed in as None, make them anonymous
if not user: if not user:
...@@ -146,13 +162,18 @@ def _can_access_descriptor_with_start_date(user, descriptor, course_key): # pyl ...@@ -146,13 +162,18 @@ def _can_access_descriptor_with_start_date(user, descriptor, course_key): # pyl
Arguments: Arguments:
user (User): the user whose descriptor access we are checking. user (User): the user whose descriptor access we are checking.
descriptor (AType): the descriptor for which we are checking access. descriptor (AType): the descriptor for which we are checking access,
where AType is any descriptor that has the attributes .location and where AType is CourseDescriptor, CourseOverview, or any other class
.days_early_for_beta that represents a descriptor and has the attributes .location, .id,
.start, and .days_early_for_beta.
Returns:
AccessResponse: The result of this access check. Possible results are
ACCESS_GRANTED or a StartDateError.
""" """
start_dates_disabled = settings.FEATURES['DISABLE_START_DATES'] start_dates_disabled = settings.FEATURES['DISABLE_START_DATES']
if start_dates_disabled and not is_masquerading_as_student(user, course_key): if start_dates_disabled and not is_masquerading_as_student(user, course_key):
return True return ACCESS_GRANTED
else: else:
now = datetime.now(UTC()) now = datetime.now(UTC())
effective_start = _adjust_start_date_for_beta_testers( effective_start = _adjust_start_date_for_beta_testers(
...@@ -160,11 +181,14 @@ def _can_access_descriptor_with_start_date(user, descriptor, course_key): # pyl ...@@ -160,11 +181,14 @@ def _can_access_descriptor_with_start_date(user, descriptor, course_key): # pyl
descriptor, descriptor,
course_key=course_key course_key=course_key
) )
return ( if (
descriptor.start is None descriptor.start is None
or now > effective_start or now > effective_start
or in_preview_mode() or in_preview_mode()
) ):
return ACCESS_GRANTED
return StartDateError(descriptor.start)
def _can_view_courseware_with_prerequisites(user, course): # pylint: disable=invalid-name def _can_view_courseware_with_prerequisites(user, course): # pylint: disable=invalid-name
...@@ -177,15 +201,22 @@ def _can_view_courseware_with_prerequisites(user, course): # pylint: disable=in ...@@ -177,15 +201,22 @@ def _can_view_courseware_with_prerequisites(user, course): # pylint: disable=in
Arguments: Arguments:
user (User): the user whose course access we are checking. user (User): the user whose course access we are checking.
course (AType): the course for which we are checking access. course (AType): the course for which we are checking access.
where AType is CourseDescriptor, CourseOverview, or any other class that where AType is CourseDescriptor, CourseOverview, or any other
represents a course and has the attributes .location and .id. class that represents a course and has the attributes .location
and .id.
""" """
def _is_prerequisites_disabled():
"""
Checks if prerequisites are disabled in the settings.
"""
return ACCESS_DENIED if settings.FEATURES['ENABLE_PREREQUISITE_COURSES'] else ACCESS_GRANTED
return ( return (
not settings.FEATURES['ENABLE_PREREQUISITE_COURSES'] _is_prerequisites_disabled()
or _has_staff_access_to_descriptor(user, course, course.id) or _has_staff_access_to_descriptor(user, course, course.id)
or not course.pre_requisite_courses
or user.is_anonymous() or user.is_anonymous()
or not get_pre_requisite_courses_not_completed(user, [course.id]) or _has_fulfilled_prerequisites(user, [course.id])
) )
...@@ -209,7 +240,7 @@ def _can_load_course_on_mobile(user, course): ...@@ -209,7 +240,7 @@ def _can_load_course_on_mobile(user, course):
is_mobile_available_for_user(user, course) and is_mobile_available_for_user(user, course) and
( (
_has_staff_access_to_descriptor(user, course, course.id) or _has_staff_access_to_descriptor(user, course, course.id) or
not any_unfulfilled_milestones(course.id, user.id) _has_fulfilled_all_milestones(user, course.id)
) )
) )
...@@ -275,19 +306,19 @@ def _has_access_course_desc(user, action, course): ...@@ -275,19 +306,19 @@ def _has_access_course_desc(user, action, course):
# (sorry that it's confusing :( ) # (sorry that it's confusing :( )
if user is not None and user.is_authenticated() and CourseEnrollmentAllowed: if user is not None and user.is_authenticated() and CourseEnrollmentAllowed:
if CourseEnrollmentAllowed.objects.filter(email=user.email, course_id=course.id): if CourseEnrollmentAllowed.objects.filter(email=user.email, course_id=course.id):
return True return ACCESS_GRANTED
if _has_staff_access_to_descriptor(user, course, course.id): if _has_staff_access_to_descriptor(user, course, course.id):
return True return ACCESS_GRANTED
# Invitation_only doesn't apply to CourseEnrollmentAllowed or has_staff_access_access # Invitation_only doesn't apply to CourseEnrollmentAllowed or has_staff_access_access
if course.invitation_only: if course.invitation_only:
debug("Deny: invitation only") debug("Deny: invitation only")
return False return ACCESS_DENIED
if reg_method_ok and start < now < end: if reg_method_ok and start < now < end:
debug("Allow: in enrollment period") debug("Allow: in enrollment period")
return True return ACCESS_GRANTED
def see_exists(): def see_exists():
""" """
...@@ -314,10 +345,10 @@ def _has_access_course_desc(user, action, course): ...@@ -314,10 +345,10 @@ def _has_access_course_desc(user, action, course):
# seen by non-staff # seen by non-staff
if course.ispublic: if course.ispublic:
debug("Allow: ACCESS_REQUIRE_STAFF_FOR_COURSE and ispublic") debug("Allow: ACCESS_REQUIRE_STAFF_FOR_COURSE and ispublic")
return True return ACCESS_GRANTED
return _has_staff_access_to_descriptor(user, course, course.id) return _has_staff_access_to_descriptor(user, course, course.id)
return can_enroll() or can_load() return ACCESS_GRANTED if (can_enroll() or can_load()) else ACCESS_DENIED
def can_see_in_catalog(): def can_see_in_catalog():
""" """
...@@ -326,8 +357,8 @@ def _has_access_course_desc(user, action, course): ...@@ -326,8 +357,8 @@ def _has_access_course_desc(user, action, course):
but also allow course staff to see this. but also allow course staff to see this.
""" """
return ( return (
course.catalog_visibility == CATALOG_VISIBILITY_CATALOG_AND_ABOUT or _has_catalog_visibility(course, CATALOG_VISIBILITY_CATALOG_AND_ABOUT)
_has_staff_access_to_descriptor(user, course, course.id) or _has_staff_access_to_descriptor(user, course, course.id)
) )
def can_see_about_page(): def can_see_about_page():
...@@ -337,9 +368,9 @@ def _has_access_course_desc(user, action, course): ...@@ -337,9 +368,9 @@ def _has_access_course_desc(user, action, course):
but also allow course staff to see this. but also allow course staff to see this.
""" """
return ( return (
course.catalog_visibility == CATALOG_VISIBILITY_CATALOG_AND_ABOUT or _has_catalog_visibility(course, CATALOG_VISIBILITY_CATALOG_AND_ABOUT)
course.catalog_visibility == CATALOG_VISIBILITY_ABOUT or or _has_catalog_visibility(course, CATALOG_VISIBILITY_ABOUT)
_has_staff_access_to_descriptor(user, course, course.id) or _has_staff_access_to_descriptor(user, course, course.id)
) )
checkers = { checkers = {
...@@ -370,11 +401,15 @@ def _can_load_course_overview(user, course_overview): ...@@ -370,11 +401,15 @@ def _can_load_course_overview(user, course_overview):
The user doesn't have to be enrolled in the course in order to have load The user doesn't have to be enrolled in the course in order to have load
load access. load access.
""" """
return ( response = (
not course_overview.visible_to_staff_only _visible_to_nonstaff_users(course_overview)
and _can_access_descriptor_with_start_date(user, course_overview, course_overview.id) and _can_access_descriptor_with_start_date(user, course_overview, course_overview.id)
) or _has_staff_access_to_descriptor(user, course_overview, course_overview.id) )
return (
ACCESS_GRANTED if (response or _has_staff_access_to_descriptor(user, course_overview, course_overview.id))
else response
)
_COURSE_OVERVIEW_CHECKERS = { _COURSE_OVERVIEW_CHECKERS = {
'load': _can_load_course_overview, 'load': _can_load_course_overview,
...@@ -432,7 +467,7 @@ def _has_group_access(descriptor, user, course_key): ...@@ -432,7 +467,7 @@ def _has_group_access(descriptor, user, course_key):
# Short-circuit the process, since there are no defined user partitions that are not # Short-circuit the process, since there are no defined user partitions that are not
# user_partitions used by the split_test module. The split_test module handles its own access # user_partitions used by the split_test module. The split_test module handles its own access
# via updating the children of the split_test module. # via updating the children of the split_test module.
return True return ACCESS_GRANTED
# use merged_group_access which takes group access on the block's # use merged_group_access which takes group access on the block's
# parents / ancestors into account # parents / ancestors into account
...@@ -441,20 +476,20 @@ def _has_group_access(descriptor, user, course_key): ...@@ -441,20 +476,20 @@ def _has_group_access(descriptor, user, course_key):
# partition's group list excludes all students. # partition's group list excludes all students.
if False in merged_access.values(): if False in merged_access.values():
log.warning("Group access check excludes all students, access will be denied.", exc_info=True) log.warning("Group access check excludes all students, access will be denied.", exc_info=True)
return False return ACCESS_DENIED
# resolve the partition IDs in group_access to actual # resolve the partition IDs in group_access to actual
# partition objects, skipping those which contain empty group directives. # partition objects, skipping those which contain empty group directives.
# if a referenced partition could not be found, access will be denied. # if a referenced partition could not be found, access will be denied.
try: try:
partitions = [ partitions = [
descriptor._get_user_partition(partition_id) # pylint:disable=protected-access descriptor._get_user_partition(partition_id) # pylint: disable=protected-access
for partition_id, group_ids in merged_access.items() for partition_id, group_ids in merged_access.items()
if group_ids is not None if group_ids is not None
] ]
except NoSuchUserPartitionError: except NoSuchUserPartitionError:
log.warning("Error looking up user partition, access will be denied.", exc_info=True) log.warning("Error looking up user partition, access will be denied.", exc_info=True)
return False return ACCESS_DENIED
# next resolve the group IDs specified within each partition # next resolve the group IDs specified within each partition
partition_groups = [] partition_groups = []
...@@ -468,7 +503,7 @@ def _has_group_access(descriptor, user, course_key): ...@@ -468,7 +503,7 @@ def _has_group_access(descriptor, user, course_key):
partition_groups.append((partition, groups)) partition_groups.append((partition, groups))
except NoSuchUserPartitionGroupError: except NoSuchUserPartitionGroupError:
log.warning("Error looking up referenced user partition group, access will be denied.", exc_info=True) log.warning("Error looking up referenced user partition group, access will be denied.", exc_info=True)
return False return ACCESS_DENIED
# look up the user's group for each partition # look up the user's group for each partition
user_groups = {} user_groups = {}
...@@ -482,10 +517,10 @@ def _has_group_access(descriptor, user, course_key): ...@@ -482,10 +517,10 @@ def _has_group_access(descriptor, user, course_key):
# finally: check that the user has a satisfactory group assignment # finally: check that the user has a satisfactory group assignment
# for each partition. # for each partition.
if not all(user_groups.get(partition.id) in groups for partition, groups in partition_groups): if not all(user_groups.get(partition.id) in groups for partition, groups in partition_groups):
return False return ACCESS_DENIED
# all checks passed. # all checks passed.
return True return ACCESS_GRANTED
def _has_access_descriptor(user, action, descriptor, course_key=None): def _has_access_descriptor(user, action, descriptor, course_key=None):
...@@ -507,14 +542,20 @@ def _has_access_descriptor(user, action, descriptor, course_key=None): ...@@ -507,14 +542,20 @@ def _has_access_descriptor(user, action, descriptor, course_key=None):
students to see modules. If not, views should check the course, so we students to see modules. If not, views should check the course, so we
don't have to hit the enrollments table on every module load. don't have to hit the enrollments table on every module load.
""" """
return ( response = (
not descriptor.visible_to_staff_only _visible_to_nonstaff_users(descriptor)
and _has_group_access(descriptor, user, course_key) and _has_group_access(descriptor, user, course_key)
and ( and
'detached' in descriptor._class_tags # pylint: disable=protected-access (
_has_detached_class_tag(descriptor)
or _can_access_descriptor_with_start_date(user, descriptor, course_key) or _can_access_descriptor_with_start_date(user, descriptor, course_key)
) )
) or _has_staff_access_to_descriptor(user, descriptor, course_key) )
return (
ACCESS_GRANTED if (response or _has_staff_access_to_descriptor(user, descriptor, course_key))
else response
)
checkers = { checkers = {
'load': can_load, 'load': can_load,
...@@ -592,10 +633,13 @@ def _has_access_string(user, action, perm): ...@@ -592,10 +633,13 @@ def _has_access_string(user, action, perm):
""" """
def check_staff(): def check_staff():
"""
Checks for staff access
"""
if perm != 'global': if perm != 'global':
debug("Deny: invalid permission '%s'", perm) debug("Deny: invalid permission '%s'", perm)
return False return ACCESS_DENIED
return GlobalStaff().has_user(user) return ACCESS_GRANTED if GlobalStaff().has_user(user) else ACCESS_DENIED
checkers = { checkers = {
'staff': check_staff 'staff': check_staff
...@@ -674,38 +718,37 @@ def _has_staff_access_to_location(user, location, course_key=None): ...@@ -674,38 +718,37 @@ def _has_staff_access_to_location(user, location, course_key=None):
def _has_access_to_course(user, access_level, course_key): def _has_access_to_course(user, access_level, course_key):
''' """
Returns True if the given user has access_level (= staff or Returns True if the given user has access_level (= staff or
instructor) access to the course with the given course_key. instructor) access to the course with the given course_key.
This ensures the user is authenticated and checks if global staff or has This ensures the user is authenticated and checks if global staff or has
staff / instructor access. staff / instructor access.
access_level = string, either "staff" or "instructor" access_level = string, either "staff" or "instructor"
''' """
if user is None or (not user.is_authenticated()): if user is None or (not user.is_authenticated()):
debug("Deny: no user or anon user") debug("Deny: no user or anon user")
return False return ACCESS_DENIED
if is_masquerading_as_student(user, course_key): if is_masquerading_as_student(user, course_key):
return False return ACCESS_DENIED
if GlobalStaff().has_user(user): if GlobalStaff().has_user(user):
debug("Allow: user.is_staff") debug("Allow: user.is_staff")
return True return ACCESS_GRANTED
if access_level not in ('staff', 'instructor'): if access_level not in ('staff', 'instructor'):
log.debug("Error in access._has_access_to_course access_level=%s unknown", access_level) log.debug("Error in access._has_access_to_course access_level=%s unknown", access_level)
debug("Deny: unknown access level") debug("Deny: unknown access level")
return False return ACCESS_DENIED
staff_access = ( staff_access = (
CourseStaffRole(course_key).has_user(user) or CourseStaffRole(course_key).has_user(user) or
OrgStaffRole(course_key.org).has_user(user) OrgStaffRole(course_key.org).has_user(user)
) )
if staff_access and access_level == 'staff': if staff_access and access_level == 'staff':
debug("Allow: user has course staff access") debug("Allow: user has course staff access")
return True return ACCESS_GRANTED
instructor_access = ( instructor_access = (
CourseInstructorRole(course_key).has_user(user) or CourseInstructorRole(course_key).has_user(user) or
...@@ -714,10 +757,10 @@ def _has_access_to_course(user, access_level, course_key): ...@@ -714,10 +757,10 @@ def _has_access_to_course(user, access_level, course_key):
if instructor_access and access_level in ('staff', 'instructor'): if instructor_access and access_level in ('staff', 'instructor'):
debug("Allow: user has course instructor access") debug("Allow: user has course instructor access")
return True return ACCESS_GRANTED
debug("Deny: user did not have correct access") debug("Deny: user did not have correct access")
return False return ACCESS_DENIED
def _has_instructor_access_to_descriptor(user, descriptor, course_key): # pylint: disable=invalid-name def _has_instructor_access_to_descriptor(user, descriptor, course_key): # pylint: disable=invalid-name
...@@ -738,6 +781,64 @@ def _has_staff_access_to_descriptor(user, descriptor, course_key): ...@@ -738,6 +781,64 @@ def _has_staff_access_to_descriptor(user, descriptor, course_key):
return _has_staff_access_to_location(user, descriptor.location, course_key) return _has_staff_access_to_location(user, descriptor.location, course_key)
def _visible_to_nonstaff_users(descriptor):
"""
Returns if the object is visible to nonstaff users.
Arguments:
descriptor: object to check
"""
return VisibilityError() if descriptor.visible_to_staff_only else ACCESS_GRANTED
def _has_detached_class_tag(descriptor):
"""
Returns if the given descriptor's type is marked as detached.
Arguments:
descriptor: object to check
"""
return ACCESS_GRANTED if 'detached' in descriptor._class_tags else ACCESS_DENIED # pylint: disable=protected-access
def _has_fulfilled_all_milestones(user, course_id):
"""
Returns whether the given user has fulfilled all milestones for the
given course.
Arguments:
course_id: ID of the course to check
user_id: ID of the user to check
"""
return MilestoneError() if any_unfulfilled_milestones(course_id, user.id) else ACCESS_GRANTED
def _has_fulfilled_prerequisites(user, course_id):
"""
Returns whether the given user has fulfilled all prerequisites for the
given course.
Arguments:
user: user to check
course_id: ID of the course to check
"""
return MilestoneError() if get_pre_requisite_courses_not_completed(user, course_id) else ACCESS_GRANTED
def _has_catalog_visibility(course, visibility_type):
"""
Returns whether the given course has the given visibility type
"""
return ACCESS_GRANTED if course.catalog_visibility == visibility_type else ACCESS_DENIED
def _is_descriptor_mobile_available(descriptor):
"""
Returns if descriptor is available on mobile.
"""
return ACCESS_GRANTED if descriptor.mobile_available else MobileAvailabilityError()
def is_mobile_available_for_user(user, descriptor): def is_mobile_available_for_user(user, descriptor):
""" """
Returns whether the given course is mobile_available for the given user. Returns whether the given course is mobile_available for the given user.
...@@ -747,10 +848,11 @@ def is_mobile_available_for_user(user, descriptor): ...@@ -747,10 +848,11 @@ def is_mobile_available_for_user(user, descriptor):
Arguments: Arguments:
descriptor (CourseDescriptor|CourseOverview): course or overview of course in question descriptor (CourseDescriptor|CourseOverview): course or overview of course in question
""" """
return ( return (
descriptor.mobile_available or auth.has_access(user, CourseBetaTesterRole(descriptor.id))
auth.has_access(user, CourseBetaTesterRole(descriptor.id)) or or _has_staff_access_to_descriptor(user, descriptor, descriptor.id)
_has_staff_access_to_descriptor(user, descriptor, descriptor.id) or _is_descriptor_mobile_available(descriptor)
) )
......
"""
This file contains all the classes used by has_access for error handling
"""
from django.utils.translation import ugettext as _
class AccessResponse(object):
"""Class that represents a response from a has_access permission check."""
def __init__(self, has_access, error_code=None, developer_message=None, user_message=None):
"""
Creates an AccessResponse object.
Arguments:
has_access (bool): if the user is granted access or not
error_code (String): optional - default is None. Unique identifier
for the specific type of error
developer_message (String): optional - default is None. Message
to show the developer
user_message (String): optional - default is None. Message to
show the user
"""
self.has_access = has_access
self.error_code = error_code
self.developer_message = developer_message
self.user_message = user_message
if has_access:
assert error_code is None
def __nonzero__(self):
"""
Overrides bool().
Allows for truth value testing of AccessResponse objects, so callers
who do not need the specific error information can check if access
is granted.
Returns:
bool: whether or not access is granted
"""
return self.has_access
def to_json(self):
"""
Creates a serializable JSON representation of an AccessResponse object.
Returns:
dict: JSON representation
"""
return {
"has_access": self.has_access,
"error_code": self.error_code,
"developer_message": self.developer_message,
"user_message": self.user_message
}
class AccessError(AccessResponse):
"""
Class that holds information about the error in the case of an access
denial in has_access. Contains the error code, user and developer
messages. Subclasses represent specific errors.
"""
def __init__(self, error_code, developer_message, user_message):
"""
Creates an AccessError object.
An AccessError object represents an AccessResponse where access is
denied (has_access is False).
Arguments:
error_code (String): unique identifier for the specific type of
error developer_message (String): message to show the developer
user_message (String): message to show the user
"""
super(AccessError, self).__init__(False, error_code, developer_message, user_message)
class StartDateError(AccessError):
"""
Access denied because the course has not started yet and the user
is not staff
"""
def __init__(self, start_date):
error_code = "course_not_started"
developer_message = "Course does not start until {}".format(start_date)
user_message = _("Course does not start until {}" # pylint: disable=translation-of-non-string
.format("{:%B %d, %Y}".format(start_date)))
super(StartDateError, self).__init__(error_code, developer_message, user_message)
class MilestoneError(AccessError):
"""
Access denied because the user has unfulfilled milestones
"""
def __init__(self):
error_code = "unfulfilled_milestones"
developer_message = "User has unfulfilled milestones"
user_message = _("You have unfulfilled milestones")
super(MilestoneError, self).__init__(error_code, developer_message, user_message)
class VisibilityError(AccessError):
"""
Access denied because the user does have the correct role to view this
course.
"""
def __init__(self):
error_code = "not_visible_to_user"
developer_message = "Course is not visible to this user"
user_message = _("You do not have access to this course")
super(VisibilityError, self).__init__(error_code, developer_message, user_message)
class MobileAvailabilityError(AccessError):
"""
Access denied because the course is not available on mobile for the user
"""
def __init__(self):
error_code = "mobile_unavailable"
developer_message = "Course is not available on mobile for this user"
user_message = _("You do not have access to this course on a mobile device")
super(MobileAvailabilityError, self).__init__(error_code, developer_message, user_message)
...@@ -605,11 +605,11 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to ...@@ -605,11 +605,11 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
# the result would always be "False". # the result would always be "False".
masquerade_settings = user.real_user.masquerade_settings masquerade_settings = user.real_user.masquerade_settings
del user.real_user.masquerade_settings del user.real_user.masquerade_settings
instructor_access = has_access(user.real_user, 'instructor', descriptor, course_id) instructor_access = bool(has_access(user.real_user, 'instructor', descriptor, course_id))
user.real_user.masquerade_settings = masquerade_settings user.real_user.masquerade_settings = masquerade_settings
else: else:
staff_access = has_access(user, 'staff', descriptor, course_id) staff_access = has_access(user, 'staff', descriptor, course_id)
instructor_access = has_access(user, 'instructor', descriptor, course_id) instructor_access = bool(has_access(user, 'instructor', descriptor, course_id))
if staff_access: if staff_access:
block_wrappers.append(partial(add_staff_markup, user, instructor_access, disable_staff_debug_info)) block_wrappers.append(partial(add_staff_markup, user, instructor_access, disable_staff_debug_info))
...@@ -629,7 +629,7 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to ...@@ -629,7 +629,7 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access field_data = LmsFieldData(descriptor._field_data, student_data) # pylint: disable=protected-access
user_is_staff = has_access(user, u'staff', descriptor.location, course_id) user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id))
system = LmsModuleSystem( system = LmsModuleSystem(
track_function=track_function, track_function=track_function,
...@@ -703,7 +703,7 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to ...@@ -703,7 +703,7 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
) )
system.set(u'user_is_staff', user_is_staff) system.set(u'user_is_staff', user_is_staff)
system.set(u'user_is_admin', has_access(user, u'staff', 'global')) system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global')))
system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user)) system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user))
system.set(u'days_early_for_beta', getattr(descriptor, 'days_early_for_beta')) system.set(u'days_early_for_beta', getattr(descriptor, 'days_early_for_beta'))
......
...@@ -20,7 +20,7 @@ class EnrolledTab(CourseTab): ...@@ -20,7 +20,7 @@ class EnrolledTab(CourseTab):
def is_enabled(cls, course, user=None): def is_enabled(cls, course, user=None):
if user is None: if user is None:
return True return True
return CourseEnrollment.is_enrolled(user, course.id) or has_access(user, 'staff', course, course.id) return bool(CourseEnrollment.is_enrolled(user, course.id) or has_access(user, 'staff', course, course.id))
class CoursewareTab(EnrolledTab): class CoursewareTab(EnrolledTab):
......
# -*- coding: utf-8 -*-
"""
Test the access control framework
"""
import datetime import datetime
import ddt import ddt
import itertools import itertools
...@@ -10,18 +14,29 @@ from nose.plugins.attrib import attr ...@@ -10,18 +14,29 @@ from nose.plugins.attrib import attr
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
import courseware.access as access import courseware.access as access
import courseware.access_response as access_response
from courseware.masquerade import CourseMasquerade from courseware.masquerade import CourseMasquerade
from courseware.tests.factories import UserFactory, StaffFactory, InstructorFactory, BetaTesterFactory from courseware.tests.factories import (
BetaTesterFactory,
GlobalStaffFactory,
InstructorFactory,
StaffFactory,
UserFactory,
)
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.tests.factories import AnonymousUserFactory, CourseEnrollmentAllowedFactory, CourseEnrollmentFactory from student.tests.factories import (
AnonymousUserFactory,
CourseEnrollmentAllowedFactory,
CourseEnrollmentFactory,
)
from xmodule.course_module import ( from xmodule.course_module import (
CATALOG_VISIBILITY_CATALOG_AND_ABOUT, CATALOG_VISIBILITY_ABOUT, CATALOG_VISIBILITY_CATALOG_AND_ABOUT,
CATALOG_VISIBILITY_NONE CATALOG_VISIBILITY_ABOUT,
CATALOG_VISIBILITY_NONE,
) )
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from util.milestones_helpers import fulfill_course_milestone
from util.milestones_helpers import ( from util.milestones_helpers import (
set_prerequisite_courses, set_prerequisite_courses,
...@@ -34,26 +49,36 @@ from util.milestones_helpers import ( ...@@ -34,26 +49,36 @@ from util.milestones_helpers import (
@attr('shard_1') @attr('shard_1')
@ddt.ddt
class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
""" """
Tests for the various access controls on the student dashboard Tests for the various access controls on the student dashboard
""" """
TOMORROW = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
YESTERDAY = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
def setUp(self): def setUp(self):
super(AccessTestCase, self).setUp() super(AccessTestCase, self).setUp()
course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.course = course_key.make_usage_key('course', course_key.run) self.course = course_key.make_usage_key('course', course_key.run)
self.anonymous_user = AnonymousUserFactory() self.anonymous_user = AnonymousUserFactory()
self.beta_user = BetaTesterFactory(course_key=self.course.course_key)
self.student = UserFactory() self.student = UserFactory()
self.global_staff = UserFactory(is_staff=True) self.global_staff = UserFactory(is_staff=True)
self.course_staff = StaffFactory(course_key=self.course.course_key) self.course_staff = StaffFactory(course_key=self.course.course_key)
self.course_instructor = InstructorFactory(course_key=self.course.course_key) self.course_instructor = InstructorFactory(course_key=self.course.course_key)
self.staff = GlobalStaffFactory()
def verify_access(self, mock_unit, student_should_have_access): def verify_access(self, mock_unit, student_should_have_access, expected_error_type=None):
""" Verify the expected result from _has_access_descriptor """ """ Verify the expected result from _has_access_descriptor """
self.assertEqual( response = access._has_access_descriptor(self.anonymous_user, 'load',
student_should_have_access, mock_unit, course_key=self.course.course_key)
access._has_access_descriptor(self.anonymous_user, 'load', mock_unit, course_key=self.course.course_key) self.assertEqual(student_should_have_access, bool(response))
)
if expected_error_type is not None:
self.assertIsInstance(response, expected_error_type)
self.assertIsNotNone(response.to_json()['error_code'])
self.assertTrue( self.assertTrue(
access._has_access_descriptor(self.course_staff, 'load', mock_unit, course_key=self.course.course_key) access._has_access_descriptor(self.course_staff, 'load', mock_unit, course_key=self.course.course_key)
) )
...@@ -102,6 +127,10 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -102,6 +127,10 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.student, 'instructor', self.course.course_key self.student, 'instructor', self.course.course_key
)) ))
self.assertFalse(access._has_access_to_course(
self.student, 'not_staff_or_instructor', self.course.course_key
))
def test__has_access_string(self): def test__has_access_string(self):
user = Mock(is_staff=True) user = Mock(is_staff=True)
self.assertFalse(access._has_access_string(user, 'staff', 'not_global')) self.assertFalse(access._has_access_string(user, 'staff', 'not_global'))
...@@ -111,20 +140,24 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -111,20 +140,24 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.assertRaises(ValueError, access._has_access_string, user, 'not_staff', 'global') self.assertRaises(ValueError, access._has_access_string, user, 'not_staff', 'global')
def test__has_access_error_desc(self): @ddt.data(
('load', False, True, True),
('staff', False, True, True),
('instructor', False, False, True)
)
@ddt.unpack
def test__has_access_error_desc(self, action, expected_student, expected_staff, expected_instructor):
descriptor = Mock() descriptor = Mock()
self.assertFalse(access._has_access_error_desc(self.student, 'load', descriptor, self.course.course_key)) for (user, expected_response) in (
self.assertTrue(access._has_access_error_desc(self.course_staff, 'load', descriptor, self.course.course_key)) (self.student, expected_student),
self.assertTrue(access._has_access_error_desc(self.course_instructor, 'load', descriptor, self.course.course_key)) (self.course_staff, expected_staff),
(self.course_instructor, expected_instructor)
self.assertFalse(access._has_access_error_desc(self.student, 'staff', descriptor, self.course.course_key)) ):
self.assertTrue(access._has_access_error_desc(self.course_staff, 'staff', descriptor, self.course.course_key)) self.assertEquals(
self.assertTrue(access._has_access_error_desc(self.course_instructor, 'staff', descriptor, self.course.course_key)) bool(access._has_access_error_desc(user, action, descriptor, self.course.course_key)),
expected_response
self.assertFalse(access._has_access_error_desc(self.student, 'instructor', descriptor, self.course.course_key)) )
self.assertFalse(access._has_access_error_desc(self.course_staff, 'instructor', descriptor, self.course.course_key))
self.assertTrue(access._has_access_error_desc(self.course_instructor, 'instructor', descriptor, self.course.course_key))
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
access._has_access_error_desc(self.course_instructor, 'not_load_or_staff', descriptor, self.course.course_key) access._has_access_error_desc(self.course_instructor, 'not_load_or_staff', descriptor, self.course.course_key)
...@@ -133,6 +166,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -133,6 +166,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
# TODO: override DISABLE_START_DATES and test the start date branch of the method # TODO: override DISABLE_START_DATES and test the start date branch of the method
user = Mock() user = Mock()
descriptor = Mock(user_partitions=[]) descriptor = Mock(user_partitions=[])
descriptor._class_tags = {}
# Always returns true because DISABLE_START_DATES is set in test.py # Always returns true because DISABLE_START_DATES is set in test.py
self.assertTrue(access._has_access_descriptor(user, 'load', descriptor)) self.assertTrue(access._has_access_descriptor(user, 'load', descriptor))
...@@ -140,81 +174,68 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -140,81 +174,68 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
access._has_access_descriptor(user, 'not_load_or_staff', descriptor) access._has_access_descriptor(user, 'not_load_or_staff', descriptor)
@ddt.data(
(True, None, access_response.VisibilityError),
(False, None),
(True, YESTERDAY, access_response.VisibilityError),
(False, YESTERDAY),
(True, TOMORROW, access_response.VisibilityError),
(False, TOMORROW, access_response.StartDateError)
)
@ddt.unpack
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test__has_access_descriptor_staff_lock(self): def test__has_access_descriptor_staff_lock(self, visible_to_staff_only, start, expected_error_type=None):
""" """
Tests that "visible_to_staff_only" overrides start date. Tests that "visible_to_staff_only" overrides start date.
""" """
expected_access = expected_error_type is None
mock_unit = Mock(user_partitions=[]) mock_unit = Mock(user_partitions=[])
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
mock_unit.visible_to_staff_only = visible_to_staff_only
mock_unit.start = start
self.verify_access(mock_unit, expected_access, expected_error_type)
# No start date, staff lock on def test__has_access_descriptor_beta_user(self):
mock_unit.visible_to_staff_only = True mock_unit = Mock(user_partitions=[])
self.verify_access(mock_unit, False) mock_unit._class_tags = {}
mock_unit.days_early_for_beta = 2
# No start date, staff lock off. mock_unit.start = self.TOMORROW
mock_unit.visible_to_staff_only = False
self.verify_access(mock_unit, True)
# Start date in the past, staff lock on.
mock_unit.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
mock_unit.visible_to_staff_only = True
self.verify_access(mock_unit, False)
# Start date in the past, staff lock off.
mock_unit.visible_to_staff_only = False mock_unit.visible_to_staff_only = False
self.verify_access(mock_unit, True)
# Start date in the future, staff lock on.
mock_unit.start = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) # release date in the future
mock_unit.visible_to_staff_only = True
self.verify_access(mock_unit, False)
# Start date in the future, staff lock off. self.assertTrue(bool(access._has_access_descriptor(
mock_unit.visible_to_staff_only = False self.beta_user, 'load', mock_unit, course_key=self.course.course_key)))
self.verify_access(mock_unit, False)
@ddt.data(None, YESTERDAY, TOMORROW)
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
@patch('courseware.access.get_current_request_hostname', Mock(return_value='preview.localhost')) @patch('courseware.access.get_current_request_hostname', Mock(return_value='preview.localhost'))
def test__has_access_descriptor_in_preview_mode(self): def test__has_access_descriptor_in_preview_mode(self, start):
""" """
Tests that descriptor has access in preview mode. Tests that descriptor has access in preview mode.
""" """
mock_unit = Mock(user_partitions=[]) mock_unit = Mock(user_partitions=[])
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
# No start date.
mock_unit.visible_to_staff_only = False mock_unit.visible_to_staff_only = False
mock_unit.start = start
self.verify_access(mock_unit, True) self.verify_access(mock_unit, True)
# Start date in the past. @ddt.data(
mock_unit.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) (TOMORROW, access_response.StartDateError),
self.verify_access(mock_unit, True) (None, None),
(YESTERDAY, None)
# Start date in the future. ) # ddt throws an error if I don't put the None argument there
mock_unit.start = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) # release date in the future @ddt.unpack
self.verify_access(mock_unit, True)
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
@patch('courseware.access.get_current_request_hostname', Mock(return_value='localhost')) @patch('courseware.access.get_current_request_hostname', Mock(return_value='localhost'))
def test__has_access_descriptor_when_not_in_preview_mode(self): def test__has_access_descriptor_when_not_in_preview_mode(self, start, expected_error_type):
""" """
Tests that descriptor has no access when start date in future & without preview. Tests that descriptor has no access when start date in future & without preview.
""" """
expected_access = expected_error_type is None
mock_unit = Mock(user_partitions=[]) mock_unit = Mock(user_partitions=[])
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
# No start date.
mock_unit.visible_to_staff_only = False mock_unit.visible_to_staff_only = False
self.verify_access(mock_unit, True) mock_unit.start = start
self.verify_access(mock_unit, expected_access, expected_error_type)
# Start date in the past.
mock_unit.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
self.verify_access(mock_unit, True)
# Start date in the future.
mock_unit.start = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) # release date in the future
self.verify_access(mock_unit, False)
def test__has_access_course_desc_can_enroll(self): def test__has_access_course_desc_can_enroll(self):
yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
...@@ -301,6 +322,16 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -301,6 +322,16 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.assertTrue(access._has_access_course_desc(staff, 'see_in_catalog', course)) self.assertTrue(access._has_access_course_desc(staff, 'see_in_catalog', course))
self.assertTrue(access._has_access_course_desc(staff, 'see_about_page', course)) self.assertTrue(access._has_access_course_desc(staff, 'see_about_page', course))
@ddt.data(True, False)
@patch.dict("django.conf.settings.FEATURES", {'ACCESS_REQUIRE_STAFF_FOR_COURSE': True})
def test_see_exists(self, ispublic):
"""
Test if user can see course
"""
user = UserFactory.create(is_staff=False)
course = Mock(ispublic=ispublic)
self.assertEquals(bool(access._has_access_course_desc(user, 'see_exists', course)), ispublic)
@patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True}) @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True})
def test_access_on_course_with_pre_requisites(self): def test_access_on_course_with_pre_requisites(self):
""" """
...@@ -319,10 +350,11 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -319,10 +350,11 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
) )
set_prerequisite_courses(course.id, pre_requisite_courses) set_prerequisite_courses(course.id, pre_requisite_courses)
#user should not be able to load course even if enrolled # user should not be able to load course even if enrolled
CourseEnrollmentFactory(user=user, course_id=course.id) CourseEnrollmentFactory(user=user, course_id=course.id)
self.assertFalse(access._has_access_course_desc(user, 'view_courseware_with_prerequisites', course)) response = access._has_access_course_desc(user, 'view_courseware_with_prerequisites', course)
self.assertFalse(response)
self.assertIsInstance(response, access_response.MilestoneError)
# Staff can always access course # Staff can always access course
staff = StaffFactory.create(course_key=course.id) staff = StaffFactory.create(course_key=course.id)
self.assertTrue(access._has_access_course_desc(staff, 'view_courseware_with_prerequisites', course)) self.assertTrue(access._has_access_course_desc(staff, 'view_courseware_with_prerequisites', course))
...@@ -331,6 +363,26 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -331,6 +363,26 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
fulfill_course_milestone(pre_requisite_course.id, user) fulfill_course_milestone(pre_requisite_course.id, user)
self.assertTrue(access._has_access_course_desc(user, 'view_courseware_with_prerequisites', course)) self.assertTrue(access._has_access_course_desc(user, 'view_courseware_with_prerequisites', course))
@ddt.data(
(True, True, True),
(False, False, True)
)
@ddt.unpack
def test__access_on_mobile(self, mobile_available, student_expected, staff_expected):
"""
Test course access on mobile for staff and students.
"""
descriptor = Mock(user_partitions=[])
descriptor._class_tags = {}
descriptor.visible_to_staff_only = False
descriptor.mobile_available = mobile_available
self.assertEqual(
bool(access._has_access_course_desc(self.student, 'load_mobile', descriptor)),
student_expected
)
self.assertEqual(bool(access._has_access_course_desc(self.staff, 'load_mobile', descriptor)), staff_expected)
@patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True}) @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True})
def test_courseware_page_unfulfilled_prereqs(self): def test_courseware_page_unfulfilled_prereqs(self):
""" """
...@@ -504,11 +556,11 @@ class CourseOverviewAccessTestCase(ModuleStoreTestCase): ...@@ -504,11 +556,11 @@ class CourseOverviewAccessTestCase(ModuleStoreTestCase):
course_overview = CourseOverview.get_from_id(course.id) course_overview = CourseOverview.get_from_id(course.id)
self.assertEqual( self.assertEqual(
access.has_access(user, action, course, course_key=course.id), bool(access.has_access(user, action, course, course_key=course.id)),
access.has_access(user, action, course_overview, course_key=course.id) bool(access.has_access(user, action, course_overview, course_key=course.id))
) )
def test_course_overivew_unsupported_action(self): def test_course_overview_unsupported_action(self):
""" """
Check that calling has_access with an unsupported action raises a Check that calling has_access with an unsupported action raises a
ValueError. ValueError.
......
...@@ -185,7 +185,7 @@ class GroupAccessTestCase(ModuleStoreTestCase): ...@@ -185,7 +185,7 @@ class GroupAccessTestCase(ModuleStoreTestCase):
DRY helper. DRY helper.
""" """
self.assertIs( self.assertIs(
access.has_access(user, 'load', modulestore().get_item(block_location), self.course.id), bool(access.has_access(user, 'load', modulestore().get_item(block_location), self.course.id)),
is_accessible is_accessible
) )
......
...@@ -766,7 +766,7 @@ def syllabus(request, course_id): ...@@ -766,7 +766,7 @@ def syllabus(request, course_id):
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course) staff_access = bool(has_access(request.user, 'staff', course))
return render_to_response('courseware/syllabus.html', { return render_to_response('courseware/syllabus.html', {
'course': course, 'course': course,
...@@ -828,7 +828,7 @@ def course_about(request, course_id): ...@@ -828,7 +828,7 @@ def course_about(request, course_id):
registered = registered_for_course(course, request.user) registered = registered_for_course(course, request.user)
staff_access = has_access(request.user, 'staff', course) staff_access = bool(has_access(request.user, 'staff', course))
studio_url = get_studio_url(course, 'settings/details') studio_url = get_studio_url(course, 'settings/details')
if has_access(request.user, 'load', course): if has_access(request.user, 'load', course):
...@@ -836,7 +836,7 @@ def course_about(request, course_id): ...@@ -836,7 +836,7 @@ def course_about(request, course_id):
else: else:
course_target = reverse('about_course', args=[course.id.to_deprecated_string()]) course_target = reverse('about_course', args=[course.id.to_deprecated_string()])
show_courseware_link = ( show_courseware_link = bool(
( (
has_access(request.user, 'load', course) has_access(request.user, 'load', course)
and has_access(request.user, 'view_courseware_with_prerequisites', course) and has_access(request.user, 'view_courseware_with_prerequisites', course)
...@@ -865,7 +865,7 @@ def course_about(request, course_id): ...@@ -865,7 +865,7 @@ def course_about(request, course_id):
can_add_course_to_cart = _is_shopping_cart_enabled and registration_price can_add_course_to_cart = _is_shopping_cart_enabled and registration_price
# Used to provide context to message to student if enrollment not allowed # Used to provide context to message to student if enrollment not allowed
can_enroll = has_access(request.user, 'enroll', course) can_enroll = bool(has_access(request.user, 'enroll', course))
invitation_only = course.invitation_only invitation_only = course.invitation_only
is_course_full = CourseEnrollment.objects.is_course_full(course) is_course_full = CourseEnrollment.objects.is_course_full(course)
...@@ -931,10 +931,10 @@ def mktg_course_about(request, course_id): ...@@ -931,10 +931,10 @@ def mktg_course_about(request, course_id):
else: else:
course_target = reverse('about_course', args=[course.id.to_deprecated_string()]) course_target = reverse('about_course', args=[course.id.to_deprecated_string()])
allow_registration = has_access(request.user, 'enroll', course) allow_registration = bool(has_access(request.user, 'enroll', course))
show_courseware_link = (has_access(request.user, 'load', course) or show_courseware_link = bool(has_access(request.user, 'load', course) or
settings.FEATURES.get('ENABLE_LMS_MIGRATION')) settings.FEATURES.get('ENABLE_LMS_MIGRATION'))
course_modes = CourseMode.modes_for_course_dict(course.id) course_modes = CourseMode.modes_for_course_dict(course.id)
context = { context = {
...@@ -1028,7 +1028,7 @@ def _progress(request, course_key, student_id): ...@@ -1028,7 +1028,7 @@ def _progress(request, course_key, student_id):
if survey.utils.must_answer_survey(course, request.user): if survey.utils.must_answer_survey(course, request.user):
return redirect(reverse('course_survey', args=[unicode(course.id)])) return redirect(reverse('course_survey', args=[unicode(course.id)]))
staff_access = has_access(request.user, 'staff', course) staff_access = bool(has_access(request.user, 'staff', course))
if student_id is None or student_id == request.user.id: if student_id is None or student_id == request.user.id:
# always allowed to see your own profile # always allowed to see your own profile
...@@ -1175,7 +1175,7 @@ def submission_history(request, course_id, student_username, location): ...@@ -1175,7 +1175,7 @@ def submission_history(request, course_id, student_username, location):
return HttpResponse(escape(_(u'Invalid location.'))) return HttpResponse(escape(_(u'Invalid location.')))
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course) staff_access = bool(has_access(request.user, 'staff', course))
# Permission Denied if they don't have staff access and are trying to see # Permission Denied if they don't have staff access and are trying to see
# somebody else's submission history. # somebody else's submission history.
...@@ -1478,7 +1478,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True): ...@@ -1478,7 +1478,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
'disable_header': True, 'disable_header': True,
'disable_window_wrap': True, 'disable_window_wrap': True,
'disable_preview_menu': True, 'disable_preview_menu': True,
'staff_access': has_access(request.user, 'staff', course), 'staff_access': bool(has_access(request.user, 'staff', course)),
'xqa_server': settings.FEATURES.get('XQA_SERVER', 'http://your_xqa_server.com'), 'xqa_server': settings.FEATURES.get('XQA_SERVER', 'http://your_xqa_server.com'),
} }
return render_to_response('courseware/courseware-chromeless.html', context) return render_to_response('courseware/courseware-chromeless.html', context)
...@@ -484,7 +484,7 @@ def un_flag_abuse_for_thread(request, course_id, thread_id): ...@@ -484,7 +484,7 @@ def un_flag_abuse_for_thread(request, course_id, thread_id):
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_by_id(course_key) course = get_course_by_id(course_key)
thread = cc.Thread.find(thread_id) thread = cc.Thread.find(thread_id)
remove_all = ( remove_all = bool(
has_permission(request.user, 'openclose_thread', course_key) or has_permission(request.user, 'openclose_thread', course_key) or
has_access(request.user, 'staff', course) has_access(request.user, 'staff', course)
) )
...@@ -519,7 +519,7 @@ def un_flag_abuse_for_comment(request, course_id, comment_id): ...@@ -519,7 +519,7 @@ def un_flag_abuse_for_comment(request, course_id, comment_id):
user = cc.User.from_django_user(request.user) user = cc.User.from_django_user(request.user)
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_by_id(course_key) course = get_course_by_id(course_key)
remove_all = ( remove_all = bool(
has_permission(request.user, 'openclose_thread', course_key) or has_permission(request.user, 'openclose_thread', course_key) or
has_access(request.user, 'staff', course) has_access(request.user, 'staff', course)
) )
......
...@@ -272,11 +272,11 @@ def forum_form_discussion(request, course_key): ...@@ -272,11 +272,11 @@ def forum_form_discussion(request, course_key):
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'course': course, 'course': course,
#'recent_active_threads': recent_active_threads, #'recent_active_threads': recent_active_threads,
'staff_access': has_access(request.user, 'staff', course), 'staff_access': bool(has_access(request.user, 'staff', course)),
'threads': _attr_safe_json(threads), 'threads': _attr_safe_json(threads),
'thread_pages': query_params['num_pages'], 'thread_pages': query_params['num_pages'],
'user_info': _attr_safe_json(user_info), 'user_info': _attr_safe_json(user_info),
'flag_moderator': ( 'flag_moderator': bool(
has_permission(request.user, 'openclose_thread', course.id) or has_permission(request.user, 'openclose_thread', course.id) or
has_access(request.user, 'staff', course) has_access(request.user, 'staff', course)
), ),
...@@ -385,7 +385,7 @@ def single_thread(request, course_key, discussion_id, thread_id): ...@@ -385,7 +385,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
'is_moderator': is_moderator, 'is_moderator': is_moderator,
'thread_pages': query_params['num_pages'], 'thread_pages': query_params['num_pages'],
'is_course_cohorted': is_course_cohorted(course_key), 'is_course_cohorted': is_course_cohorted(course_key),
'flag_moderator': ( 'flag_moderator': bool(
has_permission(request.user, 'openclose_thread', course.id) or has_permission(request.user, 'openclose_thread', course.id) or
has_access(request.user, 'staff', course) has_access(request.user, 'staff', course)
), ),
......
...@@ -24,7 +24,7 @@ class PaidCourseEnrollmentReportProvider(BaseAbstractEnrollmentReportProvider): ...@@ -24,7 +24,7 @@ class PaidCourseEnrollmentReportProvider(BaseAbstractEnrollmentReportProvider):
Returns the User Enrollment information. Returns the User Enrollment information.
""" """
course = get_course_by_id(course_id, depth=0) course = get_course_by_id(course_id, depth=0)
is_course_staff = has_access(user, 'staff', course) is_course_staff = bool(has_access(user, 'staff', course))
# check the user enrollment role # check the user enrollment role
if user.is_staff: if user.is_staff:
......
...@@ -62,7 +62,7 @@ class InstructorDashboardTab(CourseTab): ...@@ -62,7 +62,7 @@ class InstructorDashboardTab(CourseTab):
""" """
Returns true if the specified user has staff access. Returns true if the specified user has staff access.
""" """
return user and has_access(user, 'staff', course, course.id) return bool(user and has_access(user, 'staff', course, course.id))
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -79,10 +79,10 @@ def instructor_dashboard_2(request, course_id): ...@@ -79,10 +79,10 @@ def instructor_dashboard_2(request, course_id):
access = { access = {
'admin': request.user.is_staff, 'admin': request.user.is_staff,
'instructor': has_access(request.user, 'instructor', course), 'instructor': bool(has_access(request.user, 'instructor', course)),
'finance_admin': CourseFinanceAdminRole(course_key).has_user(request.user), 'finance_admin': CourseFinanceAdminRole(course_key).has_user(request.user),
'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user), 'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user),
'staff': has_access(request.user, 'staff', course), 'staff': bool(has_access(request.user, 'staff', course)),
'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR), 'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
} }
......
...@@ -84,7 +84,7 @@ def instructor_dashboard(request, course_id): ...@@ -84,7 +84,7 @@ def instructor_dashboard(request, course_id):
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'staff', course_key, depth=None) course = get_course_with_access(request.user, 'staff', course_key, depth=None)
instructor_access = has_access(request.user, 'instructor', course) # an instructor can manage staff lists instructor_access = bool(has_access(request.user, 'instructor', course)) # an instructor can manage staff lists
forum_admin_access = has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR) forum_admin_access = has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR)
......
...@@ -118,7 +118,7 @@ def combined_notifications(course, user): ...@@ -118,7 +118,7 @@ def combined_notifications(course, user):
#Initialize controller query service using our mock system #Initialize controller query service using our mock system
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string) controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, render_to_string)
student_id = unique_id_for_user(user) student_id = unique_id_for_user(user)
user_is_staff = has_access(user, 'staff', course) user_is_staff = bool(has_access(user, 'staff', course))
course_id = course.id course_id = course.id
notification_type = "combined" notification_type = "combined"
......
...@@ -22,7 +22,7 @@ def index(request, course_id, book_index, page=None): ...@@ -22,7 +22,7 @@ def index(request, course_id, book_index, page=None):
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course) staff_access = bool(has_access(request.user, 'staff', course))
book_index = int(book_index) book_index = int(book_index)
if book_index < 0 or book_index >= len(course.textbooks): if book_index < 0 or book_index >= len(course.textbooks):
...@@ -79,7 +79,7 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None): ...@@ -79,7 +79,7 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None):
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course) staff_access = bool(has_access(request.user, 'staff', course))
book_index = int(book_index) book_index = int(book_index)
if book_index < 0 or book_index >= len(course.pdf_textbooks): if book_index < 0 or book_index >= len(course.pdf_textbooks):
...@@ -147,7 +147,7 @@ def html_index(request, course_id, book_index, chapter=None): ...@@ -147,7 +147,7 @@ def html_index(request, course_id, book_index, chapter=None):
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course) staff_access = bool(has_access(request.user, 'staff', course))
notes_enabled = notes_enabled_for_course(course) notes_enabled = notes_enabled_for_course(course)
book_index = int(book_index) book_index = int(book_index)
......
...@@ -21,5 +21,5 @@ class LmsSearchInitializer(SearchInitializer): ...@@ -21,5 +21,5 @@ class LmsSearchInitializer(SearchInitializer):
course_key = CourseKey.from_string(kwargs['course_id']) course_key = CourseKey.from_string(kwargs['course_id'])
except InvalidKeyError: except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id']) course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id'])
staff_access = has_access(request.user, 'staff', course_key) staff_access = bool(has_access(request.user, 'staff', course_key))
setup_masquerade(request, course_key, staff_access) setup_masquerade(request, course_key, staff_access)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment