web/views/sessions.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """web session: by default the session is actually the db connection """
       
    19 __docformat__ = "restructuredtext en"
       
    20 
       
    21 from time import time
       
    22 from logging import getLogger
       
    23 
       
    24 from logilab.common.registry import RegistrableObject, yes
       
    25 
       
    26 from cubicweb import RepositoryError, Unauthorized, set_log_methods
       
    27 from cubicweb.web import InvalidSession
       
    28 
       
    29 from cubicweb.web.views import authentication
       
    30 
       
    31 
       
    32 class AbstractSessionManager(RegistrableObject):
       
    33     """manage session data associated to a session identifier"""
       
    34     __abstract__ = True
       
    35     __select__ = yes()
       
    36     __registry__ = 'sessions'
       
    37     __regid__ = 'sessionmanager'
       
    38 
       
    39     def __init__(self, repo):
       
    40         vreg = repo.vreg
       
    41         self.session_time = vreg.config['http-session-time'] or None
       
    42         self.authmanager = authentication.RepositoryAuthenticationManager(repo)
       
    43         interval = (self.session_time or 0) / 2.
       
    44         if vreg.config.anonymous_user()[0] is not None:
       
    45             self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 5 * 60
       
    46             assert self.cleanup_anon_session_time > 0
       
    47             if self.session_time is not None:
       
    48                 self.cleanup_anon_session_time = min(self.session_time,
       
    49                                                      self.cleanup_anon_session_time)
       
    50             interval = self.cleanup_anon_session_time / 2.
       
    51         # we don't want to check session more than once every 5 minutes
       
    52         self.clean_sessions_interval = max(5 * 60, interval)
       
    53 
       
    54     def clean_sessions(self):
       
    55         """cleanup sessions which has not been unused since a given amount of
       
    56         time. Return the number of sessions which have been closed.
       
    57         """
       
    58         self.debug('cleaning http sessions')
       
    59         session_time = self.session_time
       
    60         closed, total = 0, 0
       
    61         for session in self.current_sessions():
       
    62             total += 1
       
    63             last_usage_time = session.mtime
       
    64             no_use_time = (time() - last_usage_time)
       
    65             if session.anonymous_session:
       
    66                 if no_use_time >= self.cleanup_anon_session_time:
       
    67                     self.close_session(session)
       
    68                     closed += 1
       
    69             elif session_time is not None and no_use_time >= session_time:
       
    70                 self.close_session(session)
       
    71                 closed += 1
       
    72         return closed, total - closed
       
    73 
       
    74     def current_sessions(self):
       
    75         """return currently open sessions"""
       
    76         raise NotImplementedError()
       
    77 
       
    78     def get_session(self, req, sessionid):
       
    79         """return existing session for the given session identifier"""
       
    80         raise NotImplementedError()
       
    81 
       
    82     def open_session(self, req):
       
    83         """open and return a new session for the given request.
       
    84 
       
    85         raise :exc:`cubicweb.AuthenticationError` if authentication failed
       
    86         (no authentication info found or wrong user/password)
       
    87         """
       
    88         raise NotImplementedError()
       
    89 
       
    90     def close_session(self, session):
       
    91         """close session on logout or on invalid session detected (expired out,
       
    92         corrupted...)
       
    93         """
       
    94         raise NotImplementedError()
       
    95 
       
    96 
       
    97 set_log_methods(AbstractSessionManager, getLogger('cubicweb.sessionmanager'))
       
    98 
       
    99 
       
   100 class InMemoryRepositorySessionManager(AbstractSessionManager):
       
   101     """manage session data associated to a session identifier"""
       
   102 
       
   103     def __init__(self, *args, **kwargs):
       
   104         super(InMemoryRepositorySessionManager, self).__init__(*args, **kwargs)
       
   105         # XXX require a RepositoryAuthenticationManager which violates
       
   106         #     authenticate interface by returning a session instead of a user
       
   107         #assert isinstance(self.authmanager, RepositoryAuthenticationManager)
       
   108         self._sessions = {}
       
   109 
       
   110     # dump_data / restore_data to avoid loosing open sessions on registry
       
   111     # reloading
       
   112     def dump_data(self):
       
   113         return self._sessions
       
   114     def restore_data(self, data):
       
   115         self._sessions = data
       
   116 
       
   117     def current_sessions(self):
       
   118         return self._sessions.values()
       
   119 
       
   120     def get_session(self, req, sessionid):
       
   121         """return existing session for the given session identifier"""
       
   122         if sessionid not in self._sessions:
       
   123             raise InvalidSession()
       
   124         session = self._sessions[sessionid]
       
   125         try:
       
   126             user = self.authmanager.validate_session(req, session)
       
   127         except InvalidSession:
       
   128             self.close_session(session)
       
   129             raise
       
   130         if session.closed:
       
   131             self.close_session(session)
       
   132             raise InvalidSession()
       
   133         return session
       
   134 
       
   135     def open_session(self, req):
       
   136         """open and return a new session for the given request. The session is
       
   137         also bound to the request.
       
   138 
       
   139         raise :exc:`cubicweb.AuthenticationError` if authentication failed
       
   140         (no authentication info found or wrong user/password)
       
   141         """
       
   142         session, login = self.authmanager.authenticate(req)
       
   143         self._sessions[session.sessionid] = session
       
   144         session.mtime = time()
       
   145         return session
       
   146 
       
   147     def postlogin(self, req, session):
       
   148         """postlogin: the user have been related to a session
       
   149 
       
   150         Both req and session are passed to this function because actually
       
   151         linking the request to the session is not yet done and not the
       
   152         responsability of this object.
       
   153         """
       
   154         # Update last connection date
       
   155         # XXX: this should be in a post login hook in the repository, but there
       
   156         #      we can't differentiate actual login of automatic session
       
   157         #      reopening. Is it actually a problem?
       
   158         if 'last_login_time' in req.vreg.schema:
       
   159             self._update_last_login_time(session)
       
   160         req.set_message(req._('welcome %s!') % session.user.login)
       
   161 
       
   162     def _update_last_login_time(self, session):
       
   163         # XXX should properly detect missing permission / non writeable source
       
   164         # and avoid "except (RepositoryError, Unauthorized)" below
       
   165         try:
       
   166             with session.new_cnx() as cnx:
       
   167                 cnx.execute('SET X last_login_time NOW WHERE X eid %(x)s',
       
   168                            {'x' : session.user.eid})
       
   169                 cnx.commit()
       
   170         except (RepositoryError, Unauthorized):
       
   171             pass
       
   172 
       
   173     def close_session(self, session):
       
   174         """close session on logout or on invalid session detected (expired out,
       
   175         corrupted...)
       
   176         """
       
   177         self.info('closing http session %s' % session.sessionid)
       
   178         self._sessions.pop(session.sessionid, None)
       
   179         if not session.closed:
       
   180             session.repo.close(session.sessionid)