Use new repoapi for the web stack
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Thu, 27 Jun 2013 18:21:04 +0200
changeset 9071 46885bfa4150
parent 9070 4a803380f718
child 9072 774029a4a718
Use new repoapi for the web stack The publisher now link repoapi.ClientConnection to request. and explicitly control there scope. Web side, appobject._cw.cnx is now a repoapi.ClientConnection. This actually kill webonly possibility until the repoapi is able to use some RPC. The change in the authentication stack is very hasty and need cleanup
devtools/testlib.py
web/application.py
web/request.py
web/test/unittest_application.py
web/test/unittest_session.py
web/views/authentication.py
web/views/sessions.py
--- a/devtools/testlib.py	Tue Jun 25 10:59:01 2013 +0200
+++ b/devtools/testlib.py	Thu Jun 27 18:21:04 2013 +0200
@@ -838,7 +838,8 @@
     def assertAuthSuccess(self, req, origsession, nbsessions=1):
         sh = self.app.session_handler
         session = self.app.get_session(req)
-        req.set_session(session)
+        clt_cnx = repoapi.ClientConnection(session)
+        req.set_cnx(clt_cnx)
         self.assertEqual(len(self.open_sessions), nbsessions, self.open_sessions)
         self.assertEqual(session.login, origsession.login)
         self.assertEqual(session.anonymous_session, False)
--- a/web/application.py	Tue Jun 25 10:59:01 2013 +0200
+++ b/web/application.py	Thu Jun 27 18:21:04 2013 +0200
@@ -35,7 +35,7 @@
     ValidationError, Unauthorized, Forbidden,
     AuthenticationError, NoSelectableObject,
     BadConnectionId, CW_EVENT_MANAGER)
