web/captcha.py
author Sylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 28 Apr 2010 10:06:01 +0200
branchstable
changeset 5421 8167de96c523
parent 5037 7778a2bbdf9d
child 5423 e15abfdcce38
child 5424 8ecbcbff9777
permissions -rw-r--r--
proper licensing information (LGPL-2.1). Hope I get it right this time.
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
5421
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     1
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     2
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     3
#
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     4
# This file is part of CubicWeb.
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     5
#
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     6
# CubicWeb is free software: you can redistribute it and/or modify it under the
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     7
# terms of the GNU Lesser General Public License as published by the Free
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     8
# Software Foundation, either version 2.1 of the License, or (at your option)
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
     9
# any later version.
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    10
#
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    11
# logilab-common is distributed in the hope that it will be useful, but WITHOUT
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    13
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    14
# details.
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    15
#
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    16
# You should have received a copy of the GNU Lesser General Public License along
8167de96c523 proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 5037
diff changeset
    17
# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
4595
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    18
"""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
    19
if you want something better...
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    20
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
__docformat__ = "restructuredtext en"
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    23
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    24
from random import randint, choice
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    25
from cStringIO import StringIO
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    26
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    27
import Image, ImageFont, ImageDraw, ImageFilter
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    28
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    29
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    30
from time import time
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    31
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    32
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
    33
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
    34
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    35
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    36
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
    37
    """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
    38
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    39
    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
    40
    """
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    41
    # randomly select the foreground color
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    42
    fgcolor = randint(0, 0xffff00)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    43
    # 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
    44
    bgcolor = fgcolor ^ 0xffffff
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    45
    # create a font object
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    46
    font = ImageFont.truetype(fontfile, fontsize)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    47
    # determine dimensions of the text
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    48
    dim = font.getsize(text)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    49
    # 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
    50
    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
    51
    draw = ImageDraw.Draw(img)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    52
    # 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
    53
    x, y = img.size
4722
9c13d5db03d9 pylint suggested refactorings
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4621
diff changeset
    54
    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
    55
        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
    56
                        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
    57
                       fill=randint(0, 0xffffff))
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    58
    # 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
    59
    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
    60
    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
    61
    return img
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
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    64
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
    65
    """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
    66
    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
    67
    """
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    68
    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
    69
    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
    70
    out = StringIO()
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    71
    img.save(out, format)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    72
    out.seek(0)
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    73
    return text, out
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    74
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    75
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    76
class CaptchaWidget(fw.TextInput):
bb08a75832e6 backport crypto/captcha utilities from the registration cube
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    77
    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
    78
        # 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
    79
        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
    80
                                 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
    81
        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
    82
        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
    83
        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
    84
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    85
    def process_field_data(self, form, field):
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    86
        captcha = form._cw.get_session_data(field.input_name(form), None,
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    87
                                            pop=True)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    88
        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
    89
        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
    90
            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
    91
        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
    92
            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
    93
            raise ProcessFormError(msg)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    94
        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
    95
            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
    96
            raise ProcessFormError(msg)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    97
        return val