PageRenderTime 99ms CodeModel.GetById 76ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/django/contrib/comments/forms.py

https://code.google.com/p/mango-py/
Python | 206 lines | 195 code | 6 blank | 5 comment | 7 complexity | 172d0c2fefe63e587dbafb8c2df63656 MD5 | raw file
  1import time
  2import datetime
  3
  4from django import forms
  5from django.forms.util import ErrorDict
  6from django.conf import settings
  7from django.contrib.contenttypes.models import ContentType
  8from models import Comment
  9from django.utils.crypto import salted_hmac, constant_time_compare
 10from django.utils.encoding import force_unicode
 11from django.utils.hashcompat import sha_constructor
 12from django.utils.text import get_text_list
 13from django.utils.translation import ungettext, ugettext_lazy as _
 14
 15COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000)
 16
 17class CommentSecurityForm(forms.Form):
 18    """
 19    Handles the security aspects (anti-spoofing) for comment forms.
 20    """
 21    content_type  = forms.CharField(widget=forms.HiddenInput)
 22    object_pk     = forms.CharField(widget=forms.HiddenInput)
 23    timestamp     = forms.IntegerField(widget=forms.HiddenInput)
 24    security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput)
 25
 26    def __init__(self, target_object, data=None, initial=None):
 27        self.target_object = target_object
 28        if initial is None:
 29            initial = {}
 30        initial.update(self.generate_security_data())
 31        super(CommentSecurityForm, self).__init__(data=data, initial=initial)
 32
 33    def security_errors(self):
 34        """Return just those errors associated with security"""
 35        errors = ErrorDict()
 36        for f in ["honeypot", "timestamp", "security_hash"]:
 37            if f in self.errors:
 38                errors[f] = self.errors[f]
 39        return errors
 40
 41    def clean_security_hash(self):
 42        """Check the security hash."""
 43        security_hash_dict = {
 44            'content_type' : self.data.get("content_type", ""),
 45            'object_pk' : self.data.get("object_pk", ""),
 46            'timestamp' : self.data.get("timestamp", ""),
 47        }
 48        expected_hash = self.generate_security_hash(**security_hash_dict)
 49        actual_hash = self.cleaned_data["security_hash"]
 50        if not constant_time_compare(expected_hash, actual_hash):
 51            # Fallback to Django 1.2 method for compatibility
 52            # PendingDeprecationWarning <- here to remind us to remove this
 53            # fallback in Django 1.5
 54            expected_hash_old = self._generate_security_hash_old(**security_hash_dict)
 55            if not constant_time_compare(expected_hash_old, actual_hash):
 56                raise forms.ValidationError("Security hash check failed.")
 57        return actual_hash
 58
 59    def clean_timestamp(self):
 60        """Make sure the timestamp isn't too far (> 2 hours) in the past."""
 61        ts = self.cleaned_data["timestamp"]
 62        if time.time() - ts > (2 * 60 * 60):
 63            raise forms.ValidationError("Timestamp check failed")
 64        return ts
 65
 66    def generate_security_data(self):
 67        """Generate a dict of security data for "initial" data."""
 68        timestamp = int(time.time())
 69        security_dict =   {
 70            'content_type'  : str(self.target_object._meta),
 71            'object_pk'     : str(self.target_object._get_pk_val()),
 72            'timestamp'     : str(timestamp),
 73            'security_hash' : self.initial_security_hash(timestamp),
 74        }
 75        return security_dict
 76
 77    def initial_security_hash(self, timestamp):
 78        """
 79        Generate the initial security hash from self.content_object
 80        and a (unix) timestamp.
 81        """
 82
 83        initial_security_dict = {
 84            'content_type' : str(self.target_object._meta),
 85            'object_pk' : str(self.target_object._get_pk_val()),
 86            'timestamp' : str(timestamp),
 87          }
 88        return self.generate_security_hash(**initial_security_dict)
 89
 90    def generate_security_hash(self, content_type, object_pk, timestamp):
 91        """
 92        Generate a HMAC security hash from the provided info.
 93        """
 94        info = (content_type, object_pk, timestamp)
 95        key_salt = "django.contrib.forms.CommentSecurityForm"
 96        value = "-".join(info)
 97        return salted_hmac(key_salt, value).hexdigest()
 98
 99    def _generate_security_hash_old(self, content_type, object_pk, timestamp):
