[core] Protect session data from unwanted loading.
authorChristophe de Vienne <christophe@unlish.com>
Wed, 28 Jan 2015 00:00:05 +0100
changeset 11553 a322a02ca301
parent 11552 d92a608c1a16
child 11554 5631764c51ba
[core] Protect session data from unwanted loading. Use specialised Session and Connection types that forward their 'data' and 'session_data' attributes to the pyramid request.session attribute. This forwarding is done with properties, instead of copying a reference, which allow to access request.session (and the session factory) if and only if Session.data or Connection.session_data is accessed. In some cases, most notably the static resources requests, it can mean no access the session during the request handling, which saves a request to the session persistence layer. Closes #4891437
pyramid_cubicweb/core.py
--- a/pyramid_cubicweb/core.py	Mon Jan 26 18:06:58 2015 +0100
+++ b/pyramid_cubicweb/core.py	Wed Jan 28 00:00:05 2015 +0100
@@ -9,7 +9,7 @@
 
 import cubicweb
 import cubicweb.web
-from cubicweb.server.session import Session
+from cubicweb.server import session as cwsession
 
 from pyramid import httpexceptions
 
@@ -20,6 +20,56 @@
 log = logging.getLogger(__name__)
 
 
+class Connection(cwsession.Connection):
+    """ A specialised Connection that access the session data through a
+    property.
+
+    This behavior makes sure the actual session data is not loaded until
+    actually accessed.
+    """
+    def __init__(self, session, *args, **kw):
+        super(Connection, self).__init__(session, *args, **kw)
+        self._session = session
+
+    def _get_session_data(self):
+        return self._session.data
+
+    def _set_session_data(self, data):
+        pass
+
+    _session_data = property(_get_session_data, _set_session_data)
+
+
+class Session(cwsession.Session):
+    """ A Session that access the session data through a property.
+
+    Along with :class:`Connection`, it avoid any load of the pyramid session
+    data until it is actually accessed.
+    """
+    def __init__(self, pyramid_request, user, repo):
+        super(Session, self).__init__(user, repo)
+        self._pyramid_request = pyramid_request
+
+    def get_data(self):
+        if not getattr(self, '_protect_data_access', False):
+            self._data_accessed = True
+            return self._pyramid_request.session
+
+    def set_data(self, data):
+        if getattr(self, '_data_accessed', False):
+            self._pyramid_request.session.clear()
+            self._pyramid_request.session.update(data)
+
+    data = property(get_data, set_data)
+
+    def new_cnx(self):
+        self._protect_data_access = True
+        try:
+            return Connection(self)
+        finally:
+            self._protect_data_access = False
+
+
 @contextmanager
 def cw_to_pyramid(request):
     """ Context manager to wrap a call to the cubicweb API.
@@ -175,12 +225,12 @@
     return cnx
 
 
-def repo_connect(repo, eid):
+def repo_connect(request, repo, eid):
     """A lightweight version of
     :meth:`cubicweb.server.repository.Repository.connect` that does not keep
     track of opened sessions, removing the need of closing them"""
     user = tools.cached_build_user(repo, eid)
-    session = Session(user, repo, None)
+    session = Session(request, user, repo)
     tools.cnx_attach_entity(session, user)
     # Calling the hooks should be done only once, disabling it completely for
     # now
@@ -203,14 +253,10 @@
 
     if not request.authenticated_userid:
         session = repo_connect(
-            repo, eid=request.registry['cubicweb.anonymous_eid'])
+            request, repo, eid=request.registry['cubicweb.anonymous_eid'])
     else:
         session = request._cw_cached_session
 
-    # XXX Ideally we store the cw session data in the pyramid session.
-    # BUT some data in the cw session data dictionnary makes pyramid fail.
-    session.data = request.session
-
     return session
 
 
@@ -249,7 +295,7 @@
     repo = request.registry['cubicweb.repository']
 
     try:
-        session = repo_connect(repo, eid=login)
+        session = repo_connect(request, repo, eid=login)
         request._cw_cached_session = session
     except:
         log.exception("Failed")