moksha/api/widgets/containers/templates/layout_applist.mak | 2 moksha/middleware/csrfmiddleware.py | 132 +++++++++++++ moksha/public/javascript/moksha.js | 2 3 files changed, 134 insertions(+), 2 deletions(-)
New commits: commit 1ea5af5b15c0b2d6b763d5afe0a93d1f03a64111 Author: John (J5) Palmieri johnp@redhat.com Date: Thu Feb 26 17:05:33 2009 -0500
add new csrf middleware and fix the csrf_rewrite_url function
* we now have a middleware that strips the csrf tokens from the query parameters and puts them in environ and a metadata plugin which strips the identity and credentials if the csrf check fails. To use this feature you need to override add_auth_middleware in your AppConfig. Here is a sample for the fas auth plugins:
def fas_make_who_middleware(app, log_stream): # This is both the middleware and the metadata plugin csrf_app = CSRFProtectionMiddleware() faswho = FASWhoPlugin(fasurl)
form = RedirectingFormPlugin('/login', '/login_handler', '/logout', rememberer_name='fasident') form.classifications = { IIdentifier:['browser'], IChallenger:['browser'] } # only for browser
identifiers = [('form', form),('fasident', faswho)] authenticators = [('fasauth', faswho)] challengers = [('form',form)] # add csrf as a metadata plugin mdproviders = [('fasmd', faswho), ('csrfmd', csrf_app)]
if os.environ.get('FAS_WHO_LOG'): log_stream = sys.stdout
# create the auth middleware as usual middleware = PluggableAuthenticationMiddleware( app, identifiers, authenticators, challengers, mdproviders, default_request_classifier, default_challenge_decider, log_stream = log_stream )
# but instead of returning it register it with the csrf app csrf_app.register_application(middleware)
# return the csrf app return csrf_app
* the csrf_rewrite_url function was double writting the token when we moved to modifying the query hash instead of just concatinating. While we moved to modifying the query hash we also forgot to remove the code that concatinated the value to the query string
diff --git a/moksha/api/widgets/containers/templates/layout_applist.mak b/moksha/api/widgets/containers/templates/layout_applist.mak index b2e21bc..24be703 100644 --- a/moksha/api/widgets/containers/templates/layout_applist.mak +++ b/moksha/api/widgets/containers/templates/layout_applist.mak @@ -25,7 +25,7 @@ data: ${app['params']} };
- $.ajax(ajaxOptions); + $(document).ready(function () {$.ajax(ajaxOptions)}); </script> % endif
diff --git a/moksha/middleware/csrfmiddleware.py b/moksha/middleware/csrfmiddleware.py new file mode 100644 index 0000000..33e5132 --- /dev/null +++ b/moksha/middleware/csrfmiddleware.py @@ -0,0 +1,132 @@ +# This file is part of Moksha. +# +# Moksha 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. +# +# Moksha 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 Moksha. If not, see http://www.gnu.org/licenses/. +# +# Copyright 2008, Red Hat, Inc. +# Authors: John (J5) Palmieri johnp@redhat.com + +import logging + +from webob import Request, Response +from hashlib import sha1 + +from paste.request import construct_url +from paste.httpexceptions import HTTPFound +from paste.request import parse_formvars, parse_dict_querystring +from paste.response import replace_header + +from urlparse import urlparse, urlunparse + +log = logging.getLogger(__name__) + +class CSRFProtectionMiddleware(object): + """ + A layer of WSGI middleware that is responsible for making sure authenticated + requests originated from the user inside of the app's domain + and not a malicious website + + This middleware works with Repoze.who and Repoze.what auth middleware + and must be run right before the who middleware and as a metadata plugin + for repoze.who. If the user is authenticated + and the csrf token is not the sha1 hash of the session id or + environ['CSRF_AUTH_SESSION_ID'] we clear environ['repoze.who.identity'] and + environ['repoze.what.credentials'] + + If the check succeeds we add the sha1 hash to the identity hash as + {self.csrf_token: sessionid_sha1_hash} + """ + + _connectors = {} + + def __init__(self, + session_cookie='tg-visit', + session_env='CSRF_AUTH_SESSION_ID', + token_env='CSRF_TOKEN', + csrf_token_id = '_csrf_token', + clear_env = ['repoze.who.identity', 'repoze.what.credentials']): + log.info('Creating CSRFProtectionMiddleware') + self.application = None + self.session_cookie = session_cookie + self.session_env = session_env + self.csrf_token_id = csrf_token_id + self.clear_env = clear_env + self.token_env = token_env + + def register_application(self, app): + self.application = app + + def __call__(self, environ, start_response): + """ Clear out the csrf token and place it in the environ + + This middleware must be placed before the repoze.who middleware + """ + request = Request(environ) + + csrf_token = None + d = parse_dict_querystring(environ) + if self.csrf_token_id in d: + csrf_token= d.getone(self.csrf_token_id) + d.__delitem__(self.csrf_token_id) + + qs = [] + for k in d: + qs.append('%s=%s'%(k,d[k])) + + qs = '&'.join(qs) + environ['QUERY_STRING'] = qs + + d = parse_formvars(environ, False) + if self.csrf_token_id in d: + csrf_token = d.getone(self.csrf_token_id) + d.__delitem__(self.csrf_token_id) + environ['paste.parsed_formvars'] = d + + environ[self.token_env] = csrf_token + + response = request.get_response(self.application) + return response(environ, start_response) + + def add_metadata(self, environ, identity): + """ + Repoze.who metadata pluging. This must be placed as the last + metadata plugin to run. It clears the identity and credentials + if the csrf check fails. The Repoze.who Auth plugin must also + set CSRF_AUTH_SESSION_ID in the environ so we don't clear + in the initial authorization stage + """ + request = Request(environ) + + session_id = environ.get(self.session_env, None) + auth_state = True + if not session_id: + auth_state = False + session_id = request.cookies.get(self.session_cookie) + + token = None + if session_id: + token = sha1(session_id) + token = token.hexdigest() + info = {self.csrf_token_id: token} + identity.update(info) + + # check for csrf + if (auth_state): + app = environ['repoze.who.application'] + p = list(urlparse(app.location())) + p[4] += '&' + self.csrf_token_id + '=' + token + replace_header(app.headers, 'location', urlunparse(tuple(p))) + elif not token or token != environ.get(self.token_env): + for k in self.clear_env: + if k in environ: + del environ[k] diff --git a/moksha/public/javascript/moksha.js b/moksha/public/javascript/moksha.js index 662c4e3..d5ae03f 100644 --- a/moksha/public/javascript/moksha.js +++ b/moksha/public/javascript/moksha.js @@ -109,7 +109,7 @@ moksha = { }
var query = qlist.join('&') - newurl += '?'+ query + moksha_csrf_token; + newurl += '?'+ query;
if (ourl.anchor) newurl += '#' + ourl.anchor;