I am trying to write a flask application to integrate with quickbooks online api, and am having trouble with authentication. Following their guide for python I have put together the code below. Currently my problem is that I can't figure out how to pass the AuthClient object between requests. I assumed I could just use flask sessions but the AuthClient object when recalled from sessions is incomplete, it only contains the shell and none of the populated data as far as I can tell.
Do I need to try to subclass the AuthClient and rewrite the methods used for pickling? If so any hints on how to get started there would be very helpful.
Also, if anyone has any experience integrating with quickbooks tips on that would be helpful.
from flask import Flask, session, redirect, request
from flask_session import Session
from intuitlib.client import AuthClient
from intuitlib.enums import Scopes
import os
import requests
SECRET_KEY = 'something'
DEBUG = True
REDIS_URL = 'redis://10.74.10.235:6379/0'
app = Flask(__name__)
app.debug = DEBUG
app.secret_key = SECRET_KEY
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SECRET_KEY'] = SECRET_KEY
app.config['REDIS_URL'] = REDIS_URL
sess = Session()
sess.init_app(app)
#app.route('/auth')
def auth():
client_id = os.environ['CLIENT_ID']
client_secret = os.environ['CLIENT_SECRET']
redirect_uri = os.environ['REDIRECT_URI']
environment = os.environ['ENVIRONMENT']
auth_client = AuthClient(client_id, client_secret, redirect_uri, environment)
url = auth_client.get_authorization_url([Scopes.ACCOUNTING])
session['auth_client'] = auth_client
return redirect(url)
#app.route('/callback')
def callback():
auth_client = session['auth_client']
state = str(request.args.get('state'))
auth_code = str(request.args.get('auth_code'))
realm_id = str(request.args.get('realm_id'))
auth_client.get_bearer_token(auth_code, realm_id=realm_id)
return 'boobs'
if __name__ == '__main__':
app.run()
Welp, I was trying to save the wrong info. Found an example app that actually used the intuitlib module here. https://github.com/IntuitDeveloper/SampleOAuth2_UsingPythonClient/blob/master/app/views.py
Turns out instead of passing the AuthCLient around you recreate it each time giving it extra arguments as you acquire them such as:
auth_client = AuthClient(
settings.client_id,
settings.client_secret,
settings.redirect_uri,
settings.environment,
access_token=session.get('access_token', None),
refresh_token=session.get('refresh_token', None),
id_token=session.get('id_token', None),
)
Saving just the tokens in the session.
Still having some troubles with getting the bearer token and a 400 error but this issue is resolved.
Related
I'm new to python and Flask, and I have a project from the production ENV which I'm trying to run on my local.
I was able to install all the packages and bring them up on http://127.0.0.1:5000, but the problem is that is the only page that actually works on my local. and when I try to do Authorization or even simple post, it does not do anything on my local ( I put some print on the other files and none of them get fire) so I assume they keep going to production as it does have some APIs as well.
Here is the main page (application.py) which is working on my local.
import os
import jwt
import logging
from datetime import datetime, timedelta
from http import HTTPStatus
from pydantic import BaseModel
from passlib.context import CryptContext
from flask import Flask, request, jsonify
from flask_restplus import Api, Resource, fields
from werkzeug.middleware.proxy_fix import ProxyFix
from applicationinsights.flask.ext import AppInsights
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
api = Api(app, doc='/')
ns = api.namespace(name='Room Parsing', path='/')
swaggerTokenParser = api.parser()
swaggerTokenParser.add_argument('username', location='form')
swaggerTokenParser.add_argument('password', location='form')
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
rtp = RoomTitleParser(room_prototype_XX_path)
ALGORITHM = "HS256"
app.config["SECRET_KEY"] = os.getenv('SECRET_KEY')
app.config["APPINSIGHTS_INSTRUMENTATIONKEY"] = os.getenv('APPINSIGHTS_INSTRUMENTATIONKEY')
appinsights = AppInsights(app)
app.logger.setLevel(level=logging.INFO)
logger = app.logger
#ns.route("/api/room/parser")
class RoomParser(Resource):
#api.expect(swaggerRoom)
#api.doc(description='Process a JSON object with a room description and a unique identifier in order to run it through the parser. This will result in a list of keywords which will be extracted from the description. The result will be returned in a JSON format')
def post(self):
try:
room_desc = "deluxe suite queen ocean view"
room_id = "ID123"
print('11111111111')
if not room_desc or not room_id:
return make_json_error_message("Please send a a room with description and id",HTTPStatus.BAD_REQUEST)
room_dict = dict(Room(description=room_desc, id=room_id))
print(parsed)
parsed = rtp.parse_title(room_dict)
print(parsed)
return jsonify(parsed['room'])
except Exception as e:
logger.error("Error parsing a room: " + repr(e))
return make_json_error_message("We have encountered an error. Please try again later",HTTPStatus.BAD_REQUEST)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
As you can see I have some print statements and all of them are working on my local consol. But when I track down the code for Example this line,
parsed = rtp.parse_title(room_dict)
and put some print command inside the parse_title() function which is located in another file, I do NOT see any output in the console as well as webpage!
Why? I have no idea!!! LOL and that is why I'm here.
I believe it might be related to the #ns.route("/api/room/parser") that I have on top of the class, but not sure.
Can you guys please drop some knowledge here so I can learn and get this code to work on my local completely?
Thanks for your help!
With what you've provided, there doesn't appear to be any reference to the production environment.
The only thing that sticks out to me is
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
The Werkzeug Documentation states that this middleware can set REMOTE_ADDR, HTTP_HOST from X-Forwarded headers. You might try removing that for a bit and see if that helps. There might be some reference to production in that proxy. I don't know enough about that middleware to know for sure however.
It might be helpful to know of any other configuration information or environment you have setup.
It tuned out to be related to my conda env,
So I uninstalled the Anaconda and then installed the plain python and installed pyCharm as well and setup a new env in pycharm. Then it worked like a charm!
Thanks
I've been making api with flask on Google App Engine and When I send request to this app from browser after deploy, I got 502 error. I'm sure this error is caused by credential of GCP by "gcloud app logs tail -s test" but The path of credential Json file and file name seems OK . I have googled and I tried every articles I have found there but could not solve.
I have already done export GOOGLE_APPLICATION_CREDENTIALS="/home/user/secret_key/bq.json"
Could anyone tell me the solution??
If there is lack of any info , please let me know . Thank you .
besides, my api function is getting luid parameter over http request and run SQL with that luid and if the row of the luid has data in cv_date column in BigQuery, it returns True to client.
【The result of "gcloud app logs tail -s test"】
File "/env/lib/python3.7/site-packages/google/auth/_default.py", line 97, in load_credentials_from_file "File {} was not found.".format(filename) google.auth.exceptions.DefaultCredentialsError: File /home/user/secret_key/bq.json was not found.
【/home/user/api_dev/main.py】
from flask import Flask,request
from google.cloud import bigquery
import os
credentials_json = '/home/user/secret_key/bq.json'
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials_json
client = bigquery.Client()
app = Flask(__name__)
#app.route('/')
def get_request():
request_luid = request.args.get('luid') or ''
query = """
SELECT EXISTS(SELECT cv_date FROM `test-266110.conversion_log.conversion_log_202008*` t WHERE request_luid = p.luid)
"""
query_res = client.query(query)
return query_res
if __name__ == "__main__":
app.run()
【Remove the codes for BigQuery except import library and variables】
*This code works well and returns luid you input on url parameter
from flask import Flask, request
from google.cloud import bigquery
import os
credentials_json = '/home/user/secret_key/bq.json'
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials_json
app = Flask(__name__)
#app.route('/')
def get_request():
request_luid = request.args.get('luid') or ''
return request_luid
if __name__ == "__main__":
app.run()
I'd recommend reading through the auth docs.
https://cloud.google.com/docs/authentication/production talks about service account interactions in a bit more detail. You likely don't need to pass in your credentials in the live app. You can simply set the GOOGLE_APPLICATION_CREDENTIALS when you're running locally to use the credentials, but you don't need to set it in production.
The issue is that the path you've specified (/home/user/secret_key/bq.json) is only valid for your development environment, and either not included in your production deployment at all or the absolute path to the file in the deployed app is different.
I'm having difficulty with my Cloud Function in GCP that is simply supposed to return the raw XML stored in a GCS Bucket when invoked with a basic GET request. It works fine without any type of authentication, however since I added the Flask-HTTPAuth package to the mix in order to add some measure of security before exposing the endpoint, the application deploys fine, but crashes without any sort of hint as to why as soon as it is invoked. The error in SD Logging is as follows:
severity: "DEBUG"
textPayload: "Function execution took 1847 ms, finished with status: 'crash'"
timestamp: "2020-07-15T17:22:15.158036700Z"
The function in question (anonymized):
from flask import Flask, request, jsonify, make_response, abort
from flask_httpauth import HTTPBasicAuth
from google.cloud import storage, secretmanager
import google.cloud.logging
import logging
import sys
app = Flask(__name__)
auth = HTTPBasicAuth()
PROJECT_ID = 'example_project'
GCS_BUCKET = 'example_bucket'
users = ['example_user']
# Instantiate logger
client = google.cloud.logging.Client()
client.get_default_handler()
client.setup_logging()
#auth.verify_password
def verify_password(username, password):
# Instantiate the Secret Manager client.
sm_client = secretmanager.SecretManagerServiceClient()
# Load secrets
name = sm_client.secret_version_path(PROJECT_ID, 'example_secrets_ref', 1)
secrets_pass = sm_client.access_secret_version(name)
passwords = [secrets_pass]
if username in users and password in passwords:
logging.info('auth success')
return username
logging.info('auth fail')
return abort(403)
#app.route('/')
#auth.login_required
def latest_xml():
try:
request_json = request.get_json()#silent=True)
storage_client = storage.Client(project=PROJECT_ID)
bucket = storage_client.get_bucket(GCS_BUCKET)
blob = bucket.get_blob('latest_pull.xml')
latest_xml = blob.download_as_string()
logging.info('Loaded blob from GCS')
return(latest_xml)
except exception as e:
logging.error(str(e))
logging.error("Failed to load blob from GCS")
sys.exit(1)
if __name__ == '__main__':
app.run()
I've tried setting the entrypoint as both the main function as well as the auth function to no avail. My question is: is it possible to even use basic auth in a GCP Cloud Function or am I barking up the wrong tree here?
Your function doesn't enforce the standard signature for http function
def latest_xml(request):
...
Here you use a flask web server, which is not need, and not used by Cloud Functions. However, I recommend you to have a look to Cloud Run, and to add a simple and generic Dockerfile to deploy . You can deploy your "function" as-is in a container and to have the same behavior as Cloud Functions.
EDIT
When you use flask, the request object is global for each request. You use it like this:
request_json = request.get_json()#silent=True)
With Cloud Functions, this object is caught by the Cloud Functions platform and passed in parameter to your function.
In the request object, you have the body of the request, useless in GET for example. But also, all the request context: headers, user agent, source ip,...
I am new to API, and get a tasks of creating POST API. I have created a code somehow.
I want to add data to the hello.txt through post API, So how will I do it?
Here is my code:
import flask
from flask import request, jsonify
app = flask.Flask(__name__)
app.config["DEBUG"] = True
#app.route('/api/v1/resources/messages', methods = ['POST'])
def api_message():
if request.headers['Content-Type'] == 'text/plain':
return "Text Message: " + request.data
elif request.headers['Content-Type'] == 'application/octet-stream':
return "Binary message written!"
elif request.headers['Content-Type'] == 'application/json':
f = open('F:\Asif_Ahmed\Projects\api\hello.txt',"w")
f.write(request.data)
f.close()
return "JSON Message: " + json.dumps(request.json)
else:
return "415 Unsupported Media Type ;)"
app.run()
from flask import Flask, jsonify, render_template, request #import flask library
from flask_basicauth import BasicAuth # import flask library for create basic authentication if needed
from flask_cors import CORS # import flask library Cross-Origin Resource Sharing that is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin
app = Flask(__name__)
CORS(app) #set-up cors for my app
#if you want use basic authentication you need set-up username and password
app.config['BASIC_AUTH_USERNAME'] = 'admin'
app.config['BASIC_AUTH_PASSWORD'] = 'password'
basic_auth = BasicAuth(app)#set-up username and password for my app but in this case I'm not specifying yet in which API use them
#app.route('/api/v1/resources/add_messages', methods=['POST'])#create my POST api
#basic_auth.required# set-up basic authentication for this API, comment out if not needed
def update_credential ():
json_credential=request.get_json()#get the JSON sent via API
print (json_credential["message"])#get the node "message" of my JSON
###########
#code to write in your file, you need write the json_credential["message"]
###########
return ("ok")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1024, threaded=True)#start my flask app with local_host IP and specific port, if you don't specify the port it will run in the default port
In this case the JSON Input should be:
{"message":"your text"}
Please let me know if something is not clear, I even try this code on my local and the JSON is passed without problems.....
So you need run your python script and see that the API is running, if you had no JSON to send and was just a simple API that give back information you should have used even Chrome but in this case that you need send some JSON data I would advice you to use Postman.
See screenshot example:
I am looking to implement a SAML 2.0 based service provider in Python.
My web apps are currently all Flask applications. I plan to make a Flask blueprint/decorator that allows me to drop single sign-on capabilities into preexisting applications.
I have looked into python-saml extensively and unfortunately there are dependency issues that are not worth resolving, as I have too many preexisting servers/apps whos environments won't be compatible.
PySAML2 looks like it could work, however there is little documentation, and what documentation is available I have trouble comprehending. There are no examples of PySAML2 used in a Flask app.
The Identity Provider I have is Okta. I have Okta set up so that after I login at Okta, I am redirected to my app.
Can anyone offer any advice on using PySAML2, or perhaps advice on how to best authenticate a user using SAML 2.0 who is visiting my application?
Update: A detailed explanation on using PySAML2 with Okta is now on developer.okta.com.
Below is some sample code for implementing a SAML SP in Python/Flask. This sample code demonstrates several things:
Supporting multiple IdPs.
Using Flask-Login for user management.
Using the "SSO URL" as the audience restriction (to simplify configuration on the IdP).
Just in time provisioning of users ("SAML JIT")
Passing additional user information in Attribute Statements.
What is not demonstrated is doing SP initiated authentication requests - I'll followup with that later.
At some point, I hope to create a wrapper around pysaml2 that has opinionated defaults.
Lastly, like python-saml, the pysaml2 library makes use of the xmlsec1 binary. This might also cause dependency issues in your server environments. If that's the case, you'll want to look into replacing xmlsec1 with the signxml library.
Everything in the sample below should work with the following setup:
$ virtualenv venv
$ source venv/bin/activate
$ pip install flask flask-login pysaml2
Finally, you'll need to do to things on the Okta side for this to work.
First: In the General tab of your Okta application configuration, configure the application to send the "FirstName" and "LastName" Attribute Statements.
Second: In the Single Sign On tab of your Okta application configuration, take of the url and put them in a file named example.okta.com.metadata. You can do this with a command like the one below.
$ curl [the metadata url for your Okta application] > example.okta.com.metadata
Here is what you'll need for your Python/Flask application to handle IdP initiated SAML requests:
# -*- coding: utf-8 -*-
import base64
import logging
import os
import urllib
import uuid
import zlib
from flask import Flask
from flask import redirect
from flask import request
from flask import url_for
from flask.ext.login import LoginManager
from flask.ext.login import UserMixin
from flask.ext.login import current_user
from flask.ext.login import login_required
from flask.ext.login import login_user
from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import entity
from saml2.client import Saml2Client
from saml2.config import Config as Saml2Config
# PER APPLICATION configuration settings.
# Each SAML service that you support will have different values here.
idp_settings = {
u'example.okta.com': {
u"metadata": {
"local": [u'./example.okta.com.metadata']
}
},
}
app = Flask(__name__)
app.secret_key = str(uuid.uuid4()) # Replace with your secret key
login_manager = LoginManager()
login_manager.setup_app(app)
logging.basicConfig(level=logging.DEBUG)
# Replace this with your own user store
user_store = {}
class User(UserMixin):
def __init__(self, user_id):
user = {}
self.id = None
self.first_name = None
self.last_name = None
try:
user = user_store[user_id]
self.id = unicode(user_id)
self.first_name = user['first_name']
self.last_name = user['last_name']
except:
pass
#login_manager.user_loader
def load_user(user_id):
return User(user_id)
#app.route("/")
def main_page():
return "Hello"
#app.route("/saml/sso/<idp_name>", methods=['POST'])
def idp_initiated(idp_name):
settings = idp_settings[idp_name]
settings['service'] = {
'sp': {
'endpoints': {
'assertion_consumer_service': [
(request.url, BINDING_HTTP_REDIRECT),
(request.url, BINDING_HTTP_POST)
],
},
# Don't verify that the incoming requests originate from us via
# the built-in cache for authn request ids in pysaml2
'allow_unsolicited': True,
'authn_requests_signed': False,
'logout_requests_signed': True,
'want_assertions_signed': True,
'want_response_signed': False,
},
}
spConfig = Saml2Config()
spConfig.load(settings)
spConfig.allow_unknown_attributes = True
cli = Saml2Client(config=spConfig)
try:
authn_response = cli.parse_authn_request_response(
request.form['SAMLResponse'],
entity.BINDING_HTTP_POST)
authn_response.get_identity()
user_info = authn_response.get_subject()
username = user_info.text
valid = True
except Exception as e:
logging.error(e)
valid = False
return str(e), 401
# "JIT provisioning"
if username not in user_store:
user_store[username] = {
'first_name': authn_response.ava['FirstName'][0],
'last_name': authn_response.ava['LastName'][0],
}
user = User(username)
login_user(user)
# TODO: If it exists, redirect to request.form['RelayState']
return redirect(url_for('user'))
#app.route("/user")
#login_required
def user():
msg = u"Hello {user.first_name} {user.last_name}".format(user=current_user)
return msg
if __name__ == "__main__":
port = int(os.environ.get('PORT', 5000))
if port == 5000:
app.debug = True
app.run(host='0.0.0.0', port=port)