Merge pull request #78 from edx/rsrct-preview-tnl4194
Create the security fix #2 for the RC 2016-03-22
This commit is contained in:
@@ -61,7 +61,10 @@ from courseware.access_response import (
|
||||
MobileAvailabilityError,
|
||||
VisibilityError,
|
||||
)
|
||||
from courseware.access_utils import adjust_start_date, check_start_date, debug, ACCESS_GRANTED, ACCESS_DENIED
|
||||
from courseware.access_utils import (
|
||||
adjust_start_date, check_start_date, debug, ACCESS_GRANTED, ACCESS_DENIED,
|
||||
in_preview_mode
|
||||
)
|
||||
|
||||
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
|
||||
from lms.djangoapps.ccx.models import CustomCourseForEdX
|
||||
@@ -135,6 +138,10 @@ def has_access(user, action, obj, course_key=None):
|
||||
if isinstance(course_key, CCXLocator):
|
||||
course_key = course_key.to_course_locator()
|
||||
|
||||
if in_preview_mode():
|
||||
if not bool(has_staff_access_to_preview_mode(user=user, obj=obj, course_key=course_key)):
|
||||
return ACCESS_DENIED
|
||||
|
||||
# delegate the work to type-specific functions.
|
||||
# (start with more specific types, then get more general)
|
||||
if isinstance(obj, CourseDescriptor):
|
||||
@@ -172,6 +179,52 @@ def has_access(user, action, obj, course_key=None):
|
||||
|
||||
|
||||
# ================ Implementation helpers ================================
|
||||
|
||||
def has_staff_access_to_preview_mode(user, obj, course_key=None):
|
||||
"""
|
||||
Returns whether user has staff access to specified modules or not.
|
||||
|
||||
Arguments:
|
||||
|
||||
user: a Django user object.
|
||||
|
||||
obj: The object to check access for.
|
||||
|
||||
course_key: A course_key specifying which course this access is for.
|
||||
|
||||
Returns an AccessResponse object.
|
||||
"""
|
||||
if course_key is None:
|
||||
if isinstance(obj, CourseDescriptor) or isinstance(obj, CourseOverview):
|
||||
course_key = obj.id
|
||||
|
||||
elif isinstance(obj, ErrorDescriptor):
|
||||
course_key = obj.location.course_key
|
||||
|
||||
elif isinstance(obj, XModule):
|
||||
course_key = obj.descriptor.course_key
|
||||
|
||||
elif isinstance(obj, XBlock):
|
||||
course_key = obj.location.course_key
|
||||
|
||||
elif isinstance(obj, CCXLocator):
|
||||
course_key = obj.to_course_locator()
|
||||
|
||||
elif isinstance(obj, CourseKey):
|
||||
course_key = obj
|
||||
|
||||
elif isinstance(obj, UsageKey):
|
||||
course_key = obj.course_key
|
||||
|
||||
if course_key is None:
|
||||
if GlobalStaff().has_user(user):
|
||||
return ACCESS_GRANTED
|
||||
else:
|
||||
return ACCESS_DENIED
|
||||
|
||||
return _has_access_to_course(user, 'staff', course_key=course_key)
|
||||
|
||||
|
||||
def _can_access_descriptor_with_start_date(user, descriptor, course_key): # pylint: disable=invalid-name
|
||||
"""
|
||||
Checks if a user has access to a descriptor based on its start date.
|
||||
|
||||
@@ -17,6 +17,7 @@ from mock import Mock, patch
|
||||
from nose.plugins.attrib import attr
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
from ccx.tests.factories import CcxFactory
|
||||
import courseware.access as access
|
||||
import courseware.access_response as access_response
|
||||
from courseware.masquerade import CourseMasquerade
|
||||
@@ -39,17 +40,21 @@ from student.tests.factories import (
|
||||
CourseEnrollmentAllowedFactory,
|
||||
CourseEnrollmentFactory,
|
||||
)
|
||||
|
||||
from xmodule.course_module import (
|
||||
CATALOG_VISIBILITY_CATALOG_AND_ABOUT,
|
||||
CATALOG_VISIBILITY_ABOUT,
|
||||
CATALOG_VISIBILITY_NONE,
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.error_module import ErrorDescriptor
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
ModuleStoreTestCase,
|
||||
SharedModuleStoreTestCase,
|
||||
TEST_DATA_SPLIT_MODULESTORE
|
||||
)
|
||||
from xmodule.modulestore.xml import CourseLocationManager
|
||||
from xmodule.tests import get_test_system
|
||||
|
||||
from util.milestones_helpers import (
|
||||
set_prerequisite_courses,
|
||||
@@ -157,23 +162,23 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
"""
|
||||
TOMORROW = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
|
||||
YESTERDAY = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
|
||||
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
|
||||
|
||||
def setUp(self):
|
||||
super(AccessTestCase, self).setUp()
|
||||
course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
|
||||
self.course = course_key.make_usage_key('course', course_key.run)
|
||||
self.course = CourseFactory.create(org='edX', course='toy', run='test_run')
|
||||
self.anonymous_user = AnonymousUserFactory()
|
||||
self.beta_user = BetaTesterFactory(course_key=self.course.course_key)
|
||||
self.beta_user = BetaTesterFactory(course_key=self.course.id)
|
||||
self.student = UserFactory()
|
||||
self.global_staff = UserFactory(is_staff=True)
|
||||
self.course_staff = StaffFactory(course_key=self.course.course_key)
|
||||
self.course_instructor = InstructorFactory(course_key=self.course.course_key)
|
||||
self.course_staff = StaffFactory(course_key=self.course.id)
|
||||
self.course_instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.staff = GlobalStaffFactory()
|
||||
|
||||
def verify_access(self, mock_unit, student_should_have_access, expected_error_type=None):
|
||||
""" Verify the expected result from _has_access_descriptor """
|
||||
response = access._has_access_descriptor(self.anonymous_user, 'load',
|
||||
mock_unit, course_key=self.course.course_key)
|
||||
mock_unit, course_key=self.course.id)
|
||||
self.assertEqual(student_should_have_access, bool(response))
|
||||
|
||||
if expected_error_type is not None:
|
||||
@@ -181,55 +186,142 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
self.assertIsNotNone(response.to_json()['error_code'])
|
||||
|
||||
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.id)
|
||||
)
|
||||
|
||||
def test_has_staff_access_to_preview_mode(self):
|
||||
"""
|
||||
Tests users have right access to content in preview mode.
|
||||
"""
|
||||
course_key = self.course.id
|
||||
usage_key = self.course.scope_ids.usage_id
|
||||
chapter = ItemFactory.create(category="chapter", parent_location=self.course.location)
|
||||
overview = CourseOverview.get_from_id(course_key)
|
||||
test_system = get_test_system()
|
||||
|
||||
ccx = CcxFactory(course_id=course_key)
|
||||
ccx_locator = CCXLocator.from_course_locator(course_key, ccx.id)
|
||||
|
||||
error_descriptor = ErrorDescriptor.from_xml(
|
||||
u"<problem>ABC \N{SNOWMAN}</problem>",
|
||||
test_system,
|
||||
CourseLocationManager(course_key),
|
||||
"error msg"
|
||||
)
|
||||
# Enroll student to the course
|
||||
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
|
||||
|
||||
modules = [
|
||||
self.course,
|
||||
overview,
|
||||
chapter,
|
||||
ccx_locator,
|
||||
error_descriptor,
|
||||
course_key,
|
||||
usage_key,
|
||||
]
|
||||
# Course key is not None
|
||||
self.assertTrue(
|
||||
bool(access.has_staff_access_to_preview_mode(self.global_staff, obj=self.course, course_key=course_key))
|
||||
)
|
||||
|
||||
for user in [self.global_staff, self.course_staff, self.course_instructor]:
|
||||
for obj in modules:
|
||||
self.assertTrue(bool(access.has_staff_access_to_preview_mode(user, obj=obj)))
|
||||
self.assertFalse(bool(access.has_staff_access_to_preview_mode(self.student, obj=obj)))
|
||||
|
||||
def test_student_has_access(self):
|
||||
"""
|
||||
Tests course student have right access to content w/o preview.
|
||||
"""
|
||||
course_key = self.course.id
|
||||
chapter = ItemFactory.create(category="chapter", parent_location=self.course.location)
|
||||
overview = CourseOverview.get_from_id(course_key)
|
||||
|
||||
# Enroll student to the course
|
||||
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
|
||||
|
||||
modules = [
|
||||
self.course,
|
||||
overview,
|
||||
chapter,
|
||||
]
|
||||
with patch('courseware.access.in_preview_mode') as mock_preview:
|
||||
mock_preview.return_value = False
|
||||
for obj in modules:
|
||||
self.assertTrue(bool(access.has_access(self.student, 'load', obj, course_key=self.course.id)))
|
||||
|
||||
with patch('courseware.access.in_preview_mode') as mock_preview:
|
||||
mock_preview.return_value = True
|
||||
for obj in modules:
|
||||
self.assertFalse(bool(access.has_access(self.student, 'load', obj, course_key=self.course.id)))
|
||||
|
||||
def test_string_has_staff_access_to_preview_mode(self):
|
||||
"""
|
||||
Tests different users has right access to string content in preview mode.
|
||||
"""
|
||||
self.assertTrue(bool(access.has_staff_access_to_preview_mode(self.global_staff, obj='global')))
|
||||
self.assertFalse(bool(access.has_staff_access_to_preview_mode(self.course_staff, obj='global')))
|
||||
self.assertFalse(bool(access.has_staff_access_to_preview_mode(self.course_instructor, obj='global')))
|
||||
self.assertFalse(bool(access.has_staff_access_to_preview_mode(self.student, obj='global')))
|
||||
|
||||
@patch('courseware.access.in_preview_mode', Mock(return_value=True))
|
||||
def test_has_access_with_preview_mode(self):
|
||||
"""
|
||||
Tests particular user's can access content via has_access in preview mode.
|
||||
"""
|
||||
self.assertTrue(bool(access.has_access(self.global_staff, 'staff', self.course, course_key=self.course.id)))
|
||||
self.assertTrue(bool(access.has_access(self.course_staff, 'staff', self.course, course_key=self.course.id)))
|
||||
self.assertTrue(bool(access.has_access(self.course_instructor, 'staff', self.course, course_key=self.course.id)))
|
||||
self.assertFalse(bool(access.has_access(self.student, 'staff', self.course, course_key=self.course.id)))
|
||||
self.assertFalse(bool(access.has_access(self.student, 'load', self.course, course_key=self.course.id)))
|
||||
|
||||
def test_has_access_to_course(self):
|
||||
self.assertFalse(access._has_access_to_course(
|
||||
None, 'staff', self.course.course_key
|
||||
None, 'staff', self.course.id
|
||||
))
|
||||
|
||||
self.assertFalse(access._has_access_to_course(
|
||||
self.anonymous_user, 'staff', self.course.course_key
|
||||
self.anonymous_user, 'staff', self.course.id
|
||||
))
|
||||
self.assertFalse(access._has_access_to_course(
|
||||
self.anonymous_user, 'instructor', self.course.course_key
|
||||
self.anonymous_user, 'instructor', self.course.id
|
||||
))
|
||||
|
||||
self.assertTrue(access._has_access_to_course(
|
||||
self.global_staff, 'staff', self.course.course_key
|
||||
self.global_staff, 'staff', self.course.id
|
||||
))
|
||||
self.assertTrue(access._has_access_to_course(
|
||||
self.global_staff, 'instructor', self.course.course_key
|
||||
self.global_staff, 'instructor', self.course.id
|
||||
))
|
||||
|
||||
# A user has staff access if they are in the staff group
|
||||
self.assertTrue(access._has_access_to_course(
|
||||
self.course_staff, 'staff', self.course.course_key
|
||||
self.course_staff, 'staff', self.course.id
|
||||
))
|
||||
self.assertFalse(access._has_access_to_course(
|
||||
self.course_staff, 'instructor', self.course.course_key
|
||||
self.course_staff, 'instructor', self.course.id
|
||||
))
|
||||
|
||||
# A user has staff and instructor access if they are in the instructor group
|
||||
self.assertTrue(access._has_access_to_course(
|
||||
self.course_instructor, 'staff', self.course.course_key
|
||||
self.course_instructor, 'staff', self.course.id
|
||||
))
|
||||
self.assertTrue(access._has_access_to_course(
|
||||
self.course_instructor, 'instructor', self.course.course_key
|
||||
self.course_instructor, 'instructor', self.course.id
|
||||
))
|
||||
|
||||
# A user does not have staff or instructor access if they are
|
||||
# not in either the staff or the the instructor group
|
||||
self.assertFalse(access._has_access_to_course(
|
||||
self.student, 'staff', self.course.course_key
|
||||
self.student, 'staff', self.course.id
|
||||
))
|
||||
self.assertFalse(access._has_access_to_course(
|
||||
self.student, 'instructor', self.course.course_key
|
||||
self.student, 'instructor', self.course.id
|
||||
))
|
||||
|
||||
self.assertFalse(access._has_access_to_course(
|
||||
self.student, 'not_staff_or_instructor', self.course.course_key
|
||||
self.student, 'not_staff_or_instructor', self.course.id
|
||||
))
|
||||
|
||||
def test__has_access_string(self):
|
||||
@@ -256,12 +348,12 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
(self.course_instructor, expected_instructor)
|
||||
):
|
||||
self.assertEquals(
|
||||
bool(access._has_access_error_desc(user, action, descriptor, self.course.course_key)),
|
||||
bool(access._has_access_error_desc(user, action, descriptor, self.course.id)),
|
||||
expected_response
|
||||
)
|
||||
|
||||
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.id)
|
||||
|
||||
def test__has_access_descriptor(self):
|
||||
# TODO: override DISABLE_START_DATES and test the start date branch of the method
|
||||
@@ -304,7 +396,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
|
||||
mock_unit.visible_to_staff_only = False
|
||||
|
||||
self.assertTrue(bool(access._has_access_descriptor(
|
||||
self.beta_user, 'load', mock_unit, course_key=self.course.course_key)))
|
||||
self.beta_user, 'load', mock_unit, course_key=self.course.id)))
|
||||
|
||||
@ddt.data(None, YESTERDAY, TOMORROW)
|
||||
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
|
||||
|
||||
Reference in New Issue
Block a user