100        """Generate a (SHA1) security hash from the provided info."""
101        # Django 1.2 compatibility
102        info = (content_type, object_pk, timestamp, settings.SECRET_KEY)
103        return sha_constructor("".join(info)).hexdigest()
104
105class CommentDetailsForm(CommentSecurityForm):
106    """
107    Handles the specific details of the comment (name, comment, etc.).
108    """
109    name          = forms.CharField(label=_("Name"), max_length=50)
110    email         = forms.EmailField(label=_("Email address"))
111    url           = forms.URLField(label=_("URL"), required=False)
112    comment       = forms.CharField(label=_('Comment'), widget=forms.Textarea,
113                                    max_length=COMMENT_MAX_LENGTH)
114
115    def get_comment_object(self):
116        """
117        Return a new (unsaved) comment object based on the information in this
118        form. Assumes that the form is already validated and will throw a
119        ValueError if not.
120
121        Does not set any of the fields that would come from a Request object
122        (i.e. ``user`` or ``ip_address``).
123        """
124        if not self.is_valid():
125            raise ValueError("get_comment_object may only be called on valid forms")
126
127        CommentModel = self.get_comment_model()
128        new = CommentModel(**self.get_comment_create_data())
129        new = self.check_for_duplicate_comment(new)
130
131        return new
132
133    def get_comment_model(self):
134        """
135        Get the comment model to create with this form. Subclasses in custom
136        comment apps should override this, get_comment_create_data, and perhaps
137        check_for_duplicate_comment to provide custom comment models.
138        """
139        return Comment
140
141    def get_comment_create_data(self):
142        """
143        Returns the dict of data to be used to create a comment. Subclasses in
144        custom comment apps that override get_comment_model can override this
145        method to add extra fields onto a custom comment model.
146        """
147        return dict(
148            content_type = ContentType.objects.get_for_model(self.target_object),
149            object_pk    = force_unicode(self.target_object._get_pk_val()),
150            user_name    = self.cleaned_data["name"],
151            user_email   = self.cleaned_data["email"],
152            user_url     = self.cleaned_data["url"],
153            comment      = self.cleaned_data["comment"],
154            submit_date  = datetime.datetime.now(),
155            site_id      = settings.SITE_ID,
156            is_public    = True,
157            is_removed   = False,
158        )
159
160    def check_for_duplicate_comment(self, new):
161        """
162        Check that a submitted comment isn't a duplicate. This might be caused
163        by someone posting a comment twice. If it is a dup, silently return the *previous* comment.
164        """
165        possible_duplicates = self.get_comment_model()._default_manager.using(
166            self.target_object._state.db
167        ).filter(
168            content_type = new.content_type,
169            object_pk = new.object_pk,
170            user_name = new.user_name,
171            user_email = new.user_email,
172            user_url = new.user_url,
173        )
174        for old in possible_duplicates:
175            if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment:
176                return old
177
178        return new
179
180    def clean_comment(self):
181        """
182        If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't
183        contain anything in PROFANITIES_LIST.
184        """
185        comment = self.cleaned_data["comment"]
186        if settings.COMMENTS_ALLOW_PROFANITIES == False:
187            bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()]
188            if bad_words:
189                plural = len(bad_words) > 1
190                raise forms.ValidationError(ungettext(
191                    "Watch your mouth! The word %s is not allowed here.",
192                    "Watch your mouth! The words %s are not allowed here.", plural) % \
193                    get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and'))
194        return comment
195
196class CommentForm(CommentDetailsForm):
197    honeypot      = forms.CharField(required=False,
198                                    label=_('If you enter anything in this field '\
199                                            'your comment will be treated as spam'))
200
201    def clean_honeypot(self):
202        """Check that nothing's been entered into the honeypot."""
203        value = self.cleaned_data["honeypot"]
204        if value:
205            raise forms.ValidationError(self.fields["honeypot"].label)
206        return value