-from cubicweb.dbapi import anonymous_session
+from cubicweb.repoapi import anonymous_cnx
 from cubicweb.web import LOGGER, component
 from cubicweb.web import (
     StatusResponse, DirectResponse, Redirect, NotFound, LogOut,
@@ -50,12 +50,14 @@
 
 @contextmanager
 def anonymized_request(req):
-    orig_session = req.session
-    req.set_session(anonymous_session(req.vreg))
+    orig_cnx = req.cnx
+    anon_clt_cnx = anonymous_cnx(orig_cnx._session.repo)
+    req.set_cnx(anon_clt_cnx)
     try:
-        yield req
+        with anon_clt_cnx:
+            yield req
     finally:
-        req.set_session(orig_session)
+        req.set_cnx(orig_cnx)
 
 class AbstractSessionManager(component.Component):
     """manage session data associated to a session identifier"""
@@ -338,16 +340,22 @@
         try:
             try:
                 session = self.get_session(req)
-                req.set_session(session)
+                from  cubicweb import repoapi
+                cnx = repoapi.ClientConnection(session)
+                req.set_cnx(cnx)
             except AuthenticationError:
                 # Keep the dummy session set at initialisation.
                 # such session with work to an some extend but raise an
                 # AuthenticationError on any database access.
-                pass
+                import contextlib
+                @contextlib.contextmanager
+                def dummy():
+                    yield
+                cnx = dummy()
                 # XXX We want to clean up this approach in the future. But
                 # several cubes like registration or forgotten password rely on
                 # this principle.
-            assert req.session is not None
+
             # DENY https acces for anonymous_user
             if (req.https
                 and req.session.anonymous_session
@@ -358,7 +366,8 @@
             # handler
             try:
                 ### Try to generate the actual request content
-                content = self.core_handle(req, path)
+                with cnx:
+                    content = self.core_handle(req, path)
             # Handle user log-out
             except LogOut as ex:
                 # When authentification is handled by cookie the code that
--- a/web/request.py	Tue Jun 25 10:59:01 2013 +0200
+++ b/web/request.py	Thu Jun 27 18:21:04 2013 +0200
@@ -1082,7 +1082,7 @@
 
 
 
-CubicWebRequestBase = DBAPICubicWebRequestBase
+CubicWebRequestBase = ConnectionCubicWebRequestBase
 
 
 ## HTTP-accept parsers / utilies ##############################################
--- a/web/test/unittest_application.py	Tue Jun 25 10:59:01 2013 +0200
+++ b/web/test/unittest_application.py	Thu Jun 27 18:21:04 2013 +0200
@@ -31,6 +31,7 @@
 from cubicweb.web.views.basecontrollers import ViewController
 from cubicweb.web.application import anonymized_request
 from cubicweb.dbapi import DBAPISession, _NeedAuthAccessMock
+from cubicweb import repoapi
 
 class FakeMapping:
     """emulates a mapping module"""
@@ -336,7 +337,7 @@
         # req.form['__password'] = self.admpassword
         # self.assertAuthFailure(req)
         # option allow-email-login set
-        origsession.login = address
+        #origsession.login = address
         self.set_option('allow-email-login', True)
         req.form['__login'] = address
         req.form['__password'] = self.admpassword
@@ -360,19 +361,21 @@
 
     def _test_auth_anon(self, req):
         asession = self.app.get_session(req)
-        req.set_session(asession)
+        # important otherwise _reset_cookie will not use the right session
+        req.set_cnx(repoapi.ClientConnection(asession))
         self.assertEqual(len(self.open_sessions), 1)
         self.assertEqual(asession.login, 'anon')
         self.assertTrue(asession.anonymous_session)
         self._reset_cookie(req)
 
     def _test_anon_auth_fail(self, req):
-        self.assertEqual(len(self.open_sessions), 1)
+        self.assertEqual(1, len(self.open_sessions))
         session = self.app.get_session(req)
-        req.set_session(session)
+        # important otherwise _reset_cookie will not use the right session
+        req.set_cnx(repoapi.ClientConnection(session))
         self.assertEqual(req.message, 'authentication failure')
         self.assertEqual(req.session.anonymous_session, True)
-        self.assertEqual(len(self.open_sessions), 1)
+        self.assertEqual(1, len(self.open_sessions))
         self._reset_cookie(req)
 
     def test_http_auth_anon_allowed(self):
@@ -397,19 +400,19 @@
         req.form['__password'] = self.admpassword
         self.assertAuthSuccess(req, origsession)
         self.assertRaises(LogOut, self.app_handle_request, req, 'logout')
-        self.assertEqual(len(self.open_sessions), 0)
+        self.assertEqual(0, len(self.open_sessions))
 
     def test_anonymized_request(self):
         req = self.request()
-        self.assertEqual(req.session.login, self.admlogin)
+        self.assertEqual(self.admlogin, req.session.user.login)
         # admin should see anon + admin
-        self.assertEqual(len(list(req.find_entities('CWUser'))), 2)
+        self.assertEqual(2, len(list(req.find_entities('CWUser'))))
         with anonymized_request(req):
-            self.assertEqual(req.session.login, 'anon')
+            self.assertEqual('anon', req.session.login, 'anon')
             # anon should only see anon user
-            self.assertEqual(len(list(req.find_entities('CWUser'))), 1)
-        self.assertEqual(req.session.login, self.admlogin)
-        self.assertEqual(len(list(req.find_entities('CWUser'))), 2)
+            self.assertEqual(1, len(list(req.find_entities('CWUser'))))
+        self.assertEqual(self.admlogin, req.session.login)
+        self.assertEqual(2, len(list(req.find_entities('CWUser'))))
 
     def test_non_regr_optional_first_var(self):
         req = self.request()
--- a/web/test/unittest_session.py	Tue Jun 25 10:59:01 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""unit tests for cubicweb.web.application
-
-:organization: Logilab
-:copyright: 2001-2011 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web import InvalidSession
-
-class SessionTC(CubicWebTC):
-
-    def test_session_expiration(self):
-        sm = self.app.session_handler.session_manager
-        # make is if the web session has been opened by the session manager
-        sm._sessions[self.cnx.sessionid] = self.websession
-        sessionid = self.websession.sessionid
-        self.assertEqual(len(sm._sessions), 1)
-        self.assertEqual(self.websession.sessionid, self.websession.cnx.sessionid)
-        # fake the repo session is expiring
-        self.repo.close(sessionid)
-        try:
-            # fake an incoming http query with sessionid in session cookie
-            # don't use self.request() which try to call req.set_session
-            req = self.requestcls(self.vreg)
-            self.assertRaises(InvalidSession, sm.get_session, req, sessionid)
-            self.assertEqual(len(sm._sessions), 0)
-        finally:
-            # avoid error in tearDown by telling this connection is closed...
-            self.cnx._closed = True
-
-if __name__ == '__main__':
-    from logilab.common.testlib import unittest_main
-    unittest_main()
--- a/web/views/authentication.py	Tue Jun 25 10:59:01 2013 +0200
+++ b/web/views/authentication.py	Thu Jun 27 18:21:04 2013 +0200
@@ -127,13 +127,6 @@
         # actual user login
         if login and session.login != login:
             raise InvalidSession('login mismatch')
-        try:
-            # calling cnx.user() check connection validity, raise
-            # BadConnectionId on failure
-            user = session.cnx.user(req)
-        except BadConnectionId:
-            raise InvalidSession('bad connection id')
-        return user
 
     def authenticate(self, req):
         """authenticate user using connection information found in the request,
@@ -149,27 +142,24 @@
             except NoAuthInfo:
                 continue
             try:
-                cnx = self._authenticate(login, authinfo)
+                session = self._authenticate(login, authinfo)
             except AuthenticationError:
                 retriever.cleanup_authentication_information(req)
                 continue # the next one may succeed
             for retriever_ in self.authinforetrievers:
-                retriever_.authenticated(retriever, req, cnx, login, authinfo)
-            return cnx, login
+                retriever_.authenticated(retriever, req, session, login, authinfo)
+            return session, login
         # false if no authentication info found, eg this is not an
         # authentication failure
         if 'login' in locals():
             req.set_message(req._('authentication failure'))
         login, authinfo = self.anoninfo
         if login:
-            cnx = self._authenticate(login, authinfo)
-            return cnx, login
+            session = self._authenticate(login, authinfo)
+            return session, login
         raise AuthenticationError()
 
     def _authenticate(self, login, authinfo):
-        cnxprops = ConnectionProperties(close=False, log=self.log_queries)
-        cnx = _repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
-        # decorate connection
-        cnx.vreg = self.vreg
-        return cnx
+        sessionid = self.repo.connect(login, **authinfo)
+        return self.repo._sessions[sessionid]
 
--- a/web/views/sessions.py	Tue Jun 25 10:59:01 2013 +0200
+++ b/web/views/sessions.py	Thu Jun 27 18:21:04 2013 +0200
@@ -26,6 +26,7 @@
 from cubicweb.web import InvalidSession, Redirect
 from cubicweb.web.application import AbstractSessionManager
 from cubicweb.dbapi import ProgrammingError, DBAPISession
+from cubicweb import repoapi
 
 
 class InMemoryRepositorySessionManager(AbstractSessionManager):
@@ -53,13 +54,14 @@
         if sessionid not in self._sessions:
             raise InvalidSession()
         session = self._sessions[sessionid]
-        if session.cnx:
-            try:
-                user = self.authmanager.validate_session(req, session)
-            except InvalidSession:
-                # invalid session
-                self.close_session(session)
-                raise
+        try:
+            user = self.authmanager.validate_session(req, session)
+        except InvalidSession:
+            self.close_session(session)
+            raise
+        if session.closed:
+            self.close_session(session)
+            raise InvalidSession()
         return session
 
     def open_session(self, req):
@@ -69,8 +71,7 @@
         raise :exc:`cubicweb.AuthenticationError` if authentication failed
         (no authentication info found or wrong user/password)
         """
-        cnx, login = self.authmanager.authenticate(req)
-        session = DBAPISession(cnx, login)
+        session, login = self.authmanager.authenticate(req)
         self._sessions[session.sessionid] = session
         return session
 
@@ -87,31 +88,25 @@
         #      reopening. Is it actually a problem?
         if 'last_login_time' in req.vreg.schema:
             self._update_last_login_time(session)
-        req.set_message(req._('welcome %s !') % session.cnx.user().login)
+        req.set_message(req._('welcome %s !') % session.user.login)
 
     def _update_last_login_time(self, session):
         # XXX should properly detect missing permission / non writeable source
         # and avoid "except (RepositoryError, Unauthorized)" below
         try:
-            cu = session.cnx.cursor()
-            cu.execute('SET X last_login_time NOW WHERE X eid %(x)s',
-                       {'x' : session.cnx.user().eid})
-            session.cnx.commit()
+            cnx = repoapi.ClientConnection(session)
+            with cnx:
+                cnx.execute('SET X last_login_time NOW WHERE X eid %(x)s',
+                           {'x' : session.user.eid})
+                cnx.commit()
         except (RepositoryError, Unauthorized):
-            session.cnx.rollback()
-        except Exception:
-            session.cnx.rollback()
-            raise
+            pass
 
     def close_session(self, session):
         """close session on logout or on invalid session detected (expired out,
         corrupted...)
         """
         self.info('closing http session %s' % session.sessionid)
-        del self._sessions[session.sessionid]
-        if session.cnx:
-            try:
-                session.cnx.close()
-            except (ProgrammingError, BadConnectionId): # expired on the repository side
-                pass
-            session.cnx = None
+        self._sessions.pop(session.sessionid, None)
+        if not session.closed:
+            session.repo.close(session.id)