changeset 5143:43ad9c3b7d5d

middleware: use secure cookies over secure connections HTTP cookie spec defines secure cookies, which are transmitted only over secure connections (HTTPS). Using them helps protect against some attacks, but cookies shouldn't be made secure when we don't have HTTPS configured. As it is now, it's left at user's discretion, but probably it's a good idea to force secure cookies when they can be used. In the current implementation, cookies are issued to users before they actually try to log in, on the first page load. So if that happens over HTTPS, it's probably safe to assume secure cookies can be used, and to default to normal "insecure" cookies if HTTPS isn't available. It's not easy to sneak into Beaker's internals, and it doesn't support selective secureness, so we use our own wrapper around Beaker's SessionMiddleware class to give secure cookies over HTTPS connections. Beaker's built-in mechanism for secure cookies is forced to add the flag when needed only.
author Andrew Shadura <andrew@shadura.me>
date Wed, 04 Mar 2015 23:57:57 +0100
parents 12e6de3d7e29
children 7a5db341a942
files kallithea/config/middleware.py kallithea/lib/middleware/sessionmiddleware.py
diffstat 2 files changed, 64 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/config/middleware.py	Wed Mar 25 10:11:54 2015 +0100
+++ b/kallithea/config/middleware.py	Wed Mar 04 23:57:57 2015 +0100
@@ -15,7 +15,6 @@
     Pylons middleware initialization
 """
 
-from beaker.middleware import SessionMiddleware
 from routes.middleware import RoutesMiddleware
 from paste.cascade import Cascade
 from paste.registry import RegistryManager
@@ -29,6 +28,7 @@
 from kallithea.lib.middleware.simplehg import SimpleHg
 from kallithea.lib.middleware.simplegit import SimpleGit
 from kallithea.lib.middleware.https_fixup import HttpsFixup
+from kallithea.lib.middleware.sessionmiddleware import SecureSessionMiddleware
 from kallithea.config.environment import load_environment
 from kallithea.lib.middleware.wrapper import RequestWrapper
 
@@ -60,7 +60,7 @@
 
     # Routing/Session/Cache Middleware
     app = RoutesMiddleware(app, config['routes.map'])
-    app = SessionMiddleware(app, config)
+    app = SecureSessionMiddleware(app, config)
 
     # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
     if asbool(config['pdebug']):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/middleware/sessionmiddleware.py	Wed Mar 04 23:57:57 2015 +0100
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.sessionmiddleware
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+session management middleware
+
+This file overrides Beaker's built-in SessionMiddleware
+class to automagically use secure cookies over HTTPS.
+
+Original Beaker SessionMiddleware class written by Ben Bangert
+"""
+
+from beaker.session import SessionObject
+from beaker.middleware import SessionMiddleware
+
+class SecureSessionMiddleware(SessionMiddleware):
+    def __call__(self, environ, start_response):
+        """
+        This function's implementation is taken directly from Beaker,
+        with HTTPS detection added. When accessed over HTTPS, force
+        setting cookie's secure flag.
+
+        The only difference from that original code is that we switch
+        the secure option on and off depending on the URL scheme (first
+        two lines). To avoid concurrency issues, we use a local options
+        variable.
+        """
+        options = dict(self.options)
+        options["secure"] = environ['wsgi.url_scheme'] == 'https'
+
+        session = SessionObject(environ, **options)
+        if environ.get('paste.registry'):
+            if environ['paste.registry'].reglist:
+                environ['paste.registry'].register(self.session, session)
+        environ[self.environ_key] = session
+        environ['beaker.get_session'] = self._get_session
+
+        if 'paste.testing_variables' in environ and 'webtest_varname' in options:
+            environ['paste.testing_variables'][options['webtest_varname']] = session
+
+        def session_start_response(status, headers, exc_info=None):
+            if session.accessed():
+                session.persist()
+                if session.__dict__['_headers']['set_cookie']:
+                    cookie = session.__dict__['_headers']['cookie_out']
+                    if cookie:
+                        headers.append(('Set-cookie', cookie))
+            return start_response(status, headers, exc_info)
+        return self.wrap_app(environ, session_start_response)