# 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
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import hashlib
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
+from mediagoblin.meddleware import BaseMeddleware
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+_log = logging.getLogger(__name__)
# Use the system (hardware-based) random number generator if it exists.
# -- this optimization is lifted from Django
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."""
csrf_token = HiddenField("",
- [validators.Required()])
+ [validators.InputRequired()])
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
-class CsrfMeddleware(object):
+class CsrfMeddleware(BaseMeddleware):
"""CSRF Protection Meddleware
Adds a CSRF Cookie to responses and verifies that it is present
CSRF_KEYLEN = 64
SAFE_HTTP_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE")
- def __init__(self, mg_app):
- self.app = mg_app
-
- def process_request(self, request):
+ def process_request(self, request, controller):
"""For non-safe requests, confirm that the tokens are present
and match.
"""
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)
# 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)
httponly=True)
# update the Vary header
- response.vary = (getattr(response, 'vary', None) or []) + ['Cookie']
+ response.vary = list(getattr(response, 'vary', None) or []) + ['Cookie']
def _make_token(self, request):
"""Generate a new token to use for CSRF protection."""
None)
if cookie_token is None:
- # the CSRF cookie must be present in the request
- return HTTPForbidden()
+ # 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.<br/>'
+ 'Make sure to permit the settings of cookies for '
+ 'this domain.'))
# 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
# either the tokens didn't match or the form token wasn't
# present; either way, the request is denied
- return HTTPForbidden()
+ errstr = 'CSRF validation failed'
+ _log.error(errstr)
+ raise Forbidden(errstr)