Capturing variable returned by API in Flask - python

I'm trying to capture a variable generated by Flask after visiting a certain URL.
I want to use that variable in further authorization in the API.
Due to the nature of Flask, every time the user visits the web app's URL, a function is called that returns a variable.
I want to capture that variable and use it further, without executing the function (the function should be executed only once).
Here's the code I have:
#app.route("/callback/")
def callback_code():
auth_code = request.args.get('code')
return auth_code
code = callback_code()
tracks_endpoint = "https://api.spotify.com/v1/me/tracks"
tracks_auth = {
"Authorization":f"Bearer {code}"
}
tracks = requests.get(tracks_endpoint, headers=tracks_auth)
The problem is caused by:
code = callback_code()
After visiting http://127.0.0.1:5000/auth, Flask displays the following error:
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
How can I capture auth_code and store it, without executing the function?
Thanks for all your help!

So first problem is that you're expecting the server to be running and get some callback, but the code that is not intended in a function is going to be immeadiately ran, externally from the Flask server lifecycle.
Best way I can think to handle that would be to have your callback redirect to another endpoint, and save off your auth token somewhere in between (such as a session cookie)
Or using a separate variable, for example
AUTH = {
'spotify': None
}
tracks_endpoint = "https://api.spotify.com/v1/me/tracks"
#app.route("/callback/")
def callback_code():
auth_code = request.args.get('code')
AUTH['spotify'] = auth_code
return redirect(url_for('tracks'))
#app.route("/tracks")
def tracks():
if not AUTH['spotify']:
raise('Auth code not loaded. Login first')
tracks_auth = {
"Authorization":f"Bearer AUTH['spotify']"
}
tracks = requests.get(tracks_endpoint, headers=tracks_auth)

Related

Django not responding back to react

I am requesting React frontend to a view in Django (rest framework).
The view in Django will request another server (Google-Ads API), the request works fine and I get the data perfectly. The post-processing of that data also works fine but Django gets stuck after that means It will not run return Response(data).
Django view sample code
def get(self,request):
data = google_api_request(credentials)
data = preprocess(data)
print("end") # this will be executed
return Response(data,status) # this will not be executed
google_api_request code
def google_api_request(credentials):
data = list()
# if I remove this for loop it works
for x in sample_list:
d = google_ads_request(x)
data.append(d)
return data
React sample code:
axiosInstance.get("url").then(
(result) => {
if (result.status == 200) {
console.log("success")
}
}).catch((error) => {
throw error;
})
When I paste the "URL" directly in my browser it works and returns the Response, but only when I run it from react it does not respond.
When I commented google_api_request() code and load the data from a CSV file in the local machine for debugging It returns the Response.
Note: There is no errors message in the console or Django and my CORS setting is correct as other function works fine.
Edit: I found that issue was making requests inside a for a loop.
When I ran only one request without for loop while debugging it worked. so now the question is how to make multiple requests inside Django using for loop. One important thing to note is this problem is only caused when I call the API using React, things work fine when I paste the URL directly in my browser.

Dash Cognito Authentication Logout

I have managed to use this package here to authorize login for my users. However, I am having trouble designing a logout system.
So far, what I’ve done is code up the following method in the class defined here. It essentially calls the revoke endpoint documented here. The revoke endpoint returns a 200 response.
def logout_request(self):
if self.is_authorized():
client_id = self.cognito_bp.client_id
client_secret = self.cognito_bp.client_secret
token = session.get("cognito_oauth_token")["refresh_token"]
resp = cognito.post(
"/oauth2/revoke",
params={"token": token},
headers={"Content-Type": "application/x-www-form-urlencoded"},
auth=HTTPBasicAuth(client_id, client_secret)
)
assert resp.ok, resp.text
del self.cognito_bp.token
session.clear()
return render_template("logout.html")
else:
return self.login_request()
Then, in the application.py folder, I have a Flask route defined:
#application.route("/logout", methods=["GET", "POST"])
def logout_user():
return auth.logout_request()
However, for some reason, the system still keeps me logged in. I feel like I need to delete a cookie server side. Any ideas on how to accomplish this? Jumping ahead, how would I be able to design a multi page concept given that I have written an explicit server route for the “logout” endpoint?

