From ce5ae8da19707019cd62d42533e591d71071f4fe Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Fri, 25 Nov 2011 12:13:56 -0600 Subject: Rename MediaGoblin middleware to meddleware to avoid confusion w/ wsgi middleware hehehehehe, "meddleware" --- mediagoblin/meddleware/csrf.py | 132 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 mediagoblin/meddleware/csrf.py (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py new file mode 100644 index 00000000..051afe58 --- /dev/null +++ b/mediagoblin/meddleware/csrf.py @@ -0,0 +1,132 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import hashlib +import random + +from webob.exc import HTTPForbidden +from wtforms import Form, HiddenField, validators + +from mediagoblin import mg_globals + +# Use the system (hardware-based) random number generator if it exists. +# -- this optimization is lifted from Django +if hasattr(random, 'SystemRandom'): + getrandbits = random.SystemRandom().getrandbits +else: + getrandbits = random.getrandbits + + +class CsrfForm(Form): + """Simple form to handle rendering a CSRF token and confirming it + is included in the POST.""" + + csrf_token = HiddenField("", + [validators.Required()]) + + +def render_csrf_form_token(request): + """Render the CSRF token in a format suitable for inclusion in a + form.""" + + form = CsrfForm(csrf_token=request.environ['CSRF_TOKEN']) + + return form.csrf_token + + +class CsrfMeddleware(object): + """CSRF Protection Meddleware + + Adds a CSRF Cookie to responses and verifies that it is present + and matches the form token for non-safe requests. + """ + + CSRF_KEYLEN = 64 + SAFE_HTTP_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE") + + def __init__(self, mg_app): + self.app = mg_app + + def process_request(self, request): + """For non-safe requests, confirm that the tokens are present + and match. + """ + + # get the token from the cookie + try: + request.environ['CSRF_TOKEN'] = \ + request.cookies[mg_globals.app_config['csrf_cookie_name']] + + except KeyError, e: + # if it doesn't exist, make a new one + request.environ['CSRF_TOKEN'] = self._make_token(request) + + # if this is a non-"safe" request (ie, one that could have + # side effects), confirm that the CSRF tokens are present and + # valid + if request.method not in self.SAFE_HTTP_METHODS \ + and ('gmg.verify_csrf' in request.environ or + 'paste.testing' not in request.environ): + + return self.verify_tokens(request) + + def process_response(self, request, response): + """Add the CSRF cookie to the response if needed and set Vary + headers. + """ + + # set the CSRF cookie + response.set_cookie( + mg_globals.app_config['csrf_cookie_name'], + request.environ['CSRF_TOKEN'], + path=request.environ['SCRIPT_NAME'], + domain=mg_globals.app_config.get('csrf_cookie_domain'), + secure=(request.scheme.lower() == 'https'), + httponly=True) + + # update the Vary header + response.vary = (getattr(response, 'vary', None) or []) + ['Cookie'] + + def _make_token(self, request): + """Generate a new token to use for CSRF protection.""" + + return "%s" % (getrandbits(self.CSRF_KEYLEN),) + + def verify_tokens(self, request): + """Verify that the CSRF Cookie exists and that it matches the + form value.""" + + # confirm the cookie token was presented + cookie_token = request.cookies.get( + mg_globals.app_config['csrf_cookie_name'], + None) + + if cookie_token is None: + # the CSRF cookie must be present in the request + return HTTPForbidden() + + # get the form token and confirm it matches + form = CsrfForm(request.POST) + if form.validate(): + form_token = form.csrf_token.data + + if form_token == cookie_token: + # all's well that ends well + return + + # either the tokens didn't match or the form token wasn't + # present; either way, the request is denied + return HTTPForbidden() -- cgit v1.2.3 From 56dc1c9d3eb73b86bf8e165ffc79ad4929239603 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 25 Nov 2011 22:16:18 +0100 Subject: Add base class for Meddleware Created a BaseMeddleware which all Meddleware should derive from. This is not strictly needed, but will greatly help. The base class has the common __init__ of all the other Meddlwares and fall backs for all hooks. That way a new Meddlware only needs to override what it actually wants to implement. --- mediagoblin/meddleware/csrf.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 051afe58..ca2eca5f 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -21,6 +21,7 @@ from webob.exc import HTTPForbidden from wtforms import Form, HiddenField, validators from mediagoblin import mg_globals +from mediagoblin.meddleware import BaseMeddleware # Use the system (hardware-based) random number generator if it exists. # -- this optimization is lifted from Django @@ -47,7 +48,7 @@ def render_csrf_form_token(request): return form.csrf_token -class CsrfMeddleware(object): +class CsrfMeddleware(BaseMeddleware): """CSRF Protection Meddleware Adds a CSRF Cookie to responses and verifies that it is present @@ -57,9 +58,6 @@ class CsrfMeddleware(object): CSRF_KEYLEN = 64 SAFE_HTTP_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE") - def __init__(self, mg_app): - self.app = mg_app - def process_request(self, request): """For non-safe requests, confirm that the tokens are present and match. -- cgit v1.2.3 From 91cf67385a78a59af7874df327b96f7ea0b4259b Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Sat, 26 Nov 2011 14:34:36 -0800 Subject: Issue 680: Dispatch meddleware request processing post-routing --- mediagoblin/meddleware/csrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index ca2eca5f..961fa7a6 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -58,7 +58,7 @@ class CsrfMeddleware(BaseMeddleware): CSRF_KEYLEN = 64 SAFE_HTTP_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE") - def process_request(self, request): + def process_request(self, request, controller): """For non-safe requests, confirm that the tokens are present and match. """ -- cgit v1.2.3 From ca9ebfe2e05c83248d647b442ff29a9758a6a05c Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Sat, 26 Nov 2011 15:32:35 -0800 Subject: Issue 680 Allow decorating views to prevent CSRF protection. --- mediagoblin/meddleware/csrf.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 961fa7a6..16541bee 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -31,6 +31,13 @@ else: getrandbits = random.getrandbits +def csrf_exempt(func): + """Decorate a Controller to exempt it from CSRF protection.""" + + func.csrf_enabled = False + return func + + class CsrfForm(Form): """Simple form to handle rendering a CSRF token and confirming it is included in the POST.""" @@ -75,9 +82,11 @@ class CsrfMeddleware(BaseMeddleware): # if this is a non-"safe" request (ie, one that could have # side effects), confirm that the CSRF tokens are present and # valid - if request.method not in self.SAFE_HTTP_METHODS \ - and ('gmg.verify_csrf' in request.environ or - 'paste.testing' not in request.environ): + if (getattr(controller, 'csrf_enabled', True) and + request.method not in self.SAFE_HTTP_METHODS and + ('gmg.verify_csrf' in request.environ or + 'paste.testing' not in request.environ) + ): return self.verify_tokens(request) -- cgit v1.2.3 From 71c6c432a5fe8fe0f96dac284562a8e1b981d669 Mon Sep 17 00:00:00 2001 From: Elrond Date: Sat, 3 Dec 2011 21:20:11 +0100 Subject: Bug #685: only provide CSRF token if it exists This was suggested by Nathan Yergler in the bug logs. Just implementing it. - Let render_csrf_form_token return None, if the CSRF_TOKEN is not available in the environ, because the process_request part of the meddleware has not yet run. - In render_template: If the returned value from above is None, then do not add the csrf_token to the templates context. --- mediagoblin/meddleware/csrf.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 16541bee..a4e4e5c6 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -50,6 +50,9 @@ def render_csrf_form_token(request): """Render the CSRF token in a format suitable for inclusion in a form.""" + if 'CSRF_TOKEN' not in request.environ: + return None + form = CsrfForm(csrf_token=request.environ['CSRF_TOKEN']) return form.csrf_token -- cgit v1.2.3 From cf29e8a824e0ef4612f1144f079c80c1d20b89e5 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 2 Feb 2012 09:44:13 -0600 Subject: It's 2012 all up in here --- mediagoblin/meddleware/csrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index a4e4e5c6..ea8372bf 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -1,5 +1,5 @@ # GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by -- cgit v1.2.3 From f10c3bb8e5fc44b6d580261a05f1e4b4639e0949 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Sun, 13 May 2012 00:44:09 +0200 Subject: Added logging to meddleware.csrf --- mediagoblin/meddleware/csrf.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index ea8372bf..8e8ec7c4 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -16,6 +16,7 @@ import hashlib import random +import logging from webob.exc import HTTPForbidden from wtforms import Form, HiddenField, validators @@ -23,6 +24,8 @@ from wtforms import Form, HiddenField, validators from mediagoblin import mg_globals from mediagoblin.meddleware import BaseMeddleware +_log = logging.getLogger(__name__) + # Use the system (hardware-based) random number generator if it exists. # -- this optimization is lifted from Django if hasattr(random, 'SystemRandom'): @@ -126,6 +129,7 @@ class CsrfMeddleware(BaseMeddleware): if cookie_token is None: # the CSRF cookie must be present in the request + _log.error('CSRF cookie not present') return HTTPForbidden() # get the form token and confirm it matches @@ -139,4 +143,5 @@ class CsrfMeddleware(BaseMeddleware): # either the tokens didn't match or the form token wasn't # present; either way, the request is denied + _log.error('CSRF validation failed') return HTTPForbidden() -- cgit v1.2.3 From a855e92a985a9bdc9c5062cad268eba3d8e19f84 Mon Sep 17 00:00:00 2001 From: Will Kahn-Greene Date: Sun, 3 Jun 2012 15:53:34 -0400 Subject: Fix problems from pyflakes output --- mediagoblin/meddleware/csrf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 8e8ec7c4..74502dc4 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import hashlib import random import logging @@ -81,7 +80,7 @@ class CsrfMeddleware(BaseMeddleware): request.environ['CSRF_TOKEN'] = \ request.cookies[mg_globals.app_config['csrf_cookie_name']] - except KeyError, e: + except KeyError: # if it doesn't exist, make a new one request.environ['CSRF_TOKEN'] = self._make_token(request) -- cgit v1.2.3 From 111a609df526bd3690fc03e623eaf5826f33f4d2 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Sat, 29 Sep 2012 21:07:15 +0200 Subject: Replaced all request.POST with request.form, ... - Fixed error handling in OAuth plugin - Changed request.POST file fields to request.files --- mediagoblin/meddleware/csrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 74502dc4..1488e6d9 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -132,7 +132,7 @@ class CsrfMeddleware(BaseMeddleware): return HTTPForbidden() # get the form token and confirm it matches - form = CsrfForm(request.POST) + form = CsrfForm(request.form) if form.validate(): form_token = form.csrf_token.data -- cgit v1.2.3 From 62d14bf50baf45ac15fe5276be74b073de880f77 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 15 Nov 2012 16:55:15 +0100 Subject: Transition webob.HttpForbidden to webob's exceptions Forbidden Also the BadRequest exception. --- mediagoblin/meddleware/csrf.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 1488e6d9..65db9827 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -17,7 +17,7 @@ import random import logging -from webob.exc import HTTPForbidden +from werkzeug.exceptions import Forbidden from wtforms import Form, HiddenField, validators from mediagoblin import mg_globals @@ -128,8 +128,9 @@ class CsrfMeddleware(BaseMeddleware): if cookie_token is None: # the CSRF cookie must be present in the request - _log.error('CSRF cookie not present') - return HTTPForbidden() + errstr = 'CSRF cookie not present' + _log.error(errstr) + return Forbidden(errstr) # get the form token and confirm it matches form = CsrfForm(request.form) @@ -142,5 +143,6 @@ class CsrfMeddleware(BaseMeddleware): # either the tokens didn't match or the form token wasn't # present; either way, the request is denied - _log.error('CSRF validation failed') - return HTTPForbidden() + errstr = 'CSRF validation failed' + _log.error(errstr) + return Forbidden(errstr) -- cgit v1.2.3 From cfa922295e5ddfaab336a3c2c0403422f77758b6 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Sun, 23 Dec 2012 11:58:51 +0100 Subject: Convert return HttpException to raise HttpException controllers (view function) raise HttpException's and do not return them. Signed-off-by: Sebastian Spaeth --- mediagoblin/meddleware/csrf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 65db9827..2984ebb9 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -130,7 +130,7 @@ class CsrfMeddleware(BaseMeddleware): # the CSRF cookie must be present in the request errstr = 'CSRF cookie not present' _log.error(errstr) - return Forbidden(errstr) + raise Forbidden(errstr) # get the form token and confirm it matches form = CsrfForm(request.form) @@ -145,4 +145,4 @@ class CsrfMeddleware(BaseMeddleware): # present; either way, the request is denied errstr = 'CSRF validation failed' _log.error(errstr) - return Forbidden(errstr) + raise Forbidden(errstr) -- cgit v1.2.3 From 947c08ae43914184116995b2d51f11778497a2be Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Mon, 14 Jan 2013 16:09:24 +0100 Subject: Improve error message wording (#564) Improve error message wording if no csf cookie could be detected. Also, make the error text translatable. --- mediagoblin/meddleware/csrf.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'mediagoblin/meddleware/csrf.py') diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 2984ebb9..661f0ba2 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -22,6 +22,7 @@ from wtforms import Form, HiddenField, validators from mediagoblin import mg_globals from mediagoblin.meddleware import BaseMeddleware +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ _log = logging.getLogger(__name__) @@ -127,10 +128,13 @@ class CsrfMeddleware(BaseMeddleware): None) if cookie_token is None: - # the CSRF cookie must be present in the request - errstr = 'CSRF cookie not present' - _log.error(errstr) - raise Forbidden(errstr) + # the CSRF cookie must be present in the request, if not a + # cookie blocker might be in action (in the best case) + _log.error('CSRF cookie not present') + raise Forbidden(_('CSRF cookie not present. This is most likely ' + 'the result of a cookie blocker or somesuch.
' + 'Make sure to permit the settings of cookies for ' + 'this domain.')) # get the form token and confirm it matches form = CsrfForm(request.form) -- cgit v1.2.3