Commit 02dffea3 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

captcha/crypto utilities moved to cw 3.6.1

parent ab07dab3d118
"""Simple captcha library, based on PIL. Monkey patch functions in this module
if you want something better...
:organization: Logilab
:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: --
:license: GNU Lesser General Public License, v2.1 -
__docformat__ = "restructuredtext en"
from random import randint, choice
from cStringIO import StringIO
import Image, ImageFont, ImageDraw, ImageFilter
def pil_captcha(text, fontfile, fontsize):
"""Generate a captcha image. Return a PIL image object.
adapted from
# randomly select the foreground color
fgcolor = randint(0, 0xffff00)
# make the background color the opposite of fgcolor
bgcolor = fgcolor ^ 0xffffff
# create a font object
font = ImageFont.truetype(fontfile, fontsize)
# determine dimensions of the text
dim = font.getsize(text)
# create a new image slightly larger that the text
img ='RGB', (dim[0]+5, dim[1]+5), bgcolor)
draw = ImageDraw.Draw(img)
# draw 100 random colored boxes on the background
x, y = img.size
for num in range(100):
draw.rectangle((randint(0, x), randint(0, y),
randint(0, x), randint(0, y)),
fill=randint(0, 0xffffff))
# add the text to the image
draw.text((3, 3), text, font=font, fill=fgcolor)
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img
def captcha(fontfile, fontsize, size=5, format='JPEG'):
"""Generate an arbitrary text, return it together with a buffer containing
the captcha image for the text
text = u''.join(choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(size))
img = pil_captcha(text, fontfile, fontsize)
out = StringIO(), format)
return text, out
# -*- coding: utf-8 -*-
from os.path import dirname, abspath, join
HERE = abspath(dirname(__file__))
options = (
{'type' : 'string',
'default': u"this is my dummy registration cypher seed",
'help': 'seed used to cypher registration data in confirmation email link',
'group': 'registration', 'inputlevel': 2,
{'type' : 'string',
'default': join(HERE, 'data', 'porkys.ttf'),
'help': 'True type font to use for captcha image generation',
'group': 'registration', 'inputlevel': 2,
{'type' : 'int',
'default': 25,
'help': 'Font size to use for captcha image generation',
'group': 'registration', 'inputlevel': 2,
import re, cgi, urlparse
from cubicweb import ValidationError
from cubicweb.crypto import encrypt, decrypt
from cubicweb.web import Redirect
from cubicweb.devtools.testlib import MAILBOX, CubicWebTC
from cubes.registration.views import RegistrationSendMailController, decrypt, encrypt
from cubes.registration.views import RegistrationSendMailController
class RegistrationTC(CubicWebTC):
......@@ -18,7 +19,7 @@ class RegistrationTC(CubicWebTC):
'captcha': captcha_value}
def setup_database(self):
self.config.global_set_option('cypher-seed', u'dummy cypher key')
self.config.global_set_option('registration-cypher-seed', u'dummy cypher key')
super(RegistrationTC, self).setup_database()
def test_registration_form(self):
......@@ -61,7 +62,7 @@ class RegistrationTC(CubicWebTC):
key = dict(cgi.parse_qsl(urlparse.urlsplit(url)[3]))['key']
d =
self.assertDictEquals(decrypt(key, self.config['cypher-seed']), d)
self.assertDictEquals(decrypt(key, self.config['registration-cypher-seed']), d)
def test_send_mail_failure(self):
req = self.request()
......@@ -83,7 +84,7 @@ class RegistrationTC(CubicWebTC):
def _confirm_ctrl(self, key=None):
req = self.request()
req.form = {'key': key or encrypt(, self.config['cypher-seed'])}
req.form = {'key': key or encrypt(, self.config['registration-cypher-seed'])}
return self.vreg['controllers'].select('registration_confirm', req,
# -*- coding: utf-8 -*-
from pickle import dumps, loads
from base64 import b64encode, b64decode
from time import time
from Crypto.Cipher import Blowfish
from logilab.common.decorators import clear_cache
from cubicweb import tags, mail
from cubicweb import mail, crypto
from cubicweb.view import StartupView
from cubicweb.web import Redirect, ValidationError
from cubicweb.web import (controller, httpcache, form,
formwidgets as wdg, formfields as ff)
from cubicweb.web import controller, form, captcha
from cubicweb.web import formwidgets as fw, formfields as ff
from cubicweb.web.views import forms, basecomponents, urlrewrite
from cubes.registration import captcha
def _cypherer(seed):
return _CYPHERERS[seed]
except KeyError:
_CYPHERERS[seed] =, Blowfish.MODE_ECB)
return _CYPHERERS[seed]
def encrypt(data, seed):
string = dumps(data)
string = string + '*' * (8 - len(string) % 8)
string = b64encode(_cypherer(seed).encrypt(string))
return unicode(string)
def decrypt(string, seed):
# pickle ignores trailing characters so we do not need to strip them off
string = _cypherer(seed).decrypt(b64decode(string))
return loads(string)
class CaptchaView(StartupView):
http_cache_manager = httpcache.NoHTTPCacheManager
__regid__ = 'captcha'
binary = True
templatable = False
content_type = 'image/jpg'
def call(self):
text, data = captcha.captcha(self._cw.vreg.config['captcha-font-file'],
self._cw.set_session_data('captcha', text)
class CaptchaWidget(wdg.TextInput):
def render(self, form, field, renderer=None):
# t=int(time()*100) to make sure img is not cached
src = form._cw.build_url('view', vid='captcha', t=int(time()*100))
img = tags.img(src=src, alt=u'captcha')
img = u'<div class="captcha">%s</div>' % img
if renderer is None: # XXX cw < 3.4
return img + super(CaptchaWidget, self).render(form, field)
return img + super(CaptchaWidget, self).render(form, field, renderer)
class RegistrationForm(forms.FieldsForm):
......@@ -73,12 +19,12 @@ class RegistrationForm(forms.FieldsForm):
def action(self):
return self._cw.build_url(u'registration_sendmail')
login = ff.StringField(widget=wdg.TextInput(), required=True)
upassword = ff.StringField(widget=wdg.PasswordInput(), required=True)
use_email = ff.StringField(widget=wdg.TextInput(), required=True)
firstname = ff.StringField(widget=wdg.TextInput())
surname = ff.StringField(widget=wdg.TextInput())
captcha = ff.StringField(widget=CaptchaWidget(), required=True,
login = ff.StringField(widget=fw.TextInput(), required=True)
upassword = ff.StringField(widget=fw.PasswordInput(), required=True)
use_email = ff.StringField(widget=fw.TextInput(), required=True)
firstname = ff.StringField(widget=fw.TextInput())
surname = ff.StringField(widget=fw.TextInput())
captcha = ff.StringField(widget=captcha.CaptchaWidget(), required=True,
help=_('please copy the letters from the image'))
......@@ -88,7 +34,7 @@ class RegistrationFormView(form.FormViewMixIn, StartupView):
def call(self):
form = self._cw.vreg['forms'].select('registration', self._cw)
class RegistrationSendMailController(controller.Controller):
......@@ -147,7 +93,7 @@ See you soon on %(base_url)s !
subject=subject, config=self._cw.vreg.config)
def activation_url(self, data):
key = encrypt(data, self._cw.vreg.config['cypher-seed'])
key = crypto.encrypt(data, self._cw.vreg.config['registration-cypher-seed'])
return self._cw.build_url('registration_confirm', key=key)
def success_redirect_url(self):
......@@ -162,7 +108,8 @@ class RegistrationConfirmController(controller.Controller):
def publish(self, rset=None):
req = self._cw
data = decrypt(req.form['key'], self._cw.vreg.config['cypher-seed'])
data = crypto.decrypt(req.form['key'],
self.debug('registration data: %s', data)
msg = req._(u'Invalid registration data. Please try registering again.')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment