view captchamiddleware/middleware.py @ 4:ce757057961c

flow contol now works; bumping version
author k0s <k0scist@gmail.com>
date Wed, 24 Feb 2010 20:11:25 -0500
parents b0ef5452a740
children 59245309f054
line wrap: on
line source

"""
CAPTCHA middleware
"""

import os
import random
import sys
from lxml import etree
from lxmlmiddleware import LXMLMiddleware
from skimpyGimpy import skimpyAPI
from urllib2 import urlopen
from webob import Request, exc


class CAPTCHAmiddleware(LXMLMiddleware):
    """
    put CAPTCHAs on forms for unauthorized users
    """

    ### class level variables
    defaults = { 
                 'dictionary': '/usr/share/dict/words',
                 'error': '<span class="error">Please type the CAPTCHA</span>',
                 'minimum_length': 5, 
                 'path': "/input[@type='submit']",
                 }

    def __init__(self, app, **kw):
        self.app = app

        # set instance parameters from kw and defaults
        for key in self.defaults:
            setattr(self, key, kw.get(key, self.defaults[key]))
        self.minimum_length = int(self.minimum_length)
        assert os.path.exists(self.dictionary)
        
        # get dictionary
        if self.dictionary.startswith('http://') or self.dictionary.startswith('https://'):
            f = urlopen(self.dictionary)
        else:
            f = file(self.dictionary)
        
        # characters skimpygimpy doesnt know about
        forbidden_characters = set(["'"])

        self.words = [ i.strip().lower() for i in f.readlines()
                       if (len(i.strip()) > self.minimum_length)
                       and not forbidden_characters.intersection(i) ]
        random.shuffle(self.words)

    def check_captcha(self, request):
        captcha = request.POST.get('captcha', '').lower()
        key = request.POST.get('key')
        if not key: return False
        try:
            key = int(key)
        except ValueError:
            return False
        try:
            value = self.words[key]
        except IndexError:
            raise # TODO: better error handling
        return value == captcha

    def __call__(self, environ, start_response):
        request = Request(environ)
        if request.method == 'POST' and not request.remote_user:
            if not self.check_captcha(request):
                location = request.referrer
                return exc.HTTPSeeOther(location=location)(environ, start_response)
            # TODO: set a cookie to record an error
            # stage 2: record form values from request.POST,
            # and reinsert them into the form so that users
            # don't hate me ;)

        return LXMLMiddleware.__call__(self, environ, start_response)

    def manipulate(self, environ, tree):
        """manipulate the DOM; should return an etree._Element"""

        request = Request(environ)

        # don't use CAPTCHAs for authorized users
        if request.remote_user:
            return tree 

        for element in tree.findall(".//form[@method='post']"):
            key = random.Random().randint(0, len(self.words))
            word = self.words[key]
            captcha = skimpyAPI.Pre(word).data()
            string = '<div class="captcha">%s<input type="hidden" name="key" value="%s"/><input type="text" name="captcha"/></div>' % (captcha, key)
            addition = etree.fromstring(string)
            insertion_point = element.find('.' + self.path)
            insertion_point.addprevious(addition)

        return tree