diff --git a/cms/djangoapps/contentstore/tests/test_course_listing.py b/cms/djangoapps/contentstore/tests/test_course_listing.py
index be6c519f40..d0ea4f3422 100644
--- a/cms/djangoapps/contentstore/tests/test_course_listing.py
+++ b/cms/djangoapps/contentstore/tests/test_course_listing.py
@@ -9,6 +9,9 @@ from mock import patch, Mock
import ddt
from django.test import RequestFactory
+from django.test.client import Client
+
+from common.test.utils import XssTestMixin
from xmodule.course_module import CourseSummary
from contentstore.views.course import (_accessible_courses_list, _accessible_courses_list_from_groups,
@@ -30,7 +33,7 @@ USER_COURSES_COUNT = 50
@ddt.ddt
-class TestCourseListing(ModuleStoreTestCase):
+class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
"""
Unit tests for getting the list of courses for a logged in user
"""
@@ -72,6 +75,30 @@ class TestCourseListing(ModuleStoreTestCase):
self.client.logout()
ModuleStoreTestCase.tearDown(self)
+ def test_course_listing_is_escaped(self):
+ """
+ Tests course listing returns escaped data.
+ """
+ escaping_content = ""
+
+ # Make user staff to access course listing
+ self.user.is_staff = True
+ self.user.save() # pylint: disable=no-member
+
+ self.client = Client()
+ self.client.login(username=self.user.username, password='test')
+
+ # Change 'display_coursenumber' field and update the course.
+ course = CourseFactory.create()
+ course.display_coursenumber = escaping_content
+ course = self.store.update_item(course, self.user.id) # pylint: disable=no-member
+ self.assertEqual(course.display_coursenumber, escaping_content)
+
+ # Check if response is escaped
+ response = self.client.get('/home')
+ self.assertEqual(response.status_code, 200)
+ self.assert_no_xss(response, escaping_content)
+
def test_get_course_list(self):
"""
Test getting courses with new access group format e.g. 'instructor_edx.course.run'
diff --git a/cms/djangoapps/contentstore/views/tests/test_programs.py b/cms/djangoapps/contentstore/views/tests/test_programs.py
index 4ec26edac8..751f39f598 100644
--- a/cms/djangoapps/contentstore/views/tests/test_programs.py
+++ b/cms/djangoapps/contentstore/views/tests/test_programs.py
@@ -10,6 +10,7 @@ from provider.constants import CONFIDENTIAL
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
+from openedx.core.djangolib.markup import escape
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
@@ -63,7 +64,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
self.mock_programs_api(data={'results': []})
response = self.client.get(self.studio_home)
- self.assertIn("You haven't created any programs yet.", response.content)
+ self.assertIn(escape("You haven't created any programs yet."), response.content)
# When data is provided, expect a program listing.
self.mock_programs_api()
diff --git a/cms/templates/index.html b/cms/templates/index.html
index 2496fcad9c..ba72e4aa63 100644
--- a/cms/templates/index.html
+++ b/cms/templates/index.html
@@ -1,4 +1,5 @@
-<%! from django.utils.translation import ugettext as _ %>
+<%! from openedx.core.djangolib.markup import HTML, ugettext as _ %>
+<%page expression_filter="h"/>
<%inherit file="base.html" />
@@ -79,7 +80,10 @@
## Translators: This is an example for the name of the organization sponsoring a course, seen when filling out the form to create a new course. The organization name cannot contain spaces.
## Translators: "e.g. UniversityX or OrganizationX" is a placeholder displayed when user put no data into this field.
- ${_("The name of the organization sponsoring the course.")} ${_("Note: The organization name is part of the course URL")} ${_("This cannot be changed, but you can set a different display name in Advanced Settings later.")}
+ ${_("The name of the organization sponsoring the course. {strong_start}Note: The organization name is part of the course URL.{strong_end} This cannot be changed, but you can set a different display name in Advanced Settings later.").format(
+ strong_start=HTML(''),
+ strong_end=HTML(''),
+ )}
@@ -89,7 +93,10 @@
## seen when filling out the form to create a new course. The number here is
## short for "Computer Science 101". It can contain letters but cannot contain spaces.
- ${_("The unique number that identifies your course within your organization.")} ${_("Note: This is part of your course URL, so no spaces or special characters are allowed and it cannot be changed.")}
+ ${_("The unique number that identifies your course within your organization. {strong_start}Note: This is part of your course URL, so no spaces or special characters are allowed and it cannot be changed.{strong_end}").format(
+ strong_start=HTML(''),
+ strong_end=HTML(''),
+ )}
@@ -98,7 +105,10 @@
## Translators: This is an example for the "run" used to identify different
## instances of a course, seen when filling out the form to create a new course.
- ${_("The term in which your course will run.")} ${_("Note: This is part of your course URL, so no spaces or special characters are allowed and it cannot be changed.")}
+ ${_("The term in which your course will run. {strong_start}Note: This is part of your course URL, so no spaces or special characters are allowed and it cannot be changed.{strong_end}").format(
+ strong_start=HTML(''),
+ strong_end=HTML(''),
+ )}
@@ -155,7 +165,10 @@
## for "Computer Science Problems". The example number may contain letters
## but must not contain spaces.
- ${_("The unique code that identifies this library.")} ${_("Note: This is part of your library URL, so no spaces or special characters are allowed.")} ${_("This cannot be changed.")}
+ ${_("The unique code that identifies this library. {strong_start}Note: This is part of your library URL, so no spaces or special characters are allowed.{strong_end} This cannot be changed.").format(
+ strong_start=HTML(''),
+ strong_end=HTML(''),
+ )}
@@ -181,10 +194,10 @@
%for course_info in sorted(in_process_course_actions, key=lambda s: s['display_name'].lower() if s['display_name'] is not None else ''):
%if course_info['is_in_progress']:
-
+
-
${course_info['display_name'] | h}
+
${course_info['display_name']}
@@ -216,8 +229,8 @@
${_('The new course will be added to your course list in 5-10 minutes. Return to this page or {link_start}refresh it{link_end} to update the course list. The new course will need some manual configuration.').format(
- link_start='',
- link_end='',
+ link_start=HTML(''),
+ link_end=HTML(''),
)}
${_("Your request to author courses in {studio_name} has been denied. Please {link_start}contact {platform_name} Staff with further questions{link_end}.").format(
studio_name=settings.STUDIO_NAME,
platform_name=settings.PLATFORM_NAME,
- link_start=help_link_start,
- link_end=help_link_end,
+ link_start=HTML('').format(email=settings.TECH_SUPPORT_EMAIL),
+ link_end=HTML(''),
)}
@@ -603,14 +610,14 @@ help_link_end = ''
-
${_("Thanks for signing up, %(name)s!") % dict(name= user.username)}
+
${_("Thanks for signing up, {name}!").format(name=user.username)}
${_("We need to verify your email address")}
-
${_('Almost there! In order to complete your sign up we need you to verify your email address (%(email)s). An activation message and next steps should be waiting for you there.') % dict(email=user.email)}
+
${_('Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.').format(email=user.email)}