cubicweb/pyramid/session.py
changeset 11631 faf279e33298
parent 11629 0459094d9728
child 11896 327585fd7670
equal deleted inserted replaced
11478:1817f8946c22 11631:faf279e33298
       
     1 import warnings
       
     2 import logging
       
     3 from contextlib import contextmanager
       
     4 
       
     5 from pyramid.compat import pickle
       
     6 from pyramid.session import SignedCookieSessionFactory
       
     7 
       
     8 from cubicweb import Binary
       
     9 
       
    10 
       
    11 log = logging.getLogger(__name__)
       
    12 
       
    13 
       
    14 def logerrors(logger):
       
    15     def wrap(fn):
       
    16         def newfn(*args, **kw):
       
    17             try:
       
    18                 return fn(*args, **kw)
       
    19             except:
       
    20                 logger.exception("Error in %s" % fn.__name__)
       
    21         return newfn
       
    22     return wrap
       
    23 
       
    24 
       
    25 @contextmanager
       
    26 def unsafe_cnx_context_manager(request):
       
    27     """Return a connection for use as a context manager, with security disabled
       
    28 
       
    29     If request has an attached connection, its security will be deactived in the context manager's
       
    30     scope, else a new internal connection is returned.
       
    31     """
       
    32     cnx = request.cw_cnx
       
    33     if cnx is None:
       
    34         with request.registry['cubicweb.repository'].internal_cnx() as cnx:
       
    35             yield cnx
       
    36     else:
       
    37         with cnx.security_enabled(read=False, write=False):
       
    38             yield cnx
       
    39 
       
    40 
       
    41 def CWSessionFactory(
       
    42         secret,
       
    43         cookie_name='session',
       
    44         max_age=None,
       
    45         path='/',
       
    46         domain=None,
       
    47         secure=False,
       
    48         httponly=True,
       
    49         set_on_exception=True,
       
    50         timeout=1200,
       
    51         reissue_time=120,
       
    52         hashalg='sha512',
       
    53         salt='pyramid.session.',
       
    54         serializer=None):
       
    55     """ A pyramid session factory that store session data in the CubicWeb
       
    56     database.
       
    57 
       
    58     Storage is done with the 'CWSession' entity, which is provided by the
       
    59     'pyramid' cube.
       
    60 
       
    61     .. warning::
       
    62 
       
    63         Although it provides a sane default behavior, this session storage has
       
    64         a serious overhead because it uses RQL to access the database.
       
    65 
       
    66         Using pure SQL would improve a bit (it is roughly twice faster), but it
       
    67         is still pretty slow and thus not an immediate priority.
       
    68 
       
    69         It is recommended to use faster session factory
       
    70         (pyramid_redis_sessions_ for example) if you need speed.
       
    71 
       
    72     .. _pyramid_redis_sessions: http://pyramid-redis-sessions.readthedocs.org/
       
    73                                 en/latest/index.html
       
    74     """
       
    75 
       
    76     SignedCookieSession = SignedCookieSessionFactory(
       
    77         secret,
       
    78         cookie_name=cookie_name,
       
    79         max_age=max_age,
       
    80         path=path,
       
    81         domain=domain,
       
    82         secure=secure,
       
    83         httponly=httponly,
       
    84         set_on_exception=set_on_exception,
       
    85         timeout=timeout,
       
    86         reissue_time=reissue_time,
       
    87         hashalg=hashalg,
       
    88         salt=salt,
       
    89         serializer=serializer)
       
    90 
       
    91     class CWSession(SignedCookieSession):
       
    92         def __init__(self, request):
       
    93             # _set_accessed will be called by the super __init__.
       
    94             # Setting _loaded to True inhibates it.
       
    95             self._loaded = True
       
    96 
       
    97             # the super __init__ will load a single value in the dictionnary,
       
    98             # the session id.
       
    99             super(CWSession, self).__init__(request)
       
   100 
       
   101             # Remove the session id from the dict
       
   102             self.sessioneid = self.pop('sessioneid', None)
       
   103             self.repo = request.registry['cubicweb.repository']
       
   104 
       
   105             # We need to lazy-load only for existing sessions
       
   106             self._loaded = self.sessioneid is None
       
   107 
       
   108         @logerrors(log)
       
   109         def _set_accessed(self, value):
       
   110             self._accessed = value
       
   111 
       
   112             if self._loaded:
       
   113                 return
       
   114 
       
   115             with unsafe_cnx_context_manager(self.request) as cnx:
       
   116                 value_rset = cnx.execute('Any D WHERE X eid %(x)s, X cwsessiondata D',
       
   117                                          {'x': self.sessioneid})
       
   118                 value = value_rset[0][0]
       
   119                 if value:
       
   120                     # Use directly dict.update to avoir _set_accessed to be
       
   121                     # recursively called
       
   122                     dict.update(self, pickle.load(value))
       
   123 
       
   124             self._loaded = True
       
   125 
       
   126         def _get_accessed(self):
       
   127             return self._accessed
       
   128 
       
   129         accessed = property(_get_accessed, _set_accessed)
       
   130 
       
   131         @logerrors(log)
       
   132         def _set_cookie(self, response):
       
   133             # Save the value in the database
       
   134             data = Binary(pickle.dumps(dict(self)))
       
   135             sessioneid = self.sessioneid
       
   136 
       
   137             with unsafe_cnx_context_manager(self.request) as cnx:
       
   138                 if not sessioneid:
       
   139                     session = cnx.create_entity(
       
   140                         'CWSession', cwsessiondata=data)
       
   141                     sessioneid = session.eid
       
   142                 else:
       
   143                     session = cnx.entity_from_eid(sessioneid)
       
   144                     session.cw_set(cwsessiondata=data)
       
   145                 cnx.commit()
       
   146 
       
   147             # Only if needed actually set the cookie
       
   148             if self.new or self.accessed - self.renewed > self._reissue_time:
       
   149                 dict.clear(self)
       
   150                 dict.__setitem__(self, 'sessioneid', sessioneid)
       
   151                 return super(CWSession, self)._set_cookie(response)
       
   152 
       
   153             return True
       
   154 
       
   155     return CWSession
       
   156 
       
   157 
       
   158 def includeme(config):
       
   159     """ Activate the CubicWeb session factory.
       
   160 
       
   161     Usually called via ``config.include('cubicweb.pyramid.auth')``.
       
   162 
       
   163     See also :ref:`defaults_module`
       
   164     """
       
   165     settings = config.registry.settings
       
   166     secret = settings.get('cubicweb.session.secret', '')
       
   167     if not secret:
       
   168         secret = config.registry['cubicweb.config'].get('pyramid-session-secret')
       
   169         warnings.warn('''
       
   170         Please migrate pyramid-session-secret from
       
   171         all-in-one.conf to cubicweb.session.secret config entry in
       
   172         your pyramid.ini file.
       
   173         ''')
       
   174     if not secret:
       
   175         secret = 'notsosecret'
       
   176         warnings.warn('''
       
   177 
       
   178             !! WARNING !! !! WARNING !!
       
   179 
       
   180             The session cookies are signed with a static secret key.
       
   181             To put your own secret key, edit your pyramid.ini file
       
   182             and set the 'cubicweb.session.secret' key.
       
   183 
       
   184             YOU SHOULD STOP THIS INSTANCE unless your really know what you
       
   185             are doing !!
       
   186 
       
   187         ''')
       
   188     session_factory = CWSessionFactory(secret)
       
   189     config.set_session_factory(session_factory)