Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion cms/djangoapps/contentstore/tests/test_course_listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
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,
Expand All @@ -30,7 +33,7 @@


@ddt.ddt
class TestCourseListing(ModuleStoreTestCase):
class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
"""
Unit tests for getting the list of courses for a logged in user
"""
Expand Down Expand Up @@ -72,6 +75,30 @@ def tearDown(self):
self.client.logout()
ModuleStoreTestCase.tearDown(self)

def test_course_listing_is_escaped(self):
"""
Tests course listing returns escaped data.
"""
escaping_content = "<script>alert('ESCAPE')</script>"

# 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'
Expand Down
3 changes: 2 additions & 1 deletion cms/djangoapps/contentstore/views/tests/test_programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

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

Expand Down Expand Up @@ -63,7 +64,7 @@ def test_programs_displayed(self):
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()
Expand Down
63 changes: 35 additions & 28 deletions cms/templates/index.html
Original file line number Diff line number Diff line change
@@ -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" />

Expand Down Expand Up @@ -79,7 +80,10 @@ <h3 class="title">${_("Create a New Course")}</h3>
## 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.
<input class="new-course-org" id="new-course-org" type="text" name="new-course-org" required placeholder="${_('e.g. UniversityX or OrganizationX')}" aria-describedby="tip-new-course-org tip-error-new-course-org" />
<span class="tip" id="tip-new-course-org">${_("The name of the organization sponsoring the course.")} <strong>${_("Note: The organization name is part of the course URL")}</strong> ${_("This cannot be changed, but you can set a different display name in Advanced Settings later.")}</span>
<span class="tip" id="tip-new-course-org">${_("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>'),
strong_end=HTML('</strong>'),
)}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-course-org"></span>
</li>

Expand All @@ -89,7 +93,10 @@ <h3 class="title">${_("Create a New Course")}</h3>
## 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.
<input class="new-course-number" id="new-course-number" type="text" name="new-course-number" required placeholder="${_('e.g. CS101')}" aria-describedby="tip-new-course-number tip-error-new-course-number" />
<span class="tip" id="tip-new-course-number">${_("The unique number that identifies your course within your organization.")} <strong>${_("Note: This is part of your course URL, so no spaces or special characters are allowed and it cannot be changed.")}</strong></span>
<span class="tip" id="tip-new-course-number">${_("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>'),
strong_end=HTML('</strong>'),
)}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-course-number"></span>
</li>

Expand All @@ -98,7 +105,10 @@ <h3 class="title">${_("Create a New Course")}</h3>
## 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.
<input class="new-course-run" id="new-course-run" type="text" name="new-course-run" required placeholder="${_('e.g. 2014_T1')}" aria-describedby="tip-new-course-run tip-error-new-course-run" />
<span class="tip" id="tip-new-course-run">${_("The term in which your course will run.")} <strong>${_("Note: This is part of your course URL, so no spaces or special characters are allowed and it cannot be changed.")}</strong></span>
<span class="tip" id="tip-new-course-run">${_("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>'),
strong_end=HTML('</strong>'),
)}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-course-run"></span>
</li>
</ol>
Expand Down Expand Up @@ -155,7 +165,10 @@ <h3 class="title">${_("Create a New Library")}</h3>
## for "Computer Science Problems". The example number may contain letters
## but must not contain spaces.
<input class="new-library-number" id="new-library-number" type="text" name="new-library-number" required placeholder="${_('e.g. CSPROB')}" aria-describedby="tip-new-library-number tip-error-new-library-number" />
<span class="tip" id="tip-new-library-number">${_("The unique code that identifies this library.")} <strong>${_("Note: This is part of your library URL, so no spaces or special characters are allowed.")}</strong> ${_("This cannot be changed.")}</span>
<span class="tip" id="tip-new-library-number">${_("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>'),
strong_end=HTML('</strong>'),
)}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-library-number"></span>
</li>
</ol>
Expand All @@ -181,10 +194,10 @@ <h3 class="title">${_("Courses Being Processed")}</h3>
%for course_info in sorted(in_process_course_actions, key=lambda s: s['display_name'].lower() if s['display_name'] is not None else ''):
<!-- STATE: re-run is processing -->
%if course_info['is_in_progress']:
<li class="wrapper-course has-status" data-course-key="${course_info['course_key'] | h}">
<li class="wrapper-course has-status" data-course-key="${course_info['course_key']}">
<div class="course-item course-rerun is-processing">
<div class="course-details" href="#">
<h3 class="course-title">${course_info['display_name'] | h}</h3>
<h3 class="course-title">${course_info['display_name']}</h3>

<div class="course-metadata">
<span class="course-org metadata-item">
Expand Down Expand Up @@ -216,8 +229,8 @@ <h3 class="course-title">${course_info['display_name'] | h}</h3>

