Permalink
Browse files

Added html5lib powered html cleaning to text plugin (in form) and tes…

…ts for it
  • Loading branch information...
Jonas Obrist
Jonas Obrist committed Aug 24, 2011
1 parent c3880b6 commit 9ca7738bc1cef827765589c5b254810370a0fc0b
Showing with 77 additions and 7 deletions.
  1. +32 −2 cms/plugins/text/forms.py
  2. +37 −2 cms/tests/security.py
  3. +1 −0 setup.py
  4. +7 −3 tests/buildout.cfg
View
@@ -1,11 +1,41 @@
-from django.forms.models import ModelForm
from cms.plugins.text.models import Text
from django import forms
+from django.forms.models import ModelForm
+import html5lib
+from html5lib import sanitizer
+
+def _get_inner_body(doc):
+ # find 'body'
+ def _rec(node):
+ if node.type == 5: # Element Type
+ if node.name == 'body': # the body element
+ return node
+ for child in node.childNodes:
+ childfound = _rec(child)
+ if childfound:
+ return childfound
+ return None
+ body = _rec(doc)
+ # if the first element after <body> is a html tag, this returns an Element
+ # instance, otherwise a (unicode) string, this is why we need to check
+ # the output of this and potentially call .toxml() again.
+ out = reduce(lambda x,y:x.toxml()+y.toxml(), body.childNodes)
+ if isinstance(out, basestring):
+ return out
+ return out.toxml()
class TextForm(ModelForm):
body = forms.CharField()
class Meta:
model = Text
- exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type')
+ exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type')
+
+ parser = html5lib.HTMLParser(tokenizer=sanitizer.HTMLSanitizer)
+
+ def clean_body(self):
+ data = self.cleaned_data['body']
+ doc = self.parser.parse(data)
+ html = _get_inner_body(doc)
+ return html
View
@@ -1,8 +1,10 @@
+from cms.models.pagemodel import Page
from cms.models.pluginmodel import CMSPlugin
from cms.plugins.text.models import Text
from cms.test.testcases import (CMSTestCase, URL_CMS_PLUGIN_ADD,
- URL_CMS_PLUGIN_EDIT, URL_CMS_PLUGIN_REMOVE)
+ URL_CMS_PLUGIN_EDIT, URL_CMS_PLUGIN_REMOVE, URL_CMS_PAGE_ADD)
from django.conf import settings
+from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
@@ -172,4 +174,37 @@ def test_delete_ph(self):
response = self.client.post(url, plugin_data)
# the user is logged in and the security check fails, so it should 404.
self.assertEqual(response.status_code, 404)
- self.assertEqual(CMSPlugin.objects.count(), 1)
+ self.assertEqual(CMSPlugin.objects.count(), 1)
+
+ def test_text_plugin_xss(self):
+ super_user = User(username="test", is_staff=True, is_active=True, is_superuser=True)
+ super_user.set_password("test")
+ super_user.save()
+
+ self.login_user(super_user)
+
+ page_data = self.get_new_page_data()
+ self.client.post(URL_CMS_PAGE_ADD, page_data)
+ page = Page.objects.all()[0]
+ plugin_data = {
+ 'plugin_type': "TextPlugin",
+ 'language': settings.LANGUAGES[0][0],
+ 'placeholder': page.placeholders.get(slot="body").pk,
+ }
+ response = self.client.post(URL_CMS_PLUGIN_ADD, plugin_data)
+ self.assertEquals(response.status_code, 200)
+ self.assertEquals(int(response.content), CMSPlugin.objects.all()[0].pk)
+ edit_url = URL_CMS_PLUGIN_EDIT + response.content + "/"
+ response = self.client.get(edit_url)
+ self.assertEquals(response.status_code, 200)
+ # ACTUAL TEST STARTS HERE.
+ data = {
+ "body": "<div onload='do_evil_stuff();'>divcontent</div><a href='javascript:do_evil_stuff()'>acontent</a>"
+ }
+ response = self.client.post(edit_url, data)
+ self.assertEquals(response.status_code, 200)
+ txt = Text.objects.all()[0]
+ self.assertEquals(txt.body, '<div>divcontent</div><a>acontent</a>')
+
+ self.client.logout()
+ self.user = None
View
@@ -49,6 +49,7 @@
'Django>=1.2',
'django-classy-tags>=0.2.2',
'south>=0.7.2',
+ 'html5lib',
],
packages=find_packages(exclude=["example", "example.*","testdata","testdata.*"]),
package_data={
View
@@ -3,6 +3,7 @@ parts =
python
django
eggs =
+ django
django-cms
coverage
unittest-xml-reporting
@@ -11,6 +12,8 @@ eggs =
django
South
sphinx
+extra-paths =
+ testapp
develop =
../
versions = versions
@@ -24,13 +27,14 @@ scripts =
[django]
-recipe = djangorecipe
-version = 1.2.5
-project = testapp
+recipe = djangoprojectrecipe
+project = project
settings = settings
eggs = ${buildout:eggs}
+extra-paths = ${buildout:extra-paths}
[versions]
coverage = 3.4
unittest-xml-reporting = 1.0.3
django-reversion = 1.3.3
+django = 1.2.5

0 comments on commit 9ca7738

Please sign in to comment.