M docs/release_notes.rst => docs/release_notes.rst +26 -1
@@ 37,12 37,37 @@ django-wiki 0.2 (dev)
* Updated languages since 0.1: Chinese, French, German, German, Russian, Spanish
* Added Django 1.10 support #563
+ * Security: Do not depend on markdown ``safe_mode``, instead use ``bleach``.
* Fix duplicate search results when logged in #582 (duvholt)
* Fix memory leak in markdown extensions setting #564
* Updated translations - Languages > 90% completed: Chinese (China), Portuguese (Brazil), Korean (Korea), French, Slovak, Spanish, Dutch, German, Russian, Finnish.
* Taiwanese Chinese added (39% completed)
* Cleanup documentation structure #575
-Support removed for:
+
+HTML contents
+~~~~~~~~~~~~~
+
+`Bleach <https://github.com/mozilla/bleach>`_ is now used to sanitize HTML
+before invoking Markdown.
+
+HTML escaping is done before Markdown parsing happens. In future Markdown
+versions, HTML escaping is no longer done, and ``safe_mode`` is removed. We have
+already removed ``safe_mode`` from the default ``WIKI_MARKDOWN_KWARGS`` setting,
+however if you have configured this yourself, you are advised to remove
+``safe_mode``.
+
+Allowed tags are from Bleach's default settings: ``a``, ``abbr``, ``acronym``,
+``b``, ``blockquote``, ``code``, ``em``, ``i``, ``li``, ``ol``, ``strong``,
+``ul``.
+
+Please use new setting ``WIKI_MARKDOWN_HTML_WHITELIST`` and set a list of
+allowed tags to customize behavior.
+
+
+Python and Django support
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Support has been removed for:
* Python 2.6
* Django < 1.8
M setup.py => setup.py +1 -0
@@ 28,6 28,7 @@ def read_file(fname):
requirements = [
"Django>=1.8",
+ "bleach>=1.5,<2",
"Pillow",
"django-nyt>=1.0b1",
"six",
M tox.ini => tox.ini +1 -0
@@ 20,6 20,7 @@ deps =
mock>=2.0
Markdown==2.6.7
django_nyt==1.0b1
+ bleach==1.5.0
django18: Django==1.8.2
django19: Django==1.9
django110: Django==1.10.2
M wiki/conf/settings.py => wiki/conf/settings.py +9 -1
@@ 1,5 1,7 @@
from __future__ import absolute_import, unicode_literals
+import bleach
+
from django.conf import settings as django_settings
from django.core.files.storage import default_storage
from django.core.urlresolvers import reverse_lazy
@@ 26,13 28,19 @@ MARKDOWN_KWARGS = {
'codehilite',
'sane_lists',
],
- 'safe_mode': 'replace',
'extension_configs': {
'toc': {
'title': _('Table of Contents')}},
}
MARKDOWN_KWARGS.update(getattr(django_settings, 'WIKI_MARKDOWN_KWARGS', {}))
+# Allowed tags in Markdown article contents.
+MARKDOWN_HTML_WHITELIST = getattr(
+ django_settings,
+ 'WIKI_MARKDOWN_HTML_WHITELIST',
+ bleach.ALLOWED_TAGS
+)
+
# This slug is used in URLPath if an article has been deleted. The children of the
# URLPath of that article are moved to lost and found. They keep their permissions
# and all their content.
M wiki/core/markdown/__init__.py => wiki/core/markdown/__init__.py +7 -1
@@ 1,10 1,12 @@
from __future__ import absolute_import, unicode_literals
+import bleach
+import markdown
+
from wiki.conf import settings
from wiki.core.markdown.mdx.previewlinks import PreviewLinksExtension
from wiki.core.markdown.mdx.responsivetable import ResponsiveTableExtension
from wiki.core.plugins import registry as plugin_registry
-import markdown
class ArticleMarkdown(markdown.Markdown):
@@ 27,6 29,10 @@ class ArticleMarkdown(markdown.Markdown):
extensions += plugin_registry.get_markdown_extensions()
return extensions
+ def convert(self, text, *args, **kwargs):
+ text = bleach.clean(text, tags=settings.MARKDOWN_HTML_WHITELIST)
+ return super(ArticleMarkdown, self).convert(text, *args, **kwargs)
+
def article_markdown(text, article, *args, **kwargs):
md = ArticleMarkdown(article, *args, **kwargs)
M wiki/tests/test_markdown.py => wiki/tests/test_markdown.py +20 -3
@@ 1,12 1,16 @@
from __future__ import absolute_import, unicode_literals
-from django.test import TestCase
import markdown
+from django.test import TestCase
+from mock import patch
from wiki.core.markdown import ArticleMarkdown
from wiki.core.markdown.mdx.responsivetable import ResponsiveTableExtension
-from mock import patch
+from wiki.models import URLPath
+from wiki.tests.base import ArticleTestBase
+
+
+class ArticleMarkdownTests(ArticleTestBase):
-class ArticleMarkdownTests(TestCase):
@patch('wiki.core.markdown.settings')
def test_do_not_modify_extensions(self, settings):
extensions = ['footnotes', 'attr_list', 'sane_lists']
@@ 15,7 19,20 @@ class ArticleMarkdownTests(TestCase):
ArticleMarkdown(None)
self.assertEqual(len(extensions), number_of_extensions)
+ def test_html_removal(self):
+
+ urlpath = URLPath.create_article(
+ self.root,
+ 'html_removal',
+ title="Test 1",
+ content="</html>only_this"
+ )
+
+ self.assertEqual(urlpath.article.render(), "<p></html>only_this</p>")
+
+
class ResponsiveTableTests(TestCase):
+
def setUp(self):
self.md = markdown.Markdown(extensions=[
'extra',