Commit e30e9baf authored by Joseph Wilhelm's avatar Joseph Wilhelm
Browse files

Initial commit of everything. Not a single bit of this is tested, but I...

Initial commit of everything. Not a single bit of this is tested, but I believe it’s feature complete.
parents
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+6 −0
Original line number Diff line number Diff line
*.log
*.pot
*.pyc
.*project
.DS_Store
.settings/

README.md

0 → 100644
+4 −0
Original line number Diff line number Diff line
django-botscout
===============

This provides an easy hook into the Botscout system for Django forms

botscout/__init__.py

0 → 100644
+0 −0

Empty file added.

botscout/forms.py

0 → 100644
+80 −0
Original line number Diff line number Diff line
import logging
from urllib import urlencode
import urllib2

from django.core import cache
from django import forms
from django.utils.translation import ugettext_lazy as _

from .settings import (BOTSCOUT_API_KEY, BOTSCOUT_API_URL,
                       BOTSCOUT_CACHE_TIMEOUT, BOTSCOUT_NETWORK_TIMEOUT)

logger = logging.getLogger(__name__)


class BotScoutForm(object):
    """
    BotScoutForm is a form mixin which should work with both regular and model
    forms to prevent spam bots. It checks all available data against the
    botscout.com database for any bots known to them.

    This mixin should precede forms.Form/ModelForm.
    """

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        error_msg = getattr(self, 'BOTSCOUT_ERROR_MESSAGE',
                            _('This request was matched in the BotScout database'))
        super(BotScoutForm, self).__init__(*args, **kwargs)
        self.error_messages['botscout'] = error_msg

    def clean(self):
        data = super(BotScoutForm, self).clean()
        try:
            bot_cache = cache.get_cache('botscout')
        except cache.InvalidCacheBackendError:
            bot_cache = cache.get_cache('default')
        using_ip = self.request is not None
        name_field = getattr(self, 'BOTSCOUT_NAME_FIELD', 'name')
        email_field = getattr(self, 'BOTSCOUT_EMAIL_FIELD', 'email')
        test_data = {'name': data.get(name_field, None),
                     'mail': data.get(email_field, None)}
        if using_ip:
            test_data['ip'] = self.request.META.get('REMOTE_ADDR', None)
        test_data = dict((x, y) for x, y in test_data.iteritems() if y is not None)
        cached_data = bot_cache.get_many(['botscout:%s:%s' % (x, y)
                                          for x, y in test_data.iteritems()])
        if any(cached_data.values()):
            raise forms.ValidationError(self.error_messages['botscout'])
        untested = dict((x, y) for x, y in test_data.iteritems()
                        if 'botscout:%s:%s' % (x, y) not in cached_data)
        if untested:
            test_url = '%s?multi%s' % (BOTSCOUT_API_URL, urlencode(untested))
            if BOTSCOUT_API_KEY is not None:
                test_url = '%s&key=%s' % (test_url, BOTSCOUT_API_KEY)
            try:
                scout_handle = urllib2.urlopen(test_url, timeout=BOTSCOUT_NETWORK_TIMEOUT)
                response = scout_handle.read(1024)
                if response.startswith('!'):
                    logger.error(response)
                else:
                    # Format: Y|MULTI|IP|3|MAIL|1|NAME|2
                    (unused, unused, unused, ip_hits, unused, mail_hits,
                     unused, name_hits) = response.split('|')
                    hit_data = {}
                    if 'ip' in untested:
                        hit_data['botscout:ip:%s' % untested['ip']] = int(ip_hits)
                    if 'mail' in untested:
                        hit_data['botscout:mail:%s' % untested['mail']] = int(mail_hits)
                    if 'name' in untested:
                        hit_data['botscout:name:%s' % untested['name']] = int(name_hits)
                    bot_cache.set_many(hit_data)
                    if any(hit_data.values()):
                        raise forms.ValidationError(self.error_messages['botscout'])
            except urllib2.URLError:
                """
                In case of an error on BotScout's side, we don't want to block this
                request.
                """
                pass
        return data

botscout/settings.py

0 → 100644
+7 −0
Original line number Diff line number Diff line
from django.conf import settings


BOTSCOUT_API_KEY = getattr(settings, 'BOTSCOUT_API_KEY', None)
BOTSCOUT_API_URL = getattr(settings, 'BOTSCOUT_API_URL', 'http://botscout.com/test/')
BOTSCOUT_CACHE_TIMEOUT = getattr(settings, 'BOTSCOUT_CACHE_TIMEOUT', (60 * 30))
BOTSCOUT_NETWORK_TIMEOUT = getattr(settings, 'BOTSCOUT_NETWORK_TIMEOUT', 5)