Commit a6776f69 by cahrens

Always check group access when masquerading.

TNL-6050
parent 0e3bb60c
...@@ -469,6 +469,10 @@ def _has_group_access(descriptor, user, course_key): ...@@ -469,6 +469,10 @@ def _has_group_access(descriptor, user, course_key):
# via updating the children of the split_test module. # via updating the children of the split_test module.
return ACCESS_GRANTED return ACCESS_GRANTED
# Allow staff and instructors roles group access, as they are not masquerading as a student.
if get_user_role(user, course_key) in ['staff', 'instructor']:
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
merged_access = descriptor.merged_group_access merged_access = descriptor.merged_group_access
...@@ -550,14 +554,20 @@ def _has_access_descriptor(user, action, descriptor, course_key=None): ...@@ -550,14 +554,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.
""" """
# If the user (or the role the user is currently masquerading as) does not have
# access to this content, then deny access. The problem with calling _has_staff_access_to_descriptor
# before this method is that _has_staff_access_to_descriptor short-circuits and returns True
# for staff users in preview mode.
if not _has_group_access(descriptor, user, course_key):
return ACCESS_DENIED
# If the user has staff access, they can load the module and checks below are not needed.
if _has_staff_access_to_descriptor(user, descriptor, course_key): if _has_staff_access_to_descriptor(user, descriptor, course_key):
return ACCESS_GRANTED return ACCESS_GRANTED
# if the user has staff access, they can load the module so this code doesn't need to run
return ( return (
_visible_to_nonstaff_users(descriptor) and _visible_to_nonstaff_users(descriptor) and
_can_access_descriptor_with_milestones(user, descriptor, course_key) and _can_access_descriptor_with_milestones(user, descriptor, course_key) and
_has_group_access(descriptor, user, course_key) and
( (
_has_detached_class_tag(descriptor) or _has_detached_class_tag(descriptor) or
_can_access_descriptor_with_start_date(user, descriptor, course_key) _can_access_descriptor_with_start_date(user, descriptor, course_key)
......
""" """
Helpers for courseware tests. Helpers for courseware tests.
""" """
import crum
import json import json
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -10,6 +9,10 @@ from django.test import TestCase ...@@ -10,6 +9,10 @@ from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from courseware.access import has_access from courseware.access import has_access
from courseware.masquerade import (
handle_ajax,
setup_masquerade
)
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.models import Registration from student.models import Registration
...@@ -178,3 +181,39 @@ class CourseAccessTestMixin(TestCase): ...@@ -178,3 +181,39 @@ class CourseAccessTestMixin(TestCase):
""" """
self.assertFalse(has_access(user, action, course)) self.assertFalse(has_access(user, action, course))
self.assertFalse(has_access(user, action, CourseOverview.get_from_id(course.id))) self.assertFalse(has_access(user, action, CourseOverview.get_from_id(course.id)))
def masquerade_as_group_member(user, course, partition_id, group_id):
"""
Installs a masquerade for the specified user and course, to enable
the user to masquerade as belonging to the specific partition/group
combination.
Arguments:
user (User): a user.
course (CourseDescriptor): a course.
partition_id (int): the integer partition id, referring to partitions already
configured in the course.
group_id (int); the integer group id, within the specified partition.
Returns: the status code for the AJAX response to update the user's masquerade for
the specified course.
"""
request = _create_mock_json_request(
user,
data={"role": "student", "user_partition_id": partition_id, "group_id": group_id}
)
response = handle_ajax(request, unicode(course.id))
setup_masquerade(request, course.id, True)
return response.status_code
def _create_mock_json_request(user, data, method='POST'):
"""
Returns a mock JSON request for the specified user.
"""
factory = RequestFactory()
request = factory.generic(method, '/', content_type='application/json', data=json.dumps(data))
request.user = user
request.session = {}
return request
...@@ -27,7 +27,7 @@ from courseware.tests.factories import ( ...@@ -27,7 +27,7 @@ from courseware.tests.factories import (
StaffFactory, StaffFactory,
UserFactory, UserFactory,
) )
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase, masquerade_as_group_member
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.roles import CourseCcxCoachRole, CourseStaffRole from student.roles import CourseCcxCoachRole, CourseStaffRole
...@@ -44,6 +44,9 @@ from xmodule.course_module import ( ...@@ -44,6 +44,9 @@ from xmodule.course_module import (
CATALOG_VISIBILITY_NONE, CATALOG_VISIBILITY_NONE,
) )
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from xmodule.partitions.partitions import Group, UserPartition
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, ModuleStoreTestCase,
...@@ -293,6 +296,57 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes ...@@ -293,6 +296,57 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
bool(access.has_access(self.student, 'staff', self.course, course_key=self.course.id)) bool(access.has_access(self.student, 'staff', self.course, course_key=self.course.id))
) )
@patch('courseware.access.in_preview_mode', Mock(return_value=True))
def test_has_access_in_preview_mode_with_group(self):
"""
Test that a user masquerading as a member of a group sees appropriate content in preview mode.
"""
partition_id = 0
group_0_id = 0
group_1_id = 1
user_partition = UserPartition(
partition_id, 'Test User Partition', '',
[Group(group_0_id, 'Group 1'), Group(group_1_id, 'Group 2')],
scheme_id='cohort'
)
self.course.user_partitions.append(user_partition)
self.course.cohort_config = {'cohorted': True}
chapter = ItemFactory.create(category="chapter", parent_location=self.course.location)
chapter.group_access = {partition_id: [group_0_id]}
chapter.user_partitions = self.course.user_partitions
modulestore().update_item(self.course, ModuleStoreEnum.UserID.test)
# User should not be able to preview when masquerading as student (and not in the group above).
with patch('courseware.access.get_user_role') as mock_user_role:
mock_user_role.return_value = 'student'
self.assertFalse(
bool(access.has_access(self.global_staff, 'load', chapter, course_key=self.course.id))
)
# Should be able to preview when in staff or instructor role.
for mocked_role in ['staff', 'instructor']:
with patch('courseware.access.get_user_role') as mock_user_role:
mock_user_role.return_value = mocked_role
self.assertTrue(
bool(access.has_access(self.global_staff, 'load', chapter, course_key=self.course.id))
)
# Now install masquerade group and set staff as a member of that.
self.assertEqual(200, masquerade_as_group_member(self.global_staff, self.course, partition_id, group_0_id))
# Can load the chapter since user is in the group.
self.assertTrue(
bool(access.has_access(self.global_staff, 'load', chapter, course_key=self.course.id))
)
# Move the user to be a part of the second group.
self.assertEqual(200, masquerade_as_group_member(self.global_staff, self.course, partition_id, group_1_id))
# Cannot load the chapter since user is in a different group.
self.assertFalse(
bool(access.has_access(self.global_staff, 'load', chapter, course_key=self.course.id))
)
def test_has_access_to_course(self): def test_has_access_to_course(self):
self.assertFalse(access._has_access_to_course( self.assertFalse(access._has_access_to_course(
None, 'staff', self.course.id None, 'staff', self.course.id
......
...@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr ...@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
from datetime import datetime from datetime import datetime
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase, RequestFactory from django.test import TestCase
from django.utils.timezone import UTC from django.utils.timezone import UTC
from capa.tests.response_xml_factory import OptionResponseXMLFactory from capa.tests.response_xml_factory import OptionResponseXMLFactory
...@@ -20,7 +20,7 @@ from courseware.masquerade import ( ...@@ -20,7 +20,7 @@ from courseware.masquerade import (
get_masquerading_group_info get_masquerading_group_info
) )
from courseware.tests.factories import StaffFactory from courseware.tests.factories import StaffFactory
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase, masquerade_as_group_member
from courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin from courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xblock.runtime import DictKeyValueStore from xblock.runtime import DictKeyValueStore
...@@ -107,16 +107,6 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -107,16 +107,6 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
) )
return self.client.get(url) return self.client.get(url)
def _create_mock_json_request(self, user, data, method='POST', session=None):
"""
Returns a mock JSON request for the specified user
"""
factory = RequestFactory()
request = factory.generic(method, '/', content_type='application/json', data=json.dumps(data))
request.user = user
request.session = session or {}
return request
def verify_staff_debug_present(self, staff_debug_expected): def verify_staff_debug_present(self, staff_debug_expected):
""" """
Verifies that the staff debug control visibility is as expected (for staff only). Verifies that the staff debug control visibility is as expected (for staff only).
...@@ -162,6 +152,19 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -162,6 +152,19 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
"Profile link should point to real user", "Profile link should point to real user",
) )
def ensure_masquerade_as_group_member(self, partition_id, group_id):
"""
Installs a masquerade for the test_user and test course, to enable the
user to masquerade as belonging to the specific partition/group combination.
Also verifies that the call to install the masquerade was successful.
Arguments:
partition_id (int): the integer partition id, referring to partitions already
configured in the course.
group_id (int); the integer group id, within the specified partition.
"""
self.assertEqual(200, masquerade_as_group_member(self.test_user, self.course, partition_id, group_id))
@attr(shard=1) @attr(shard=1)
class NormalStudentVisibilityTest(MasqueradeTestCase): class NormalStudentVisibilityTest(MasqueradeTestCase):
...@@ -405,13 +408,7 @@ class TestGetMasqueradingGroupId(StaffMasqueradeTestCase): ...@@ -405,13 +408,7 @@ class TestGetMasqueradingGroupId(StaffMasqueradeTestCase):
self.assertIsNone(user_partition_id) self.assertIsNone(user_partition_id)
# Install a masquerading group # Install a masquerading group
request = self._create_mock_json_request( self.ensure_masquerade_as_group_member(0, 1)
self.test_user,
data={"role": "student", "user_partition_id": 0, "group_id": 1}
)
response = handle_ajax(request, unicode(self.course.id))
self.assertEquals(response.status_code, 200)
setup_masquerade(request, self.course.id, True)
# Verify that the masquerading group is returned # Verify that the masquerading group is returned
group_id, user_partition_id = get_masquerading_group_info(self.test_user, self.course.id) group_id, user_partition_id = get_masquerading_group_info(self.test_user, self.course.id)
......
...@@ -6,7 +6,6 @@ import django.test ...@@ -6,7 +6,6 @@ import django.test
from mock import patch from mock import patch
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from courseware.masquerade import handle_ajax, setup_masquerade
from courseware.tests.test_masquerade import StaffMasqueradeTestCase from courseware.tests.test_masquerade import StaffMasqueradeTestCase
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError
...@@ -341,30 +340,17 @@ class TestMasqueradedGroup(StaffMasqueradeTestCase): ...@@ -341,30 +340,17 @@ class TestMasqueradedGroup(StaffMasqueradeTestCase):
scheme_id='cohort' scheme_id='cohort'
) )
self.course.user_partitions.append(self.user_partition) self.course.user_partitions.append(self.user_partition)
self.session = {}
modulestore().update_item(self.course, self.test_user.id) modulestore().update_item(self.course, self.test_user.id)
def _verify_masquerade_for_group(self, group): def _verify_masquerade_for_group(self, group):
""" """
Verify that the masquerade works for the specified group id. Verify that the masquerade works for the specified group id.
""" """
# Send the request to set the masquerade self.ensure_masquerade_as_group_member( # pylint: disable=no-member
request_json = { self.user_partition.id,
"role": "student", group.id if group is not None else None
"user_partition_id": self.user_partition.id,
"group_id": group.id if group is not None else None
}
request = self._create_mock_json_request(
self.test_user,
data=request_json,
session=self.session
) )
response = handle_ajax(request, unicode(self.course.id))
# pylint has issues analyzing this class (maybe due to circular imports?)
self.assertEquals(response.status_code, 200) # pylint: disable=no-member
# Now setup the masquerade for the test user
setup_masquerade(request, self.course.id, True)
scheme = self.user_partition.scheme scheme = self.user_partition.scheme
self.assertEqual( self.assertEqual(
scheme.get_group_for_user(self.course.id, self.test_user, self.user_partition), scheme.get_group_for_user(self.course.id, self.test_user, self.user_partition),
......
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