web/captcha.py
author Sylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 14 Apr 2010 17:31:41 +0200
changeset 5250 1c0eb5f74fd4
parent 5223 6abd6e3599f4
child 5423 e15abfdcce38
permissions -rw-r--r--
[packaging] 3.8 depends on lgc 0.50 (new argument to dot generator in lgc.graph)
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
4595
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     1
"""Simple captcha library, based on PIL. Monkey patch functions in this module
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     2
if you want something better...
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     3
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     4
:organization: Logilab
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     5
:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     6
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     7
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     8
"""
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     9
__docformat__ = "restructuredtext en"
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    10
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    11
from random import randint, choice
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    12
from cStringIO import StringIO
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    13
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    14
import Image, ImageFont, ImageDraw, ImageFilter
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    15
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    16
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    17
from time import time
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    18
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    19
from cubicweb import tags
5037
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    20
from cubicweb.web import ProcessFormError, formwidgets as fw
4595
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    21
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    22
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    23
def pil_captcha(text, fontfile, fontsize):
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    24
    """Generate a captcha image. Return a PIL image object.
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    25
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    26
    adapted from http://code.activestate.com/recipes/440588/
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    27
    """
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    28
    # randomly select the foreground color
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    29
    fgcolor = randint(0, 0xffff00)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    30
    # make the background color the opposite of fgcolor
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    31
    bgcolor = fgcolor ^ 0xffffff
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    32
    # create a font object
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    33
    font = ImageFont.truetype(fontfile, fontsize)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    34
    # determine dimensions of the text
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    35
    dim = font.getsize(text)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    36
    # create a new image slightly larger that the text
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    37
    img = Image.new('RGB', (dim[0]+5, dim[1]+5), bgcolor)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    38
    draw = ImageDraw.Draw(img)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    39
    # draw 100 random colored boxes on the background
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    40
    x, y = img.size
4722
9c13d5db03d9 pylint suggested refactorings
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4621
diff changeset
    41
    for num in xrange(100):
4595
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    42
        draw.rectangle((randint(0, x), randint(0, y),
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    43
                        randint(0, x), randint(0, y)),
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    44
                       fill=randint(0, 0xffffff))
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    45
    # add the text to the image
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    46
    draw.text((3, 3), text, font=font, fill=fgcolor)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    47
    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    48
    return img
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    49
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    50
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    51
def captcha(fontfile, fontsize, size=5, format='JPEG'):
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    52
    """Generate an arbitrary text, return it together with a buffer containing
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    53
    the captcha image for the text
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    54
    """
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    55
    text = u''.join(choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(size))
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    56
    img = pil_captcha(text, fontfile, fontsize)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    57
    out = StringIO()
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    58
    img.save(out, format)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    59
    out.seek(0)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    60
    return text, out
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    61
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    62
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    63
class CaptchaWidget(fw.TextInput):
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    64
    def render(self, form, field, renderer=None):
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    65
        # t=int(time()*100) to make sure img is not cached
5037
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    66
        src = form._cw.build_url('view', vid='captcha', t=int(time()*100),
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    67
                                 captchakey=field.input_name(form))
4595
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    68
        img = tags.img(src=src, alt=u'captcha')
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    69
        img = u'<div class="captcha">%s</div>' % img
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    70
        return img + super(CaptchaWidget, self).render(form, field, renderer)
5037
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    71
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    72
    def process_field_data(self, form, field):
5223
6abd6e3599f4 #773448: refactor session and 'no connection' handling, by introducing proper web session. We should now be able to see page even when no anon is configured, and be redirected to the login form as soon as one tries to do a query.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    73
        captcha = form._cw.session.data.pop(field.input_name(form), None)
5037
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    74
        val = super(CaptchaWidget, self).process_field_data(form, field)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    75
        if val is None:
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    76
            return val # required will be checked by field
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    77
        if captcha is None:
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    78
            msg = form._cw._('unable to check captcha, please try again')
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    79
            raise ProcessFormError(msg)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    80
        elif val.lower() != captcha.lower():
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    81
            msg = form._cw._('incorrect captcha value')
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    82
            raise ProcessFormError(msg)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    83
        return val