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>
This commit is contained in:
@@ -115,7 +115,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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"""
|
||||
return u""
|
||||
|
||||
@XBlock.handler
|
||||
def xmodule_handler(self, request, suffix=None):
|
||||
"""
|
||||
XBlock handler that wraps `handle_ajax`
|
||||
|
||||
@@ -14,7 +14,7 @@ from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import Http404
|
||||
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 courseware.access import has_access
|
||||
@@ -482,6 +482,14 @@ def xqueue_callback(request, course_id, userid, mod_id, dispatch):
|
||||
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):
|
||||
"""
|
||||
Generic view for extensions. This is where AJAX calls go.
|
||||
@@ -496,6 +504,17 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
|
||||
the location and course_id do not identify a valid module, the module is
|
||||
not accessible by the user, or the module raises NotFoundError. If the
|
||||
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)
|
||||
|
||||
@@ -503,9 +522,6 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
|
||||
if not Location.is_valid(location):
|
||||
raise Http404("Invalid location")
|
||||
|
||||
if not request.user.is_authenticated():
|
||||
raise PermissionDenied
|
||||
|
||||
# Check submitted files
|
||||
files = request.FILES or {}
|
||||
error_msg = _check_files_limits(files)
|
||||
@@ -525,15 +541,14 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
|
||||
|
||||
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
|
||||
course_id,
|
||||
request.user,
|
||||
user,
|
||||
descriptor
|
||||
)
|
||||
|
||||
instance = get_module(request.user, request, location, field_data_cache, course_id, grade_bucket_type='ajax')
|
||||
instance = get_module(user, request, location, field_data_cache, course_id, grade_bucket_type='ajax')
|
||||
if instance is None:
|
||||
# Either permissions just changed, or someone is trying to be clever
|
||||
# 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
|
||||
|
||||
req = django_to_webob_request(request)
|
||||
|
||||
@@ -185,9 +185,11 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
return mock_file
|
||||
|
||||
def test_invalid_location(self):
|
||||
request = self.request_factory.post('dummy_url', data={'position': 1})
|
||||
request.user = self.mock_user
|
||||
with self.assertRaises(Http404):
|
||||
render.handle_xblock_callback(
|
||||
None,
|
||||
request,
|
||||
'dummy/course/id',
|
||||
'invalid Location',
|
||||
'dummy_handler'
|
||||
|
||||
@@ -58,11 +58,31 @@ def 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,
|
||||
'usage_id': quote_slashes(str(block.scope_ids.usage_id)),
|
||||
'handler': handler,
|
||||
@@ -72,8 +92,11 @@ def handler_url(course_id, block, handler, suffix='', query=''):
|
||||
|
||||
def handler_prefix(course_id, block):
|
||||
"""
|
||||
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.
|
||||
Returns a prefix for use by the Javascript handler_url function.
|
||||
|
||||
The prefix is a valid handler url after the handler name is slash-appended
|
||||
to it.
|
||||
|
||||
"""
|
||||
return handler_url(course_id, block, '').rstrip('/')
|
||||
|
||||
@@ -86,10 +109,11 @@ class LmsHandlerUrls(object):
|
||||
This must be mixed in to a runtime that already accepts and stores
|
||||
a course_id
|
||||
"""
|
||||
|
||||
def handler_url(self, block, handler_name, suffix='', query=''): # pylint: disable=unused-argument
|
||||
# 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`"""
|
||||
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
|
||||
|
||||
@@ -178,7 +178,9 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$',
|
||||
'courseware.module_render.handle_xblock_callback',
|
||||
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
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
|
||||
|
||||
# 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/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
|
||||
|
||||
Reference in New Issue
Block a user