Commit ef483650 by cewing

MIT CCX: Use CCX Keys - responses to code review

remove references to middleware that were missed previously

use key apis rather than local implementation of key conversion.  remove local implementationa

remove spurious test for attribute

fix test setUp to avoid unneeded flattening

code quality fixes

add security check ensuring that the coach is coach for *this* CCX.

prevent ccx/deprecated course id problems

1.  do not allow ccx objects to be created if the course id is deprecated
2.  filter out any ccx memberships that involve deprecated course ids (in case there are bad ccxs in the database)

Fix test failures and errors arising from incorrect code path execution

Create context manager to handle unwrapping and restoring ccx values for the modulestore wrapper, employ it throughout modulestore wrapper implementation
parent 6a0c9aee
......@@ -48,23 +48,17 @@ class TestCCXModulestoreWrapper(ModuleStoreTestCase):
2010, 7, 7, 0, 0, tzinfo=pytz.UTC)
chapters = [ItemFactory.create(start=start, parent=course)
for _ in xrange(2)]
sequentials = flatten([
[
ItemFactory.create(parent=chapter) for _ in xrange(2)
] for chapter in chapters
])
verticals = flatten([
[
ItemFactory.create(
due=due, parent=sequential, graded=True, format='Homework'
) for _ in xrange(2)
] for sequential in sequentials
])
blocks = flatten([ # pylint: disable=unused-variable
[
ItemFactory.create(parent=vertical) for _ in xrange(2)
] for vertical in verticals
])
sequentials = [
ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters
]
verticals = [
ItemFactory.create(
due=due, parent=s, graded=True, format='Homework'
) for _ in xrange(2) for s in sequentials
]
blocks = [
ItemFactory.create(parent=v) for _ in xrange(2) for v in verticals
]
self.ccx = ccx = CustomCourseForEdX(
course_id=course.id,
......
......@@ -69,9 +69,9 @@ class TestEmailEnrollmentState(ModuleStoreTestCase):
"""verify behavior for non-user email address
"""
ee_state = self.create_one(email='nobody@nowhere.com')
for attr in ['user', 'member', 'full_name', 'in_ccx']:
value = getattr(ee_state, attr, 'missing attribute')
self.assertFalse(value, "{}: {}".format(value, attr))
for attribute in ['user', 'member', 'full_name', 'in_ccx']:
value = getattr(ee_state, attribute, 'missing attribute')
self.assertFalse(value, "{}: {}".format(value, attribute))
def test_enrollment_state_for_non_member_user(self):
"""verify behavior for email address of user who is not a ccx memeber
......@@ -89,10 +89,10 @@ class TestEmailEnrollmentState(ModuleStoreTestCase):
self.create_user()
self.register_user_in_ccx()
ee_state = self.create_one()
for attr in ['user', 'in_ccx']:
for attribute in ['user', 'in_ccx']:
self.assertTrue(
getattr(ee_state, attr, False),
"attribute {} is missing or False".format(attr)
getattr(ee_state, attribute, False),
"attribute {} is missing or False".format(attribute)
)
self.assertEqual(ee_state.member, self.user)
self.assertEqual(ee_state.full_name, self.user.profile.name)
......
......@@ -262,6 +262,17 @@ def get_ccx_membership_triplets(user, course_org_filter, org_filter_out_set):
elif course.location.org in org_filter_out_set:
continue
# If, somehow, we've got a ccx that has been created for a
# course with a deprecated ID, we must filter it out. Emit a
# warning to the log so we can clean up.
if course.location.deprecated:
log.warning(
"CCX {} exists for course {} with deprecated id".format(
ccx, ccx.course_id
)
)
continue
yield (ccx, membership, course)
else:
log.error("User {0} enrolled in {2} course {1}".format( # pylint: disable=logging-format-interpolation
......
......@@ -18,6 +18,7 @@ from django.http import (
HttpResponseForbidden,
HttpResponseRedirect,
)
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.http import Http404
......@@ -74,8 +75,6 @@ def coach_dashboard(view):
course_key = CourseKey.from_string(course_id)
ccx = None
if isinstance(course_key, CCXLocator):
# is there a security leak here in not checking that this user is
# the coach for this ccx?
ccx_id = course_key.ccx
ccx = CustomCourseForEdX.objects.get(pk=ccx_id)
course_key = ccx.course_id
......@@ -86,6 +85,15 @@ def coach_dashboard(view):
_('You must be a CCX Coach to access this view.'))
course = get_course_by_id(course_key, depth=None)
# if there is a ccx, we must validate that it is the ccx for this coach
if ccx is not None:
coach_ccx = get_ccx_for_coach(course, request.user)
if coach_ccx is None or coach_ccx.id != ccx.id:
return HttpResponseForbidden(
_('You must be the coach for this ccx to access this view')
)
return view(request, course, ccx)
return wrapper
......@@ -140,6 +148,17 @@ def create_ccx(request, course, ccx=None):
Create a new CCX
"""
name = request.POST.get('name')
# prevent CCX objects from being created for deprecated course ids.
if course.id.deprecated:
messages.error(_(
"You cannot create a CCX from a course using a deprecated id. "
"Please create a rerun of this course in the studio to allow "
"this action.")
)
url = reverse('ccx_coach_dashboard', kwargs={'course_id', course.id})
return redirect(url)
ccx = CustomCourseForEdX(
course_id=course.id,
coach=request.user,
......
......@@ -110,9 +110,10 @@ def has_access(user, action, obj, course_key=None):
if isinstance(obj, XBlock):
return _has_access_descriptor(user, action, obj, course_key)
if isinstance(obj, CCXLocator):
return _has_access_ccx_key(user, action, obj)
if isinstance(obj, CourseKey):
if isinstance(obj, CCXLocator):
obj = obj.to_course_locator()
return _has_access_course_key(user, action, obj)
if isinstance(obj, UsageKey):
......@@ -493,6 +494,10 @@ def _has_access_course_key(user, action, course_key):
return _dispatch(checkers, action, user, course_key)
def _has_access_ccx_key(user, action, ccx_key):
course_key = ccx_key.to_course_locator()
return _has_access_course_key(user, action, course_key)
def _has_access_string(user, action, perm):
"""
......
......@@ -43,6 +43,7 @@ from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.xml import XMLModuleStore
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from ccx.modulestore import CCXModulestoreWrapper
log = logging.getLogger(__name__)
......@@ -59,9 +60,13 @@ class SysadminDashboardView(TemplateView):
modulestore_type and return msg
"""
self.def_ms = modulestore()
self.def_ms = check_ms = modulestore()
# if the modulestore is wrapped by CCX, unwrap it for checking purposes
if isinstance(check_ms, CCXModulestoreWrapper):
check_ms = check_ms._modulestore
self.is_using_mongo = True
if isinstance(self.def_ms, XMLModuleStore):
if isinstance(check_ms, XMLModuleStore):
self.is_using_mongo = False
self.msg = u''
self.datatable = []
......
......@@ -33,6 +33,7 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
from xmodule.modulestore.xml import XMLModuleStore
from ccx.modulestore import CCXModulestoreWrapper
TEST_MONGODB_LOG = {
......@@ -315,8 +316,12 @@ class TestSysadmin(SysadminBaseTestCase):
# Create git loaded course
response = self._add_edx4edx()
def_ms = modulestore()
self.assertIn('xml', str(def_ms.__class__))
def_ms = check_ms = modulestore()
# if the modulestore is wrapped by CCX, unwrap it for testing purposes
if isinstance(check_ms, CCXModulestoreWrapper):
check_ms = check_ms._modulestore
self.assertIn('xml', str(check_ms.__class__))
course = def_ms.courses.get('{0}/edx4edx_lite'.format(
os.path.abspath(settings.DATA_DIR)), None)
self.assertIsNotNone(course)
......
......@@ -612,7 +612,6 @@ ECOMMERCE_API_TIMEOUT = ENV_TOKENS.get('ECOMMERCE_API_TIMEOUT', ECOMMERCE_API_TI
##### Custom Courses for EdX #####
if FEATURES.get('CUSTOM_COURSES_EDX'):
INSTALLED_APPS += ('ccx',)
MIDDLEWARE_CLASSES += ('ccx.overrides.CcxMiddleware',)
FIELD_OVERRIDE_PROVIDERS += (
'ccx.overrides.CustomCoursesForEdxOverrideProvider',
)
......
......@@ -307,7 +307,6 @@ GRADES_DOWNLOAD_ROUTING_KEY = HIGH_MEM_QUEUE
##### Custom Courses for EdX #####
if FEATURES.get('CUSTOM_COURSES_EDX'):
INSTALLED_APPS += ('ccx',)
MIDDLEWARE_CLASSES += ('ccx.overrides.CcxMiddleware',)
FIELD_OVERRIDE_PROVIDERS += (
'ccx.overrides.CustomCoursesForEdxOverrideProvider',
)
......
......@@ -22,6 +22,18 @@ from django.core.urlresolvers import reverse
<section class="instructor-dashboard-content-2" id="ccx-coach-dashboard-content">
<h1>${_("CCX Coach Dashboard")}</h1>
% if messages:
<ul class="messages">
% for message in messages:
% if message.tags:
<li class="${message.tags}">${message}</li>
% else:
<li>${message}</li>
% endif
% endfor
</ul>
% endif
%if not ccx:
<section>
<form action="${create_ccx_url}" method="POST">
......
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