content_type errors are broken, when combined with require_csrf
Closed this issue · 4 comments
Bottom line up front:
Somehow, the interaction of require_csrf
and content_type
causes a “Bad CSRF Token” error response when passed the wrong Content-Type into views with CSRF checking disabled.
Consider the following service (fully runnable):
from wsgiref.simple_server import make_server
from cornice import Service
from pyramid.config import Configurator
from pyramid.csrf import CookieCSRFStoragePolicy
from pyramid.request import Request
from pyramid.response import Response
CONTENT_TYPE = 'application/json'
HOST = 'localhost'
service = Service(name='service', path='/', content_type=CONTENT_TYPE)
@service.post(require_csrf=False)
def post(__: Request) -> Response:
return Response("POSTed!")
if __name__ == '__main__':
with Configurator() as config:
# Turn on automatic CSRF checking.
# NOTE: Our view disables CSRF checking, so this *should* be NOOP.
config.set_csrf_storage_policy(
CookieCSRFStoragePolicy(domain=HOST))
config.set_default_csrf_options(require_csrf=True)
config.include('cornice')
config.add_cornice_service(service)
app = config.make_wsgi_app()
make_server(HOST, 6543, app).serve_forever()
However, when passed the wrong Content-Type, the POST in fact fails with:
<html>
<head>
<title>400 Bad CSRF Token</title>
</head>
<body>
<h1>400 Bad CSRF Token</h1>
Access is denied. This server can not verify that your cross-site request forgery token belongs to your login session. Either you supplied the wrong cross-site request forgery token or your session no longer exists. This may be due to session timeout or because browser is not supplying the credentials required, as can happen when the browser has cookies turned off.
<br/>
<br/>
check_csrf_token(): Invalid token
</body>
</html>
Since I’ve disabled require_csrf
for the POST view, there should be no circumstance under which this error pops up. When Content-Type: application/json
, everything works as intended.
I tried to debug this myself inside Cornice, but I ran out of time after going pretty deep, without luck.
Thanks for this detailed bug report!
I confirm, I could reproduce the issue.
I modified your example to rely on pure-Pyramid and it works fine:
--- 458.py 2017-10-19 16:32:40.467257968 +0200
+++ 458-.py 2017-10-19 16:31:32.225185482 +0200
@@ -1,20 +1,18 @@
from wsgiref.simple_server import make_server
from cornice import Service
+from cornice.util import ContentTypePredicate
from pyramid.config import Configurator
from pyramid.csrf import CookieCSRFStoragePolicy
from pyramid.request import Request
from pyramid.response import Response
CONTENT_TYPE = 'application/json'
HOST = 'localhost'
-service = Service(name='service', path='/', content_type=CONTENT_TYPE)
-
-
-@service.post(require_csrf=False)
-def post(__: Request) -> Response:
+def post(request):
return Response("POSTed!")
if __name__ == '__main__':
@@ -24,9 +22,11 @@
config.set_csrf_storage_policy(
CookieCSRFStoragePolicy(domain=HOST))
config.set_default_csrf_options(require_csrf=True)
- config.include('cornice')
- config.add_cornice_service(service)
+ config.add_view_predicate('content_type', ContentTypePredicate)
+
+ config.add_route('hello', '/')
+ config.add_view(post, route_name='hello', require_csrf=False, content_type=CONTENT_TYPE)
app = config.make_wsgi_app()
make_server(HOST, 6543, app).serve_forever()
It comes from the internal Cornice fallback view with receives the default CSRF option.
The fix in Cornice is a one-liner:
diff --git a/cornice/pyramidhook.py b/cornice/pyramidhook.py
index e2f9841..a8f2b7e 100644
--- a/cornice/pyramidhook.py
+++ b/cornice/pyramidhook.py
@@ -274,7 +274,8 @@ def register_service_views(config, service):
# Add the fallback view last
config.add_view(view=get_fallback_view(service),
route_name=route_name,
- permission=NO_PERMISSION_REQUIRED)
+ permission=NO_PERMISSION_REQUIRED,
+ require_csrf=False)
config.commit()
The behaviour is then as excepted:
$ http POST :6543 "Content-Type: application/json"
HTTP/1.0 200 OK
Content-Length: 7
Content-Type: text/html; charset=UTF-8
Date: Thu, 19 Oct 2017 14:43:37 GMT
Server: WSGIServer/0.1 Python/2.7.12+
X-Content-Type-Options: nosniff
POSTed!
$ http POST :6543
HTTP/1.0 415 Unsupported Media Type
Content-Length: 155
Content-Type: application/json
Date: Thu, 19 Oct 2017 14:43:41 GMT
Server: WSGIServer/0.1 Python/2.7.12+
X-Content-Type-Options: nosniff
{
"errors": [
{
"description": "Content-Type header should be one of ['application/json']",
"location": "header",
"name": "Content-Type"
}
],
"status": "error"
}
This breaks compatibility with the older versions of pyramid.
One of the projects I'm working on is still using Pyramid 1.5.8 and the following error occurs when I'm trying to run app tests.
Traceback (most recent call last):
File "/home/avolkov/repos/test_proj/test_proj/tests/api/orders/test_rest_post.py", line 506, in setUp
super(TestInternetIPTVPhonePost, self).setUp()
File "/home/avolkov/repos/test_proj/test_proj/tests/common_setup.py", line 117, in setUp
self.app = main(config_dict)
File "/home/avolkov/repos/test_proj/test_proj/__init__.py", line 48, in main
config.scan(ignore=b'test_proj.tests')
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 930, in scan
ignore=ignore)
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/venusian/__init__.py", line 239, in scan
invoke(modname, name, ob)
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/venusian/__init__.py", line 190, in invoke
callback(self, name, ob)
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/cornice/resource.py", line 123, in callback
config.add_cornice_service(service)
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/util.py", line 535, in wrapper
result = wrapped(self, *arg, **kw)
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/cornice/pyramidhook.py", line 264, in register_service_views
config.commit()
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 610, in commit
self.action_state.execute_actions(introspector=self.introspector)
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 1048, in execute_actions
for action in resolveConflicts(self.actions):
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/config/__init__.py", line 1132, in resolveConflicts
discriminator = undefer(action['discriminator'])
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/registry.py", line 250, in undefer
v = v.resolve()
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/registry.py", line 242, in resolve
return self.func()
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/config/views.py", line 1166, in discrim_func
order, preds, phash = predlist.make(self, **pvals)
File "/home/avolkov/.virtualenvs/test_proj/local/lib/python2.7/site-packages/pyramid/config/util.py", line 158, in make
raise ConfigurationError('Unknown predicate values: %r' % (kw,))
ConfigurationError: Unknown predicate values: {'require_csrf': False}
It looks like check_csrf was renamed some time ago to require_csrf some time ago -- Pylons/pyramid#2367
The changes were merged into Pyramid 1.7 -- Pylons/pyramid#2413
Is it possible to make this change backward-compatible, or add a requirement so cornice from then on depends on Pyramid 1.7 and higher? -- https://github.com/Cornices/cornice/blob/2.4.X/setup.py