Merge branch 'fix/csrf', closes #40

This commit is contained in:
Martin Zimmermann 2013-12-02 12:13:42 +01:00
commit 59b70e7109
2 changed files with 32 additions and 0 deletions

View File

@ -12,6 +12,7 @@ from werkzeug.http import dump_cookie
from werkzeug.routing import Rule from werkzeug.routing import Rule
from werkzeug.wrappers import Response from werkzeug.wrappers import Response
from werkzeug.exceptions import BadRequest, Forbidden, NotFound from werkzeug.exceptions import BadRequest, Forbidden, NotFound
from werkzeug.useragents import UserAgent
from isso.compat import text_type as str from isso.compat import text_type as str
@ -31,6 +32,31 @@ class JSON(Response):
return super(JSON, self).__init__(*args, content_type='application/json') return super(JSON, self).__init__(*args, content_type='application/json')
def csrf(view):
"""A decorator to check if HTTP_Origin matches configured host. If not,
return 401 Forbidden. See
* https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Checking_The_Origin_Header
* http://tools.ietf.org/html/draft-abarth-origin-09
* https://wiki.mozilla.org/Security/Origin
for details.
"""
def dec(self, environ, request, *args, **kwargs):
if UserAgent(environ).browser == "msie": # yup
origin = request.headers.get("Referer", "")
else:
origin = request.headers.get("Origin", "")
if parse.host(origin) not in map(parse.host, self.conf.getiter("host")):
raise Forbidden("CSRF")
return view(self, environ, request, *args, **kwargs)
return dec
class API(object): class API(object):
FIELDS = set(['id', 'parent', 'text', 'author', 'website', 'email', FIELDS = set(['id', 'parent', 'text', 'author', 'website', 'email',
@ -91,6 +117,7 @@ class API(object):
return True, "" return True, ""
@csrf
@requires(str, 'uri') @requires(str, 'uri')
def new(self, environ, request, uri): def new(self, environ, request, uri):
@ -174,6 +201,7 @@ class API(object):
return Response(json.dumps(rv), 200, content_type='application/json') return Response(json.dumps(rv), 200, content_type='application/json')
@csrf
def edit(self, environ, request, id): def edit(self, environ, request, id):
try: try:
@ -217,6 +245,7 @@ class API(object):
resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"]))
return resp return resp
@csrf
def delete(self, environ, request, id, key=None): def delete(self, environ, request, id, key=None):
try: try:
@ -294,11 +323,13 @@ class API(object):
return JSON(json.dumps(rv), 200) return JSON(json.dumps(rv), 200)
@csrf
def like(self, environ, request, id): def like(self, environ, request, id):
nv = self.comments.vote(True, id, utils.anonymize(str(request.remote_addr))) nv = self.comments.vote(True, id, utils.anonymize(str(request.remote_addr)))
return Response(json.dumps(nv), 200) return Response(json.dumps(nv), 200)
@csrf
def dislike(self, environ, request, id): def dislike(self, environ, request, id):
nv = self.comments.vote(False, id, utils.anonymize(str(request.remote_addr))) nv = self.comments.vote(False, id, utils.anonymize(str(request.remote_addr)))

View File

@ -11,6 +11,7 @@ class FakeIP(object):
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
environ['REMOTE_ADDR'] = self.ip environ['REMOTE_ADDR'] = self.ip
environ['HTTP_ORIGIN'] = "http://localhost:8080"
return self.app(environ, start_response) return self.app(environ, start_response)