Add json escaping to Studio
Make escaping for json simpler and more consistent in Mako templates - add escape_json_dumps to escape and json.dumps - add escape_js_str to escape javascript string - refactor Studio to use escape_json_dumps in Mako templates TNL-2646: Escape json.dumps
This commit is contained in:
@@ -376,7 +376,7 @@ def certificates_list_handler(request, course_key_string):
|
||||
'certificate_url': certificate_url,
|
||||
'course_outline_url': course_outline_url,
|
||||
'upload_asset_url': upload_asset_url,
|
||||
'certificates': json.dumps(certificates),
|
||||
'certificates': certificates,
|
||||
'course_modes': course_modes,
|
||||
'certificate_web_view_url': certificate_web_view_url,
|
||||
'is_active': is_active,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.http import HttpResponseBadRequest, Http404
|
||||
@@ -153,7 +152,7 @@ def container_handler(request, usage_key_string):
|
||||
'section': section,
|
||||
'new_unit_category': 'vertical',
|
||||
'ancestor_xblocks': ancestor_xblocks,
|
||||
'component_templates': json.dumps(component_templates),
|
||||
'component_templates': component_templates,
|
||||
'xblock_info': xblock_info,
|
||||
'draft_preview_link': preview_lms_link,
|
||||
'published_preview_link': lms_link,
|
||||
|
||||
@@ -36,6 +36,7 @@ from opaque_keys.edx.locations import Location
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update
|
||||
from contentstore.course_group_config import (
|
||||
GroupConfiguration,
|
||||
@@ -317,10 +318,10 @@ def course_search_index_handler(request, course_key_string):
|
||||
try:
|
||||
reindex_course_and_check_access(course_key, request.user)
|
||||
except SearchIndexingError as search_err:
|
||||
return HttpResponse(json.dumps({
|
||||
return HttpResponse(escape_json_dumps({
|
||||
"user_message": search_err.error_list
|
||||
}), content_type=content_type, status=500)
|
||||
return HttpResponse(json.dumps({
|
||||
return HttpResponse(escape_json_dumps({
|
||||
"user_message": _("Course has been successfully reindexed.")
|
||||
}), content_type=content_type, status=200)
|
||||
|
||||
@@ -554,9 +555,6 @@ def course_index(request, course_key):
|
||||
'sections': sections,
|
||||
'course_structure': course_structure,
|
||||
'initial_state': course_outline_initial_state(locator_to_show, course_structure) if locator_to_show else None,
|
||||
'course_graders': json.dumps(
|
||||
CourseGradingModel.fetch(course_key).graders
|
||||
),
|
||||
'rerun_notification_id': current_action.id if current_action else None,
|
||||
'course_release_date': course_release_date,
|
||||
'settings_url': settings_url,
|
||||
@@ -1056,7 +1054,7 @@ def grading_handler(request, course_key_string, grader_index=None):
|
||||
return render_to_response('settings_graders.html', {
|
||||
'context_course': course_module,
|
||||
'course_locator': course_key,
|
||||
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder),
|
||||
'course_details': course_details,
|
||||
'grading_url': reverse_course_url('grading_handler', course_key),
|
||||
'is_credit_course': is_credit_course(course_key),
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
from contentstore.views.helpers import create_xblock, remove_entrance_exam_graders
|
||||
from contentstore.views.item import delete_item
|
||||
from models.settings.course_metadata import CourseMetadata
|
||||
@@ -185,7 +186,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
|
||||
try:
|
||||
exam_descriptor = modulestore().get_item(exam_key)
|
||||
return HttpResponse(
|
||||
_serialize_entrance_exam(exam_descriptor),
|
||||
escape_json_dumps({'locator': unicode(exam_descriptor.location)}),
|
||||
status=200, mimetype='application/json')
|
||||
except ItemNotFoundError:
|
||||
return HttpResponse(status=404)
|
||||
@@ -241,15 +242,6 @@ def _delete_entrance_exam(request, course_key):
|
||||
return HttpResponse(status=204)
|
||||
|
||||
|
||||
def _serialize_entrance_exam(entrance_exam_module):
|
||||
"""
|
||||
Internal helper to convert an entrance exam module/object into JSON
|
||||
"""
|
||||
return json.dumps({
|
||||
'locator': unicode(entrance_exam_module.location)
|
||||
})
|
||||
|
||||
|
||||
def add_entrance_exam_milestone(course_id, x_block):
|
||||
# Add an entrance exam milestone if one does not already exist for given xBlock
|
||||
# As this is a standalone method for entrance exam, We should check that given xBlock should be an entrance exam.
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.http import (HttpResponse, HttpResponseServerError,
|
||||
HttpResponseNotFound)
|
||||
from edxmako.shortcuts import render_to_string, render_to_response
|
||||
import functools
|
||||
import json
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
|
||||
__all__ = ['not_found', 'server_error', 'render_404', 'render_500']
|
||||
|
||||
@@ -18,7 +18,7 @@ def jsonable_error(status=500, message="The Studio servers encountered an error"
|
||||
@functools.wraps(func)
|
||||
def inner(request, *args, **kwargs):
|
||||
if request.is_ajax():
|
||||
content = json.dumps({"error": message})
|
||||
content = escape_json_dumps({"error": message})
|
||||
return HttpResponse(content, content_type="application/json",
|
||||
status=status)
|
||||
else:
|
||||
|
||||
@@ -853,7 +853,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
"due_date": get_default_time_display(xblock.due),
|
||||
"due": xblock.fields['due'].to_json(xblock.due),
|
||||
"format": xblock.format,
|
||||
"course_graders": json.dumps([grader.get('type') for grader in graders]),
|
||||
"course_graders": [grader.get('type') for grader in graders],
|
||||
"has_changes": has_changes,
|
||||
"actions": xblock_actions,
|
||||
"explanatory_message": explanatory_message,
|
||||
|
||||
@@ -191,7 +191,7 @@ def library_blocks_view(library, user, response_format):
|
||||
return render_to_response('library.html', {
|
||||
'can_edit': can_edit,
|
||||
'context_library': library,
|
||||
'component_templates': json.dumps(component_templates),
|
||||
'component_templates': component_templates,
|
||||
'xblock_info': xblock_info,
|
||||
'templates': CONTAINER_TEMPLATES,
|
||||
})
|
||||
|
||||
@@ -1642,7 +1642,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['display_name'], 'Week 1')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertIsNone(xblock_info.get('edited_by', None))
|
||||
self.assertEqual(xblock_info['course_graders'], '["Homework", "Lab", "Midterm Exam", "Final Exam"]')
|
||||
self.assertEqual(xblock_info['course_graders'], ['Homework', 'Lab', 'Midterm Exam', 'Final Exam'])
|
||||
self.assertEqual(xblock_info['start'], '2030-01-01T00:00:00Z')
|
||||
self.assertEqual(xblock_info['graded'], False)
|
||||
self.assertEqual(xblock_info['due'], None)
|
||||
|
||||
@@ -65,7 +65,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u
|
||||
published: true,
|
||||
edited_on: 'Jul 02, 2014 at 20:56 UTC',
|
||||
edited_by: 'MockUser',
|
||||
course_graders: '["Lab", "Howework"]',
|
||||
course_graders: ["Lab", "Howework"],
|
||||
has_explicit_staff_lock: false,
|
||||
child_info: {
|
||||
category: 'vertical',
|
||||
|
||||
@@ -410,7 +410,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
|
||||
getContext: function () {
|
||||
return {
|
||||
graderTypes: JSON.parse(this.model.get('course_graders'))
|
||||
graderTypes: this.model.get('course_graders')
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.defaultfilters import escapejs
|
||||
import json
|
||||
from openedx.core.lib.js_utils import (
|
||||
escape_json_dumps, escape_js_string
|
||||
)
|
||||
%>
|
||||
<!doctype html>
|
||||
<!--[if lte IE 9]><html class="ie9 lte9" lang="${LANGUAGE_CODE}"><![endif]-->
|
||||
@@ -41,7 +42,7 @@ import json
|
||||
<a class="nav-skip" href="#content">${_("Skip to main content")}</a>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.baseUrl = ${json.dumps(settings.STATIC_URL)};
|
||||
window.baseUrl = "${escape_js_string(settings.STATIC_URL) | n}";
|
||||
var require = {baseUrl: window.baseUrl};
|
||||
</script>
|
||||
<script type="text/javascript" src="${static.url("js/vendor/require.js")}"></script>
|
||||
@@ -79,14 +80,14 @@ import json
|
||||
% if context_course:
|
||||
require(['js/factories/course'], function(CourseFactory) {
|
||||
CourseFactory({
|
||||
id: "${context_course.id | escapejs}",
|
||||
id: "${escape_js_string(context_course.id) | n}",
|
||||
name: "${context_course.display_name_with_default | h}",
|
||||
url_name: "${context_course.location.name | h}",
|
||||
org: "${context_course.location.org | h}",
|
||||
num: "${context_course.location.course | h}",
|
||||
display_course_number: "${_(context_course.display_coursenumber)}",
|
||||
revision: "${context_course.location.revision | h}",
|
||||
self_paced: ${json.dumps(context_course.self_paced)}
|
||||
self_paced: ${escape_json_dumps(context_course.self_paced) | n}
|
||||
});
|
||||
});
|
||||
% endif
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<%def name="online_help_token()"><% return "certificates" %></%def>
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
import json
|
||||
from contentstore import utils
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
|
||||
<%block name="title">${_("Course Certificates")}</%block>
|
||||
@@ -30,7 +30,7 @@ CMS.User.isGlobalStaff = '${is_global_staff}'=='True' ? true : false;
|
||||
|
||||
<%block name="requirejs">
|
||||
require(["js/certificates/factories/certificates_page_factory"], function(CertificatesPageFactory) {
|
||||
CertificatesPageFactory(${json.dumps(certificates)}, "${certificate_url}", "${course_outline_url}", ${json.dumps(course_modes)}, ${json.dumps(certificate_web_view_url)}, ${json.dumps(is_active)}, ${json.dumps(certificate_activation_handler_url)} );
|
||||
CertificatesPageFactory(${escape_json_dumps(certificates) | n}, "${certificate_url}", "${course_outline_url}", ${escape_json_dumps(course_modes) | n}, ${escape_json_dumps(certificate_web_view_url) | n}, ${escape_json_dumps(is_active) | n}, ${escape_json_dumps(certificate_activation_handler_url) | n} );
|
||||
});
|
||||
</%block>
|
||||
|
||||
|
||||
@@ -8,10 +8,9 @@ else:
|
||||
%>
|
||||
</%def>
|
||||
<%!
|
||||
import json
|
||||
|
||||
from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%block name="title">${xblock.display_name_with_default} ${xblock_type_display_name(xblock) | h}</%block>
|
||||
<%block name="bodyclass">is-signedin course container view-container</%block>
|
||||
@@ -33,10 +32,11 @@ from django.utils.translation import ugettext as _
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/container"], function(ContainerFactory) {
|
||||
ContainerFactory(
|
||||
${component_templates | n}, ${json.dumps(xblock_info) | n},
|
||||
${ escape_json_dumps(component_templates) | n },
|
||||
${ escape_json_dumps(xblock_info) | n },
|
||||
"${action | h}",
|
||||
{
|
||||
isUnitPage: ${json.dumps(is_unit_page)},
|
||||
isUnitPage: ${ escape_json_dumps(is_unit_page) | n },
|
||||
canEdit: true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<%def name="online_help_token()"><% return "updates" %></%def>
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.defaultfilters import escapejs
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
|
||||
## TODO decode course # from context_course into title.
|
||||
@@ -26,7 +26,7 @@ from django.template.defaultfilters import escapejs
|
||||
"${updates_url}",
|
||||
"${handouts_locator | escapejs}",
|
||||
"${base_asset_url}",
|
||||
${json.dumps(push_notification_enabled)}
|
||||
${escape_json_dumps(push_notification_enabled) | n}
|
||||
);
|
||||
});
|
||||
</%block>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<%inherit file="base.html" />
|
||||
<%def name="online_help_token()"><% return "outline" %></%def>
|
||||
<%!
|
||||
import json
|
||||
import logging
|
||||
from util.date_utils import get_default_time_display
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
from contentstore.utils import reverse_usage_url
|
||||
from microsite_configuration import microsite
|
||||
%>
|
||||
@@ -15,7 +15,7 @@ from microsite_configuration import microsite
|
||||
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/outline"], function (OutlineFactory) {
|
||||
OutlineFactory(${json.dumps(course_structure) | n}, ${json.dumps(initial_state) | n});
|
||||
OutlineFactory(${escape_json_dumps(course_structure) | n}, ${escape_json_dumps(initial_state) | n});
|
||||
});
|
||||
</%block>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ else:
|
||||
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
import json
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%block name="title">
|
||||
%if library:
|
||||
@@ -24,11 +24,11 @@ else:
|
||||
|
||||
<%block name="requirejs">
|
||||
% if in_err:
|
||||
var hasUnit = ${json.dumps(bool(unit))},
|
||||
var hasUnit = ${escape_json_dumps(bool(unit)) | n},
|
||||
editUnitUrl = "${edit_unit_url or ""}",
|
||||
courselikeHomeUrl = "${courselike_home_url or ""}",
|
||||
is_library = ${json.dumps(library)}
|
||||
errMsg = ${json.dumps(raw_err_msg or "")};
|
||||
is_library = ${escape_json_dumps(library) | n}
|
||||
errMsg = ${escape_json_dumps(raw_err_msg or "") | n};
|
||||
|
||||
require(["js/factories/export"], function(ExportFactory) {
|
||||
ExportFactory(hasUnit, editUnitUrl, courselikeHomeUrl, is_library, errMsg);
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<%def name="experiment_group_configurations_help_token()"><% return "group_configurations" %></%def>
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
import json
|
||||
from contentstore import utils
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
|
||||
<%block name="title">${_("Group Configurations")}</%block>
|
||||
@@ -21,7 +21,7 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/group_configurations"], function(GroupConfigurationsFactory) {
|
||||
GroupConfigurationsFactory(${json.dumps(should_show_experiment_groups)}, ${json.dumps(experiment_group_configurations)}, ${json.dumps(content_group_configuration)}, "${group_configuration_url}", "${course_outline_url}");
|
||||
GroupConfigurationsFactory(${escape_json_dumps(should_show_experiment_groups) | n}, ${escape_json_dumps(experiment_group_configurations) | n}, ${escape_json_dumps(content_group_configuration) | n}, "${group_configuration_url}", "${course_outline_url}");
|
||||
});
|
||||
</%block>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ else:
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
import json
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%block name="title">
|
||||
%if library:
|
||||
@@ -239,6 +239,6 @@ else:
|
||||
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/import"], function(ImportFactory) {
|
||||
ImportFactory("${import_status_url}", ${json.dumps(library)});
|
||||
ImportFactory("${import_status_url}", ${escape_json_dumps(library) | n});
|
||||
});
|
||||
</%block>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<%inherit file="base.html" />
|
||||
<%def name="online_help_token()"><% return "content_libraries" %></%def>
|
||||
<%!
|
||||
import json
|
||||
|
||||
from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%block name="title">${context_library.display_name_with_default} ${xblock_type_display_name(context_library)}</%block>
|
||||
<%block name="bodyclass">is-signedin course container view-container view-library</%block>
|
||||
@@ -25,8 +24,8 @@ from django.utils.translation import ugettext as _
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/library"], function(LibraryFactory) {
|
||||
LibraryFactory(
|
||||
${component_templates | n},
|
||||
${json.dumps(xblock_info) | n},
|
||||
${escape_json_dumps(component_templates) | n},
|
||||
${escape_json_dumps(xblock_info) | n},
|
||||
{
|
||||
isUnitPage: false,
|
||||
page_size: 10,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<%inherit file="base.html" />
|
||||
<%!
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%def name="online_help_token()"><% return "team_course" %></%def>
|
||||
<%block name="title">${_("Course Team Settings")}</%block>
|
||||
@@ -115,7 +115,7 @@ from django.core.urlresolvers import reverse
|
||||
require(["js/factories/manage_users"], function(ManageCourseUsersFactory) {
|
||||
ManageCourseUsersFactory(
|
||||
"${context_course.display_name | h}",
|
||||
${json.dumps(users)},
|
||||
${escape_json_dumps(users) | n},
|
||||
"${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': unicode(context_course.id), 'email': '@@EMAIL@@'})}",
|
||||
${ request.user.id },
|
||||
${str(allow_actions).lower()}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<%inherit file="base.html" />
|
||||
<%!
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%def name="online_help_token()"><% return "team_library" %></%def>
|
||||
<%block name="title">${_("Library User Access")}</%block>
|
||||
@@ -108,7 +108,7 @@ from django.core.urlresolvers import reverse
|
||||
require(["js/factories/manage_users_lib"], function(ManageLibraryUsersFactory) {
|
||||
ManageLibraryUsersFactory(
|
||||
"${context_library.display_name_with_default | h}",
|
||||
${json.dumps(users)},
|
||||
${escape_json_dumps(users) | n},
|
||||
"${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': library_key, 'email': '@@EMAIL@@'})}",
|
||||
${ request.user.id },
|
||||
${str(allow_actions).lower()}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
import json
|
||||
import urllib
|
||||
from django.utils.translation import ugettext as _
|
||||
from contentstore import utils
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
|
||||
<%block name="header_extras">
|
||||
@@ -31,7 +31,7 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
|
||||
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/settings"], function(SettingsFactory) {
|
||||
SettingsFactory("${details_url}", ${json.dumps(show_min_grade_warning)});
|
||||
SettingsFactory("${details_url}", ${escape_json_dumps(show_min_grade_warning) | n});
|
||||
});
|
||||
</%block>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from contentstore import utils
|
||||
from openedx.core.lib.json_utils import escape_json_dumps
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%block name="title">${_("Advanced Settings")}</%block>
|
||||
<%block name="bodyclass">is-signedin course advanced view-settings</%block>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import json
|
||||
from contentstore import utils
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
from models.settings.course_details import CourseSettingsEncoder
|
||||
%>
|
||||
|
||||
<%block name="header_extras">
|
||||
@@ -23,7 +25,7 @@
|
||||
</%block>
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/settings_graders"], function(SettingsGradersFactory) {
|
||||
SettingsGradersFactory(_.extend(${course_details|n}, {is_credit_course: ${json.dumps(is_credit_course)}}), "${grading_url}");
|
||||
SettingsGradersFactory(_.extend(${escape_json_dumps(course_details, cls=CourseSettingsEncoder) | n}, {is_credit_course: ${escape_json_dumps(is_credit_course) | n}}), "${grading_url}");
|
||||
});
|
||||
</%block>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from contentstore.views.helpers import xblock_studio_url
|
||||
from contentstore.utils import is_visible_to_specific_content_groups
|
||||
import json
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%
|
||||
xblock_url = xblock_studio_url(xblock)
|
||||
@@ -10,7 +10,7 @@ show_inline = xblock.has_children and not xblock_url
|
||||
section_class = "level-nesting" if show_inline else "level-element"
|
||||
collapsible_class = "is-collapsible" if xblock.has_children else ""
|
||||
label = xblock.display_name_with_default or xblock.scope_ids.block_type
|
||||
messages = json.dumps(xblock.validate().to_json())
|
||||
messages = xblock.validate().to_json()
|
||||
%>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
@@ -24,7 +24,7 @@ messages = json.dumps(xblock.validate().to_json())
|
||||
<script>
|
||||
require(["jquery", "js/factories/xblock_validation"], function($, XBlockValidationFactory) {
|
||||
XBlockValidationFactory(
|
||||
${messages},
|
||||
${escape_json_dumps(messages) | n},
|
||||
$.parseJSON("${bool(xblock_url)}".toLowerCase()), // xblock_url will be None or a string
|
||||
$.parseJSON("${bool(is_root)}".toLowerCase()), // is_root will be None or a boolean
|
||||
$('div.xblock-validation-messages[data-locator="${xblock.location | h}"]')
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<%def name="online_help_token()"><% return "textbooks" %></%def>
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
|
||||
<%block name="title">${_("Textbooks")}</%block>
|
||||
@@ -28,7 +28,7 @@ CMS.URL.LMS_BASE = "${settings.LMS_BASE}"
|
||||
</%block>
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/textbooks"], function(TextbooksFactory) {
|
||||
TextbooksFactory(${json.dumps(textbooks)});
|
||||
TextbooksFactory(${escape_json_dumps(textbooks) | n});
|
||||
});
|
||||
</%block>
|
||||
|
||||
|
||||
@@ -9,11 +9,10 @@ from mock import patch
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
|
||||
|
||||
from student.tests.factories import UserFactory
|
||||
from third_party_auth.tasks import fetch_saml_metadata
|
||||
from third_party_auth.tests import testutil
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
|
||||
|
||||
TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth'
|
||||
@@ -190,7 +189,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
|
||||
response = self.client.get(self.login_page_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("TestShib", response.content)
|
||||
self.assertIn(json.dumps(TPA_TESTSHIB_LOGIN_URL, cls=EscapedEdxJSONEncoder), response.content)
|
||||
self.assertIn(escape_json_dumps(TPA_TESTSHIB_LOGIN_URL), response.content)
|
||||
return response
|
||||
|
||||
def _check_register_page(self):
|
||||
@@ -198,7 +197,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
|
||||
response = self.client.get(self.register_page_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("TestShib", response.content)
|
||||
self.assertIn(json.dumps(TPA_TESTSHIB_REGISTER_URL, cls=EscapedEdxJSONEncoder), response.content)
|
||||
self.assertIn(escape_json_dumps(TPA_TESTSHIB_REGISTER_URL), response.content)
|
||||
return response
|
||||
|
||||
def _configure_testshib_provider(self, **kwargs):
|
||||
|
||||
@@ -19,7 +19,7 @@ from django.test.client import RequestFactory
|
||||
|
||||
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
|
||||
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH
|
||||
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
from student.tests.factories import UserFactory
|
||||
from student_account.views import account_settings_context
|
||||
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
|
||||
@@ -385,7 +385,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
|
||||
"finishAuthUrl": finish_auth_url,
|
||||
"errorMessage": None,
|
||||
}
|
||||
auth_info = json.dumps(auth_info, cls=EscapedEdxJSONEncoder)
|
||||
auth_info = escape_json_dumps(auth_info)
|
||||
|
||||
expected_data = '"third_party_auth": {auth_info}'.format(
|
||||
auth_info=auth_info
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## mako
|
||||
<%! import json %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from openedx.core.lib.json_utils import EscapedEdxJSONEncoder %>
|
||||
<%! from openedx.core.lib.js_utils import escape_json_dumps %>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%inherit file="/main.html" />
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
<%static:require_module module_name="teams/js/teams_tab_factory" class_name="TeamsTabFactory">
|
||||
TeamsTabFactory({
|
||||
courseID: '${ unicode(course.id) }',
|
||||
topics: ${ json.dumps(topics, cls=EscapedEdxJSONEncoder) },
|
||||
userInfo: ${ json.dumps(user_info, cls=EscapedEdxJSONEncoder) },
|
||||
topics: ${ escape_json_dumps(topics) | n },
|
||||
userInfo: ${ escape_json_dumps(user_info) | n },
|
||||
topicUrl: '${ topic_url }',
|
||||
topicsUrl: '${ topics_url }',
|
||||
teamsUrl: '${ teams_url }',
|
||||
@@ -44,8 +44,8 @@
|
||||
teamMembershipDetailUrl: '${ team_membership_detail_url }',
|
||||
myTeamsUrl: '${ my_teams_url }',
|
||||
maxTeamSize: ${ course.teams_max_size },
|
||||
languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) },
|
||||
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) },
|
||||
languages: ${ escape_json_dumps(languages) | n },
|
||||
countries: ${ escape_json_dumps(countries) | n },
|
||||
teamsBaseUrl: '${ teams_base_url }'
|
||||
});
|
||||
</%static:require_module>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
from microsite_configuration import microsite
|
||||
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%inherit file="../main.html" />
|
||||
<%
|
||||
@@ -20,7 +20,7 @@
|
||||
% endfor
|
||||
<%static:require_module module_name="js/discovery/discovery_factory" class_name="DiscoveryFactory">
|
||||
DiscoveryFactory(
|
||||
${json.dumps(course_discovery_meanings, cls=EscapedEdxJSONEncoder)},
|
||||
${ escape_json_dumps(course_discovery_meanings) | n },
|
||||
getParameterByName('search_query')
|
||||
);
|
||||
</%static:require_module>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%!
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<%block name="js_extra">
|
||||
<%static:require_module module_name="js/student_account/logistration_factory" class_name="LogistrationFactory">
|
||||
var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) };
|
||||
var options = ${ escape_json_dumps(data) | n };
|
||||
LogistrationFactory(options);
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import json
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
|
||||
from openedx.core.lib.js_utils import escape_json_dumps
|
||||
%>
|
||||
|
||||
<%block name="pagetitle">${_("Learner Profile")}</%block>
|
||||
@@ -24,7 +24,7 @@ from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
|
||||
|
||||
<%block name="js_extra">
|
||||
<%static:require_module module_name="js/student_profile/views/learner_profile_factory" class_name="LearnerProfileFactory">
|
||||
var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) };
|
||||
var options = ${ escape_json_dumps(data) | n };
|
||||
LearnerProfileFactory(options);
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
|
||||
83
openedx/core/lib/js_utils.py
Normal file
83
openedx/core/lib/js_utils.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Utilities for dealing with Javascript and JSON.
|
||||
"""
|
||||
import json
|
||||
from django.template.defaultfilters import escapejs
|
||||
from mako.filters import decode
|
||||
from xmodule.modulestore import EdxJSONEncoder
|
||||
|
||||
|
||||
def _escape_json_for_html(json_string):
|
||||
"""
|
||||
Escape JSON that is safe to be embedded in HTML.
|
||||
|
||||
This implementation is based on escaping performed in simplejson.JSONEncoderForHTML.
|
||||
|
||||
Arguments:
|
||||
json_string (string): The JSON string to be escaped
|
||||
|
||||
Returns:
|
||||
(string) Escaped JSON that is safe to be embedded in HTML.
|
||||
|
||||
"""
|
||||
json_string = json_string.replace("&", "\\u0026")
|
||||
json_string = json_string.replace(">", "\\u003e")
|
||||
json_string = json_string.replace("<", "\\u003c")
|
||||
return json_string
|
||||
|
||||
|
||||
def escape_json_dumps(obj, cls=EdxJSONEncoder):
|
||||
"""
|
||||
JSON dumps and escapes JSON that is safe to be embedded in HTML.
|
||||
|
||||
Usage:
|
||||
Can be used inside a Mako template inside a <SCRIPT> as follows:
|
||||
var my_json = ${escape_json_dumps(my_object) | n}
|
||||
|
||||
Use the "n" Mako filter above. It is possible that the
|
||||
default filter may include html escaping in the future, and
|
||||
we must make sure to get the proper escaping.
|
||||
|
||||
Ensure ascii in json.dumps (ensure_ascii=True) allows safe skipping of Mako's
|
||||
default filter decode.utf8.
|
||||
|
||||
Arguments:
|
||||
obj: The json object to be encoded and dumped to a string
|
||||
cls (class): The JSON encoder class (defaults to EdxJSONEncoder)
|
||||
|
||||
Returns:
|
||||
(string) Escaped encoded JSON
|
||||
|
||||
"""
|
||||
encoded_json = json.dumps(obj, ensure_ascii=True, cls=cls)
|
||||
encoded_json = _escape_json_for_html(encoded_json)
|
||||
return encoded_json
|
||||
|
||||
|
||||
def escape_js_string(js_string):
|
||||
"""
|
||||
Escape a javascript string that is safe to be embedded in HTML.
|
||||
|
||||
Usage:
|
||||
Can be used inside a Mako template inside a <SCRIPT> as follows:
|
||||
var my_js_string = "${escape_js_string(my_js_string) | n}"
|
||||
|
||||
Must include the surrounding quotes for the string.
|
||||
|
||||
Use the "n" Mako filter above. It is possible that the
|
||||
default filter may include html escaping in the future, and
|
||||
we must make sure to get the proper escaping.
|
||||
|
||||
Mako's default filter decode.utf8 is applied here since this default
|
||||
filter is skipped in the Mako template with "n".
|
||||
|
||||
Arguments:
|
||||
js_string (string): The javascript string to be escaped
|
||||
|
||||
Returns:
|
||||
(string) Escaped javascript as unicode
|
||||
|
||||
"""
|
||||
js_string = decode.utf8(js_string)
|
||||
js_string = escapejs(js_string)
|
||||
return js_string
|
||||
@@ -1,70 +0,0 @@
|
||||
"""
|
||||
Utilities for dealing with JSON.
|
||||
"""
|
||||
import json
|
||||
import simplejson
|
||||
|
||||
|
||||
from xmodule.modulestore import EdxJSONEncoder
|
||||
|
||||
|
||||
class EscapedEdxJSONEncoder(EdxJSONEncoder):
|
||||
"""
|
||||
Class for encoding edx JSON which will be printed inline into HTML
|
||||
templates.
|
||||
"""
|
||||
def encode(self, obj):
|
||||
"""
|
||||
Encodes JSON that is safe to be embedded in HTML.
|
||||
"""
|
||||
return simplejson.dumps(
|
||||
simplejson.loads(super(EscapedEdxJSONEncoder, self).encode(obj)),
|
||||
cls=simplejson.JSONEncoderForHTML
|
||||
)
|
||||
|
||||
|
||||
def _escape_json_for_html(json_str):
|
||||
"""
|
||||
Escape JSON that is safe to be embedded in HTML.
|
||||
|
||||
This implementation is based on escaping performed in simplejson.JSONEncoderForHTML.
|
||||
|
||||
Arguments:
|
||||
json_str (str): The JSON string to be escaped
|
||||
|
||||
Returns:
|
||||
(str) Escaped JSON that is safe to be embedded in HTML.
|
||||
|
||||
"""
|
||||
json_str = json_str.replace("&", "\\u0026")
|
||||
json_str = json_str.replace(">", "\\u003e")
|
||||
json_str = json_str.replace("<", "\\u003c")
|
||||
return json_str
|
||||
|
||||
|
||||
def escape_json_dumps(obj, cls=EdxJSONEncoder):
|
||||
"""
|
||||
JSON dumps encoded JSON that is safe to be embedded in HTML.
|
||||
|
||||
Usage:
|
||||
Can be used inside a Mako template inside a <SCRIPT> as follows:
|
||||
var my_json = ${escape_json_dumps(my_object) | n}
|
||||
|
||||
Use the "n" Mako filter above. It is possible that the
|
||||
default filter may include html encoding in the future, and
|
||||
we must make sure to get the proper escaping.
|
||||
|
||||
Ensure ascii in json.dumps (ensure_ascii=True) allows safe skipping of Mako's
|
||||
default filter decode.utf8.
|
||||
|
||||
Arguments:
|
||||
obj: The json object to be encoded and dumped to a string
|
||||
cls (class): The JSON encoder class (defaults to EdxJSONEncoder)
|
||||
|
||||
Returns:
|
||||
str: Escaped encoded JSON
|
||||
|
||||
"""
|
||||
encoded_json = json.dumps(obj, ensure_ascii=True, cls=cls)
|
||||
encoded_json = _escape_json_for_html(encoded_json)
|
||||
return encoded_json
|
||||
@@ -1,16 +1,16 @@
|
||||
"""
|
||||
Tests for json_utils.py
|
||||
Tests for js_utils.py
|
||||
"""
|
||||
import json
|
||||
from unittest import TestCase
|
||||
from openedx.core.lib.json_utils import (
|
||||
escape_json_dumps, EscapedEdxJSONEncoder
|
||||
from openedx.core.lib.js_utils import (
|
||||
escape_json_dumps, escape_js_string
|
||||
)
|
||||
|
||||
|
||||
class TestJsonUtils(TestCase):
|
||||
class TestJSUtils(TestCase):
|
||||
"""
|
||||
Test JSON Utils
|
||||
Test JS utils
|
||||
"""
|
||||
|
||||
class NoDefaultEncoding(object):
|
||||
@@ -28,16 +28,6 @@ class TestJsonUtils(TestCase):
|
||||
def default(self, noDefaultEncodingObj):
|
||||
return noDefaultEncodingObj.value.replace("<script>", "sample-encoder-was-here")
|
||||
|
||||
def test_escapes_forward_slashes(self):
|
||||
"""
|
||||
Verify that we escape forward slashes with backslashes.
|
||||
"""
|
||||
malicious_json = {'</script><script>alert("hello, ");</script>': '</script><script>alert("world!");</script>'}
|
||||
self.assertNotIn(
|
||||
'</script>',
|
||||
json.dumps(malicious_json, cls=EscapedEdxJSONEncoder)
|
||||
)
|
||||
|
||||
def test_escape_json_dumps_escapes_unsafe_html(self):
|
||||
"""
|
||||
Test escape_json_dumps properly escapes &, <, and >.
|
||||
@@ -70,3 +60,15 @@ class TestJsonUtils(TestCase):
|
||||
|
||||
encoded_json = escape_json_dumps(malicious_json, cls=self.SampleJSONEncoder)
|
||||
self.assertEquals(expected_custom_encoded_json, encoded_json)
|
||||
|
||||
def test_escape_js_string_escapes_unsafe_html(self):
|
||||
"""
|
||||
Test escape_js_string escapes &, <, and >, as well as returns a unicode type
|
||||
"""
|
||||
malicious_js_string = "</script><script>alert('hello, ');</script>"
|
||||
|
||||
expected_escaped_js_string = unicode(
|
||||
r"\u003C/script\u003E\u003Cscript\u003Ealert(\u0027hello, \u0027)\u003B\u003C/script\u003E"
|
||||
)
|
||||
escaped_js_string = escape_js_string(malicious_js_string)
|
||||
self.assertEquals(expected_escaped_js_string, escaped_js_string)
|
||||
Reference in New Issue
Block a user