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 >>> 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 {} >>> t = TF(initial={"name": "theju", "age": 20, "email": "test@example.com"}, ... exclude_fields=["email"]) >>> t.initial['form_hash'] '98a7d8b771644c7e86717c2e03d8f0a9' >>> p = TF({"name": "theju", "age": 30, "email": "test@example.com", ... "form_hash": t.initial["form_hash"]}, exclude_fields=["email"]) >>> p.is_valid() False >>> p.errors {'__all__': [u'Form has been tampered!']} >>> p = TF({"name": "theju", "age": 20, "email": "test1@example.com", ... "form_hash": t.initial["form_hash"]}, exclude_fields=["email"]) >>> p.is_valid() True >>> p.errors {} >>> p = TF({"name": "theju", "age": 20, "email": "test1@example.com", ... "form_hash": t.initial["form_hash"]}, exclude_fields=("email",)) >>> p.is_valid() True >>> p.errors {} """ def __init__(self, *args, **kwargs): exclude_fields = kwargs.pop("exclude_fields", None) super(SecureForm, self).__init__(*args, **kwargs) form_hash = forms.CharField(widget=forms.HiddenInput(), required=False) self.fields.update({'form_hash': form_hash}) self.exclude_fields = ["form_hash"] if exclude_fields and isinstance(exclude_fields, (list, tuple)): self.exclude_fields += list(exclude_fields) hash_str = u"" for name, field in self.fields.items(): if field.widget.is_hidden and name not in self.exclude_fields and (self.initial.get(name, None) or field.initial): hash_str += unicode(self.initial.get(name, None) or field.initial) if hash_str: hash_val = md5(hash_str.encode("utf-8") + settings.SECRET_KEY).hexdigest() self.initial['form_hash'] = hash_val def clean(self): cleaned_data = super(SecureForm, self).clean() hash_str = u"" for name, field in self.fields.items(): if field.widget.is_hidden and name not in self.exclude_fields and cleaned_data.get(name, None): hash_str += unicode(cleaned_data[name]) if hash_str: hash_val = md5(hash_str.encode("utf-8") + settings.SECRET_KEY).hexdigest() if hash_val and hash_val != cleaned_data['form_hash']: raise forms.ValidationError("Form has been tampered!") return cleaned_data if __name__ == '__main__': import doctest doctest.testmod()