web/captcha.py
author Sylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 29 Mar 2010 13:34:06 +0200
branchstable
changeset 5060 ee3b856e1406
parent 5037 7778a2bbdf9d
child 5223 6abd6e3599f4
child 5421 8167de96c523
permissions -rw-r--r--
[repo] optimize massive insertion/deletion by using the new set_operation function Idea is that on massive insertion, cost of handling the list of operation become non negligeable, so we should minimize the number of operations in that list. The set_operation function ease usage of operation associated to data in session.transaction_data, and we only add the operation when data set isn't initialized yet, else we simply add data to the set. The operation then simply process accumulated data.
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):
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    73
        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
    74
                                            pop=True)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    75
        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
    76
        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
    77
            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
    78
        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
    79
            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
    80
            raise ProcessFormError(msg)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    81
        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
    82
            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
    83
            raise ProcessFormError(msg)
7778a2bbdf9d [captcha] handle captcha validation properly in the captcha widget
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents: 4722
diff changeset
    84
        return val