PUT request not working with web2py? - python

We are having some issue with Cross Origin Resource Sharing (CORS) implementation in a restfull web service on web2py.
We try to implement CORS on the server side in web2py as suggested here: ( https://groups.google.com/forum/#!msg/web2py/kSUtyNcUQGI/qfiIqfUiWLwJ )
We added following to models/0.py, (to have the response header updated before actual restfull api handler in the controler)
===============================
if request.env.http_origin:
response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
response.headers['Access-Control-Allow-Origin'] = "*"
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Max-Age'] = 86400
if request.env.request_method == 'OPTIONS':
if request.env.http_access_control_request_method:
print request.env.http_access_control_request_method
response.headers['Access-Control-Allow-Methods'] = request.env.http_access_control_request_method
if request.env.http_access_control_request_headers:
response.headers['Access-Control-Allow-Headers'] = request.env.http_access_control_request_headers
==========================
RESTful POST & GET are now working
but PUT and DELETE aren't because preflight http OPTIONS request is rejected as "400 BAD REQUEST" by web2py
So for example when calling the restful webservice using ajax call from a local web page,
we get the following error msg in NetBeans log.
Failed to load resource: the server responded with a status of 400
(BAD REQUEST) (10:46:36:182 | error, network) at
127.0.0.1:8000/test/default/api/entries/2.json Failed to load resource: Origin localhost:8383 is not allowed by
Access-Control-Allow-Origin. (10:46:36:183 | error, network) at
127.0.0.1:8000/test/default /api/entries/2.json XMLHttpRequest cannot load 127.0.0.1:8000/test/default /api/entries/2.json. Origin
localhost:8383 is not allowed by Access-Control-Allow-Origin.
(10:46:36:183 | error, javascript) at www/page/test.html

You can add the following line:
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"

This is a really old question, but I just managed to solve the exact same problem. In my case the issue was with the controllers; I had to add the following wrapper before any actions:
def CORS(f):
"""
Enables CORS for any action
"""
def wrapper(*args, **kwds):
if request.env.http_origin and request.env.request_method == 'OPTIONS':
response.view = 'generic.json'
return dict()
return f(*args, **kwds)
return wrapper
Then in your controller justy write
#CORS
def whatever():
do_stuff
return dict(stuff)

Related

Dash Client Side Error Handling on Callbacks

Some of our Dash callbacks involve calling an external API which is authorized through OAuth (Authlib is being used). When a user signs out their OAuth token is removed from their current session. However, if that user has another tab open, the (ReactJS) AJAX callbacks can continue to be called from that tab but will ultimately fail as there is no longer a valid token.
Ideally what would happen is if the user is no longer authorized during a callback a 401 response will be returned and that would somehow trigger the browser to redirect to the Flask application root (which will now detect they need to login and redirect to the OAuth server for login)
In my callbacks I can include something similar to:
#dashapp.callback(
Output('some-div', 'children'),
Input('some-input', 'data')
)
def my_fancy_callback(inputdata):
if not session.get('token'):
raise SessionExpiredException
jsonResult = api.get('https://myapi.com/someinterestingdata')
return jsonResult
And then have a Flask error handler that catches this specific exception and returns a 401 response:
#app.errorhandler(SessionExpiredException)
def handle_SessionExpired_exception(e):
return Response("Access Denied", 401)
This indeed results in the AJAX call returning a 401 response (as seen on the browser network output). However, I'm not sure how I can hook into this call on the React/Browser side of things? With Dash I could look at adding some custom JavaScript files to maybe somehow intercept XMLHttpRequest globally but I was wondering if there was already built in support with Dash to accomplish this?
I am very new to Python/Flask/Dash/Plotly so if there is another best practice for client side error handling I would welcome any suggestions.
Protecting Dash apps on Flask behind OAuth is possible without the Dash Enterprise middleware but it's not intuitive to get it set up. I use an application factory method with the following components to protect dash applications.
This pattern will check auth status on all Dash layouts and callbacks without any specific callback code.
my_dash_app = Dash(
__name__,
server=flask_server,
url_base_pathname=f'/{base_pathname}/',
assets_folder=assets_folder,
meta_tags=[meta_viewport],
external_stylesheets=external_stylesheets,
suppress_callback_exceptions=True,
external_scripts=external_scripts
)
protect_dash_views(my_dash_app)
def protect_dash_views(my_dash_app):
for view_func in my_dash_app.server.view_functions:
if view_func.startswith(my_dash_app.config.url_base_pathname):
my_dash_app.server.view_functions[view_func] = azure_authentication_required(
my_dash_app.server.view_functions[view_func])
def azure_authentication_required(protected_function):
#wraps(protected_function)
def decorated_function(*args, **kwargs):
if not user_is_employee(): # flask session cookie has expired, force user to login again.
return redirect(url_for("login"))
if user_is_blocked_from_access():
return abort(403, 'You do not have permission to access This Application.') # prevents further code execution.
if azure_access_token_is_expired():
user_id = get_azure_user_id()
get_refresh_token_from_cache(user_id=user_id) # this routine aborts code 403 if not successful.
return protected_function(*args, **kwargs)
return decorated_function

