# HG changeset patch # User Andrew Shadura # Date 2015-03-04 23:57:57 # Node ID 43ad9c3b7d5dee9a237adadac0eac4553385949d # Parent 12e6de3d7e29aa4175ca624510f532bf73057354 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. diff --git a/kallithea/config/middleware.py b/kallithea/config/middleware.py --- a/kallithea/config/middleware.py +++ b/kallithea/config/middleware.py @@ -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 pylons.wsgiapp import PylonsApp 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 @@ def make_app(global_conf, full_stack=Tru # 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']): diff --git a/kallithea/lib/middleware/sessionmiddleware.py b/kallithea/lib/middleware/sessionmiddleware.py new file mode 100644 --- /dev/null +++ b/kallithea/lib/middleware/sessionmiddleware.py @@ -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 . +""" +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)