Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Flask cross site HTTP POST - doesn't work for specific allowed origins

I'm trying to get Flask to handle cross-site scripting properly. I've taken the crossdomain decorator snippet from here: http://flask.pocoo.org/snippets/56/

In the code below, I've put the decorator snippet and the basic flask server.

I'm calling the decorator with headers='Content-Type' because otherwise I was getting "Request header field Content-Type is not allowed by Access-Control-Allow-Headers." in the browser.

So here is my question: As-is, the code below works. But when I want to restrict to only a specific server like so:

@crossdomain(origin='myserver.com', headers='Content-Type')

I get the browser error

"Origin http://myserver.com is not allowed by Access-Control-Allow-Origin."

I can't get it working for anything other than origin='*'.

Does anybody have any ideas?

Here is the complete code:

from datetime import timedelta
from flask import make_response, request, current_app, Flask, jsonify
from functools import update_wrapper

def crossdomain(origin=None, methods=None, headers=None,
            max_age=21600, attach_to_all=True,
            automatic_options=True):
    if methods is not None:
        methods = ', '.join(sorted(x.upper() for x in methods))
    if headers is not None and not isinstance(headers, basestring):
        headers = ', '.join(x.upper() for x in headers)
    if not isinstance(origin, basestring):
        origin = ', '.join(origin)
    if isinstance(max_age, timedelta):
        max_age = max_age.total_seconds()

    def get_methods():
        if methods is not None:
            return methods

        options_resp = current_app.make_default_options_response()
        return options_resp.headers['allow']

    def decorator(f):
        def wrapped_function(*args, **kwargs):
            if automatic_options and request.method == 'OPTIONS':
            resp = current_app.make_default_options_response()
            else:
                resp = make_response(f(*args, **kwargs))
            if not attach_to_all and request.method != 'OPTIONS':
                return resp

            h = resp.headers

            h['Access-Control-Allow-Origin'] = origin
            h['Access-Control-Allow-Methods'] = get_methods()
            h['Access-Control-Max-Age'] = str(max_age)
            if headers is not None:
                h['Access-Control-Allow-Headers'] = headers
            return resp

        f.provide_automatic_options = False
        return update_wrapper(wrapped_function, f)
    return decorator

app = Flask(__name__)

@app.route('/my_service', methods=['POST', 'OPTIONS'])
@crossdomain(origin='*', headers='Content-Type')
def my_service():
    return jsonify(foo='cross domain ftw')

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080, debug=True)

For reference my python version is 2.7.2 Flask version is 0.7.2

like image 647
Nate Avatar asked Apr 26 '13 16:04

Nate


People also ask

How do you allow cross origins in Flask?

In the simplest case, initialize the Flask-Cors extension with default arguments in order to allow CORS on all routes. app = Flask(__name__) cors = CORS(app) @app. route("/") def helloWorld(): return "Hello, cross-origin-world!"

How do you fix a CORS error in Python?

When you see this error, it means your code is triggering your browser to send a CORS preflight OPTIONS request, and the server's responding with a 3xx redirect. To avoid the error, your request needs to get a 2xx success response instead. That's useful, thanks for sharing!


2 Answers

I just tried the same code with python version 2.7.3 and Flask version 0.8.

With these versions, it fails with

@crossdomain(origin='myserver.com', headers='Content-Type')

but it works with

@crossdomain(origin='http://myserver.com', headers='Content-Type')

Perhaps it just doesn't work with Flask 0.7.2? (despite what it says on the snippet page).


EDIT: After playing with this a lot more (and upgrading to Flask 0.9) it seems that the real problem (or yet another problem) might be related to having multiple allowed origins in a list. In other words, using the above code like this:

@crossdomain(origin=['http://myserver.com', 'http://myserver2.com'], headers='Content-Type')

doesn't work.

To fix this problem I tweaked the decorator. See code here: http://chopapp.com/#351l7gc3

This code returns only the domain of the requesting site if it is in the list. Kinda quirky, but at least for me, problem solved :)

like image 91
Nate Avatar answered Oct 23 '22 02:10

Nate


Python is trying hard to prevent you from exposing yourself to cross site scripting attacks.

One fix is by giving in, and having your requests hit the same server the flask script is running on. Fetching JSON from far away servers defined in strings is risky business anyway.

I was able to fix it by letting the browser keep itself on the same server, like this:

$('a#calculate').bind('click', function() {
  $.getJSON('/_add_numbers', { 
    a: $('input[name="a"]').val(),
    b: $('input[name="b"]').val()
  }, function(data) {
    $("#result").text(data.request);
  });
  return false;
});

Notice how getJSON method is passed a /_add_numbers. That communicates to the browser to stay on the same host and look for that page. Then the browser is happy and secure we are staying on the same host, and you never get the error:

Origin http://myserver.com is not allowed by Access-Control-Allow-Origin
like image 44
Eric Leschinski Avatar answered Oct 23 '22 01:10

Eric Leschinski