--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/web/views/sessions.py Sat Jan 16 13:48:51 2016 +0100
@@ -0,0 +1,180 @@
+# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+"""web session: by default the session is actually the db connection """
+__docformat__ = "restructuredtext en"
+
+from time import time
+from logging import getLogger
+
+from logilab.common.registry import RegistrableObject, yes
+
+from cubicweb import RepositoryError, Unauthorized, set_log_methods
+from cubicweb.web import InvalidSession
+
+from cubicweb.web.views import authentication
+
+
+class AbstractSessionManager(RegistrableObject):
+ """manage session data associated to a session identifier"""
+ __abstract__ = True
+ __select__ = yes()
+ __registry__ = 'sessions'
+ __regid__ = 'sessionmanager'
+
+ def __init__(self, repo):
+ vreg = repo.vreg
+ self.session_time = vreg.config['http-session-time'] or None
+ self.authmanager = authentication.RepositoryAuthenticationManager(repo)
+ interval = (self.session_time or 0) / 2.
+ if vreg.config.anonymous_user()[0] is not None:
+ self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 5 * 60
+ assert self.cleanup_anon_session_time > 0
+ if self.session_time is not None:
+ self.cleanup_anon_session_time = min(self.session_time,
+ self.cleanup_anon_session_time)
+ interval = self.cleanup_anon_session_time / 2.
+ # we don't want to check session more than once every 5 minutes
+ self.clean_sessions_interval = max(5 * 60, interval)
+
+ def clean_sessions(self):
+ """cleanup sessions which has not been unused since a given amount of
+ time. Return the number of sessions which have been closed.
+ """
+ self.debug('cleaning http sessions')
+ session_time = self.session_time
+ closed, total = 0, 0
+ for session in self.current_sessions():
+ total += 1
+ last_usage_time = session.mtime
+ no_use_time = (time() - last_usage_time)
+ if session.anonymous_session:
+ if no_use_time >= self.cleanup_anon_session_time:
+ self.close_session(session)
+ closed += 1
+ elif session_time is not None and no_use_time >= session_time:
+ self.close_session(session)
+ closed += 1
+ return closed, total - closed
+
+ def current_sessions(self):
+ """return currently open sessions"""
+ raise NotImplementedError()
+
+ def get_session(self, req, sessionid):
+ """return existing session for the given session identifier"""
+ raise NotImplementedError()
+
+ def open_session(self, req):
+ """open and return a new session for the given request.
+
+ raise :exc:`cubicweb.AuthenticationError` if authentication failed
+ (no authentication info found or wrong user/password)
+ """
+ raise NotImplementedError()
+
+ def close_session(self, session):
+ """close session on logout or on invalid session detected (expired out,
+ corrupted...)
+ """
+ raise NotImplementedError()
+
+
+set_log_methods(AbstractSessionManager, getLogger('cubicweb.sessionmanager'))
+
+
+class InMemoryRepositorySessionManager(AbstractSessionManager):
+ """manage session data associated to a session identifier"""
+
+ def __init__(self, *args, **kwargs):
+ super(InMemoryRepositorySessionManager, self).__init__(*args, **kwargs)
+ # XXX require a RepositoryAuthenticationManager which violates
+ # authenticate interface by returning a session instead of a user
+ #assert isinstance(self.authmanager, RepositoryAuthenticationManager)
+ self._sessions = {}
+
+ # dump_data / restore_data to avoid loosing open sessions on registry
+ # reloading
+ def dump_data(self):
+ return self._sessions
+ def restore_data(self, data):
+ self._sessions = data
+
+ def current_sessions(self):
+ return self._sessions.values()
+
+ def get_session(self, req, sessionid):
+ """return existing session for the given session identifier"""
+ if sessionid not in self._sessions:
+ raise InvalidSession()
+ session = self._sessions[sessionid]
+ 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):
+ """open and return a new session for the given request. The session is
+ also bound to the request.
+
+ raise :exc:`cubicweb.AuthenticationError` if authentication failed
+ (no authentication info found or wrong user/password)
+ """
+ session, login = self.authmanager.authenticate(req)
+ self._sessions[session.sessionid] = session
+ session.mtime = time()
+ return session
+
+ def postlogin(self, req, session):
+ """postlogin: the user have been related to a session
+
+ Both req and session are passed to this function because actually
+ linking the request to the session is not yet done and not the
+ responsability of this object.
+ """
+ # Update last connection date
+ # XXX: this should be in a post login hook in the repository, but there
+ # we can't differentiate actual login of automatic session
+ # 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.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:
+ with session.new_cnx() as cnx:
+ cnx.execute('SET X last_login_time NOW WHERE X eid %(x)s',
+ {'x' : session.user.eid})
+ cnx.commit()
+ except (RepositoryError, Unauthorized):
+ 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)
+ self._sessions.pop(session.sessionid, None)
+ if not session.closed:
+ session.repo.close(session.sessionid)