Cookie not setting with Flask

I am trying to set a cookie with Flask after login and redirect on the front end in Javascript.
#app.route("/login")
#auth.login_required
def get_auth_token():
token = g.user.generate_auth_token()
request = make_response()
token = str(token.decode("ascii"))
request.set_cookie("token", value = token)
return request, 200
No matter if I have the redirect in or not, the cookie never sets. I've tried commenting out my redirect on the front end, I've tried setting my cookie with secure = false but none of that seems to work. What am I missing? If needed, I can provide the generate_suth_token function, but I know that is working properly. I am serving on localhost:5000 and using Flask 0.12.2, and received no cookie warnings in the server log.
If Flask service and client service are being hosted on different domains (e. g Flask uses 127.0.0.1:8080 and a client uses 127.0.0.1:3000) in this case, cookies should be set with domain parameter otherwise they will not be available.
resp.set_cookie('cookie_key', value="cookie_value", domain='127.0.0.1')
Find more info about domain parameter here

customizing flask error handlers for json returning api

I have a flask app which has two types of routes :
(1). routes for website, like /home, /user/, /news_feed
(2). json returning apis for mobile app, like /api/user, /api/weather etc.
I am using custom error pages for common errors like 404 and 500 via #app.errorhandler decorator provided by flask - for my website
#app_instance.errorhandler(404)
def page_note_found_error(err):
return render_template("err_404.html"), 404
#app_instance.errorhandler(500)
def internal_server_error(err):
db_instance.session.rollback()
return render_template("err_500.html"), 500
I do not want my mobile apis to return these error pages if say i get a 500 error via the mobile api.
Is there a way to bypass or customize the error handler for some routes(api) so that it returns json response instead of my custom error pages
You can dig into the details of the request to determine the URL path. If the path is prefixed by /api/ then you can treat it as an API request and return a JSON response.
from flask import request, jsonify
API_PATH_PREFIX = '/api/'
#app_instance.errorhandler(404)
def page_not_found_error(error):
if request.path.startswith(API_PATH_PREFIX):
return jsonify({'error': True, 'msg': 'API endpoint {!r} does not exist on this server'.format(request.path)}), error.code
return render_template('err_{}.html'.format(error.code)), error.code
This is not ideal. I thought that you might have been able to handle this with Flask blueprints, but the blueprint specific error handler does not work for 404, instead the app level handler is invoked.

How to enable CORS on Google App Engine Python Server?

I am see the following error on Javascript console:
VM31:1 XMLHttpRequest cannot load '<some-url>'. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '<my-url>' is therefore not allowed access.
How do I enable Cross-Origin Resource Sharing with Google App Engine (Python) to access ?
You'll have to use the Access-Control-Allow-Origin http header in your yaml configuration
handlers:
- url: /
...
http_headers:
Access-Control-Allow-Origin: http://my-url
Find more under CORS Support in the docs
For a python script you can add the following line near other self.response.header lines.
self.response.headers['Access-Control-Allow-Origin'] = '*'
This worked for me. The idea was taken from a php issue listed in the notes of another answer.
For those who are wondering how to basically allow all origins for the AppEngine instance in Springboot:
use the #CrossOrigin(origins = "*") annotation on the #RestController classes your project has
or use use the same annotation above for any of your specific resource methods that has one of the #GetMapping, #PostMapping, etc annotations.
No need to set any of the handlers in the app.yaml. Actually it didn't work when changing the app.yaml file as explained in the docs
...
...
...
#SpringBootApplication
#RestController
#CrossOrigin(origins = "*") // <--- here
public class SpringbootApplication {
...
...
#GetMapping("/")
#CrossOrigin(origins = "*"). // <--- or here
public String hello() {
.....
}
}
If you want to serve a script, you cannot use Jeffrey Godwyll's answer, unfortunately. The documentation, somewhat hidden in the second sentence of the http_headers states: "If you need to set HTTP headers in your script handlers, you should instead do that in your app's code."
Another possibility is to allow your app to handle pre-flight requests by "prematurely" returning the headers. GOTCHA: If you are building a POST endpoint, make it so that it returns the allow cross site origin request headers on everything BUT your desired request method. Sometimes there may be a pre-flight GET as well (for some odd reason):
from flask import Flask, request
HEADERS = {
"Access-Control-Allow-Origin": "*",
}
app = Flask(__name__)
#app.route("/", methods=["GET", "POST"])
def main():
if request.method != "POST":
return ("", 204, HEADERS)
return "After the POST"
If you are building an app for GET only, you can instead write if request.method == "OPTIONS":..., like in the Cloud Functions documentation

