Commit b7698123 by Robert Raposa

Merge pull request #10484 from edx/robrap/TNL-2646

TNL-2646: Escape json in Studio
parents acf5bb57 3682fac8
......@@ -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,
......
from __future__ import absolute_import
import json
import logging
from django.http import HttpResponseBadRequest, Http404
......@@ -74,66 +73,6 @@ def _advanced_component_types():
return [c_type for c_type in ADVANCED_COMPONENT_TYPES if c_type not in settings.DEPRECATED_ADVANCED_COMPONENT_TYPES]
@require_GET
@login_required
def subsection_handler(request, usage_key_string):
"""
The restful handler for subsection-specific requests.
GET
html: return html page for editing a subsection
json: not currently supported
"""
if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
usage_key = UsageKey.from_string(usage_key_string)
try:
course, item, lms_link, preview_link = _get_item_in_course(request, usage_key)
except ItemNotFoundError:
return HttpResponseBadRequest()
# make sure that location references a 'sequential', otherwise return
# BadRequest
if item.location.category != 'sequential':
return HttpResponseBadRequest()
parent = get_parent_xblock(item)
# remove all metadata from the generic dictionary that is presented in a
# more normalized UI. We only want to display the XBlocks fields, not
# the fields from any mixins that have been added
fields = getattr(item, 'unmixed_class', item.__class__).fields
policy_metadata = dict(
(field.name, field.read_from(item))
for field
in fields.values()
if field.name not in ['display_name', 'start', 'due', 'format'] and field.scope == Scope.settings
)
can_view_live = False
subsection_units = item.get_children()
can_view_live = any([modulestore().has_published_version(unit) for unit in subsection_units])
return render_to_response(
'edit_subsection.html',
{
'subsection': item,
'context_course': course,
'new_unit_category': 'vertical',
'lms_link': lms_link,
'preview_link': preview_link,
'course_graders': json.dumps(CourseGradingModel.fetch(item.location.course_key).graders),
'parent_item': parent,
'locator': item.location,
'policy_metadata': policy_metadata,
'subsection_units': subsection_units,
'can_view_live': can_view_live
}
)
else:
return HttpResponseBadRequest("Only supports html requests")
def _load_mixed_class(category):
"""
Load an XBlock by category name, and apply all defined mixins
......@@ -213,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>
......
<%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
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
......@@ -16,7 +16,7 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
<%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>
<%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,
......
<%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()}
......
<%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
......
## 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>
......
<%!
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>
"""
Utilities for dealing with JSON.
Utilities for dealing with Javascript and JSON.
"""
import json
import simplejson
from django.template.defaultfilters import escapejs
from mako.filters import decode
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):
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_str (str): The JSON string to be escaped
json_string (string): The JSON string to be escaped
Returns:
(str) Escaped JSON that is safe to be embedded in HTML.
(string) 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
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 encoded JSON that is safe to be embedded in HTML.
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 encoding in the future, and
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
......@@ -62,9 +46,38 @@ def escape_json_dumps(obj, cls=EdxJSONEncoder):
cls (class): The JSON encoder class (defaults to EdxJSONEncoder)
Returns:
str: Escaped encoded JSON
(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
"""
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)
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