diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 3e3d39294..d5a7e76c8 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -19,24 +19,32 @@ from openslides.utils.person import PersonFormField, MultiplePersonFormField from .models import Motion, Category -class BaseMotionForm(CleanHtmlFormMixin, forms.ModelForm, CssClassMixin): - """Base FormClass for a Motion. +class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm): + """ + Base FormClass for a Motion. For it's own, it append the version data to the fields. The class can be mixed with the following mixins to add fields for the submitter, supporters etc. """ + clean_html_fields = ('text', 'reason') title = forms.CharField(widget=forms.TextInput(), label=_("Title")) - """Title of the motion. Will be saved in a MotionVersion object.""" + """ + Title of the motion. Will be saved in a MotionVersion object. + """ text = forms.CharField(widget=forms.Textarea(), label=_("Text")) - """Text of the motion. Will be saved in a MotionVersion object.""" + """ + Text of the motion. Will be saved in a MotionVersion object. + """ reason = forms.CharField( widget=forms.Textarea(), required=False, label=_("Reason")) - """Reason of the motion. will be saved in a MotionVersion object.""" + """ + Reason of the motion. will be saved in a MotionVersion object. + """ class Meta: model = Motion @@ -52,12 +60,6 @@ class BaseMotionForm(CleanHtmlFormMixin, forms.ModelForm, CssClassMixin): self.initial['reason'] = self.motion.reason super(BaseMotionForm, self).__init__(*args, **kwargs) - def get_clean_html_fields(self): - ''' - The fields 'text' and 'reason' contain HTML, clean them - ''' - return ('text', 'reason',) - class MotionSubmitterMixin(forms.ModelForm): """Mixin to append the submitter field to a MotionForm.""" diff --git a/openslides/static/javascript/ckeditor-config.js b/openslides/static/javascript/ckeditor-config.js index fc1d36658..a51057f85 100644 --- a/openslides/static/javascript/ckeditor-config.js +++ b/openslides/static/javascript/ckeditor-config.js @@ -3,33 +3,33 @@ */ $(function() { var ck_options = { - + // Using a custom CSS file allows us to specifically style elements entered // using the editor. Using the CSS prefix class .ckeditor_html prevents these // styles from meddling with the main layout - + contentsCss: "/static/styles/ckeditor.css", bodyClass: "ckeditor_html", - + allowedContent: 'h1 h2 h3 pre blockquote b i u strike;' + - + // A workaround for the problem described in http://dev.ckeditor.com/ticket/10192 // Hopefully, the problem will be solved in the final version of CKEditor 4.1 // If so, then {margin-left} can be removed 'p{margin-left};' + - + 'a[!href];' + 'table tr th td caption;' + 'ol ul li;' + 'span{!font-family};' + 'span{!color};', removePlugins: "save, print, preview, pagebreak, templates, showblocks, magicline", - + // Not part of the standard package of CKEditor // http://ckeditor.com/addon/insertpre extraPlugins: "insertpre", - + toolbar_Full: [ { name: 'document', items : [ 'Source','-','Save','DocProps','Preview','Print','-','Templates' ] }, { name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] }, @@ -44,10 +44,6 @@ $(function() { toolbar: 'Full' }; - // Override the tags 'strong' and 'em' so that reportlab can read it - CKEDITOR.config.coreStyles_bold = { element : 'b', overrides : 'strong' }; - CKEDITOR.config.coreStyles_italic = { element : 'i', overrides : 'em' }; - CKEDITOR.replace('id_text', ck_options); CKEDITOR.replace('id_reason', ck_options); -}); \ No newline at end of file +}); diff --git a/openslides/utils/forms.py b/openslides/utils/forms.py index 38b86e33b..145df2045 100644 --- a/openslides/utils/forms.py +++ b/openslides/utils/forms.py @@ -13,15 +13,16 @@ import bleach from django import forms -from django.views.generic.edit import FormMixin -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy # Allowed tags, attributes and styles allowed in textareas edited with a JS # editor. Everything not in these whitelists is stripped. HTML_TAG_WHITELIST = ('a', 'i', + 'em', 'b', + 'strong', 'ul', 'ol', 'li', @@ -36,11 +37,10 @@ HTML_TAG_WHITELIST = ('a', 'h3',) HTML_ATTRIBUTES_WHITELIST = { - '*': ['style'], 'a': ['href'], } -HTML_STYLES_WHITELIST = ('text-decoration',) +HTML_STYLES_WHITELIST = () class CssClassMixin(object): @@ -60,26 +60,27 @@ class LocalizedModelMultipleChoiceField(forms.ModelMultipleChoiceField): c = [] for (id, text) in super(LocalizedModelMultipleChoiceField, self)._get_choices(): text = text.split(' | ')[-1] - c.append((id, _(text))) + c.append((id, ugettext_lazy(text))) return c choices = property(_localized_get_choices, forms.ChoiceField._set_choices) -class CleanHtmlFormMixin(FormMixin): - ''' +class CleanHtmlFormMixin(object): + """ A form mixin that pre-processes the form, cleaning up the HTML code found in the fields in clean_html. All HTML tags, attributes and styles not in the whitelists are stripped from the output, leaving only the text content:
foo
simply becomes 'foo' - ''' + """ + clean_html_fields = () def get_clean_html_fields(self): - ''' - the list of elements to strip of potential malicious HTML - ''' - return() + """ + The list of elements to strip of potential malicious HTML. + """ + return self.clean_html_fields def clean(self): cleaned_data = super(CleanHtmlFormMixin, self).clean() @@ -89,7 +90,4 @@ class CleanHtmlFormMixin(FormMixin): attributes=HTML_ATTRIBUTES_WHITELIST, styles=HTML_STYLES_WHITELIST, strip=True) - - # Needed for reportlab - cleaned_data[field] = cleaned_data[field].replace('
', '
') return cleaned_data diff --git a/openslides/utils/pdf.py b/openslides/utils/pdf.py index d7d62e144..bf0934eab 100755 --- a/openslides/utils/pdf.py +++ b/openslides/utils/pdf.py @@ -45,7 +45,6 @@ PAGE_WIDTH = defaultPageSize[0] stylesheet = StyleSheet1() stylesheet.add(ParagraphStyle( name='Normal', - #fontName='Ubuntu', fontSize=10, leading=12, )) diff --git a/requirements.txt b/requirements.txt index c7ded514d..56f94168d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ # Requirements for OpenSlides Core Django==1.5.1 django-mptt==0.5.5 -beautifulsoup4==4.1.3 -bleach==1.2.1 pillow==2.0.0 qrcode==2.7 reportlab==2.7 tornado==3.0.1 +bleach==1.2.1 +beautifulsoup4==4.1.3 # required for travis Fabric==1.6.0 diff --git a/tests/forms/__init__.py b/tests/forms/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/forms/test_clean_html.py b/tests/forms/test_clean_html.py index 6680be09b..c59621d0d 100644 --- a/tests/forms/test_clean_html.py +++ b/tests/forms/test_clean_html.py @@ -1,33 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ - Unit test for OpenSlides __init__.py + Test the openslides forms. :copyright: 2011, 2012, 2013 by the OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ -from django.test import TestCase from django import forms from django.db import models +from openslides.utils.test import TestCase from openslides.utils.forms import CleanHtmlFormMixin -from openslides.motion.models import Motion class HtmlTestForm(CleanHtmlFormMixin, forms.Form): text = forms.CharField() text2 = forms.CharField() - - def get_clean_html_fields(self): - ''' - The field 'text' contains HTML, clean it - ''' - return ('text', ) + clean_html_fields = ('text',) class CleanHtmlTest(TestCase): - def clean_html(self, dirty='', clean=False): form = HtmlTestForm({'text': dirty, 'text2': dirty}) form.is_valid() @@ -38,31 +31,31 @@ class CleanHtmlTest(TestCase): # Something was removed else: - self.assertEqual(form.cleaned_data['text'], cleaned) + self.assertEqual(form.cleaned_data['text'], clean) # Field text2 has the same content, but is never passed through the # HTML-cleanup and should never change self.assertEqual(form.cleaned_data['text2'], dirty) def test_clean_html(self): - ''' + """ Test that the correct HTML tags and attributes are removed - ''' + """ # Forbidden tags and attributes self.clean_html('', 'do_evil();') self.clean_html('evil', 'evil') - self.clean_html('good?', 'good?') self.clean_html('

good?

', '

good?

') self.clean_html('

Not evil

', '

Not evil

') self.clean_html('
evil
', 'evil') - self.clean_html('

bad

', '

bad

') + self.clean_html('

bad

', '

bad

') + self.clean_html('
OK
', 'OK') + self.clean_html('
OK
', 'OK') + self.clean_html('

OK

', '

OK

') # Allowed tags and attributes + self.clean_html('good?') self.clean_html('

OK

') - self.clean_html('
OK
') self.clean_html('

OK

') self.clean_html('
OK
') - self.clean_html('
OK
') self.clean_html('') - self.clean_html('

OK

')