decode to unicode before loading in json again, for py3
[mediagoblin.git] / mediagoblin / meddleware / csrf.py
index 051afe586a15c0b9a6ec3fe37ea938fa70cff3ee..6cad6fa76d1a161ed6982b4adb8cbd668c5471ac 100644 (file)
@@ -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
 # 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
@@ -30,24 +34,34 @@ 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."""
 
     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
@@ -57,10 +71,7 @@ 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):
+    def process_request(self, request, controller):
         """For non-safe requests, confirm that the tokens are present
         and match.
         """
@@ -70,16 +81,18 @@ class CsrfMeddleware(object):
             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)
 
@@ -98,7 +111,7 @@ class CsrfMeddleware(object):
             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."""
@@ -115,11 +128,16 @@ class CsrfMeddleware(object):
             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
 
@@ -129,4 +147,6 @@ class CsrfMeddleware(object):
 
         # 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)