Prevent form tampering of hidden fields

 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
from django import forms
from django.conf import settings
try:
    from hashlib import md5
except ImportError:
    from md5 import md5

class SecureForm(forms.Form):
    """
    >>> from django import forms
    >>> from forms_test.secure_forms import SecureForm
    >>>                                               
    >>> class TF(SecureForm):                         
    ...    name  = forms.CharField(max_length=10)     
    ...    age   = forms.IntegerField(widget=forms.HiddenInput())
    ...    email = forms.EmailField(widget=forms.HiddenInput(), required=False)
    ...                                                                        
    >>>
    >>> t = TF(initial={'age':20})
    >>> p = TF({'name':'theju','age': 30, 'form_hash': t.initial['form_hash']})
    >>> p.is_valid()
    False
    >>> p.errors
    {'__all__': [u'Form has been tampered!']}
    >>> p = TF({'name':'theju','age': 20, 'form_hash': t.initial['form_hash'], 'email': 'test@example.com'})
    >>> p.is_valid()
    False
    >>> p.errors
    {'__all__': [u'Form has been tampered!']}
    >>> t = TF(initial={'age': 20, 'email': 'test@example.com'})
    >>> p = TF({'name':'theju','age': 20, 'form_hash': t.initial['form_hash'], 'email': 'test@example.com'})
    >>> p.is_valid()
    True
    >>> p = TF({'name':'theju','age': 20, 'form_hash': 'nonsense', 'email': 'test@example.com'})
    >>> p.is_valid()
    False
    >>> p.errors
    {'__all__': [u'Form has been tampered!']}
    >>>
    >>> class TestForm(SecureForm):
    ...    name = forms.CharField(max_length=10)
    ...    age  = forms.IntegerField()
    ...
    >>> t = TestForm({'name':'theju','age':50})
    >>> t.is_valid()
    True
    >>> t.errors
    {}
    """
    def __init__(self, *args, **kwargs):
        super(SecureForm, self).__init__(*args, **kwargs)
        form_hash = forms.CharField(widget=forms.HiddenInput(),required=False)
        self.fields.update({'form_hash': form_hash})
        hash_str = ""
        for name, field in self.fields.items():
            if name != 'form_hash' and field.widget.is_hidden and self.initial.get(name, None):
                hash_str += unicode(self.initial[name])
        if hash_str:
            hash_val = md5(hash_str+settings.SECRET_KEY).hexdigest()
            self.initial['form_hash'] = hash_val

    def clean(self):
        hash_str = ""
        for name, field in self.fields.items():
            if name != 'form_hash' and field.widget.is_hidden and self.cleaned_data.get(name, None):
                hash_str += unicode(self.cleaned_data[name])
        if hash_str:
            hash_val = md5(hash_str+settings.SECRET_KEY).hexdigest()
            if hash_val and hash_val != self.cleaned_data['form_hash']:
                raise forms.ValidationError("Form has been tampered!")
        return self.cleaned_data

Comments

(Forgotten your password?)

You may use Markdown syntax here, but raw HTML will be removed.