Commit 76545ef4 by cahrens

HTML-encode string values in keyword substitution.

TNL-4193
parent f3b33ef1
...@@ -12,6 +12,7 @@ file and check it in at the same time as your model changes. To do that, ...@@ -12,6 +12,7 @@ file and check it in at the same time as your model changes. To do that,
""" """
import logging import logging
import markupsafe
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
...@@ -176,7 +177,7 @@ class CourseEmailTemplate(models.Model): ...@@ -176,7 +177,7 @@ class CourseEmailTemplate(models.Model):
which is rendered using format() with the provided `context` dict. which is rendered using format() with the provided `context` dict.
Any keywords encoded in the form %%KEYWORD%% found in the message Any keywords encoded in the form %%KEYWORD%% found in the message
body are subtituted with user data before the body is inserted into body are substituted with user data before the body is inserted into
the template. the template.
Output is returned as a unicode string. It is not encoded as utf-8. Output is returned as a unicode string. It is not encoded as utf-8.
...@@ -215,6 +216,10 @@ class CourseEmailTemplate(models.Model): ...@@ -215,6 +216,10 @@ class CourseEmailTemplate(models.Model):
Convert HTML text body (`htmltext`) into HTML email message using the Convert HTML text body (`htmltext`) into HTML email message using the
stored HTML template and the provided `context` dict. stored HTML template and the provided `context` dict.
""" """
# HTML-escape string values in the context (used for keyword substitution).
for key, value in context.iteritems():
if isinstance(value, basestring):
context[key] = markupsafe.escape(value)
return CourseEmailTemplate._render(self.html_template, htmltext, context) return CourseEmailTemplate._render(self.html_template, htmltext, context)
......
...@@ -97,6 +97,15 @@ class CourseEmailTemplateTest(TestCase): ...@@ -97,6 +97,15 @@ class CourseEmailTemplateTest(TestCase):
context['course_image_url'] = "/location/of/course/image/url" context['course_image_url'] = "/location/of/course/image/url"
return context return context
def _add_xss_fields(self, context):
""" Add fields to the context for XSS testing. """
context['course_title'] = "<script>alert('Course Title!');</alert>"
context['name'] = "<script>alert('Profile Name!');</alert>"
# Must have user_id and course_id present in order to do keyword substitution
context['user_id'] = 12345
context['course_id'] = "course-v1:edx+100+1"
return context
def test_get_template(self): def test_get_template(self):
# Get the default template, which has name=None # Get the default template, which has name=None
template = CourseEmailTemplate.get_template() template = CourseEmailTemplate.get_template()
...@@ -134,11 +143,31 @@ class CourseEmailTemplateTest(TestCase): ...@@ -134,11 +143,31 @@ class CourseEmailTemplateTest(TestCase):
context = self._get_sample_html_context() context = self._get_sample_html_context()
template.render_htmltext("My new html text.", context) template.render_htmltext("My new html text.", context)
def test_render_html_xss(self):
template = CourseEmailTemplate.get_template()
context = self._add_xss_fields(self._get_sample_html_context())
message = template.render_htmltext(
"Dear %%USER_FULLNAME%%, thanks for enrolling in %%COURSE_DISPLAY_NAME%%.", context
)
self.assertNotIn("<script>", message)
self.assertIn("&lt;script&gt;alert(&#39;Course Title!&#39;);&lt;/alert&gt;", message)
self.assertIn("&lt;script&gt;alert(&#39;Profile Name!&#39;);&lt;/alert&gt;", message)
def test_render_plain(self): def test_render_plain(self):
template = CourseEmailTemplate.get_template() template = CourseEmailTemplate.get_template()
context = self._get_sample_plain_context() context = self._get_sample_plain_context()
template.render_plaintext("My new plain text.", context) template.render_plaintext("My new plain text.", context)
def test_render_plain_no_escaping(self):
template = CourseEmailTemplate.get_template()
context = self._add_xss_fields(self._get_sample_plain_context())
message = template.render_plaintext(
"Dear %%USER_FULLNAME%%, thanks for enrolling in %%COURSE_DISPLAY_NAME%%.", context
)
self.assertNotIn("&lt;script&gt;", message)
self.assertIn(context['course_title'], message)
self.assertIn(context['name'], message)
@attr('shard_1') @attr('shard_1')
class CourseAuthorizationTest(TestCase): class CourseAuthorizationTest(TestCase):
......
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