pyramid_cubicweb/session.py
author Christophe de Vienne <christophe@unlish.com>
Sat, 08 Nov 2014 23:07:20 +0100
changeset 11524 54c83bfda277
parent 11506 bfc1aa1dba30
child 11537 caf268942436
permissions -rw-r--r--
Don't rollback if exception is HTTPSuccessful or HTTPRedirection In the request finishing, the 'cleanup' callback set by _cw_cnx automatically commit the transaction except is an exception is set on the request. Problem is, redirections and successul http return code can raise exceptions. This patch detects such exceptions and avoid rolling back the transaction. Closes #4566482

import warnings
import logging

from pyramid.compat import pickle
from pyramid.session import SignedCookieSessionFactory

from cubicweb import Binary


log = logging.getLogger(__name__)


def logerrors(logger):
    def wrap(fn):
        def newfn(*args, **kw):
            try:
                return fn(*args, **kw)
            except:
                logger.exception("Error in %s" % fn.__name__)
        return newfn
    return wrap


def CWSessionFactory(
        secret,
        cookie_name='session',
        max_age=None,
        path='/',
        domain=None,
        secure=False,
        httponly=False,
        set_on_exception=True,
        timeout=1200,
        reissue_time=120,
        hashalg='sha512',
        salt='pyramid.session.',
        serializer=None):

    SignedCookieSession = SignedCookieSessionFactory(
        secret,
        cookie_name=cookie_name,
        max_age=max_age,
        path=path,
        domain=domain,
        secure=secure,
        httponly=httponly,
        set_on_exception=set_on_exception,
        timeout=timeout,
        reissue_time=reissue_time,
        hashalg=hashalg,
        salt=salt,
        serializer=serializer)

    class CWSession(SignedCookieSession):
        def __init__(self, request):
            # _set_accessed will be called by the super __init__.
            # Setting _loaded to True inhibates it.
            self._loaded = True

            # the super __init__ will load a single value in the dictionnary,
            # the session id.
            super(CWSession, self).__init__(request)

            # Remove the session id from the dict
            self.sessioneid = self.pop('sessioneid', None)
            self.repo = request.registry['cubicweb.repository']

            # We need to lazy-load only for existing sessions
            self._loaded = self.sessioneid is None

        @logerrors(log)
        def _set_accessed(self, value):
            self._accessed = value

            if self._loaded:
                return

            with self.repo.internal_cnx() as cnx:
                session = cnx.find('CWSession', eid=self.sessioneid).one()
                value = session.cwsessiondata
                if value:
                    # Use directly dict.update to avoir _set_accessed to be
                    # recursively called
                    dict.update(self, pickle.load(value))

            self._loaded = True

        def _get_accessed(self):
            return self._accessed

        accessed = property(_get_accessed, _set_accessed)

        @logerrors(log)
        def _set_cookie(self, response):
            # Save the value in the database
            data = Binary(pickle.dumps(dict(self)))
            sessioneid = self.sessioneid

            with self.repo.internal_cnx() as cnx:
                if not sessioneid:
                    session = cnx.create_entity(
                        'CWSession', cwsessiondata=data)
                    sessioneid = session.eid
                else:
                    session = cnx.entity_from_eid(sessioneid)
                    session.cw_set(cwsessiondata=data)
                cnx.commit()

            # Only if needed actually set the cookie
            if self.new or self.accessed - self.renewed > self._reissue_time:
                dict.clear(self)
                dict.__setitem__(self, 'sessioneid', sessioneid)
                return super(CWSession, self)._set_cookie(response)

            return True

    return CWSession


def includeme(config):
    secret = config.registry['cubicweb.config']['pyramid-session-secret']
    if not secret:
        secret = 'notsosecret'
        warnings.warn('''

            !! WARNING !! !! WARNING !!

            The session cookies are signed with a static secret key.
            To put your own secret key, edit your all-in-one.conf file
            and set the 'pyramid-session-secret' key.

            YOU SHOULD STOP THIS INSTANCE unless your really know what you
            are doing !!

        ''')
    session_factory = CWSessionFactory(secret)
    config.set_session_factory(session_factory)