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
2 changes: 1 addition & 1 deletion apps/nominations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ class Nomination(models.Model):
previous_board_service = models.CharField(max_length=1024, blank=False, null=True) # noqa: DJ001
employer = models.CharField(max_length=1024, blank=False, null=True) # noqa: DJ001
other_affiliations = models.CharField(max_length=2048, blank=True, null=True) # noqa: DJ001
nomination_statement = MarkupField(escape_html=True, markup_type="markdown", blank=False, null=True)
nomination_statement = MarkupField(markup_type="markdown", blank=False, null=True)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new pydotorg.markup stuff from seth sanitizes


nominator = models.ForeignKey(User, related_name="nominations_made", on_delete=models.CASCADE)
nominee = models.ForeignKey(
Expand Down
7 changes: 7 additions & 0 deletions apps/nominations/templates/nominations/nomination_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ <h1 class="page-title">Submit a Nomination for {{ election.name }} Election</h1>
}
#id_previous_service_years input { margin: 0; }
#statement-preview { border: 1px solid #ccc; border-radius: 4px; background: #fff; padding: 0.75em 1em; margin-top: 0.5em; }
/* Fix site styling specifically for elections */
#statement-preview ul, #statement-preview ol { margin: 0 0 1em 1.5em; }
#statement-preview ul { list-style: square; }
#statement-preview ol { list-style: decimal; }
#statement-preview li { float: none; width: auto; display: list-item; }
#statement-preview li > ul, #statement-preview li > ol { margin-bottom: 0; }
#statement-preview > *:last-child { margin-bottom: 0; }
</style>
<table>
{% for field in form %}
Expand Down
36 changes: 35 additions & 1 deletion apps/nominations/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.conf import settings
from django.test import TestCase

from apps.nominations.models import DEFAULT_ACCENT_COLOR, Election, ElectionKind
from apps.nominations.models import DEFAULT_ACCENT_COLOR, Election, ElectionKind, Nomination


class ElectionKindModelTests(TestCase):
Expand Down Expand Up @@ -70,3 +70,37 @@ def test_markdown_preserves_safe_links_and_formatting(self):
def test_restructuredtext_strips_javascript_uri(self):
rendered = self._render("restructuredtext", "`x <javascript:alert(1)>`_")
self.assertNotIn("javascript:", rendered)


class NominationStatementRenderingTests(TestCase):
"""The statement pipeline must allow markdown but never raw HTML."""

def render(self, text):
return Nomination.render_statement(text)

def test_blockquote_renders(self):
self.assertIn("<blockquote>", self.render("> quoted"))

def test_lists_render(self):
html = self.render("- one\n- two")
self.assertIn("<ul>", html)
self.assertEqual(html.count("<li>"), 2)

def test_headings_and_emphasis_render(self):
html = self.render("# Title\n\n**bold** and *italic*")
self.assertIn("<h1>Title</h1>", html)
self.assertIn("<strong>bold</strong>", html)
self.assertIn("<em>italic</em>", html)

def test_script_is_dropped(self):
html = self.render("<script>alert(1)</script>")
self.assertNotIn("script", html)
self.assertNotIn("alert(1)", html)

def test_event_handler_inside_blockquote_is_dropped(self):
html = self.render("> <img src=x onerror=alert(1)>")
self.assertIn("<blockquote>", html)
self.assertNotIn("onerror", html)

def test_unsafe_link_scheme_is_dropped(self):
self.assertNotIn("javascript:", self.render("[x](javascript:alert(1))"))