views:

66

answers:

2

Hey,

In order to avoid spamming, I would like to add a waiting time to re-submit a form (i.e. the user should wait a few seconds to submit the form, except the first time that this form is submitted).

To do that, I added a timestamp to my form (and a security_hash field containing the timestamp plus the settings.SECRET_KEY which ensures that the timestamp is not fiddled with). This look like:

class MyForm(forms.Form):
    timestamp     = forms.IntegerField(widget=forms.HiddenInput)
    security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput)
    # + some other fields..

    # + methods to build the hash and to clean the timestamp...
    # (it is based on django.contrib.comments.forms.CommentSecurityForm)

    def clean_timestamp(self):
        """Make sure the delay is over (5 seconds)."""
        ts = self.cleaned_data["timestamp"]
        if not time.time() - ts > 5:
            raise forms.ValidationError("Timestamp check failed")
        return ts

    # etc...

This works fine. However there is still an issue: the timestamp is checked the first time the form is submitted by the user, and I need to avoid this.

Any idea to fix it ?

Thank you ! :-)

+2  A: 

the timestamp is checked the first time the form is submitted by the user, and I need to avoid this.

If this is the problem, couldn't you create the form setting the timestamp -5 minutes?

Frank V
+2  A: 

One way to do this is to set an initial value to time, let's say 0, and update it to the current timestamp once the form validates, and only check timestamp when it's not 0:

class MyForm(forms.Form):
    timestamp     = forms.IntegerField(widget=forms.HiddenInput, initial=0)
    #look at the initial = 0

    security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput)


    def clean_timestamp(self):
        """Make sure the delay is over (5 seconds)."""
        ts = self.cleaned_data["timestamp"]
        if timestamp != 0 and not time.time() - ts > 5:
            raise forms.ValidationError("Timestamp check failed")
        return ts

    def clean(self):
        cleaned_data = self.cleaned_data
        if len(self._errors) == 0: #it validates
            cleaned_data["timestamp"] = time.time()
        return cleaned_data

Another possible solution is to use sessions. It's safer but not bulletproof. With the previous apporach the user can send the same post data several times, and the form will validate several times (cause he's sending the same timestamp). With sessions you need the user to have cookies enabled, but they won't be able to send post data that validates quicker than 5 seconds.

This way once a correct form submission happens you can save the time in a user's session key and check that before revalidating your form. This is easily done in a view. If you want to do it at the form logic level, you need to create your own clean method in the form that takes the request (so you can use the session). Be careful, the user can clean his cookies and post as a "new" user.

Hope this helps.

rasca
I didn't think about using the user's session. Thank you :-)