response to preflight doesn't pass access control check - python

I have a simple flask server
import pdb
from flask import Flask, jsonify, abort, make_response, request, send_from_directory
from flask_cors import CORS, cross_origin
from pprint import pprint
import argparse
from mylib.mylib_rest_api import ProcessRestApiRequest
DEBUG=True
app = Flask(__name__)
CORS(app)
parser = argparse.ArgumentParser(description='Run the server')
parser.add_argument('--ip-address', default='127.0.0.1', help='Server IP Address. Default: %(default)s')
parser.add_argument('--port', type=int, default=8081, help='Server port')
args = parser.parse_args()
#app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
def do_pre_serve_actions():
if not request.json: abort(400)
# Extract the data
dictReq = request.get_json(force=True)
if DEBUG:
pprint('dictReq: '+str(dictReq))
return dictReq
def do_post_serve_actions(dictResp):
if DEBUG:
pprint("dictResp: "+str(dictResp))
dictJSONResp = jsonify(dictResp)
objRespADF = make_response(dictJSONResp)
return objRespADF
#app.route('/<target>', methods=['POST'])
def serve(target):
dictReq = do_pre_serve_actions()
dictResp = ProcessRestApiRequest(dictReq, target)
return do_post_serve_actions(dictResp)
if __name__ == '__main__':
app.run(debug=DEBUG, host=args.ip_address, port=args.port)
This is how a request looks like:
makeRequestAndSendData(xhr, dict) {
dict['Interface'] = this.getChipInterface();
var data = JSON.stringify(dict);
var url = this.server.getUrl();
console.log("url: "+url);
console.log("Request Dictionary:");
console.log(dict);
xhr.open("POST", url, true);
xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
xhr.setRequestHeader("Access-Control-Allow-Headers","*");
xhr.setRequestHeader("Access-Control-Allow-Credentials", "true");
xhr.setRequestHeader("Content-type","application/json");
xhr.send(data);
}
Here is what I'm getting:
Failed to load http://192.168.0.132:8084/mychip: Response to preflight
request doesn't pass access control check: The
'Access-Control-Allow-Origin' header has a value 'null' that is not
equal to the supplied origin. Origin 'null' is therefore not allowed
access.
What am I doing wrong? I've looked everywhere online and it seems like it got everything I need. Am I missing something?

This can happen with Chrome and WebKit/Chrome based browsers (Opera, Brave, etc.) if you serve the page from the file system instead of the network. In that case there is no origin site (because it was served from file), and Chrome will send the header Origin: null in the preflight OPTIONS request. Your CORS enabled Flask server will reply with this origin in its preflight response, however, Chrome rejects it as an invalid origin, even though that is what Chrome sent in the first place.
Firefox also sends header Origin: null in the OPTIONS request, but it is OK with the response and so it does issue the actual POST request. I have also found that Chrome on Android does seem to work when loading from the file system.
As a work-around, serve your test file from a HTTP server. The easiest way (for testing purposes only) is to use Python's SimpleHTTPSever. Simply change to the directory containing your test HTML file and run this command:
$ python -m SimpleHTTPServer
It will start a HTTP server listening on port 8000 so you can load the file into your browser using URL http://localhost:8000/cors_test.html for example.
Also note that the Access-Control-Allow-* headers are meant to be sent in the preflight response, not the request. flask-cors will generally handle that for you and the client side XMLHttpRequest object should properly construct the request for you.

Related

Flask, add Access-Control-Allow-Private-Network true to CORS preflight

Chrome warns:
A site requested a resource from a network that it could only access because of its users' privileged network position. These requests expose devices and servers to the internet, increasing the risk of a cross-site request forgery (CSRF) attack, and/or information leakage.
To mitigate these risks, Chrome will require non-public subresources to opt-into being accessed with a preflight request and will start blocking them in Chrome 101 (April 2022).
To fix this issue, ensure that response to the preflight request for the private network resource has the Access-Control-Allow-Private-Network header set to true.
I am using flask, but unsure how to add this header to the preflight check. I can add a header to the responses manually, but how to add a header to the preflight check?
I am using Flask-Cors and this is the code:
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
I dropped the Flask-Cors package, and made my own implementation:
""" Note:
We need to use functools wraps, or else #route thinks all functions
are named the same, and errors out that a route is overriding another
Test Preflight with:
curl -i -X OPTIONS http://127.0.0.1:5000/foo/
Then test reponse with:
curl -i http://127.0.0.1:5000/api/foo/
"""
from functools import wraps
from flask import Response, request
def add_cors_preflight_headers(response):
allow_request = 'foo' in request.origin
if allow_request:
response.headers['Access-Control-Allow-Origin'] = request.origin
if request.method == 'OPTIONS':
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
# Allow chrome to access private network ajax requests
response.headers['Access-Control-Allow-Private-Network'] = 'true'
return response
def handle_cors(func):
#wraps(func)
def decorator(*args, **kwargs):
if request.method == 'OPTIONS':
response = Response()
else:
response = func(*args, **kwargs)
response = add_cors_preflight_headers(response)
return response
return decorator
Then used as follows (note how we add options to the allowed methods):
#app.route("/api/foo/", methods=['GET', 'OPTIONS'])
#handle_cors
def get_foo():
return Response({'foo': 'hello world!'})