<div class="status-message">
<p class="copy">${_('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='<a href="#" class="action-reload">',
link_end='</a>',
link_start=HTML('<a href="#" class="action-reload">'),
link_end=HTML('</a>'),
)}</p>
</div>
</li>
Expand All @@ -227,10 +240,10 @@ <h3 class="course-title">${course_info['display_name'] | h}</h3>

<!-- STATE: re-run has error -->
%if course_info['is_failed']:
<li class="wrapper-course has-status" data-course-key="${course_info['course_key'] | h}">
<li class="wrapper-course has-status" data-course-key="${course_info['course_key']}">
<div class="course-item course-rerun has-error">
<div class="course-details" href="#">
<h3 class="course-title">${course_info['display_name'] | h}</h3>
<h3 class="course-title">${course_info['display_name']}</h3>

<div class="course-metadata">
<span class="course-org metadata-item">
Expand Down Expand Up @@ -296,9 +309,9 @@ <h3 class="course-title">${course_info['display_name'] | h}</h3>
<div class="courses courses-tab active">
<ul class="list-courses">
%for course_info in sorted(courses, key=lambda s: s['display_name'].lower() if s['display_name'] is not None else ''):
<li class="course-item" data-course-key="${course_info['course_key'] | h}">
<li class="course-item" data-course-key="${course_info['course_key']}">
<a class="course-link" href="${course_info['url']}">
<h3 class="course-title">${course_info['display_name'] | h}</h3>
<h3 class="course-title">${course_info['display_name']}</h3>

<div class="course-metadata">
<span class="course-org metadata-item">
Expand Down Expand Up @@ -450,7 +463,7 @@ <h4 class="title">${_('Your Course Creator Request Status:')}</h4>
%for library_info in sorted(libraries, key=lambda s: s['display_name'].lower() if s['display_name'] is not None else ''):
<li class="course-item">
<a class="library-link" href="${library_info['url']}">
<h3 class="course-title">${library_info['display_name'] | h}</h3>
<h3 class="course-title">${library_info['display_name']}</h3>

<div class="course-metadata">
<span class="course-org metadata-item">
Expand Down Expand Up @@ -508,12 +521,12 @@ <h3 class="title">${_('Create Your First Library')}</h3>
<li class="course-item">

<a class="program-link" href=${program_authoring_url + str(program['id'])}>
<h3 class="course-title">${program['name'] | h}</h3>
<h3 class="course-title">${program['name']}</h3>

<div class="course-metadata">
<span class="course-org metadata-item">
<!-- As of this writing, programs can only be owned by one organization. If that constraint is relaxed, this will need to be revisited. -->
<span class="label">${_("Organization:")}</span> <span class="value">${program['organizations'][0]['key'] | h}</span>
<span class="label">${_("Organization:")}</span> <span class="value">${program['organizations'][0]['key']}</span>
</span>
</div>
</a>
Expand Down Expand Up @@ -565,8 +578,8 @@ <h3 class="title title-3">${_("Can I create courses in {studio_name}?").format(s
<p>${_("In order to create courses in {studio_name}, you must {link_start}contact {platform_name} staff to help you create a course{link_end}.").format(
studio_name=settings.STUDIO_NAME,
platform_name=settings.PLATFORM_NAME,
link_start='<a href="mailto:{email}">'.format(email=settings.FEATURES.get('STUDIO_REQUEST_EMAIL','')),
link_end="</a>",
link_start=HTML('<a href="mailto:{email}">').format(email=settings.FEATURES.get('STUDIO_REQUEST_EMAIL','')),
link_end=HTML("</a>"),
)}</p>
</div>
% endif
Expand All @@ -580,17 +593,11 @@ <h3 class="title title-3">${_("Can I create courses in {studio_name}?").format(s
% elif course_creator_status == "denied":
<div class="bit">
<h3 class="title title-3">${_("Can I create courses in {studio_name}?").format(studio_name=settings.STUDIO_NAME)}</h3>
<%!
from django.conf import settings

help_link_start = '<a href="mailto:{email}">'.format(email=settings.TECH_SUPPORT_EMAIL)
help_link_end = '</a>'
%>
<p>${_("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('<a href="mailto:{email}">').format(email=settings.TECH_SUPPORT_EMAIL),
link_end=HTML('</a>'),
)}</p>
</div>

Expand All @@ -603,14 +610,14 @@ <h3 class="title title-3">${_("Can I create courses in {studio_name}?").format(s
<section class="content">
<article class="content-primary" role="main">
<div class="introduction">
<h2 class="title">${_("Thanks for signing up, %(name)s!") % dict(name= user.username)}</h2>
<h2 class="title">${_("Thanks for signing up, {name}!").format(name=user.username)}</h2>
</div>

<div class="notice notice-incontext notice-instruction notice-instruction-verification">
<div class="msg">
<h3 class="title">${_("We need to verify your email address")}</h3>
<div class="copy">
<p>${_('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)}</p>
<p>${_('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)}</p>
</div>
</div>
</div>
Expand Down