diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 9e6f246688d8..73b2a835f6bf 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -11,6 +11,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.core.cache import cache +from django.core.context_processors import csrf from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.http import Http404, HttpResponse @@ -29,7 +30,7 @@ from xblock.core import XBlock from xblock.fields import Scope from xblock.runtime import KvsFieldData, KeyValueStore -from xblock.exceptions import NoSuchHandlerError +from xblock.exceptions import NoSuchHandlerError, NoSuchViewError from xblock.django.request import django_to_webob_request, webob_to_django_response from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor from xmodule.exceptions import NotFoundError, ProcessingError @@ -562,23 +563,19 @@ def xblock_resource(request, block_type, uri): # pylint: disable=unused-argumen return HttpResponse(content, mimetype=mimetype) -def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): +def _get_module_by_usage_id(request, course_id, usage_id): """ - Invoke an XBlock handler, either authenticated or not. + Gets a module instance based on its `usage_id` in a course, for a given request/user + Returns (location, descriptor, instance) """ location = unquote_slashes(usage_id) + user = request.user # Check parameters and fail fast if there's a problem if not Location.is_valid(location): raise Http404("Invalid location") - # Check submitted files - files = request.FILES or {} - error_msg = _check_files_limits(files) - if error_msg: - return HttpResponse(json.dumps({'success': error_msg})) - try: descriptor = modulestore().get_instance(course_id, location) except ItemNotFoundError: @@ -590,13 +587,6 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): ) raise Http404 - tracking_context_name = 'module_callback_handler' - tracking_context = { - 'module': { - 'display_name': descriptor.display_name_with_default, - } - } - field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_id, user, @@ -609,6 +599,29 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): log.debug("No module %s for user %s -- access denied?", location, user) raise Http404 + return (location, descriptor, instance) + + +def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): + """ + Invoke an XBlock handler, either authenticated or not. + + """ + location, descriptor, instance = _get_module_by_usage_id(request, course_id, usage_id) + + # Check submitted files + files = request.FILES or {} + error_msg = _check_files_limits(files) + if error_msg: + return HttpResponse(json.dumps({'success': error_msg})) + + tracking_context_name = 'module_callback_handler' + tracking_context = { + 'module': { + 'display_name': descriptor.display_name_with_default, + } + } + req = django_to_webob_request(request) try: with tracker.get_tracker().context(tracking_context_name, tracking_context): @@ -637,6 +650,30 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): return webob_to_django_response(resp) +def xblock_view(request, course_id, usage_id, view_name): + """ + Returns the rendered view of a given XBlock, with related resources + + Returns a json object containing two keys: + html: The rendered html of the view + resources: A list of tuples where the first element is the resource hash, and + the second is the resource description + """ + location, descriptor, instance = _get_module_by_usage_id(request, course_id, usage_id) + + try: + fragment = instance.render(view_name) + except NoSuchViewError: + log.exception("Attempt to render missing view on %s: %s", instance, view_name) + raise Http404 + + return JsonResponse({ + 'html': fragment.content, + 'resources': fragment.resources, + 'csrf_token': str(csrf(request)['csrf_token']), + }) + + def get_score_bucket(grade, max_grade): """ Function to split arbitrary score ranges into 3 buckets. diff --git a/lms/envs/aws.py b/lms/envs/aws.py index d9bd6146fe75..1e8e9495dca2 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -276,6 +276,14 @@ HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS = ENV_TOKENS.get('HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS',{}) +############# CORS headers for cross-domain requests ################# + +if FEATURES.get('ENABLE_CORS_HEADERS'): + INSTALLED_APPS += ('corsheaders',) + MIDDLEWARE_CLASSES += ('corsheaders.middleware.CorsMiddleware',) + CORS_ALLOW_CREDENTIALS = True + CORS_ORIGIN_WHITELIST = ENV_TOKENS.get('CORS_ORIGIN_WHITELIST', ()) + ############################## SECURE AUTH ITEMS ############### # Secret things: passwords, access keys, etc. diff --git a/lms/envs/common.py b/lms/envs/common.py index ac3cba4553c2..673f71eaa7d8 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -113,6 +113,9 @@ # with Shib. Feature was requested by Stanford's office of general counsel 'SHIB_DISABLE_TOS': False, + # Allows to configure the LMS to provide CORS headers to serve requests from other domains + 'ENABLE_CORS_HEADERS': False, + # Can be turned off if course lists need to be hidden. Effects views and templates. 'COURSES_ARE_BROWSABLE': True, @@ -1243,6 +1246,14 @@ INSTALLED_APPS += ('django_cas',) MIDDLEWARE_CLASSES += ('django_cas.middleware.CASMiddleware',) +############# CORS headers for cross-domain requests ################# + +if FEATURES.get('ENABLE_CORS_HEADERS'): + INSTALLED_APPS += ('corsheaders',) + MIDDLEWARE_CLASSES += ('corsheaders.middleware.CorsMiddleware',) + CORS_ALLOW_CREDENTIALS = True + CORS_ORIGIN_WHITELIST = ('') + ###################### Registration ################################## # For each of the fields, give one of the following values: diff --git a/lms/lib/xblock/runtime.py b/lms/lib/xblock/runtime.py index 895e8f2c74c5..a8f46fdc9263 100644 --- a/lms/lib/xblock/runtime.py +++ b/lms/lib/xblock/runtime.py @@ -4,6 +4,7 @@ import re +from django.conf import settings from django.core.urlresolvers import reverse from user_api import user_service @@ -106,10 +107,11 @@ def local_resource_url(self, block, uri): """ local_resource_url for Studio """ - return reverse('xblock_resource_url', kwargs={ + path = reverse('xblock_resource_url', kwargs={ 'block_type': block.scope_ids.block_type, 'uri': uri, }) + return '//{}{}'.format(settings.SITE_NAME, path) class LmsPartitionService(PartitionService): diff --git a/lms/urls.py b/lms/urls.py index b214d49741e8..cc0cd193e0ac 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -185,6 +185,9 @@ url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xblock/(?P[^/]*)/handler/(?P[^/]*)(?:/(?P.*))?$', 'courseware.module_render.handle_xblock_callback', name='xblock_handler'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xblock/(?P[^/]*)/view/(?P[^/]*)$', + 'courseware.module_render.xblock_view', + name='xblock_view'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xblock/(?P[^/]*)/handler_noauth/(?P[^/]*)(?:/(?P.*))?$', 'courseware.module_render.handle_xblock_callback_noauth', name='xblock_handler_noauth'), diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 80b3184fab7e..263714dc48d2 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -91,6 +91,9 @@ sphinx_rtd_theme==0.1.5 Babel==1.3 transifex-client==0.10 +# Used to allow to configure CORS headers for cross-domain requests +django-cors-headers==0.12 + # We've tried several times to update the debug toolbar to version 1.0.1, # and had problems each time, resulting in us rolling back to 0.9.4. Before # submitting another pull request to do this update, check the following: