Commit 6d613f9d by Calen Pennington

Enable un-authenticated handler urls

Updates to depend on the latest version of XBlock, which includes
support for service-to-service (thirdparty) handler urls, which aren't
authenticated with a user (unlike handler requests coming from the
xblock client-side javascript).

Co-author: Ned Batchelder <ned@edx.org>
parent 32941969
...@@ -115,7 +115,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method ...@@ -115,7 +115,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
""" """
An XModule ModuleSystem for use in Studio previews An XModule ModuleSystem for use in Studio previews
""" """
def handler_url(self, block, handler_name, suffix='', query=''): def handler_url(self, block, handler_name, suffix='', query='', thirdparty=False):
return handler_prefix(block, handler_name, suffix) + '?' + query return handler_prefix(block, handler_name, suffix) + '?' + query
......
...@@ -42,7 +42,7 @@ class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method ...@@ -42,7 +42,7 @@ class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
""" """
ModuleSystem for testing ModuleSystem for testing
""" """
def handler_url(self, block, handler, suffix='', query=''): def handler_url(self, block, handler, suffix='', query='', thirdparty=False):
return str(block.scope_ids.usage_id) + '/' + handler + '/' + suffix + '?' + query return str(block.scope_ids.usage_id) + '/' + handler + '/' + suffix + '?' + query
......
...@@ -403,6 +403,7 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me ...@@ -403,6 +403,7 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me
data is a dictionary-like object with the content of the request""" data is a dictionary-like object with the content of the request"""
return u"" return u""
@XBlock.handler
def xmodule_handler(self, request, suffix=None): def xmodule_handler(self, request, suffix=None):
""" """
XBlock handler that wraps `handle_ajax` XBlock handler that wraps `handle_ajax`
......
...@@ -14,7 +14,7 @@ from django.core.exceptions import PermissionDenied ...@@ -14,7 +14,7 @@ from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import Http404 from django.http import Http404
from django.http import HttpResponse from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt, csrf_protect
from capa.xqueue_interface import XQueueInterface from capa.xqueue_interface import XQueueInterface
from courseware.access import has_access from courseware.access import has_access
...@@ -482,6 +482,14 @@ def xqueue_callback(request, course_id, userid, mod_id, dispatch): ...@@ -482,6 +482,14 @@ def xqueue_callback(request, course_id, userid, mod_id, dispatch):
return HttpResponse("") return HttpResponse("")
@csrf_exempt
def handle_xblock_callback_noauth(request, course_id, usage_id, handler, suffix=None):
"""
Entry point for unauthenticated XBlock handlers.
"""
return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, request.user)
def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None): def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
""" """
Generic view for extensions. This is where AJAX calls go. Generic view for extensions. This is where AJAX calls go.
...@@ -497,15 +505,23 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None): ...@@ -497,15 +505,23 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
not accessible by the user, or the module raises NotFoundError. If the not accessible by the user, or the module raises NotFoundError. If the
module raises any other error, it will escape this function. module raises any other error, it will escape this function.
""" """
if not request.user.is_authenticated():
raise PermissionDenied
return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, request.user)
def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
"""
Invoke an XBlock handler, either authenticated or not.
"""
location = unquote_slashes(usage_id) location = unquote_slashes(usage_id)
# Check parameters and fail fast if there's a problem # Check parameters and fail fast if there's a problem
if not Location.is_valid(location): if not Location.is_valid(location):
raise Http404("Invalid location") raise Http404("Invalid location")
if not request.user.is_authenticated():
raise PermissionDenied
# Check submitted files # Check submitted files
files = request.FILES or {} files = request.FILES or {}
error_msg = _check_files_limits(files) error_msg = _check_files_limits(files)
...@@ -525,15 +541,14 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None): ...@@ -525,15 +541,14 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
field_data_cache = FieldDataCache.cache_for_descriptor_descendents( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course_id, course_id,
request.user, user,
descriptor descriptor
) )
instance = get_module(user, request, location, field_data_cache, course_id, grade_bucket_type='ajax')
instance = get_module(request.user, request, location, field_data_cache, course_id, grade_bucket_type='ajax')
if instance is None: if instance is None:
# Either permissions just changed, or someone is trying to be clever # Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to. # and load something they shouldn't have access to.
log.debug("No module %s for user %s -- access denied?", location, request.user) log.debug("No module %s for user %s -- access denied?", location, user)
raise Http404 raise Http404
req = django_to_webob_request(request) req = django_to_webob_request(request)
......
...@@ -185,9 +185,11 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -185,9 +185,11 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
return mock_file return mock_file
def test_invalid_location(self): def test_invalid_location(self):
request = self.request_factory.post('dummy_url', data={'position': 1})
request.user = self.mock_user
with self.assertRaises(Http404): with self.assertRaises(Http404):
render.handle_xblock_callback( render.handle_xblock_callback(
None, request,
'dummy/course/id', 'dummy/course/id',
'invalid Location', 'invalid Location',
'dummy_handler' 'dummy_handler'
......
...@@ -58,11 +58,31 @@ def unquote_slashes(text): ...@@ -58,11 +58,31 @@ def unquote_slashes(text):
return re.sub(r'(;;|;_)', _unquote_slashes, text) return re.sub(r'(;;|;_)', _unquote_slashes, text)
def handler_url(course_id, block, handler, suffix='', query=''): def handler_url(course_id, block, handler, suffix='', query='', thirdparty=False):
""" """
Return an xblock handler url for the specified course, block and handler Return an XBlock handler url for the specified course, block and handler.
If handler is an empty string, this function is being used to create a
prefix of the general URL, which is assumed to be followed by handler name
and suffix.
If handler is specified, then it is checked for being a valid handler
function, and ValueError is raised if not.
""" """
return reverse('xblock_handler', kwargs={ view_name = 'xblock_handler'
if handler:
# Be sure this is really a handler.
func = getattr(block, handler, None)
if not func:
raise ValueError("{!r} is not a function name".format(handler))
if not getattr(func, "_is_xblock_handler", False):
raise ValueError("{!r} is not a handler name".format(handler))
if thirdparty:
view_name = 'xblock_handler_noauth'
return reverse(view_name, kwargs={
'course_id': course_id, 'course_id': course_id,
'usage_id': quote_slashes(str(block.scope_ids.usage_id)), 'usage_id': quote_slashes(str(block.scope_ids.usage_id)),
'handler': handler, 'handler': handler,
...@@ -72,8 +92,11 @@ def handler_url(course_id, block, handler, suffix='', query=''): ...@@ -72,8 +92,11 @@ def handler_url(course_id, block, handler, suffix='', query=''):
def handler_prefix(course_id, block): def handler_prefix(course_id, block):
""" """
Returns a prefix for use by the javascript handler_url function. Returns a prefix for use by the Javascript handler_url function.
The prefix is a valid handler url the handler name is appended to it.
The prefix is a valid handler url after the handler name is slash-appended
to it.
""" """
return handler_url(course_id, block, '').rstrip('/') return handler_url(course_id, block, '').rstrip('/')
...@@ -86,10 +109,11 @@ class LmsHandlerUrls(object): ...@@ -86,10 +109,11 @@ class LmsHandlerUrls(object):
This must be mixed in to a runtime that already accepts and stores This must be mixed in to a runtime that already accepts and stores
a course_id a course_id
""" """
# pylint: disable=unused-argument
def handler_url(self, block, handler_name, suffix='', query=''): # pylint: disable=unused-argument # pylint: disable=no-member
def handler_url(self, block, handler_name, suffix='', query='', thirdparty=False):
"""See :method:`xblock.runtime:Runtime.handler_url`""" """See :method:`xblock.runtime:Runtime.handler_url`"""
return handler_url(self.course_id, block, handler_name, suffix='', query='') # pylint: disable=no-member return handler_url(self.course_id, block, handler_name, suffix='', query='', thirdparty=thirdparty)
class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract-method class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract-method
......
...@@ -178,7 +178,9 @@ if settings.COURSEWARE_ENABLED: ...@@ -178,7 +178,9 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$',
'courseware.module_render.handle_xblock_callback', 'courseware.module_render.handle_xblock_callback',
name='xblock_handler'), name='xblock_handler'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler_noauth/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$',
'courseware.module_render.handle_xblock_callback_noauth',
name='xblock_handler_noauth'),
# Software Licenses # Software Licenses
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk -e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
# Our libraries: # Our libraries:
-e git+https://github.com/edx/XBlock.git@d6d2fc91#egg=XBlock -e git+https://github.com/edx/XBlock.git@341d162f353289cfd3974a4f4f9354ce81ab60db#egg=XBlock
-e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail -e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.2.6#egg=diff_cover -e git+https://github.com/edx/diff-cover.git@v0.2.6#egg=diff_cover
-e git+https://github.com/edx/js-test-tool.git@v0.1.4#egg=js_test_tool -e git+https://github.com/edx/js-test-tool.git@v0.1.4#egg=js_test_tool
......
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