google cloud function Compute engine python api not shutting down instance on call to stop as expected

I am using a google cloud function with python 3.9
My intention is to trigger this cloud function from an http trigger to stop a compute engine instance. The code for my cloud function is as below (entry point stop_server )
# python 3.9 code for cloud function body
from googleapiclient import discovery
def stop_server (request):
"""
Responds to HTTP Get request stops server specified inget param instancename=<name>
Returns status
"""
service = discovery.build('compute', 'v1')
# Project ID for this request.
project = 'my-gcp-project'
# The name of the zone for this request.
zone = 'us-east1-b'
# determine instance to stop from http params
if request.args and 'instancename' in request.args:
instance = request.args.get('instancename')
status = service.instances().stop(project=project, zone=zone, instance=instance)
return f'instance: {instance} - stopped --> status = {status}'
else: # no instance specified
return f'specify instance in get param ?instancename="name"'
The "requirements.txt" is as follows
# Function dependencies
# package>=version
google-api-core
google-api-python-client
This code deploys fine in gcp and I can generate a "trigger url" as follows
https://us-east1-myproject.cloudfunctions.net/stop-specified-vm?instancename=name_of_instance
When I run the code through trigger URL - the code executes fine and returns the status message in http response as follows
instance: my-gcp-instance - stopped --> status =
However the specified instance does not stop as expected
So in order to debug I need to find the documentation for the stop instances.stop api - I examined type of "service object" in runtime by printing its type as follows
enter code here
service = discovery.build('compute', 'v1')
stype=(type(service))
print(f'type of service = {stype}')
that shows in logs that service object is of type "googleapiclient.discovery.Resource'
I suppose this is a base class object that holds an object of appropriate specific type based on what service argument you passed to build.
So in my case case since the service is compute engine - what is this bound to when I call
service = discovery.build('compute', 'v1')
Where can I find documentation for that api
Any other debugging tips ?
See response from John Hanley below - As he rightly pointed out I had missed calling execute() on the (request) object returned by call to service.instances().stop
So basically - I needed to understand the paradigm used here. The service.instances().stop returns an object that is a client for requested restful service (stop in this case) . And then I have to call "execute" to actually invoke that service :-)
posting full working code below again
# python 3.9 code for cloud function body
from googleapiclient import discovery
def stop_server (request):
"""
Responds to HTTP Get request stops server specified inget param instancename=<name>
Returns status
"""
service = discovery.build('compute', 'v1')
# Project ID for this request.
project = 'my-gcp-project'
# The name of the zone for this request.
zone = 'us-east1-b'
# determine instance to stop from http params
if request.args and 'instancename' in request.args:
instance = request.args.get('instancename')
request= service.instances().stop(project=project, zone=zone, instance=instance)
response = request.execute() # returning full dictionary object for caller to examine
rstr=str(response)
return f'instance: {instance} - stop requested --> response = {rstr}'
else: # no instance specified
return f'specify instance in get param ?instancename="name"'
The API stop() returns an Operation resource and not a string. You must poll the operation resource to get the status of the stop operation. This includes success and failure.
Note: Your code is not calling execute() after setting up the request.
request = service.instances().stop(project=project, zone=zone, instance=instance)
response = request.execute()
Since you are writing new code, I recommend that you do not use your current Python library.
Client libraries explained
Instead switch to the current library that is still being developed.
Python Client for Compute Engine
Example for starting and stopping an instance

Failed to use auth code flow with python MSAL

