I'm working with the imgur api. I'm using Flask without a db.
I'm trying to access a single dedicated account, so there won't be more than one user account in this app (thus i just have to manually authorize it once and do the rest with the tokens the service gives to me, no need for a database).
Once authorized the app on the account, Imgur gives me two tokens: an access_token that expires after an hour and a refresh_token that doesn't expire and can be used to request a new access_token.
I can't think any method different that this to send correct requests to the server using this two tokens and refreshing the access one only when needed:
from flask import Flask
from foobar import foo, bar
import config
app = Flask(__name__)
app.config.from_object(config)
#I initialize the access_token with the value hardcoded in the config file
access_token = config.access_token
#app.route('/',methods=['POST'])
def home():
#I access the value i initialized when starting the server
global access_token
if request.method == 'POST':
#I send the access_token to a method that contacts Imgur servers and checks if it's still valid. If not it will request and return a new one.
access_token = foo(access_token)
#I send the token, now sure it's always a valid one, to the rest of the logic that uses it to do actual requests
bar(access_token)
This works. As long i don't restart the server it will require a new access_token only if needed, but it doesn't really look a good way of doing things. The idea of hardcoding a token that expires in an hour sounds really silly and i don't really know if using that global variable there could be a good practice nor if it would be reinitialized from the config file in some scenario i'm currently ignoring. How can i avoid this? I should write the new refreshed access_token on a file and load it from there in the config? Does that create security concerns?
Also, the refresh_token doesn't expire so i think there is no problem on storing it in the cofing file, but sill every time i request an access_token it also gives me a new refresh_token, should i update that too? It seems unnecessary, the old one still works, but maybe i'm missing something.
According to imgur's API doc, access_token expires after one month not after one hour. I think it's acceptable method to store it in a file. You are already storing the refresh_token in the config file, so if an attacker can access the app's directory, it does not matter whether access_token is also there. (Of course you should run the Flask app as a separate user, and all files should be accessible only by that user.)
You can also store the expire date along with the access_token, so you can check whether you have the renew it before a request.
Related
I have a Python script that is running periodically on an AWS EC2 Ubuntu machine.
This script reads data from some files and sometimes changes data in them.
I want to download these files from OneDrive, do my own thing with them, and upload them back to OneDrive.
I want this to be done automatically, without the need for a user to approve any login or credentials. I'm ok with doing it once (i.e. approving the login on the first run) but the rest has to run automatically, without asking ever again for approvals (unless the permissions change, of course).
What is the best way to do this?
I've been reading the documentation on Microsoft Graph API but I'm struggling with the authentication part. I've created an application in Azure AAD, gave the sample permissions (to test) and created a secret credential.
I managed to do it. I'm not sure if it's the best way but it is working now. It's running automatically every hour and I don't need to touch it.
I followed the information on https://learn.microsoft.com/en-gb/azure/active-directory/develop/v2-oauth2-auth-code-flow
This is what I did.
Azure Portal
Create an application. Azure Active Directory -> App Registrations -> Applications from personal account
In Supported account types, choose the one that has personal Microsoft accounts.
In Redirect URI, choose Public client/native. We'll add the specific URI later.
In the application details, in the section Overview, take note of the Application (client) ID. We'll need this later.
In the section Authentication, click Add a Platform and choose Desktop + devices. You can use your own, I chose one of the suggested: https://login.microsoftonline.com/common/oauth2/nativeclient
In the section API permissions, you have to add all the permissions that your app will use. I added User.Read, Files.ReadWrite and offline_access. The offline_access is to be able to get the refresh token, which will be crucial to keep the app running without asking the user to login.
I did not create any Certificate or Secret.
Web
Looks like to get a token for the first time we have to use a browser or emulate something like that.
There must be a programmatic way to do this, but I had no idea how to do it. I also thought about using Selenium for this, but since it's only one time and my app will request tokens every hour (keeping the tokens fresh), I dropped that idea.
If we add new permissions, the tokens that we have will become invalid and we have to do this manual part again.
Open a browser and go to the URL below. Use the Scopes and the Redirect URI that you set up in Azure Portal.
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=your_app_client_id&response_type=code&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient&response_mode=query&scope=User.Read%20offline_access%20Files.ReadWrite
That URL will redirect you to the Redirect URI that you set up and with a code=something in the URL. Copy that something.
Do a POST request with type FORM URL Encoded. I used https://reqbin.com/ for this.
Endpoint: https://login.microsoftonline.com/common/oauth2/v2.0/token
Form URL: grant_type=authorization_code&client_id=your_app_client_id&code=use_the_code_returned_on_previous_step
This will return an Access Token and a Refresh Token. Store the Refresh Token somewhere. I'm saving it in a file.
Python
# Build the POST parameters
params = {
'grant_type': 'refresh_token',
'client_id': your_app_client_id,
'refresh_token': refresh_token_that_you_got_in_the_previous_step
}
response = requests.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', data=params)
access_token = response.json()['access_token']
new_refresh_token = response.json()['refresh_token']
# ^ Save somewhere the new refresh token.
# I just overwrite the file with the new one.
# This new one will be used next time.
header = {'Authorization': 'Bearer ' + access_token}
# Download the file
response = requests.get('https://graph.microsoft.com/v1.0/me/drive/root:' +
PATH_TO_FILE + '/' + FILE_NAME + ':/content', headers=header)
# Save the file in the disk
with open(file_name, 'wb') as file:
file.write(response.content)
So basically, I have the Refresh Token always updated.
I call the Token endpoint using that Refresh Token, and the API gives me an Access Token to use during the current session and a new Refresh Token.
I use this new Refresh Token the next time I run the program, and so on.
I've just published a repo which does this. Contributions and pull requests welcome:
https://github.com/stevemurch/onedrive-download
I'm trying to get this example to work from https://github.com/ozgur/python-linkedin. I'm using his example. When I run this code. I don't get the RETURN_URL and authorization_code talked about in the example. I'm not sure why, I think it is because I'm not setting up the HTTP API example correctly. I can't find http_api.py, and when I visit http://localhost:8080, I get a "this site can't be reached".
from linkedin import linkedin
API_KEY = 'wFNJekVpDCJtRPFX812pQsJee-gt0zO4X5XmG6wcfSOSlLocxodAXNMbl0_hw3Vl'
API_SECRET = 'daJDa6_8UcnGMw1yuq9TjoO_PMKukXMo8vEMo7Qv5J-G3SPgrAV0FqFCd0TNjQyG'
RETURN_URL = 'http://localhost:8000'
authentication = linkedin.LinkedInAuthentication(API_KEY, API_SECRET, RETURN_URL, linkedin.PERMISSIONS.enums.values())
# Optionally one can send custom "state" value that will be returned from OAuth server
# It can be used to track your user state or something else (it's up to you)
# Be aware that this value is sent to OAuth server AS IS - make sure to encode or hash it
#authorization.state = 'your_encoded_message'
print authentication.authorization_url # open this url on your browser
application = linkedin.LinkedInApplication(authentication)
http_api.py is one of the examples provided in the package. This is an HTTP server that will handle the response from LinkedIn's OAuth end point, so you'll need to boot it up for the example to work.
As stated in the guide, you'll need to execute that example file to get the server working. Note you'll also need to supply the following environment variables: LINKEDIN_API_KEY and LINKEDIN_API_SECRET.
You can run the example file by downloading the repo and calling LINKEDIN_API_KEY=yourkey LINKEDIN_API_SECRET=yoursecret python examples/http_api.py. Note you'll need Python 3.4 for it to work.
My Django application uses an API which is a SOAP service. I'm currently using a python SOAP/WSDL client called SUDS. In order to use the API, I have to initialize the client and provide it with a link to the endpoint, login, API key, and password.
Simplified code that does it:
from suds.client import Client
client = Client('link.to.WSDL.endpoint')
session_id = client.service.a_log_in_service('api_key', 'login', 'password')
# the session_id is required by all other 'client.service.xxx' services
Everything works just fine, although it takes some time to initialize the client.
Now, every time I want to send a request to the API in a view, I have to initialize the client. I can't see any way to "share" the already initialized client among many views. It would be great to have the client up and running somewhere, so that any user can send an API request using it.
Once I've done the log in in a view, I can store the session_id (in a session, db, file) and use it in other views, but the client still needs to be initialized over again, which means downloading the 200-KB XML file.
Having to download 200 KB every time a user hits a button seems not right at all.
Thanks in advance!
I am working on a webapp based on google app engine.
The application uses the google authentication apis.
Basically every handler extends from this BaseHandler and as first operation of any get/post the checkAuth is executed.
class BaseHandler(webapp2.RequestHandler):
googleUser = None
userId = None
def checkAuth(self):
user = users.get_current_user()
self.googleUser = user;
if user:
self.userId = user.user_id()
userKey=ndb.Key(PROJECTNAME, 'rootParent', 'Utente', self.userId)
dbuser = MyUser.query(MyUser.key==userKey).get(keys_only=True)
if dbuser:
pass
else:
self.redirect('/')
else:
self.redirect('/')
The idea is that it redirects to / if no user is logged in via Google OR if there is not a User in my db of users having that google id.
The problem is that I can succesfully log in my web app and make operations. Then, from gmail, o Logout from any google account BUT if i try to keep using the web app it works.
This means the users.get_current_user() still returns a valid user (valid but actually OLD).
Is that possible?
IMPORTANT UPDATE
I Do Understand what explained in the Alex Martelli's Comment: There is a cookie which keeps the former GAE authentication valid.
The problem is that the same web app also exploits the Google Api Client Library for Python https://developers.google.com/api-client-library/python/ to perform operations on Drive and Calendar. In GAE apps such library can be easily used through decorators implementing the whole OAuth2 Flow (https://developers.google.com/api-client-library/python/guide/google_app_engine).
I therefore have my Handlers get/post methods decorated with oauth_required like this
class SomeHandler(BaseHandler):
#DECORATOR.oauth_required
def get(self):
super(SomeHandler,self).checkAuth()
uid = self.googleUser.user_id()
http = DECORATOR.http()
service = build('calendar', 'v3')
calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
Where decorator is
from oauth2client.appengine import OAuth2Decorator
DECORATOR = OAuth2Decorator(
client_id='XXXXXX.apps.googleusercontent.com',
client_secret='YYYYYYY',
scope='https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file'
)
It usually works fine.
However (!!) when the app is idle for a long time it happens that the oauth2 decorator redirects me to the Google authentication page where, if I change account (I have 2 different accounts) Something WEIRD happens:
The app is still logged as the former account (retrieved through users.get_current_user()) while the api client library, and thus the oauth2 decorator, returns data (drive, calendar, etc.) belonging to the second account.
Which is REALLY not appropriate.
Following the example above (SomeHandler class) suppose I am logged as Account A. The users.get_current_user() always returns A as expected. Now suppose I stopped using the app, after a long while the oauth_required redirects me to the Google Account page. I therefore decide (or make a mistake) to log is as Account B. When accessing the Get method of the SomeHandler class the userId (retrived through users.get_current_user() is A while the list of calendars returned through the service object (Google Api client Library) is the list of calendars belonging to B (the actual currently logged user).
Am I doing something wrong? is Something expected?
Another Update
this is after the Martelli's Answer.
I have updated the handlers like this:
class SomeHandler(BaseHandler):
#DECORATOR.oauth_aware
def get(self):
if DECORATOR.has_credentials():
super(SomeHandler,self).checkAuth()
uid = self.googleUser.user_id()
try:
http = DECORATOR.http()
service = build('calendar', 'v3')
calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
except (AccessTokenRefreshError, appengine.InvalidXsrfTokenError):
self.redirect(users.create_logout_url(
DECORATOR.authorize_url()))
else:
self.redirect(users.create_logout_url(
DECORATOR.authorize_url()))
so basically I now use oauth_aware and, in case of none credentials I logout the user and redirect it to the DECORATOR.authorize_url()
I have noticed that after a period of inactivity, the handler raises AccessTokenRefreshError and appengine.InvalidXsrfTokenError exceptions (but the has_credentials() method returns True). I catch them and (again) redirect the flow to the logout and authorize_url()
It seems to work and seems to be robust to accounts switch.
Is it a reasonable solution or am I not considering some aspects of the issue?
I understand the confusion, but the system is "working as designed".
At any point in time a GAE handler can have zero or one "logged-in user" (the object returned by users.get_current_user(), or None if no logged-in user) and zero or more "oauth2 authorization tokens" (for whatever users and scopes have been granted and not revoked).
There is no constraint that forces the oauth2 thingies to match, in any sense, the "logged-in user, if any".
I would recommend checking out the very simple sample at https://code.google.com/p/google-api-python-client/source/browse/samples/appengine/main.py (to run it, you'll have to clone the whole "google-api-python-client" package, then copy into the google-api-python-client/source/browse/samples/appengine directory directories apiclient/ and oauth2client/ from this same package as well as httplib2 from https://github.com/jcgregorio/httplib2 -- and also customize the client_secrets.json -- however, you don't need to run it, just to read and follow the code).
This sample doesn't even use users.get_current_user() -- it doesn't need it nor care about it: it only shows how to use oauth2, and there is no connection between holding an oauth2-authorized token, and the users service. (This allows you for example to have cron execute on behalf of one or more users certain tasks later -- cron doesn't log in, but it doesn't matter -- if the oauth2 tokens are properly stored and retrieved then it can use them).
So the code makes a decorator from the client secrets, with scope='https://www.googleapis.com/auth/plus.me', then uses #decorator.oauth_required on a handler's get to ensure authorization, and with the decorator's authorized http, it fetches
user = service.people().get(userId='me').execute(http=http)
with service built earlier as discovery.build("plus", "v1", http=http) (with a different non-authorized http).
Should you run this locally, it's easy to add a fake login (remember, user login is faked with dev_appserver) so that users.get_current_user() returns princess#bride.com or whatever other fake email you input at the fake login screen -- and this in no way inhibits the completely separate oauth2 flow from still performing as intended (i.e, exactly the same way as it does without any such fake login).
If you deploy the modified app (with an extra user login) to production, the login will have to be a real one -- but it's just as indifferent to, and separate from, the oauth2 part of the app.
If your application's logic does require constraining the oauth2 token to the specific user who's also logged into your app, you'll have to implement this yourself -- e.g by setting scope to 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read' (plus whatever else you need), you'll get from service.people().get(userId='me') a user object with (among many other things) an emails attribute in which you can check that the authorization token is for the user with the email you intended to authorize (and take remedial action otherwise, e.g via a logout URL &c). ((This can be done more simply and in any case I doubt you really need such functionality, but, just wanted to mention it)).
I'm working with the OAuth2Decorator() and Pytgon I'm in that stage where i'm still unsure of something about the App Engine. The documentation is not providing any info or I simply can't follow it. So:
Does OAuth2Decorator() store user Crediantials?
Does OAuth2Decorator() retrieve new tokens automatically?
Conside this following example.:
decorator = OAuth2Decorator(...)
service = build("drive", "v2")
class AppHandler(BaseHandler):
#decorator.oauth_aware
def get(self):
if decorator.has_credentials():
init = service.files().list().execute(decorator.http())
items = init['items']
context = {'data': getitems(items)}
self.render_response('index.html',**context)
else:
url = decorator.authorize_url()
self.redirect(url)
The credentials get stored as CredentialsModel in the datastore.
Provided the access that's requested is 'offline' (I believe this is the default), then there will be a 'refresh token' stored alongside the temporary access-token. If a request is made with a credentials-wrapped Http client, then upon receiving a response that indicates the access token has expired, the client make a request to get a new access token automatically, and then the original request will be retried with the new access token, which will then be stored in place of the expired one.