--- a/goa/appobjects/sessions.py Fri Sep 24 18:20:57 2010 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,291 +0,0 @@
-# copyright 2003-2010 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/>.
-"""persistent sessions stored in big table
-
-
-XXX TODO:
-* cleanup persistent session
-* use user as ancestor?
-"""
-__docformat__ = "restructuredtext en"
-
-from pickle import loads, dumps
-from time import localtime, strftime
-
-from logilab.common.decorators import cached, clear_cache
-
-from cubicweb import BadConnectionId
-from cubicweb.dbapi import Connection, ConnectionProperties, repo_connect
-from cubicweb.selectors import none_rset, match_user_groups
-from cubicweb.server.session import Session
-from cubicweb.web import InvalidSession
-from cubicweb.web.application import AbstractSessionManager
-from cubicweb.web.application import AbstractAuthenticationManager
-
-from google.appengine.api.datastore import Key, Entity, Get, Put, Delete, Query
-from google.appengine.api.datastore_errors import EntityNotFoundError
-from google.appengine.api.datastore_types import Blob
-
-try:
- del Connection.__del__
-except AttributeError:
- pass # already deleted
-
-
-class GAEAuthenticationManager(AbstractAuthenticationManager):
- """authenticate user associated to a request and check session validity,
- using google authentication service
- """
-
- def __init__(self, *args, **kwargs):
- super(GAEAuthenticationManager, self).__init__(*args, **kwargs)
- self._repo = self.config.repository(vreg=self.vreg)
-
- def authenticate(self, req, _login=None, _password=None):
- """authenticate user and return an established connection for this user
-
- :raise ExplicitLogin: if authentication is required (no authentication
- info found or wrong user/password)
- """
- if _login is not None:
- login, password = _login, _password
- else:
- login, password = req.get_authorization()
- # remove possibly cached cursor coming from closed connection
- clear_cache(req, 'cursor')
- cnxprops = ConnectionProperties(self.vreg.config.repo_method,
- close=False, log=False)
- cnx = repo_connect(self._repo, login, password=password, cnxprops=cnxprops)
- self._init_cnx(cnx, login, password)
- # associate the connection to the current request
- req.set_connection(cnx)
- return cnx
-
- def _init_cnx(self, cnx, login, password):
- cnx.anonymous_connection = self.config.is_anonymous_user(login)
- cnx.vreg = self.vreg
- cnx.login = login
- cnx.password = password
-
-
-class GAEPersistentSessionManager(AbstractSessionManager):
- """manage session data associated to a session identifier"""
-
- def __init__(self, vreg, *args, **kwargs):
- super(GAEPersistentSessionManager, self).__init__(vreg, *args, **kwargs)
- self._repo = self.config.repository(vreg=vreg)
-
- def get_session(self, req, sessionid):
- """return existing session for the given session identifier"""
- # search a record for the given session
- key = Key.from_path('CubicWebSession', 'key_' + sessionid, parent=None)
- try:
- record = Get(key)
- except EntityNotFoundError:
- raise InvalidSession()
- repo = self._repo
- if self.has_expired(record):
- repo._sessions.pop(sessionid, None)
- Delete(record)
- raise InvalidSession()
- # associate it with a repository session
- try:
- reposession = repo._get_session(sessionid)
- user = reposession.user
- # touch session to avoid closing our own session when sessions are
- # cleaned (touch is done on commit/rollback on the server side, too
- # late in that case)
- reposession._touch()
- except BadConnectionId:
- # can't found session in the repository, this probably mean the
- # session is not yet initialized on this server, hijack the repo
- # to create it
- # use an internal connection
- ssession = repo.internal_session()
- # try to get a user object
- try:
- user = repo.authenticate_user(ssession, record['login'],
- record['password'])
- finally:
- ssession.close()
- reposession = Session(user, self._repo, _id=sessionid)
- self._repo._sessions[sessionid] = reposession
- cnx = Connection(self._repo, sessionid)
- return self._get_proxy(req, record, cnx, user)
-
- def open_session(self, req):
- """open and return a new session for the given request"""
- cnx = self.authmanager.authenticate(req)
- # avoid rebuilding a user
- user = self._repo._get_session(cnx.sessionid).user
- # build persistent record for session data
- record = Entity('CubicWebSession', name='key_' + cnx.sessionid)
- record['login'] = cnx.login
- record['password'] = cnx.password
- record['anonymous_connection'] = cnx.anonymous_connection
- Put(record)
- return self._get_proxy(req, record, cnx, user)
-
- def close_session(self, proxy):
- """close session on logout or on invalid session detected (expired out,
- corrupted...)
- """
- proxy.close()
-
- def current_sessions(self):
- for record in Query('CubicWebSession').Run():
- yield ConnectionProxy(record)
-
- def _get_proxy(self, req, record, cnx, user):
- proxy = ConnectionProxy(record, cnx, user)
- user.req = req
- req.set_connection(proxy, user)
- return proxy
-
-
-class ConnectionProxy(object):
-
- def __init__(self, record, cnx=None, user=None):
- self.__record = record
- self.__cnx = cnx
- self.__user = user
- self.__data = None
- self.__is_dirty = False
- self.sessionid = record.key().name()[4:] # remove 'key_' prefix
-
- def __repr__(self):
- sstr = '<ConnectionProxy %s' % self.sessionid
- if self.anonymous_connection:
- sstr += ' (anonymous)'
- elif self.__user:
- sstr += ' for %s' % self.__user.login
- sstr += ', last used %s>' % strftime('%T', localtime(self.last_usage_time))
- return sstr
-
- def __getattribute__(self, name):
- try:
- return super(ConnectionProxy, self).__getattribute__(name)
- except AttributeError:
- return getattr(self.__cnx, name)
-
- def _set_last_usage_time(self, value):
- self.__is_dirty = True
- self.__record['last_usage_time'] = value
- def _get_last_usage_time(self):
- return self.__record['last_usage_time']
-
- last_usage_time = property(_get_last_usage_time, _set_last_usage_time)
-
- @property
- def anonymous_connection(self):
- # use get() for bw compat if sessions without anonymous information are
- # found. Set default to True to limit lifetime of those sessions.
- return self.__record.get('anonymous_connection', True)
-
- @property
- @cached
- def data(self):
- if self.__record.get('data') is not None:
- try:
- return loads(self.__record['data'])
- except:
- self.__is_dirty = True
- self.exception('corrupted session data for session %s',
- self.__cnx)
- return {}
-
- def get_session_data(self, key, default=None, pop=False):
- """return value associated to `key` in session data"""
- if pop:
- try:
- value = self.data.pop(key)
- self.__is_dirty = True
- return value
- except KeyError:
- return default
- else:
- return self.data.get(key, default)
-
- def set_session_data(self, key, value):
- """set value associated to `key` in session data"""
- self.data[key] = value
- self.__is_dirty = True
-
- def del_session_data(self, key):
- """remove value associated to `key` in session data"""
- try:
- del self.data[key]
- self.__is_dirty = True
- except KeyError:
- pass
-
- def commit(self):
- if self.__is_dirty:
- self.__save()
- self.__cnx.commit()
-
- def rollback(self):
- self.__save()
- self.__cnx.rollback()
-
- def close(self):
- if self.__cnx is not None:
- self.__cnx.close()
- Delete(self.__record)
-
- def __save(self):
- if self.__is_dirty:
- self.__record['data'] = Blob(dumps(self.data))
- Put(self.__record)
- self.__is_dirty = False
-
- def user(self, req=None, props=None):
- """return the User object associated to this connection"""
- return self.__user
-
-
-import logging
-from cubicweb import set_log_methods
-set_log_methods(ConnectionProxy, logging.getLogger('cubicweb.web.goa.session'))
-
-
-from cubicweb.view import StartupView
-from cubicweb.web import application
-
-class SessionsCleaner(StartupView):
- id = 'cleansessions'
- __select__ = none_rset() & match_user_groups('managers')
-
- def call(self):
- # clean web session
- session_manager = application.SESSION_MANAGER
- nbclosed, remaining = session_manager.clean_sessions()
- self.w(u'<div class="message">')
- self.w(u'%s web sessions closed<br/>\n' % nbclosed)
- # clean repository sessions
- repo = self.config.repository(vreg=self.vreg)
- nbclosed = repo.clean_sessions()
- self.w(u'%s repository sessions closed<br/>\n' % nbclosed)
- self.w(u'%s remaining sessions<br/>\n' % remaining)
- self.w(u'</div>')
-
-
-def registration_callback(vreg):
- vreg.register(SessionsCleaner)
- vreg.register(GAEAuthenticationManager, clear=True)
- vreg.register(GAEPersistentSessionManager, clear=True)