# HG changeset patch # User Pierre-Yves David # Date 1372350064 -7200 # Node ID 46885bfa4150b2250f0c5ac04c850fe358da9051 # Parent 4a803380f718305b4f8b791f5632e73e3547bdb0 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 diff -r 4a803380f718 -r 46885bfa4150 devtools/testlib.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) diff -r 4a803380f718 -r 46885bfa4150 web/application.py --- 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 diff -r 4a803380f718 -r 46885bfa4150 web/request.py --- 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 ############################################## diff -r 4a803380f718 -r 46885bfa4150 web/test/unittest_application.py --- 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() diff -r 4a803380f718 -r 46885bfa4150 web/test/unittest_session.py --- 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() diff -r 4a803380f718 -r 46885bfa4150 web/views/authentication.py --- 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] diff -r 4a803380f718 -r 46885bfa4150 web/views/sessions.py --- 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)