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)