I'm facing an issue I can't seems to fix, despite having tried everything I could.
I'm using Flask + Flask_oauthlib to connect to SalesForce. Here's my code :
from flask import Flask, url_for
from flask_oauthlib.client import OAuth
app = Flask(__name__)
oauth = OAuth(app)
salesforce = oauth.remote_app('salesforce',
consumer_key='my_consumer_key',
# grant_type='authorization_code',
consumer_secret='my_consumer_secret',
request_token_url='https://login.salesforce.com/services/oauth2/token',
access_token_url='https://login.salesforce.com/services/oauth2/token',
authorize_url='https://login.salesforce.com/services/oauth2/authorize')
#app.route('/')
def home():
url = url_for('oauth', _external=True)
salesforce.authorize(callback=url)
return 'ok'
#app.route('/oauth')
def oauth():
resp = salesforce.authorized_response()
print resp
return 'ok'
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
When I run this and goes to /, I get the following exception :
OAuthException: Failed to generate request token
I added an output from the line in question where the exception is thrown ("/usr/lib64/python2.7/site-packages/flask_oauthlib/client.py", line 580), and this is the response from Salesforce :
{u'error_description': u'grant type not supported', u'error': u'unsupported_grant_type'}
So apparently I need to set the grant_type to something allowed.
I tried to add the grant_type as you can see in the commented section, but when I restart the code, it stops with the following error :
TypeError: init() got an unexpected keyword argument 'grant_type'
Yay ...
I took a look, and applied the answers from the following questions, without any luck :
https://salesforce.stackexchange.com/questions/34928/oauth2-token-request-using-json-fails-grant-type-not-supported
Salesforce returning "unsupported_grant_type"
Salesforce Authentication Failing
Does anyone has an idea about why I have this issue ?
Ok I've found the solution, it's really vicious !
salesforce = oauth.remote_app('salesforce',
consumer_key='my_consumer_key',
consumer_secret='my_consumer_secret',
access_token_url='https://login.salesforce.com/services/oauth2/token',
authorize_url='https://login.salesforce.com/services/oauth2/authorize')
Don't see the difference ? I had to remove the request_token_url, that's all !
Now it works!
Related
is there any way to convert my current structure to something close to what I expected? This will help to reduce alot of duplicated code as there might be 401, 402, 403,...
my current flask error handling's structure
#error.app_errorhandler(400)
def error_400(error):
message = error.description
logger.info('post request fail : {}'.format(message))
return make_response(jsonify({}), 400)
#error.app_errorhandler(429)
def error_429(error):
message = error.description
logger.info('post request fail : {}'.format(message))
return make_response(jsonify({}), 429)
my expected flask error handling's structure (not supported)
#error.app_errorhandler(400,429)
def error_4xx(error):
message = error.description
logger.info('post request fail : {}'.format(message))
return make_response(jsonify({}), error.status_code)
if i understand you well, you want some thing generic to deal with any error exception, if so try the code below:
from flask import Blueprint, jsonify
from werkzeug.exceptions import HTTPException
error = Blueprint('errors', __name__)
#error.app_errorhandler(HTTPException)
def handle_exception(e):
return jsonify(code=e.code,
name=e.name,
description=e.description), e.code
it produces a json response and you can then adapt it to your need
I am working on a simple service with my show_greeting endpoint handling Get request while set_greeting is my Post.
The purpose of this app is that when "header_message: {header parameter}" is sent to set_greeting, {header parameter} will be returned in the header for responses to show_greeting and to reset {header parameter}, "clear" would reset header_message and header.
I have tried using global variables but encountered an error with shadowing from outside the scope and am not sure which approach to take for this. For now, I would like to learn how to return {header parameter} from my /show_greeting endpoint.
Edit: The /show_greeting endpoint returns holiday_message from the request. The header that I would like to send in addition to holiday_message is "header_message".
My code is as follows:
from flask import Flask, request, make_response, Response
app = Flask(__name__)
#app.route('/show_greeting', methods=['GET'])
def show_greeting():
received = request.args
(I do not know how to set header here from header_message in set_greeting)
return received['holiday_message']
#app.route('/set_greeting', methods=['POST'])
def set_greeting():
posted = request.args
if 'header_message' in posted:
(I attempted save_message = posted['header_message'] here but this approach failed)
return "Header Message Set"
else:
return "Please Send A Header Message"
if __name__ == '__main__':
app.run()
My recommendation is to use the session object. It stores the data in a cookie, which is sent with every request.
If a cookie is not desired, there are other options for saving sessions. For this, however, an extension will be necessary.
Saving with global variables should also work, but is not recommended.
A file or a database can also be used if the data is to be saved across multiple requests from many users.
The data of the post body can be accessed via request.form, while the url parameters of a get request can be queried via request.args.
from flask import Flask
from flask import request, session
app = Flask(__name__)
app.secret_key = b'your secret here'
#app.route('/show_greeting', methods=['GET'])
def show_greeting():
received = request.args
# get the saved message or an empty string if no message is saved
header_message = session.get('header_message', '')
return f"{received['holiday_message']} - {header_message}"
#app.route('/set_greeting', methods=['POST'])
def set_greeting():
posted = request.form
if 'header_message' in posted:
# store the message
session['header_message'] = posted['header_message']
return "Header Message Set"
else:
# clear the message
session.pop('header_message', None)
return "Please Send A Header Message"
Much success in your further steps.
If I understood your problem, you can work with "g" the flask global object.
Check this code, I expect it will fix your issue.
from flask import g # Added
from flask import Flask, request, make_response, Response
app = Flask(__name__)
#app.route('/show_greeting', methods=['GET'])
def show_greeting():
received = request.args
return g.saved_message # Modified
#app.route('/set_greeting', methods=['POST'])
def set_greeting():
posted = request.args
if 'message' in posted:
g.saved_message = posted['request'] # Added
return "Message Set"
else:
return "Please Send A Greeting Message"
if __name__ == '__main__':
app.run()
I tried to use flask_oauthlib to access my twitter api, but all I get is the error : Failed to generate request token. Here is the code.
from flask_oauthlib.client import OAuth
from flask import Flask, url_for, request, jsonify
app = Flask(__name__)
oauth = OAuth()
twitter = oauth.remote_app(
'twitter',
base_url='https://api.twitter.com/1/',
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authorize',
consumer_key='dOJjyxB6gxXWTjdtfPUZcZPjl',
consumer_secret='im not telling you',
)
#app.route('/login')
def login():
return twitter.authorize(callback=url_for('authorized',
next=request.args.get('next') or request.referrer or None))
#app.route('/authorized')
#twitter.authorized_handler
def authorized(resp):
if resp is None:
return 'Access denied: error=%s' % (
request.args['error']
)
if 'oauth_token' in resp:
# session['example_oauth'] = resp
print(resp)
return jsonify(resp)
return str(resp)
if __name__ == '__main__':
app.run(port=8000, debug=True)
This didn't work while using http://term.ie/oauth/example/client.php, I managed to get a request token.
I inspired myself with https://github.com/lepture/example-oauth1-server/blob/master/client.py and http://flask-oauthlib.readthedocs.io/en/latest/client.html
EDIT
Weird fact : I tried the code here : https://github.com/lepture/flask-oauthlib/blob/master/example/twitter.py
I didn't changed the key and secret and it worked.
So I tried to change them for my own credentials, and it stopped working. I really can't understand...
Ok I found the problem. It appears that the callback URL is mandatory when using flask-oauthlib. So I added a fake one since i'm still on localhost, and it solved this problem.
In case anyone found this issue. I'm the author of Flask-OAuthlib. I suggest that you use Authlib instead, browser the source code at https://github.com/lepture/authlib. There are many built-in social connections in https://github.com/authlib/loginpass.
I've been playing around with Python/Flask on Cloud9 ide. Pretty fun so far. But when I try to add a http post to my test project, Flask returns either a 403 or a 500. From what I can tell, when I attach data or send the POST method, the 'request' object is None. It doesn't make sense though. This is pretty straight forward and should work as far as I can tell. Here's the python:
from flask import Flask, jsonify, abort, request
#app.route('/test', methods = ['POST'])
def post():
print ('started')
print request
if request.method == 'POST':
something = request.get_json()
print something
Flask is running correctly. I can hit a GET url, returning data just fine. I get an error when I land on 'print request' because request is None.
Thanks,
You have two problems here:
You're getting a 500 error
"something" is always None
The first problem is because you're not returning anything from your route function.
127.0.0.1 - - [15/Dec/2014 15:08:59] "POST /test HTTP/1.1" 500 -
Traceback (most recent call last):
...snip...
ValueError: View function did not return a response
You can remedy this by adding a return statement at the end of the function. Don't forget it needs to be a string.
#app.route('/hi', methods = ['POST'])
def post():
return "Hello, World!"
The second problem isn't what it seems. I suspect that the object isn't None, but the function that returns the string representation returns None, so that's what gets printed. Try print type(request) to see this in action.
What I think you want access to is the form field. Here is a complete example:
from flask import Flask, request
app = Flask(__name__)
#app.route('/test', methods = ['POST'])
def post():
print type(request)
if request.method == 'POST':
print request.form
return str(request.form)
app.run(debug=True)
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
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 :)
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