Commit 90f2fb86 by Robert Raposa

Merge pull request #11257 from edx/robrap/refactor-js-utils

Refactor js_util helpers
parents 901cafe5 f555ffd5
......@@ -70,7 +70,7 @@ from openedx.core.djangoapps.programs.utils import get_programs
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.lib.course_tabs import CourseTabPluginManager
from openedx.core.lib.courses import course_image_url
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from student import auth
from student.auth import has_course_author_access, has_studio_write_access, has_studio_read_access
from student.roles import (
......@@ -324,10 +324,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(escape_json_dumps({
return HttpResponse(dump_js_escaped_json({
"user_message": search_err.error_list
}), content_type=content_type, status=500)
return HttpResponse(escape_json_dumps({
return HttpResponse(dump_js_escaped_json({
"user_message": _("Course has been successfully reindexed.")
}), content_type=content_type, status=200)
......
......@@ -10,7 +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 openedx.core.djangolib.js_utils import dump_js_escaped_json
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
......@@ -186,7 +186,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
try:
exam_descriptor = modulestore().get_item(exam_key)
return HttpResponse(
escape_json_dumps({'locator': unicode(exam_descriptor.location)}),
dump_js_escaped_json({'locator': unicode(exam_descriptor.location)}),
status=200, content_type='application/json')
except ItemNotFoundError:
return HttpResponse(status=404)
......
......@@ -4,7 +4,7 @@ from django.http import (HttpResponse, HttpResponseServerError,
HttpResponseNotFound)
from edxmako.shortcuts import render_to_string, render_to_response
import functools
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
__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 = escape_json_dumps({"error": message})
content = dump_js_escaped_json({"error": message})
return HttpResponse(content, content_type="application/json",
status=status)
else:
......
......@@ -4,7 +4,7 @@ from django.utils.decorators import method_decorator
from django.views.generic import View
from django.http import HttpResponse
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from util.organizations_helpers import get_organizations
......@@ -20,4 +20,4 @@ class OrganizationListView(View):
"""Returns organization list as json."""
organizations = get_organizations()
org_names_list = [(org["short_name"]) for org in organizations]
return HttpResponse(escape_json_dumps(org_names_list), content_type='application/json; charset=utf-8')
return HttpResponse(dump_js_escaped_json(org_names_list), content_type='application/json; charset=utf-8')
......@@ -276,8 +276,9 @@ class CertificatesListHandlerTestCase(EventTestMixin, CourseTestCase, Certificat
@mock.patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': True})
def test_certificate_info_in_response(self):
"""
Test that certificate has been created and rendered properly.
Test that certificate has been created and rendered properly with non-audit course mode.
"""
CourseModeFactory.create(course_id=self.course.id, mode_slug='verified')
response = self.client.ajax_post(
self._url(),
data=CERTIFICATE_JSON_WITH_SIGNATORIES
......@@ -298,6 +299,22 @@ class CertificatesListHandlerTestCase(EventTestMixin, CourseTestCase, Certificat
self.assertEqual(data[0]['description'], 'Test description')
self.assertEqual(data[0]['version'], CERTIFICATE_SCHEMA_VERSION)
@mock.patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': True})
def test_certificate_info_not_in_response(self):
"""
Test that certificate has not been rendered audit only course mode.
"""
response = self.client.ajax_post(
self._url(),
data=CERTIFICATE_JSON_WITH_SIGNATORIES
)
self.assertEqual(response.status_code, 201)
# in html response
result = self.client.get_html(self._url())
self.assertNotIn('Test certificate', result.content)
def test_unsupported_http_accept_header(self):
"""
Test if not allowed header present in request.
......
......@@ -2,8 +2,8 @@
<%namespace name='static' file='static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import (
escape_json_dumps, escape_js_string
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<!doctype html>
......@@ -47,7 +47,7 @@ from openedx.core.lib.js_utils import (
<a class="nav-skip" href="#content">${_("Skip to main content")}</a>
<script type="text/javascript">
window.baseUrl = "${escape_js_string(settings.STATIC_URL) | n}";
window.baseUrl = "${settings.STATIC_URL | n, js_escaped_string}";
var require = {baseUrl: window.baseUrl};
</script>
<script type="text/javascript" src="${static.url("js/vendor/require.js")}"></script>
......@@ -85,14 +85,14 @@ from openedx.core.lib.js_utils import (
% if context_course:
require(['js/factories/course'], function(CourseFactory) {
CourseFactory({
id: "${escape_js_string(context_course.id) | n}",
id: "${context_course.id | n, js_escaped_string}",
name: "${context_course.display_name_with_default_escaped | 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) if context_course.display_coursenumber else ''}",
display_course_number: "${context_course.display_coursenumber | n, js_escaped_string}",
revision: "${context_course.location.revision | h}",
self_paced: ${escape_json_dumps(context_course.self_paced) | n}
self_paced: ${context_course.self_paced | n, dump_js_escaped_json}
});
});
% endif
......
......@@ -4,7 +4,9 @@
<%!
from contentstore import utils
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%block name="title">${_("Course Certificates")}</%block>
......@@ -29,11 +31,19 @@ CMS.User.isGlobalStaff = '${is_global_staff}'=='True' ? true : false;
</%block>
<%block name="requirejs">
% if has_certificate_modes:
require(["js/certificates/factories/certificates_page_factory"], function(CertificatesPageFactory) {
if(${escape_json_dumps(has_certificate_modes)}) {
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} );
}
CertificatesPageFactory(
${certificates | n, dump_js_escaped_json},
"${certificate_url | n, js_escaped_string}",
"${course_outline_url | n, js_escaped_string}",
${course_modes | n, dump_js_escaped_json},
${certificate_web_view_url | n, dump_js_escaped_json},
${is_active | n, dump_js_escaped_json},
${certificate_activation_handler_url | n, dump_js_escaped_json}
);
});
% endif
</%block>
<%block name="content">
......
......@@ -9,7 +9,9 @@ else:
</%def>
<%!
from contentstore.views.helpers import xblock_studio_url, xblock_type_display_name
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
from util.markup import HTML, ugettext as _
%>
<%block name="title">${xblock.display_name_with_default_escaped} ${xblock_type_display_name(xblock) | h}</%block>
......@@ -32,11 +34,11 @@ from util.markup import HTML, ugettext as _
<%block name="requirejs">
require(["js/factories/container"], function(ContainerFactory) {
ContainerFactory(
${ escape_json_dumps(component_templates) | n },
${ escape_json_dumps(xblock_info) | n },
"${action | h}",
${component_templates | n, dump_js_escaped_json},
${xblock_info | n, dump_js_escaped_json},
"${action | n, js_escaped_string}",
{
isUnitPage: ${ escape_json_dumps(is_unit_page) | n },
isUnitPage: ${is_unit_page | n, dump_js_escaped_json},
canEdit: true
}
);
......
......@@ -3,8 +3,9 @@
<%namespace name='static' file='static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from django.template.defaultfilters import escapejs
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
## TODO decode course # from context_course into title.
......@@ -23,11 +24,11 @@ from openedx.core.lib.js_utils import escape_json_dumps
<%block name="requirejs">
require(["js/factories/course_info"], function(CourseInfoFactory) {
CourseInfoFactory(
"${updates_url}",
"${handouts_locator | escapejs}",
"${base_asset_url}",
${escape_json_dumps(push_notification_enabled) | n}
);
"${updates_url | n, js_escaped_string}",
"${handouts_locator | n, js_escaped_string}",
"${base_asset_url | n, js_escaped_string}",
${push_notification_enabled | n, dump_js_escaped_json}
);
});
</%block>
......
......@@ -4,7 +4,7 @@
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 openedx.core.djangolib.js_utils import dump_js_escaped_json
from contentstore.utils import reverse_usage_url
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
%>
......@@ -15,7 +15,10 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
<%block name="requirejs">
require(["js/factories/outline"], function (OutlineFactory) {
OutlineFactory(${escape_json_dumps(course_structure) | n}, ${escape_json_dumps(initial_state) | n});
OutlineFactory(
${course_structure | n, dump_js_escaped_json},
${initial_state | n, dump_js_escaped_json}
);
});
</%block>
......
......@@ -11,7 +11,9 @@ else:
<%!
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%block name="title">
%if library:
......@@ -24,11 +26,11 @@ else:
<%block name="requirejs">
% if in_err:
var hasUnit = ${escape_json_dumps(bool(unit)) | n},
editUnitUrl = "${edit_unit_url or ""}",
courselikeHomeUrl = "${courselike_home_url or ""}",
is_library = ${escape_json_dumps(library) | n}
errMsg = ${escape_json_dumps(raw_err_msg or "") | n};
var hasUnit = ${bool(unit) | n, dump_js_escaped_json},
editUnitUrl = "${edit_unit_url | n, js_escaped_string}",
courselikeHomeUrl = "${courselike_home_url | n, js_escaped_string}",
is_library = ${library | n, dump_js_escaped_json}
errMsg = "${raw_err_msg | n, js_escaped_string}";
require(["js/factories/export"], function(ExportFactory) {
ExportFactory(hasUnit, editUnitUrl, courselikeHomeUrl, is_library, errMsg);
......
......@@ -5,7 +5,9 @@
<%!
from contentstore import utils
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%block name="title">${_("Group Configurations")}</%block>
......@@ -21,7 +23,13 @@ from openedx.core.lib.js_utils import escape_json_dumps
<%block name="requirejs">
require(["js/factories/group_configurations"], function(GroupConfigurationsFactory) {
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}");
GroupConfigurationsFactory(
${should_show_experiment_groups | n, dump_js_escaped_json},
${experiment_group_configurations | n, dump_js_escaped_json},
${content_group_configuration | n, dump_js_escaped_json},
"${group_configuration_url | n, js_escaped_string}",
"${course_outline_url | n, js_escaped_string}"
);
});
</%block>
......
......@@ -10,7 +10,9 @@ else:
<%namespace name='static' file='static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%block name="title">
%if library:
......@@ -239,6 +241,9 @@ else:
<%block name="requirejs">
require(["js/factories/import"], function(ImportFactory) {
ImportFactory("${import_status_url}", ${escape_json_dumps(library) | n});
ImportFactory(
"${import_status_url | n, js_escaped_string}",
${library | n, dump_js_escaped_json}
);
});
</%block>
......@@ -3,7 +3,7 @@
<%!
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
from openedx.core.djangolib.js_utils import dump_js_escaped_json
%>
<%block name="title">${context_library.display_name_with_default_escaped} ${xblock_type_display_name(context_library)}</%block>
<%block name="bodyclass">is-signedin course container view-container view-library</%block>
......@@ -24,8 +24,8 @@ from openedx.core.lib.js_utils import escape_json_dumps
<%block name="requirejs">
require(["js/factories/library"], function(LibraryFactory) {
LibraryFactory(
${escape_json_dumps(component_templates) | n},
${escape_json_dumps(xblock_info) | n},
${component_templates | n, dump_js_escaped_json},
${xblock_info | n, dump_js_escaped_json},
{
isUnitPage: false,
page_size: 10,
......
......@@ -2,7 +2,10 @@
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%def name="online_help_token()"><% return "team_course" %></%def>
<%block name="title">${_("Course Team Settings")}</%block>
......@@ -114,11 +117,11 @@ from openedx.core.lib.js_utils import escape_json_dumps
<%block name="requirejs">
require(["js/factories/manage_users"], function(ManageCourseUsersFactory) {
ManageCourseUsersFactory(
"${context_course.display_name | h}",
${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()}
"${context_course.display_name_with_default | h}",
${users | n, dump_js_escaped_json},
"${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': unicode(context_course.id), 'email': '@@EMAIL@@'}) | n, js_escaped_string}",
${request.user.id | n, dump_js_escaped_json},
${allow_actions | n, dump_js_escaped_json}
);
});
</%block>
......@@ -2,7 +2,10 @@
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%def name="online_help_token()"><% return "team_library" %></%def>
<%block name="title">${_("Library User Access")}</%block>
......@@ -107,11 +110,11 @@ from openedx.core.lib.js_utils import escape_json_dumps
<%block name="requirejs">
require(["js/factories/manage_users_lib"], function(ManageLibraryUsersFactory) {
ManageLibraryUsersFactory(
"${context_library.display_name_with_default_escaped | h}",
${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()}
"${context_library.display_name_with_default | h}",
${users | n, dump_js_escaped_json},
"${reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': library_key, 'email': '@@EMAIL@@'}) | n, js_escaped_string}",
${request.user.id | n, dump_js_escaped_json},
${allow_actions | n, dump_js_escaped_json}
);
});
</%block>
......@@ -8,7 +8,9 @@
import urllib
from django.utils.translation import ugettext as _
from contentstore import utils
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%block name="header_extras">
......@@ -31,7 +33,10 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
<%block name="requirejs">
require(["js/factories/settings"], function(SettingsFactory) {
SettingsFactory("${details_url}", ${escape_json_dumps(show_min_grade_warning) | n});
SettingsFactory(
"${details_url | n, js_escaped_string}",
${show_min_grade_warning | n, dump_js_escaped_json}
);
});
</%block>
......
......@@ -4,7 +4,9 @@
<%!
from django.utils.translation import ugettext as _
from contentstore import utils
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%block name="title">${_("Advanced Settings")}</%block>
<%block name="bodyclass">is-signedin course advanced view-settings</%block>
......@@ -19,7 +21,10 @@
<%block name="requirejs">
require(["js/factories/settings_advanced"], function(SettingsAdvancedFactory) {
SettingsAdvancedFactory(${escape_json_dumps(advanced_dict) | n}, "${advanced_settings_url}");
SettingsAdvancedFactory(
${advanced_dict | n, dump_js_escaped_json},
"${advanced_settings_url | n, js_escaped_string}"
);
});
</%block>
......
......@@ -9,7 +9,9 @@
from contentstore import utils
from django.utils.translation import ugettext as _
from models.settings.encoder import CourseSettingsEncoder
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%block name="header_extras">
......@@ -25,7 +27,11 @@
</%block>
<%block name="requirejs">
require(["js/factories/settings_graders"], function(SettingsGradersFactory) {
SettingsGradersFactory(_.extend(${escape_json_dumps(course_details, cls=CourseSettingsEncoder) | n}, {is_credit_course: ${escape_json_dumps(is_credit_course) | n}}), "${grading_url}");
SettingsGradersFactory(
_.extend(${dump_js_escaped_json(course_details, cls=CourseSettingsEncoder) | n},
{is_credit_course: ${is_credit_course | n, dump_js_escaped_json}}),
"${grading_url | n, js_escaped_string}"
);
});
</%block>
......
......@@ -2,7 +2,9 @@
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
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%
xblock_url = xblock_studio_url(xblock)
......@@ -24,10 +26,10 @@ messages = xblock.validate().to_json()
<script>
require(["jquery", "js/factories/xblock_validation"], function($, XBlockValidationFactory) {
XBlockValidationFactory(
${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}"]')
${messages | n, dump_js_escaped_json},
${bool(xblock_url) | n, dump_js_escaped_json}, // xblock_url will be None or a string
${bool(is_root) | n, dump_js_escaped_json}, // is_root will be None or a boolean
$('div.xblock-validation-messages[data-locator="${xblock.location | n, js_escaped_string}"]')
);
});
</script>
......
......@@ -3,7 +3,7 @@
<%namespace name='static' file='static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
%>
<%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(${escape_json_dumps(textbooks) | n});
TextbooksFactory(${textbooks | n, dump_js_escaped_json});
});
</%block>
......
......@@ -20,7 +20,7 @@ from django.http import HttpRequest
from course_modes.models import CourseMode
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.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
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
......@@ -387,7 +387,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
"finishAuthUrl": finish_auth_url,
"errorMessage": None,
}
auth_info = escape_json_dumps(auth_info)
auth_info = dump_js_escaped_json(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.js_utils import escape_json_dumps %>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%namespace name='static' file='/static_content.html'/>
<%inherit file="/main.html" />
......@@ -33,20 +37,20 @@
<%static:require_module module_name="teams/js/teams_tab_factory" class_name="TeamsTabFactory">
TeamsTabFactory({
courseID: '${ unicode(course.id) }',
topics: ${ escape_json_dumps(topics) | n },
userInfo: ${ escape_json_dumps(user_info) | n },
topicUrl: '${ topic_url }',
topicsUrl: '${ topics_url }',
teamsUrl: '${ teams_url }',
teamsDetailUrl: '${ teams_detail_url }',
teamMembershipsUrl: '${ team_memberships_url }',
teamMembershipDetailUrl: '${ team_membership_detail_url }',
myTeamsUrl: '${ my_teams_url }',
maxTeamSize: ${ course.teams_max_size },
languages: ${ escape_json_dumps(languages) | n },
countries: ${ escape_json_dumps(countries) | n },
teamsBaseUrl: '${ teams_base_url }'
courseID: '${unicode(course.id) | n, js_escaped_string}',
topics: ${topics | n, dump_js_escaped_json},
userInfo: ${user_info | n, dump_js_escaped_json},
topicUrl: '${topic_url | n, js_escaped_string}',
topicsUrl: '${topics_url | n, js_escaped_string}',
teamsUrl: '${teams_url | n, js_escaped_string}',
teamsDetailUrl: '${teams_detail_url | n, js_escaped_string}',
teamMembershipsUrl: '${team_memberships_url | n, js_escaped_string}',
teamMembershipDetailUrl: '${team_membership_detail_url | n, js_escaped_string}',
myTeamsUrl: '${my_teams_url | n, js_escaped_string}',
maxTeamSize: ${course.teams_max_size | n, dump_js_escaped_json},
languages: ${languages | n, dump_js_escaped_json},
countries: ${countries | n, dump_js_escaped_json},
teamsBaseUrl: '${teams_base_url | n, js_escaped_string}'
});
</%static:require_module>
</%block>
......
......@@ -495,7 +495,8 @@ MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver'
# add extra template directory for test-only templates
MAKO_TEMPLATES['main'].extend([
COMMON_ROOT / 'test' / 'templates',
COMMON_ROOT / 'test' / 'test_microsites'
COMMON_ROOT / 'test' / 'test_microsites',
REPO_ROOT / 'openedx' / 'core' / 'djangolib' / 'tests' / 'templates',
])
......
<%!
import json
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
%>
<%inherit file="../main.html" />
<%
......@@ -19,7 +19,7 @@
% endfor
<%static:require_module module_name="js/discovery/discovery_factory" class_name="DiscoveryFactory">
DiscoveryFactory(
${ escape_json_dumps(course_discovery_meanings) | n },
${course_discovery_meanings | n, dump_js_escaped_json},
getParameterByName('search_query')
);
</%static:require_module>
......
......@@ -2,21 +2,23 @@
<%!
import json
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
%>
<%namespace name='static' file='/static_content.html'/>
<%block name="js_extra">
<%static:require_module module_name="js/financial-assistance/financial_assistance_form_factory" class_name="FinancialAssistanceFactory">
FinancialAssistanceFactory({
fields: ${escape_json_dumps(fields)},
user_details: ${escape_json_dumps(user_details)},
header_text: ${escape_json_dumps(header_text)},
student_faq_url: ${json.dumps(student_faq_url)},
dashboard_url: ${json.dumps(dashboard_url)},
account_settings_url: ${json.dumps(account_settings_url)},
platform_name: ${escape_json_dumps(platform_name)},
submit_url: ${json.dumps(submit_url)}
fields: ${fields | n, dump_js_escaped_json},
user_details: ${user_details | n, dump_js_escaped_json},
header_text: ${header_text | n, dump_js_escaped_json},
student_faq_url: '${student_faq_url | n, js_escaped_string}',
dashboard_url: '${dashboard_url | n, js_escaped_string}',
account_settings_url: '${account_settings_url | n, js_escaped_string}',
platform_name: '${platform_name | n, js_escaped_string}',
submit_url: '${submit_url | n, js_escaped_string}'
});
</%static:require_module>
</%block>
......
<%!
import json
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
%>
<%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 = ${ escape_json_dumps(data) | n };
var options = ${data | n, dump_js_escaped_json};
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.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import dump_js_escaped_json
%>
<%block name="pagetitle">${_("Learner Profile")}</%block>
......@@ -24,7 +24,7 @@ from openedx.core.lib.js_utils import escape_json_dumps
<%block name="js_extra">
<%static:require_module module_name="js/student_profile/views/learner_profile_factory" class_name="LearnerProfileFactory">
var options = ${ escape_json_dumps(data) | n };
var options = ${data | n, dump_js_escaped_json};
LearnerProfileFactory(options);
</%static:require_module>
</%block>
<%!
from django.utils.translation import ugettext as _
from openedx.core.lib.js_utils import escape_json_dumps
from openedx.core.djangolib.js_utils import js_escaped_string
%>
<%namespace name='static' file='../static_content.html'/>
......@@ -11,9 +10,9 @@ from openedx.core.lib.js_utils import escape_json_dumps
<%block name="js_extra">
<%static:require_module module_name="support/js/enrollment_factory" class_name="EnrollmentFactory">
new EnrollmentFactory({
user: ${escape_json_dumps(username)},
enrollmentsUrl: ${escape_json_dumps(enrollmentsUrl)},
enrollmentSupportUrl: ${escape_json_dumps(enrollmentSupportUrl)},
user: '${username | n, js_escaped_string}',
enrollmentsUrl: '${enrollmentsUrl | n, js_escaped_string}',
enrollmentSupportUrl: '${enrollmentSupportUrl | n, js_escaped_string}',
});
</%static:require_module>
</%block>
......
......@@ -6,6 +6,7 @@ from Open edX will eventually live here, including the code in the lms, cms,
and common directories.
If you're adding a new Django app, place it in core/djangoapps. If you're adding
utilities that require Django, place them in core/djangolib. If you're adding
code that defines no Django models or views of its own but is widely useful, put it
in core/lib.
......
"""
Utilities for dealing with Javascript and JSON.
"""
import json
from django.utils.html import escapejs
from mako.filters import decode
from markupsafe import escape
from xmodule.modulestore import EdxJSONEncoder
def _escape_json_for_js(json_dumps_string):
"""
Escape output of JSON dumps that is safe to be embedded in a <SCRIPT> tag.
This implementation is based on escaping performed in
simplejson.JSONEncoderForHTML.
Arguments:
json_dumps_string (string): A JSON string to be escaped.
This must be the output of json.dumps to ensure:
1. The string contains valid JSON, and
2. That non-ascii characters are properly escaped
Returns:
(string) Escaped JSON that is safe to be embedded in HTML.
"""
json_dumps_string = json_dumps_string.replace("&", "\\u0026")
json_dumps_string = json_dumps_string.replace(">", "\\u003e")
json_dumps_string = json_dumps_string.replace("<", "\\u003c")
return json_dumps_string
def dump_js_escaped_json(obj, cls=EdxJSONEncoder):
"""
JSON dumps and escapes objects that are safe to be embedded in JavaScript.
Use this for anything but strings (e.g. dicts, tuples, lists, bools, and
numbers). For strings, use js_escaped_string.
The output of this method is also usable as plain-old JSON.
Usage:
Used as follows in a Mako template inside a <SCRIPT> tag::
var json_obj = ${obj | n, dump_js_escaped_json}
If you must use the cls argument, then use as follows::
var json_obj = ${dump_js_escaped_json(obj, cls) | n}
Use the "n" Mako filter above. It is possible that the default filter
may include html escaping in the future, and this ensures proper
escaping.
Ensure ascii in json.dumps (ensure_ascii=True) allows safe skipping of
Mako's default filter decode.utf8.
Arguments:
obj: The object soon to become a JavaScript escaped JSON string. The
object can be anything but strings (e.g. dicts, tuples, lists, bools, and
numbers).
cls (class): The JSON encoder class (defaults to EdxJSONEncoder).
Returns:
(string) Escaped encoded JSON.
"""
json_string = json.dumps(obj, ensure_ascii=True, cls=cls)
json_string = _escape_json_for_js(json_string)
return json_string
def dump_html_escaped_json(obj, cls=EdxJSONEncoder):
"""
JSON dumps and escapes objects that are safe to be embedded in HTML.
Use this for anything but strings (e.g. dicts, tuples, lists, bools, and
numbers). For strings, just used the default html filter.
Usage:
Used as follows in a Mako template inside a HTML, like in
a data attribute::
data-obj='${obj | n, dump_html_escaped_json}'
If you must use the cls argument, then use as follows::
data-obj='${dump_html_escaped_json(obj, cls) | n}'
Use the "n" Mako filter above. The default filter will include
html escaping in the future, and this ensures proper ordering of
these calls.
Ensure ascii in json.dumps (ensure_ascii=True) allows safe skipping of
Mako's default filter decode.utf8.
Arguments:
obj: The object soon to become an HTML escaped JSON string. The object
can be anything but strings (e.g. dicts, tuples, lists, bools, and
numbers).
cls (class): The JSON encoder class (defaults to EdxJSONEncoder).
Returns:
(string) Escaped encoded JSON.
"""
json_string = json.dumps(obj, ensure_ascii=True, cls=cls)
json_string = escape(json_string)
return json_string
def js_escaped_string(string_for_js):
"""
Mako filter that escapes text for use in a JavaScript string.
If None is provided, returns an empty string.
Usage:
Used as follows in a Mako template inside a <SCRIPT> tag::
var my_string_for_js = "${my_string_for_js | n, js_escaped_string}"
The surrounding quotes for the string must be included.
Use the "n" Mako filter above. It is possible that the default filter
may include html escaping in the future, and this ensures proper
escaping.
Mako's default filter decode.utf8 is applied here since this default
filter is skipped in the Mako template with "n".
Arguments:
string_for_js (string): Text to be properly escaped for use in a
JavaScript string.
Returns:
(string) Text properly escaped for use in a JavaScript string as
unicode. Returns empty string if argument is None.
"""
if string_for_js is None:
string_for_js = ""
string_for_js = decode.utf8(string_for_js)
string_for_js = escapejs(string_for_js)
return string_for_js
"""
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
"""
Tests for js_utils.py
"""
import json
from unittest import TestCase
from openedx.core.lib.js_utils import (
escape_json_dumps, escape_js_string
)
class TestJSUtils(TestCase):
"""
Test JS utils
"""
class NoDefaultEncoding(object):
"""
Helper class that has no default JSON encoding
"""
def __init__(self, value):
self.value = value
class SampleJSONEncoder(json.JSONEncoder):
"""
A test encoder that is used to prove that the encoder does its job before the escaping.
"""
# pylint: disable=method-hidden
def default(self, noDefaultEncodingObj):
return noDefaultEncodingObj.value.replace("<script>", "sample-encoder-was-here")
def test_escape_json_dumps_escapes_unsafe_html(self):
"""
Test escape_json_dumps properly escapes &, <, and >.
"""
malicious_json = {"</script><script>alert('hello, ');</script>": "</script><script>alert('&world!');</script>"}
expected_encoded_json = (
r'''{"\u003c/script\u003e\u003cscript\u003ealert('hello, ');\u003c/script\u003e": '''
r'''"\u003c/script\u003e\u003cscript\u003ealert('\u0026world!');\u003c/script\u003e"}'''
)
encoded_json = escape_json_dumps(malicious_json)
self.assertEquals(expected_encoded_json, encoded_json)
def test_escape_json_dumps_with_custom_encoder_escapes_unsafe_html(self):
"""
Test escape_json_dumps first encodes with custom JSNOEncoder before escaping &, <, and >
The test encoder class should first perform the replacement of "<script>" with
"sample-encoder-was-here", and then should escape the remaining &, <, and >.
"""
malicious_json = {
"</script><script>alert('hello, ');</script>":
self.NoDefaultEncoding("</script><script>alert('&world!');</script>")
}
expected_custom_encoded_json = (
r'''{"\u003c/script\u003e\u003cscript\u003ealert('hello, ');\u003c/script\u003e": '''
r'''"\u003c/script\u003esample-encoder-was-herealert('\u0026world!');\u003c/script\u003e"}'''
)
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)
......@@ -158,6 +158,7 @@ class SystemTestSuite(NoseTestSuite):
if self.root == 'lms':
default_test_id += " {system}/tests.py"
default_test_id += " openedx/core/djangolib"
if self.root == 'cms':
default_test_id += " {system}/tests/*"
......
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