diff options
author | Elrond <elrond+mediagoblin.org@samba-tng.org> | 2011-11-28 18:40:45 +0100 |
---|---|---|
committer | Elrond <elrond+mediagoblin.org@samba-tng.org> | 2011-11-28 18:40:45 +0100 |
commit | 72567762e36c849ffe8172b6cea4ca1be682e511 (patch) | |
tree | 549d405d3a577cf46b840a5550caecc4e64b85a1 | |
parent | a3663b407997cb8e2d45086641b7eb9f4efd476c (diff) | |
parent | ca9ebfe2e05c83248d647b442ff29a9758a6a05c (diff) | |
download | mediagoblin-72567762e36c849ffe8172b6cea4ca1be682e511.tar.lz mediagoblin-72567762e36c849ffe8172b6cea4ca1be682e511.tar.xz mediagoblin-72567762e36c849ffe8172b6cea4ca1be682e511.zip |
Merge remote branch 'remotes/nyergler/issue-680-csrf-optout'
* remotes/nyergler/issue-680-csrf-optout:
Issue 680 Allow decorating views to prevent CSRF protection.
Issue 680: Dispatch meddleware request processing post-routing
-rw-r--r-- | mediagoblin/app.py | 13 | ||||
-rw-r--r-- | mediagoblin/meddleware/__init__.py | 2 | ||||
-rw-r--r-- | mediagoblin/meddleware/csrf.py | 17 | ||||
-rw-r--r-- | mediagoblin/meddleware/noop.py | 3 | ||||
-rw-r--r-- | mediagoblin/tests/test_csrf_middleware.py | 21 |
5 files changed, 43 insertions, 13 deletions
diff --git a/mediagoblin/app.py b/mediagoblin/app.py index aafadd97..7f087ed9 100644 --- a/mediagoblin/app.py +++ b/mediagoblin/app.py @@ -107,12 +107,6 @@ class MediaGoblinApp(object): def __call__(self, environ, start_response): request = Request(environ) - # pass the request through our meddleware classes - for m in self.meddleware: - response = m.process_request(request) - if response is not None: - return response(environ, start_response) - ## Routing / controller loading stuff path_info = request.path_info route_match = self.routing.match(path_info) @@ -164,6 +158,13 @@ class MediaGoblinApp(object): return render_404(request)(environ, start_response) controller = common.import_component(route_match['controller']) + + # pass the request through our meddleware classes + for m in self.meddleware: + response = m.process_request(request, controller) + if response is not None: + return response(environ, start_response) + request.start_response = start_response # get the response from the controller diff --git a/mediagoblin/meddleware/__init__.py b/mediagoblin/meddleware/__init__.py index 729a020d..7ba70d87 100644 --- a/mediagoblin/meddleware/__init__.py +++ b/mediagoblin/meddleware/__init__.py @@ -25,7 +25,7 @@ class BaseMeddleware(object): def __init__(self, mg_app): self.app = mg_app - def process_request(self, request): + def process_request(self, request, controller): pass def process_response(self, request, response): diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index ca2eca5f..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.""" @@ -58,7 +65,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. """ @@ -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) diff --git a/mediagoblin/meddleware/noop.py b/mediagoblin/meddleware/noop.py index b43053de..f5376494 100644 --- a/mediagoblin/meddleware/noop.py +++ b/mediagoblin/meddleware/noop.py @@ -19,7 +19,8 @@ from mediagoblin.meddleware import BaseMeddleware class NoOpMeddleware(BaseMeddleware): - def process_request(self, request): + + def process_request(self, request, controller): pass def process_response(self, request, response): diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py index 691f10b9..c8fca23a 100644 --- a/mediagoblin/tests/test_csrf_middleware.py +++ b/mediagoblin/tests/test_csrf_middleware.py @@ -27,7 +27,7 @@ from mediagoblin import mg_globals def test_csrf_cookie_set(test_app): cookie_name = mg_globals.app_config['csrf_cookie_name'] - + # get login page response = test_app.get('/auth/login/') @@ -69,3 +69,22 @@ def test_csrf_token_must_match(test_app): mg_globals.app_config['csrf_cookie_name'])}, extra_environ={'gmg.verify_csrf': True}).\ status_int == 200 + +@setup_fresh_app +def test_csrf_exempt(test_app): + + # monkey with the views to decorate a known endpoint + import mediagoblin.auth.views + from mediagoblin.meddleware.csrf import csrf_exempt + + mediagoblin.auth.views.login = csrf_exempt( + mediagoblin.auth.views.login + ) + + # construct a request with no cookie or form token + assert test_app.post('/auth/login/', + extra_environ={'gmg.verify_csrf': True}, + expect_errors=False).status_int == 200 + + # restore the CSRF protection in case other tests expect it + mediagoblin.auth.views.login.csrf_enabled = True |