Commit 0e92190b authored by Florent's avatar Florent
Browse files

added captcha and tests

parent 4b26d160c419
......@@ -15,8 +15,11 @@ RegistrationSendMailController.send_email = my_send_email
class RegistrationTC(WebTest):
data = {'firstname': u'Toto', 'surname': u'Toto', 'email': u'toto@secondweb.fr',
'login': u'toto', 'password': 'toto', 'password-confirm': 'toto'}
captcha_value = u'captcha value'
data = {'firstname': u'Toto', 'surname': u'Toto', 'address': u'toto@secondweb.fr',
'login': u'toto', 'password': 'toto', 'password-confirm': 'toto',
'captcha': captcha_value}
def setup_database(self):
self.config.set_option('registration_cypherkey', u'dummy cypher key')
......@@ -30,14 +33,14 @@ class RegistrationTC(WebTest):
# check form field names
names = pageinfo.etree.xpath('//ns:form[@id="form"]//ns:input[@type!="hidden"]/@name',
namespaces=ns)
self.assertEquals(names, ['login', 'password', 'password-confirm',
'email', 'firstname', 'surname'])
self.assertEquals(set(names), set(self.data))
# check form field value
firstname = pageinfo.etree.xpath('//ns:input[@name="firstname"]/@value', namespaces=ns)
self.assertEquals(firstname, [req.form['firstname']])
def _check_redirect(self, ctrl, urlpath, **urlparams):
try:
ctrl.req.set_session_data('captcha', self.captcha_value)
ctrl.publish()
assert False, 'should redirect to ' + urlpath
except Redirect, e:
......@@ -46,6 +49,7 @@ class RegistrationTC(WebTest):
self.assertDictEquals(dict(cgi.parse_qsl(params)), urlparams)
def test_send_mail_ok(self):
self.login('anon')
req = self.request()
req.form = self.data.copy()
ctrl = self.env.app.select_controller('registration_sendmail', req)
......@@ -67,42 +71,58 @@ class RegistrationTC(WebTest):
req.form.pop('firstname')
ctrl = self.env.app.select_controller('registration_sendmail', req)
try:
ctrl.req.set_session_data('captcha', self.captcha_value)
ctrl.publish()
assert False, 'should raise ValidationError'
except ValidationError, e:
self.assertDictEquals(e.errors, {'firstname': u'required attribute'})
# check captcha has expired
self.failUnless(ctrl.req.get_session_data('captcha') is None)
def test_confirm_ok(self):
def _confirm_ctrl(self, key=None):
self.login('anon')
req = self.request()
req.form = {'key': serialize(self.data, self.config['registration_cypherkey'])}
ctrl = self.env.app.select_controller('registration_confirm', req)
req.form = {'key': key or serialize(self.data, self.config['registration_cypherkey'])}
return self.env.app.select_controller('registration_confirm', req)
def test_confirm_ok(self):
ctrl = self._confirm_ctrl()
self._check_redirect(ctrl, '', __message=u'Congratulations, your registration is complete. Welcome %(firstname)s %(surname)s !' % self.data)
req.cnx.commit()
req.cnx.close()
ctrl.req.cnx.commit()
ctrl.req.cnx.close()
self.restore_connection()
rset = self.execute('Any U WHERE U login %(login)s, U firstname %(firstname)s, '
'U surname %(surname)s, U use_email M, M address %(email)s', self.data)
'U surname %(surname)s, U use_email M, M address %(address)s',
self.data)
self.failUnless(rset.rowcount)
def test_confirm_failure_login_exists(self):
def _check_user_not_created(self):
self.restore_connection()
self.failIf(self.execute('EUser X WHERE X login %(login)s', self.data).rowcount)
def test_confirm_failure_login_already_used(self):
self.create_user(self.data['login'])
self.commit()
self.login('anon')
req = self.request()
req.form = {'key': serialize(self.data, self.config['registration_cypherkey'])}
ctrl = self.env.app.select_controller('registration_confirm', req)
ctrl = self._confirm_ctrl()
# check user is redirected to a url without its password in the url params
d = self.data.copy()
d.pop('login'), d.pop('password'), d.pop('password-confirm')
d['__message'] = u'Login is already used. Please try another one.'
d['__message'] = u'the value "%(login)s" is already used, use another one' % self.data
self._check_redirect(ctrl, 'register', **d)
def test_confirm_failure_invalid_data(self):
self.create_user(self.data['login'])
self.commit()
self.login('anon')
req = self.request()
req.form = {'key': u'dummykey'}
ctrl = self.env.app.select_controller('registration_confirm', req)
ctrl = self._confirm_ctrl(u'dummykey')
self._check_redirect(ctrl, 'register', __message=u'Invalid registration data. Please try registering again.')
self._check_user_not_created()
def test_confirm_failure_email_already_used(self):
self.create_user('test')
self.execute('INSERT EmailAddress X: U use_email X, X address %(address)s '
'WHERE U login "test"', self.data)
self.commit()
ctrl = self._confirm_ctrl()
d = self.data.copy()
d.pop('login'), d.pop('password'), d.pop('password-confirm')
d['__message'] = u'the value "%(address)s" is already used, use another one' % self.data
self._check_redirect(ctrl, 'register', **d)
self._check_user_not_created()
# -*- coding: utf-8 -*-
# XXX TODO : captcha
from pickle import dumps, loads
from base64 import b64encode, b64decode
from smtplib import SMTP
from random import choice
from cStringIO import StringIO
from time import time
import Image
import captchaimage
from Crypto.Cipher import Blowfish
from logilab.common.decorators import clear_cache
from cubicweb.common import tags
from cubicweb.common.mail import format_mail, addrheader
from cubicweb.selectors import match_user_groups, none_rset, match_form_params
from cubicweb.view import StartupView
......@@ -24,6 +28,10 @@ from cubicweb.web.formwidgets import TextInput, PasswordInput, SubmitButton
REGISTRATION_FORM_URL = u'register'
REGISTRATION_SUBMIT_URL = u'register_sendemail'
REGISTRATION_CONFIRM_URL = u'confirm'
CAPTCHA_URL = u'captcha'
CAPTCHA_FONT = u'/usr/share/fonts/truetype/freefont/FreeSerif.ttf'
CYPHERFILLCHAR = '*'
CYPHERERS = {}
......@@ -46,13 +54,58 @@ def deserialize(s, cypherkey):
return loads(s)
def get_captcha_image(code):
size_y = 32
im_data = captchaimage.create_image(CAPTCHA_FONT, 28, size_y, code)
im = Image.fromstring("L", (len(im_data) / size_y, size_y), im_data)
out = StringIO()
im.save(out,"JPEG")
out.seek(0)
return out.read()
def check_captcha(req, guess):
captcha = req.get_session_data('captcha', None, pop=True)
if (not isinstance(captcha, basestring) or not isinstance(guess, basestring)
or guess.lower() != captcha.lower()):
raise ValidationError(None, {'captcha': req._('incorrect captcha value')})
class CaptchaView(StartupView):
id = 'captcha'
binary = True
templatable = False
content_type = 'image/jpg'
def call(self):
code = u''.join([choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(5)])
self.req.set_session_data('captcha', code)
self.w(get_captcha_image(code))
class CaptchaWidget(TextInput):
def render(self, form, field):
tip = tags.span(form.req._('copy letters of the image'), **{'class': 'emphasis'})
return super(CaptchaWidget, self).render(form, field) + '\n \n' + tip
class RegistrationFormRenderer(FormRenderer):
def render_label(self, form, field):
if field.name != 'captcha':
return super(RegistrationFormRenderer, self).render_label(form, field)
return tags.img(src=CAPTCHA_URL+'?t=%s' % int(time()*100), alt=u'')
class RegistrationFormView(FormViewMixIn, StartupView):
id = 'registration'
def call(self):
self.req.add_css('cubicweb.form.css')
self.req.add_js('cubes.registration.js')
form = self.vreg.select_object('forms', 'registration', self.req, rset=None)
self.w(form.form_render())
self.w(form.form_render(renderer=RegistrationFormRenderer()))
class RegistrationForm(FieldsForm):
......@@ -62,9 +115,10 @@ class RegistrationForm(FieldsForm):
login = StringField('login', widget=TextInput(), label=_('login'), required=True)
password = StringField('password', widget=PasswordInput(), label=_('password'), required=True)
email = StringField('email', widget=TextInput(), label=_('email'), required=True)
address = StringField('address', widget=TextInput(), label=_('email'), required=True)
firstname = StringField('firstname', widget=TextInput(), label=_('firstname'), required=True)
surname = StringField('surname', widget=TextInput(), label=_('surname'), required=True)
captcha = StringField('captcha', widget=CaptchaWidget(), label=_('captcha'), required=True)
form_buttons = [SubmitButton()]
......@@ -117,8 +171,9 @@ See you soon on %(base_url)s !
return data
def publish(self, rset=None):
check_captcha(self.req, self.req.form.get('captcha'))
data = self.checked_data()
recipient = self.req.form['email']
recipient = self.req.form['address']
from_ = {'email': unicode(self.config['sender-addr'] or ''),
'name': unicode(self.config['sender-name'] or '')}
msg = self.build_email(from_, recipient, data)
......@@ -129,11 +184,11 @@ See you soon on %(base_url)s !
class RegistrationConfirmController(Controller):
id = 'registration_confirm'
def failure_redirect_url(self, data):
def failure_redirect_url(self, data, errors):
# be sure not to get a password in your redirect url
url_params = data.copy()
url_params.pop('password', None), url_params.pop('password-confirm', None)
url_params['__message'] = _(u'Login is already used. Please try another one.')
url_params['__message'] = u'<br/>'.join(v for v in errors.values())
return self.build_url(REGISTRATION_FORM_URL, **url_params)
def success_redirect_url(self, data):
......@@ -149,8 +204,11 @@ class RegistrationConfirmController(Controller):
msg = _(u'Invalid registration data. Please try registering again.')
raise Redirect(req.build_url(REGISTRATION_FORM_URL, __message=msg))
login, password = data.pop('login'), data.pop('password')
if not self.appli.repo.register_user(login, password):
raise Redirect(self.failure_redirect_url(data))
try:
self.appli.repo.register_user(login, password, email=data['address'],
firstname=data['firstname'], surname=data['surname'])
except ValidationError, e:
raise Redirect(self.failure_redirect_url(data, e.errors))
req.form['__login'] = login
req.form['__password'] = password
clear_cache(req, 'get_authorization') # force new authentication (anon until there)
......@@ -162,9 +220,6 @@ class RegistrationConfirmController(Controller):
except Redirect:
pass
assert req.user.login == login
req.execute('INSERT EmailAddress X: X address %(email)s, U use_email X '
'WHERE U eid %(ueid)s', {'email': data['email'], 'ueid': req.user.eid})
req.user.set_attributes(firstname=data['firstname'], surname=data['surname'])
raise Redirect(self.success_redirect_url(data))
......@@ -172,6 +227,7 @@ class RegistrationConfirmController(Controller):
class RegistrationSimpleReqRewriter(SimpleReqRewriter):
rules = [
('/' + REGISTRATION_FORM_URL, dict(vid='registration')),
('/' + CAPTCHA_URL, dict(vid='captcha')),
]
class RegistrationSchemaBasedRewrite(SchemaBasedRewriter):
......
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