Login

AntiSpamForm

Author:
zenx
Posted:
February 14, 2010
Language:
Python
Version:
1.1
Score:
3 (after 3 ratings)

A general AntiSpamForm using some tricks to prevent spam based on current django.contrib.comments.forms. It uses a timestamp, a security hash and a honeypot field. See AntiSpamModelForm too.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import time
from django import forms
from django.forms.util import ErrorDict
from django.conf import settings
from django.utils.hashcompat import sha_constructor

class AntiSpamForm(forms.Form):
    timestamp     = forms.IntegerField(widget=forms.HiddenInput)
    security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput)
    honeypot      = forms.CharField(required=False,
                                    widget=forms.TextInput(attrs={'style':'display:none;'}),
                                    label='')
    
    def __init__(self, data=None, initial=None):
        if initial is None:
            initial = {}
        initial.update(self.generate_security_data())
        super(AntiSpamForm, self).__init__(data=data, initial=initial)

    def security_errors(self):
        """Return just those errors associated with security"""
        errors = ErrorDict()
        for f in ["honeypot", "timestamp", "security_hash"]:
            if f in self.errors:
                errors[f] = self.errors[f]
        return errors

    def clean_security_hash(self):
        """Check the security hash."""
        security_hash_dict = {
            'timestamp' : self.data.get("timestamp", ""),
        }
        expected_hash = self.generate_security_hash(**security_hash_dict)
        actual_hash = self.cleaned_data["security_hash"]
        if expected_hash != actual_hash:
            raise forms.ValidationError("Security hash check failed.")
        return actual_hash

    def clean_timestamp(self):
        """Make sure the timestamp isn't too far (> 2 hours) in the past or too close (< 5 seg)."""
        ts = self.cleaned_data["timestamp"]
        difference = time.time() - ts
        if difference > (2 * 60 * 60) or difference < 5:
            raise forms.ValidationError("Timestamp check failed")
        return ts

    def generate_security_data(self):
        """Generate a dict of security data for "initial" data."""
        timestamp = int(time.time())
        security_dict =   {
            'timestamp'     : str(timestamp),
            'security_hash' : self.initial_security_hash(timestamp),
        }
        return security_dict

    def initial_security_hash(self, timestamp):
        """
        Generate the initial security hash from a (unix) timestamp.
        """

        initial_security_dict = {
            'timestamp' : str(timestamp),
          }
        return self.generate_security_hash(**initial_security_dict)

    def generate_security_hash(self, timestamp):
        """Generate a (SHA1) security hash from the provided info."""
        info = (timestamp, settings.SECRET_KEY)
        return sha_constructor("".join(info)).hexdigest()

    def clean_honeypot(self):
        """Check that nothing's been entered into the honeypot."""
        value = self.cleaned_data["honeypot"]
        if value:
            raise forms.ValidationError(self.fields["honeypot"].label)
        return value

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

grillermo (on March 21, 2012):

Thank you, i got it to work easily. As a reference i leave it here, you instantiate this class in one view and add it to its context

In your view

from forms import AntiSpamForm context['antispamform'] = AntiSpamForm()

In your template inside a list of fields in a form

form method='POST' {% csrf_token %} {% for field in antispamform %} {{ field }} {% endfor %} <input name='yourfield' value=''></input>

And in the view that recieves this form

form = AntiSpamForm(request.POST) if form.is_valid(): do_stuff()

That is all

#

evilclay (on July 30, 2012):

if you get a deprecation warning on sha_constructor replace it with sha1 and import: from hashlib import sha1

#

evilclay (on July 30, 2012):

also, probbaly better would be using kwargs in the init method:

def __init__(self, *args, **kwargs):

    initial = kwargs.get("initial", {})
    initial.update(self.generate_security_data())
    kwargs["initial"] = initial
    super(AntiSpamForm, self).__init__(*args, **kwargs)

#

Please login first before commenting.