Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #11953 from edx/preview-security-fix
Browse files Browse the repository at this point in the history
Restrict non-staff users to access preview content.
  • Loading branch information
nedbat committed Apr 14, 2016
2 parents eebae96 + c0e7861 commit e8186a1
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 37 deletions.
23 changes: 20 additions & 3 deletions cms/djangoapps/contentstore/tests/test_course_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import json
import copy
import mock
from mock import patch
from mock import Mock, patch
import unittest

from django.conf import settings
Expand All @@ -20,7 +20,7 @@
from models.settings.encoder import CourseSettingsEncoder
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.models.course_details import CourseDetails
from student.roles import CourseInstructorRole
from student.roles import CourseInstructorRole, CourseStaffRole
from student.tests.factories import UserFactory
from xmodule.fields import Date
from xmodule.modulestore import ModuleStoreEnum
Expand All @@ -29,7 +29,7 @@
from xmodule.tabs import InvalidTabsException
from util.milestones_helpers import seed_milestone_relationship_types

from .utils import CourseTestCase
from .utils import CourseTestCase, AjaxEnabledTestClient


def get_url(course_id, handler_name='settings_handler'):
Expand Down Expand Up @@ -955,6 +955,23 @@ def test_course_tab_configurations(self, tab_list):
tab_list.append(self.notes_tab)
self.assertEqual(tab_list, course.tabs)

@patch.dict(settings.FEATURES, {'ENABLE_EDXNOTES': True})
@patch('xmodule.util.django.get_current_request')
def test_post_settings_with_staff_not_enrolled(self, mock_request):
"""
Tests that we can post advance settings when course staff is not enrolled.
"""
mock_request.return_value = Mock(META={'HTTP_HOST': 'localhost'})
user = UserFactory.create(is_staff=True)
CourseStaffRole(self.course.id).add_users(user)

client = AjaxEnabledTestClient()
client.login(username=user.username, password=user.password)
response = self.client.ajax_post(self.course_setting_url, {
'advanced_modules': {"value": [""]}
})
self.assertEqual(response.status_code, 200)


class CourseGraderUpdatesTest(CourseTestCase):
"""
Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def lms_link_test(self):
link = utils.get_lms_link_for_item(location, True)
self.assertEquals(
link,
"//preview/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us"
"//preview.localhost/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us"
)

# now test with the course' location
Expand Down
2 changes: 1 addition & 1 deletion cms/envs/bok_choy.env.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"ENABLE_S3_GRADE_DOWNLOADS": true,
"ENTRANCE_EXAMS": true,
"MILESTONES_APP": true,
"PREVIEW_LMS_BASE": "localhost:8003",
"PREVIEW_LMS_BASE": "preview.localhost:8003",
"SUBDOMAIN_BRANDING": false,
"SUBDOMAIN_COURSE_LISTINGS": false,
"ALLOW_ALL_ADVANCED_COMPONENTS": true,
Expand Down
3 changes: 2 additions & 1 deletion cms/envs/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@
MIGRATION_MODULES = {app: "app.migrations_not_used_in_tests" for app in INSTALLED_APPS}

LMS_BASE = "localhost:8000"
FEATURES['PREVIEW_LMS_BASE'] = "preview"
FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost"


CACHES = {
# This is the cache used for most things. Askbot will not work without a
Expand Down
57 changes: 55 additions & 2 deletions lms/djangoapps/courseware/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@
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
)

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -102,6 +105,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):
Expand Down Expand Up @@ -139,6 +146,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.
Expand Down Expand Up @@ -659,7 +712,7 @@ def _has_access_to_course(user, access_level, course_key):
debug("Deny: no user or anon user")
return ACCESS_DENIED

if is_masquerading_as_student(user, course_key):
if not in_preview_mode() and is_masquerading_as_student(user, course_key):
return ACCESS_DENIED

if GlobalStaff().has_user(user):
Expand Down
3 changes: 2 additions & 1 deletion lms/djangoapps/courseware/access_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ def in_preview_mode():
Returns whether the user is in preview mode or not.
"""
hostname = get_current_request_hostname()
return bool(hostname and settings.PREVIEW_DOMAIN in hostname.split('.'))
preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE', None)
return bool(preview_lms_base and hostname and hostname.split(':')[0] == preview_lms_base.split(':')[0])
Loading

0 comments on commit e8186a1

Please sign in to comment.