I simply can't get acquire_token_by_auth_code_flow() from the MSAL package to work outside a flask app using the basic example giving in the MSAL documentation.
I think the problem comes from using the wrong authentication response which must be a "dict of the query string received from auth server" according to the documentation. In a flask app, I can simply use request.args which I'm not quite sure how to use outside of flask.
I've already tried using requests.request as well as urlsplit. The device flow is working fine as well as using the MSAL package in Java and connecting via R. So the app seems to be set up correctly.
The basic example code from the MSAL app below produces the error:
state mismatch: XXXXXXXXXXXX vs None
(so auth_response is wrong).
Any thoughts?
import requests
import msal
CLIENT_ID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" # Application (client) ID of app registration
CLIENT_SECRET = "XX-XXXXXXXX-XXXXXXXX.XX~XXXXX~XXXX" # Placeholder - for use ONLY during testing.
AUTHORITY = "https://login.microsoftonline.com/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX"
REDIRECT_PATH = "/getAToken" # Used for forming an absolute URL to your redirect URI.
# The absolute URL must match the redirect URI you set
# in the app's registration in the Azure portal.
ENDPOINT = 'https://graph.microsoft.com/v1.0/me'
SCOPE = ["https://graph.microsoft.com/.default"]
# Cache
cache = msal.SerializableTokenCache()
# Build msal app
app = msal.ConfidentialClientApplication(
CLIENT_ID, authority=AUTHORITY,
client_credential=CLIENT_SECRET, token_cache=cache)
# Initiate auth code flow
session = requests.Session()
session.flow = app.initiate_auth_code_flow(scopes=SCOPE, redirect_uri=REDIRECT_PATH)
# Aquire token
result = app.acquire_token_by_auth_code_flow(auth_code_flow=session.flow, auth_response = dict(parse.parse_qsl(parse.urlsplit(REDIRECT_PATH).query)))
The equivalent code for the last bit from the flask app looks like this with REDIRECT_PATH = "/getAToken":
#app.route(app_config.REDIRECT_PATH) # Its absolute URL must match your app's redirect_uri set in AAD
def authorized():
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}), request.args)
return redirect(url_for("index"))
Getting a token requires few requests according to documentation. To make it possible you need to create flow and store it inside session before navigating to microsoft login page.
session["flow"] = _build_auth_code_flow(authority=app_config.AUTHORITY, scopes=app_config.SCOPE)
After navigation back to your application you should use this flow object as you did in your example
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
session.get("flow", {}), request.args)
Make sure that you didn't create it twice. In this case error will be similar, but state mismatch: XXXXXXXXXXXX vs XXXXXXXXXXXX. It may happened if you route called twice.
auth_response must be a dictionary built from the current HTTP request query params.
If this is a desktop application you must switch to PublicClientApplication. You can find a sample here.

In flask, how to route a link generated by OAuth 2?

I'm building a web app with flask. I used
#app.route('/')
def home():
.....
return render_template('home.html',url=url)
to generate the index page. There is a link(the second parameter, url) on the index page which leads to weibo (the chinese "twitter") for OAuth 2. After clicking the link and inputing the weibo username and password, I'm bounced back to a link like www.myflaskwebappaddress.com/?code=valueofcode.
My question is how can I catch the value of code and do something with the code in another page.
I tried the following:
#app.route(url_for('/', code=<tokencode>))
def show_data(tokencode):
.....want to use weibo api here using the tokencode.
But it doesn't work.
Everything after the ? is a query parameter; Flask parses those and provides them as part of the request.args object:
#app.route(url_for('/')
def home():
tokencode = request.args.get('code')
if tokencode is not None:
# there was a `?code=...` query parameter.
Also see The Request Object in the Flask quickstart.
Routing is only dependent on the path and the method of the request, the parameters don't play in that, so you cannot create a separate route for requests with that parameter.
You could, instead, use a before_request handler to look out for the code:
#app.before_request
def check_token():
tokencode = request.args.get('code')
if tokencode is not None:
# there was a `?code=...` query parameter.
If this function returns None, the normal routing takes place. But you can also return valid response; in that case that is the response returned to the end user and normal routing is skipped entirely.
This also means you can use any route on your site with a ?code=<tokencode> query parameter and have the before_request hook function handle it, not just your homepage.

Categories