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
"""
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.
......@@ -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
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)
# Check parameters and fail fast if there's a problem
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
......
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