Facebook Realtime Updates Subscription Verification in Python

I've been trying to set up a subscription for my app with the Realtime Updates API but there have been some issues. For starters, this is the error I keep getting:
{"error":{"message":"(#2200) callback verification failed: Operation timed out after 6000 milliseconds with 0 bytes received","type":"OAuthException","code":2200}}
I've followed the documentation appropriately and configured a Flask endpoint on an Amazon EC2 instance that handles HTTP GET and POST. What happens is I hit and endpoint myself, manually, to invoke the subscription code.
curl -i -X GET http://public-ip-of-ec2:5000/subscribe
The above curl calls a script running in a flask application at the route of /subscribe on my ec2 instance. To make the POST with the required query string parameters including our access_token, object, fields, verify_token, and callback_url I'm using the python HTTP library requests.
VERIFY_TOKEN = 'my_verify_token'
#app.route('/subscribe')
def subscribe():
global VERIFY_TOKEN
FB_CLIENT_ID = 'my_app_id'
# access_token is sent as a query string parameter
APP_ACCESS_TOKEN = 'my_app_access_token'
# object, fields, callback_url, and verify_token are sent as urllib.urlencode([('param','val')])
CALLBACK_URL = 'http://my-public-ec2-ip:5000/'
payload_url = "https://graph.facebook.com/{0}/subscriptions".format(FB_CLIENT_ID)
payload = {"access_token": APP_ACCESS_TOKEN, "object": "user", "fields": "feed", "verify_token": VERIFY_TOKEN, "callback_url": CALLBACK_URL}
r = requests.post(payload_url, data=payload)
return r.text
#app.route('/', methods=['GET','POST'])
def handle_requests():
global VERIFY_TOKEN
if request.method == 'GET':
mode = request.args.get('hub.mode')
challenge = request.args.get('hub.challenge')
verification = request.args.get('hub.verify_token')
# if we have our verification token back echo the challenge back to facebook
if verification == VERIFY_TOKEN:
return challenge
elif request.method == 'POST':
# do some stuff with the updates
I'm confused as to why I'm getting
{"error":{"message":"(#2200) callback verification failed: Operation timed out after 6000 milliseconds with 0 bytes received","type":"OAuthException","code":2200}}
because when I fire up my flask application I can see a GET request from 173.252.110.113, which is a Facebook IP address. I have properly tested to make sure I'm echoing back the correct data by printing the challenge to my log for testing. So the code IS returning the challenge which facebook requires to verify subscription and at that point the subscription SHOULD be successful, but the aforementioned error is what I'm getting. Could it possibly just be a security issue I need to add a permission for in ec2 security groups or something??
Thanks in advance for the help!
ANSWER:
Facebook without forewarning sends multiple requests to the endpoint in question and with the Flask development server those requests have no way of being handled, therefore the timeout. I fired up a gunicorn server with a few workers to test that theory and it proved to be true because I now have a successful subscription verification. For anyone else having this issue with flask:
$ sudo pip install gunicorn
$ which gunicorn
/usr/local/bin/gunicorn
# fire up your endpoint with a few gunicorn workers to handle the load
# facebook tests our endpoint with (we will use 4 workers on port 5000)
# my_app is your_app_name.py without the .py part
$ /usr/local/bin/gunicorn -w 4 -b my-local-ipv4-ip:5000 my_app:app
"https://graph.facebook.com/{0}/subscriptions".format(FB_CLIENT_ID)
is for getting current subscriptions by GET method
to make new subscription you can try :
"https://graph.facebook.com/{0}/".format(FB_CLIENT_ID)
by POST method --- import : is do not include subscriptions at the end url

Categories

Resources