D
D
devalone2017-05-01 21:50:19
Django
devalone, 2017-05-01 21:50:19

Django. How to protect a form from multiple submissions?

Are there ready-made solutions to protect forms from spam? It is better that the annoying captcha does not appear immediately, but after suspicious actions, for example, a lot of requests from the same ip in a certain period of time, a large number of links in the text, or, if this is a login form, then a large number of attempts to log into one account with different ip addresses.

Answer the question

In order to leave comments, you need to log in

2 answer(s)
S
Sergey Gornostaev, 2017-05-01
@sergey-gornostaev

class FloodSafeMixin(object):
    def __init__(self, *args, **kwargs):
        request = kwargs.pop('request', None)
        if request:
            self._user = request.user.username if request.user.is_authenticated() else None
            self._ip = get_ip(request)
            self._period = OrderedDict()
            self._period['days'] = kwargs.pop('days', None)
            self._period['hours'] = kwargs.pop('hours', None)
            self._period['minutes'] = kwargs.pop('minutes', None)
            self._period['seconds'] = kwargs.pop('seconds', None)
            self._period['milliseconds'] = kwargs.pop('milliseconds', None)
            if not any(self._period.values()):
                self._period = {'minutes': 1}
            else:
                self._period = OrderedDict([(k, v) for k, v in self._period.items() if v])
        else:
            self._ip = None
        super(FloodSafeMixin, self).__init__(*args, **kwargs)


    def clean(self):
        cleaned_data = super(FloodSafeMixin, self).clean()
        sender_id = hashlib.md5((self._user if self._user else self._ip).encode('utf-8')).hexdigest()
        if sender_id:
            class_name = self.__class__.__name__
            cache_name = 'last-submit.{0}.{1}'.format(class_name, sender_id)

            now = datetime.now()
            last_submit = cache.get(cache_name, now - timedelta(days=1))
            if (now - last_submit) < timedelta(**self._period):
                cache.set(cache_name, now)
                period = ' '.join([u'{0} {1}'.format(v, _p('genitive', k)) for k, v in self._period.items()])
                raise forms.ValidationError(_('Form submitted less than %(period)s ago'), code='flood',
                    params={'period': period})
            else:
                if not self.errors:
                    cache.set(cache_name, now)
        return cleaned_data


class FeedbackForm(FloodSafeMixin, forms.Form):
    ...


class SomeFormHandlerView(FormView):
    def get_form_kwargs(self):
        kwargs = super(GenericFormHandlerView, self).get_form_kwargs()
        kwargs['request'] = self.request
        kwargs['minutes'] = 2
        return kwargs
    ...

A
Artyom Innokentiev, 2017-05-02
@artinnok

In my opinion, it is better to write a custom middleware that will protect you from stupid brute force. Offhand logic:
If you want to make protection on the front - use, for example, reCAPTCHA . Save you from stupid bots.
Pushing all the protection logic into a form is a strange solution, very ugly and cumbersome, plus, not extensible.
In the form - only validation and nothing more. No one will look at the code of the form, while protecting against brute force.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question