Python requests header not working - Checked Chrome developer tools -> Network

This is my python code:
import requests
from flask import Flask
app = Flask(__name__)
#app.route("/")
def index():
url = 'https://google.co.in'
headers = {
'User-Agent':'blahblahblah'
}
r = requests.get(url, headers=headers)
return 'check terminal'
So this is the way to change request headers in python requests. But if I open the url and see developer options > Network > Request headers. I see default as user agent. Means it simply doesn't work.
The request you're making is by the server, not the client (the web browser). The index page served by flask goes to a client. The client doesn't make the requests.get request you've written here, instead the server does.
Instead, the client only requests whatever you're returning from the route, which here is 'check terminal' (which should not work, and should be something in the lines of return jsonify(result='check terminal')), and is not concerned about what the server is doing internally.
So as #brunns has commented, these two requests are different. If you want to check the headers of your request, maybe try httpbin.org.

Proxy a GET request to a different site in Python

I want to forward a GET request that I get from a client to a different site,
In my case- A m3u8 playlist request to a streaming site to handle.
Does anyone know how can it be done?
If you want to proxy, first install requests:
pip install requests
then, get the file in the server and serve the content, ej:
import requests
from flask import Flask, Response
app = Flask(__name__)
#app.route('/somefile.m3u')
def proxy():
url = 'https://www.example.com/somefile.m3u'
r = requests.get(url)
return Response(r.content, mimetype="text/csv")
app.run()
If you just want to redirect, do this (requests not needed):
from flask import Flask, redirect
app = Flask(__name__)
#app.route('/redir')
def redir():
url = 'https://www.example.com/somefile.m3u'
return redirect(url, code=302)
app.run()

How too send and receive http POST requests in Python using Apache server

I need a simple Client side that can send a bool valu if he is Alive, in a HTTP POST request. I have a Linux server running Apache.
I realized from Atermi(user who helped me), i new to use a requests library, something like this:
import requests
r = requests.post("http://127.0.0.1/post", data={'key': 'val'})
print(r.text)
But I don't know how to make my apache server receive the post requests.
In addition,when running the script I am receiving an error,
The requested URL '/' was not found on this servers.
I tried a server side, for testing in the same linux server and this is the
Code,
from flask import Flask, request
app = Flask(__name__)
#app.route('/', methods=['POST'])
def res():
print(request.form['key'])
return "Revived"
but recveing the following error:
runtimerror: working outside of the request context
Thanks!

Python, Flask: How to set response header for all responses

I want to set all of my http headers responses to something like this:
response.headers["X-Frame-Options"] = "SAMEORIGIN"
I checked this question, but it only changes the header for one specific controller. I want to change all of my headers maybe in "before_request" function similar to the following logic. How can I do that?
#app.before_request
def before_request():
# response.headers["X-Frame-Options"] = "SAMEORIGIN"
Set the header in a #app.after_request() hook, at which point you have a response object to set the header on:
#app.after_request
def apply_caching(response):
response.headers["X-Frame-Options"] = "SAMEORIGIN"
return response
The flask.request context is still available when this hook runs, so you can still vary the response based on the request at this time.
The #app.after_request() hook was not adequate for my use case.
My use case is as follows: I have a google cloud function, and I want to set the CORS headers for all responses. There are possibly multiple responses, as I have to validate the input and return if there are issues with it, I have to process data and possibly return early if something fails etc. So I've created a helper function as follows:
# Helper function to return a response with status code and CORS headers
def prepare_response(res_object, status_code):
response = flask.jsonify(res_object)
response.headers.set('Access-Control-Allow-Origin', '*')
response.headers.set('Access-Control-Allow-Methods', 'GET, POST')
return response, status_code
Thus, when I want to return a response (always with CORS headers), I can now call this function and I do not duplicate the response.headers setup necessary to enable CORS.
We can set the response headers for all responses in Python Flask application gracefully using WSGI Middleware
This way of setting response headers in Flask application context using middleware is thread safe and can be used to set custom & dynamic attributes, read the request headers this is especially helpful if we are setting custom/dynamic response headers from any helper class.
file: middleware.py
import flask
from flask import request, g
class SimpleMiddleWare(object):
"""
Simple WSGI middleware
"""
def __init__(self, app):
self.app = app
self._header_name = "any_request_header"
def __call__(self, environ, start_response):
"""
middleware to capture request header from incoming http request
"""
request_id_header = environ.get(self._header_name) # reading all request headers
environ[self._header_name] = request_id_header
def new_start_response(status, response_headers, exc_info=None):
"""
set custom response headers
"""
# set the above captured request header as response header
response_headers.append((self._header_name, request_id_header))
# example to access flask.g values set in any class thats part of the Flask app & then set that as response header
values = g.get(my_response_header, {})
if values.get('x-custom-header'):
response_headers.append(('x-custom-header', values.get('x-custom-header')))
return start_response(status, response_headers, exc_info)
return self.app(environ, new_start_response)
Calling the middleware from main class
file : main.py
from flask import Flask
import asyncio
from gevent.pywsgi import WSGIServer
from middleware import SimpleMiddleWare
app = Flask(__name__)
app.wsgi_app = SimpleMiddleWare(app.wsgi_app)

Categories

Resources