[repository auth] cleanup email login by turning it into a proper repo-side authentication plugin
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 19 Jan 2011 12:47:08 +0100
changeset 6849 5a0c2cfc19bf
parent 6848 f87cd875c6db
child 6851 824d5b6eae7f
[repository auth] cleanup email login by turning it into a proper repo-side authentication plugin
server/repository.py
server/sources/native.py
web/test/unittest_application.py
--- a/server/repository.py	Wed Jan 19 12:47:06 2011 +0100
+++ b/server/repository.py	Wed Jan 19 12:47:08 2011 +0100
@@ -430,33 +430,24 @@
         except ZeroDivisionError:
             pass
 
-    def _login_from_email(self, login):
-        session = self.internal_session()
-        try:
-            rset = session.execute('Any L WHERE U login L, U primary_email M, '
-                                   'M address %(login)s', {'login': login},
-                                   build_descr=False)
-            if rset.rowcount == 1:
-                login = rset[0][0]
-        finally:
-            session.close()
-        return login
-
-    def authenticate_user(self, session, login, **kwargs):
-        """validate login / password, raise AuthenticationError on failure
-        return associated CWUser instance on success
+    def check_auth_info(self, session, login, authinfo):
+        """validate authentication, raise AuthenticationError on failure, return
+        associated CWUser's eid on success.
         """
-        if self.vreg.config['allow-email-login'] and '@' in login:
-            login = self._login_from_email(login)
         for source in self.sources:
             if source.support_entity('CWUser'):
                 try:
-                    eid = source.authenticate(session, login, **kwargs)
-                    break
+                    return source.authenticate(session, login, **authinfo)
                 except AuthenticationError:
                     continue
         else:
             raise AuthenticationError('authentication failed with all sources')
+
+    def authenticate_user(self, session, login, **authinfo):
+        """validate login / password, raise AuthenticationError on failure
+        return associated CWUser instance on success
+        """
+        eid = self.check_auth_info(session, login, authinfo)
         cwuser = self._build_user(session, eid)
         if self.config.consider_user_state and \
                not cwuser.cw_adapt_to('IWorkflowable').state in cwuser.AUTHENTICABLE_STATES:
--- a/server/sources/native.py	Wed Jan 19 12:47:06 2011 +0100
+++ b/server/sources/native.py	Wed Jan 19 12:47:08 2011 +0100
@@ -266,6 +266,8 @@
     def __init__(self, repo, source_config, *args, **kwargs):
         SQLAdapterMixIn.__init__(self, source_config)
         self.authentifiers = [LoginPasswordAuthentifier(self)]
+        if repo.config['allow-email-login']:
+            self.authentifiers.insert(0, EmailPasswordAuthentifier(self))
         AbstractSource.__init__(self, repo, source_config, *args, **kwargs)
         # sql generator
         self._rql_sqlgen = self.sqlgen_class(self.schema, self.dbhelper,
@@ -1495,3 +1497,19 @@
             return rset[0][0]
         except IndexError:
             raise AuthenticationError('bad password')
+
+
+class EmailPasswordAuthentifier(BaseAuthentifier):
+    def authenticate(self, session, login, **authinfo):
+        # email_auth flag prevent from infinite recursion (call to
+        # repo.check_auth_info at the end of this method may lead us here again)
+        if not '@' in login or authinfo.pop('email_auth', None):
+            raise AuthenticationError('not an email')
+        rset = session.execute('Any L WHERE U login L, U primary_email M, '
+                               'M address %(login)s', {'login': login},
+                               build_descr=False)
+        if rset.rowcount != 1:
+            raise AuthenticationError('unexisting email')
+        login = rset.rows[0][0]
+        authinfo['email_auth'] = True
+        return self.source.repo.check_auth_info(session, login, authinfo)
--- a/web/test/unittest_application.py	Wed Jan 19 12:47:06 2011 +0100
+++ b/web/test/unittest_application.py	Wed Jan 19 12:47:08 2011 +0100
@@ -22,7 +22,7 @@
 from urllib import unquote
 
 from logilab.common.testlib import TestCase, unittest_main
-from logilab.common.decorators import clear_cache
+from logilab.common.decorators import clear_cache, classproperty
 
 from cubicweb import AuthenticationError, Unauthorized
 from cubicweb.devtools.testlib import CubicWebTC
@@ -157,6 +157,15 @@
             raise
         self.app.error_handler = raise_hdlr
 
+    @classproperty
+    def config(cls):
+        try:
+            return cls.__dict__['_config']
+        except KeyError:
+            config = super(ApplicationTC, cls).config
+            config.global_set_option('allow-email-login', True)
+            return config
+
     def test_cnx_user_groups_sync(self):
         user = self.user()
         self.assertEqual(user.groups, set(('managers',)))
@@ -347,7 +356,7 @@
         self.execute('INSERT EmailAddress X: X address %(address)s, U primary_email X '
                      'WHERE U login %(login)s', {'address': address, 'login': login})
         self.commit()
-        # option allow-email-login not set
+        # # option allow-email-login not set
         req, origsession = self.init_authentication('cookie')
         # req.form['__login'] = address
         # req.form['__password'] = self.admpassword