I have a google app engine site, and what I want to do, is get access to the files on my drive and publish them. Note that, my account owns both the drive and the app engine page.
I have tried looking at the google drive api, and the problem is that I don't know where to start with the following boilerplate code located in their documentation.
If you take a look at this function:
def get_credentials(authorization_code, state):
"""Retrieve credentials using the provided authorization code.
This function exchanges the authorization code for an access token and queries
the UserInfo API to retrieve the user's e-mail address.
If a refresh token has been retrieved along with an access token, it is stored
in the application database using the user's e-mail address as key.
If no refresh token has been retrieved, the function checks in the application
database for one and returns it if found or raises a NoRefreshTokenException
with the authorization URL to redirect the user to.
Args:
authorization_code: Authorization code to use to retrieve an access token.
state: State to set to the authorization URL in case of error.
Returns:
oauth2client.client.OAuth2Credentials instance containing an access and
refresh token.
Raises:
CodeExchangeError: Could not exchange the authorization code.
NoRefreshTokenException: No refresh token could be retrieved from the
available sources.
"""
email_address = ''
try:
credentials = exchange_code(authorization_code)
user_info = get_user_info(credentials)
email_address = user_info.get('email')
user_id = user_info.get('id')
if credentials.refresh_token is not None:
store_credentials(user_id, credentials)
return credentials
else:
credentials = get_stored_credentials(user_id)
if credentials and credentials.refresh_token is not None:
return credentials
except CodeExchangeException, error:
logging.error('An error occurred during code exchange.')
# Drive apps should try to retrieve the user and credentials for the current
# session.
# If none is available, redirect the user to the authorization URL.
error.authorization_url = get_authorization_url(email_address, state)
raise error
except NoUserIdException:
logging.error('No user ID could be retrieved.')
# No refresh token has been retrieved.
authorization_url = get_authorization_url(email_address, state)
raise NoRefreshTokenException(authorization_url)
This is a part of the boilerplate code. However, where am I supposed to get authorisation_code from?
I recently had to implement something similar, and it is quite tricky to find the relevant pieces of documentation.
This is what worked for me.
One-time setup to enable Google Drive for your Google App Engine project
Go to the Google APIs Console and select your App Engine project. If you don't see your App Engine project listed, you need to enable the cloud integration in the App Engine admin tool first (Administration > Application Settings > Cloud Integration > Create project)
In Google APIs Console, now go to Services and look for the "Drive API" in that long list. Turn it on.
Go to the API Access section on Google APIs Console, and find back the "Simple API Access" API Key. (see screenshot below)
Getting and installing the Python Drive API Client
Download the Python Drive API Client: https://developers.google.com/api-client-library/python/start/installation#appengine
Documentation on this Python API: https://google-api-client-libraries.appspot.com/documentation/drive/v2/python/latest/
Using the Python Drive API Client
To create the Drive service object, I use this:
import httplib2
def createDriveService():
"""Builds and returns a Drive service object authorized with the
application's service account.
Returns:
Drive service object.
"""
from oauth2client.appengine import AppAssertionCredentials
from apiclient.discovery import build
credentials = AppAssertionCredentials(scope='https://www.googleapis.com/auth/drive')
http = httplib2.Http()
http = credentials.authorize(http)
return build('drive', 'v2', http=http, developerKey=API_KEY)
You can then use this service object to execute Google Drive API calls, for example, to create a folder:
service = createDriveService()
res = {'title': foldername,
'mimeType': "application/vnd.google-apps.folder"}
service.files().insert(body=res).execute()
Caveats
I was not able to get the Drive API to work in unittesting, nor on the dev_appserver. I always get an error that my credentials are not valid. However, it works fine on the real app engine server.
Related
I'm making a desktop app in Python that sends mail from Gmail. The problem is that after receiving consent (OAuth 2) through the browser, the user for whom the software receives the consent, continues to be logged in to the browser in Gmail. Is there a way to go through the authorization process without staying logged in to Gmail in your browser?
What you are referring to is Oauth2. Oauth2 gives users the ability to grant applications like yours consent to access their private data. Private data is data that is owned by someone. My gmail data is mine your application can not use it unless I grant you access.
Is there a way to go through the authorization process without staying logged in to Gmail in your browser?
Lets clear up some confusion in this statement you mention authorization which is correct a user is authorizing your application to access their data. Yet you also mention logged in which has nothing to do with authorization. Logging in a user is authentication and is not part of Oauth2. It is part of something else called openid connect.
As for how to request authorization of a user without using the browser. Once the user has consented to your application accessing my data once then your application should have what its called a refresh token, this refresh token can be used at a latter time for your application to request a new access token. Granting you access to may data without using the browser to access my data again. So you could store this refresh token in the backend some where and use that to continue to access the users data without needing to use the browser again.
storing user credentials in an installed application
It is hard to know exactly what you are doing since you did not include any code in your question, and your question is a little unclear.
In the following example please note how the users credentials are stored in gmail.dat using this code in an installed application will cause it to load the refresh token the next time the user runs the app meaning that the consent screen should not be shown, as the credentials are already stored for that user.
def initialize_gmail():
"""Initializes the gmail service object.
Returns:
analytics an authorized gmail service object.
"""
# Parse command-line arguments.
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[tools.argparser])
flags = parser.parse_args([])
# Set up a Flow object to be used if we need to authenticate.
flow = client.flow_from_clientsecrets(
CLIENT_SECRETS_PATH, scope=SCOPES,
message=tools.message_if_missing(CLIENT_SECRETS_PATH))
# Prepare credentials, and authorize HTTP object with them.
# If the credentials don't exist or are invalid run through the native client
# flow. The Storage object will ensure that if successful the good
# credentials will get written back to a file.
storage = file.Storage('gmail.dat')
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = tools.run_flow(flow, storage, flags)
http = credentials.authorize(http=httplib2.Http())
# Build the service object.
service = build('gmail ', 'v1', http=http)
return service
I am trying to build my own endpoints inside of an App Engine Application.
There is an endpoint API that needs to ask user for
"https://www.googleapis.com/auth/drive.readonly" scope.
It performs a list of the Drive API and scan the drive file of that user.
The problem is that I don't know how to make call to Drive api inside of an endpoint API.
I think inside of the endpoint method, it has the credentials we got from the user. But I don't know how to receive that.
I am using python as the backend language.
#drivetosp_test_api.api_class(resource_name='report')
class Report(remote.Service):
#endpoints.method(EmptyMessage, EmptyMessage,
name='generate',
path='report/generate',
http_method='GET'
)
def report_generate(self, request):
logging.info(endpoints)
return EmptyMessage()
You can use os.environ to access the HTTP Authorization header, that includes the access token, that was granted all scopes you asked for on the client side, include drive.readonly in your sample.
if "HTTP_AUTHORIZATION" in os.environ:
(tokentype, token) = os.environ["HTTP_AUTHORIZATION"].split(" ")
You can then use this token to make calls to the API, either directly or by using the Google APIs Client Library for Python:
credentials = AccessTokenCredentials(token, 'my-user-agent/1.0')
http = httplib2.Http()
http = credentials.authorize(http)
service = build('drive', 'v2', http=http)
files = service.files().list().execute()
Note that this approach won't work if you are using an Android client, because that uses ID-Token authorization instead of access tokens.
I'm having issues with the Directory API + Service Accounts (Google APIs). This is my current setup:
A web page has an OAuth2 login link like this: https://accounts.google.com/o/oauth2/auth?access_type=offline&state=%2Fprofile&redirect_uri=##REDIR##&response_type=code&client_id=##CLIENTID##&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadmin.directory.user.readonly
Users log in there, authorizing the app to access the Directory API in read-only mode on their behalf.
I then try to retrieve the users of the domain of a given user (by knowing its email address), using the Directory API.
Python code:
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import httplib2
CLIENT_ID = "xzxzxzxzxzxz.apps.googleusercontent.com"
APP_EMAIL = "xzxzxzxzxzxz#developer.gserviceaccount.com"
SCOPES = ('https://www.googleapis.com/auth/admin.directory.user.readonly')
f = file('key.p12', 'rb')
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(APP_EMAIL, key, SCOPES, sub="user#example.com")
http = httplib2.Http()
http = credentials.authorize(http)
directory_service = build('admin', 'directory_v1', http=http)
users = directory_service.users().list(domain="example.com").execute()
print users
I have also tried setting sub="user#example.com" to the app owner like this sub="appowner#company.com", to no avail.
Another thing I have tried is not using impersonation at all (ie. removing the sub=xx part), which leads me to this error:
apiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?domain=example.com&alt=json returned "Not Authorized to access this resource/api">
Using impersonation always yields me this. I have verified it has to do with the scopes and the api which I try to call:
oauth2client.client.AccessTokenRefreshError: access_denied
Now, the actual questions:
Should I be using service accounts? For me, it is the most convenient way as I don't have to be storing tokens which can be outdated altogether.
If service accounts are the way to go, what am I doing wrong in the way I use them? Impersonation with either the Google Apps administrator account (which logs in via OAuth web) or the app owner account does not seem to work.
I am trying to gain service account access to to the Google Drive API. I followed the Google Drive SDK example when I was building my application. My code resembles the example almost exactly:
class MainPage(webapp2.RequestHandler):
def get(self):
build = createDriveService(user)
searchFile = build.files().get(fileId='FILEID').execute()
self.response.write(searchFile)
def createDriveService(userEmail):
API_KEY = 'APIKEY'
credentials = AppAssertionCredentials(
scope='https://www.googleapis.com/auth/drive',
sub=userEmail)
http = httplib2.Http()
http = credentials.authorize(http)
return build('drive', 'v2', http=http, developerKey=API_KEY)
When I call visit my GAE page the error in the logs that I am getting is:
<"File not found: FILEID">
I know the file ID exists as I copied it from the UI. I am using the simple Key access for the variable API_KEY. Should I be validating my application a different way?
EDIT1:
I've tried following various other StackOverflow. One of which involves using the SignedJwtAssertionCredentials and converting the .p12 key to a .pem key. After this change I am getting a
cannot import SignedJwtAsserionCredentials
error. From there I made sure to include the pycrypto library in my app.yaml. Any Idea?
EDIT2
I have successfully impersonated users on app engine. I followed this previously answered question and it worked.
I do not understand the user part of your code, bacause you use a Google App Engine project user account.
See this doc on how to use and find this account. You can also fnd this account using :
from google.appengine.api import app_identity
logging.info('service account : ' + app_identity.get_service_account_name())
Make sure you have given this project user account access to your drive file or folder!
My code looks like this :
def createDriveService():
SCOPE = 'https://www.googleapis.com/auth/drive'
API_KEY = 'AIzaSyB9UkK4OH5Z_E4v3Qp6bay6QEgGpzou3bc' # GAE
credentials = AppAssertionCredentials(scope=SCOPE)
logging.info('using service account : ' + app_identity.get_service_account_name())
http = credentials.authorize(httplib2.Http())
return build('drive', 'v2', http=http, developerKey=API_KEY)
I'm looking for a good way to retrieve every emails address of my contacts from a google account for a "desktop" application in Python.
In a first time, I created an app via Google Code. I toggled Google Plus API, retrieving most of my user data, but not any of my contacts.
I started investigate, and I found a lot of stuff, but most of them was outdated.
I found a good way to retrieve my contacts, using gdata library but granting me a full read/write access on it, via https://www.google.com/m8/feeds with no feedback.
self.gd_client = gdata.contacts.client.ContactsClient(source='MyAppliName')
self.gd_client.ClientLogin(email, password, self.gd_client.source)
According to the official 'google contact api' google group, which migrated to stackoverflow, read only access is broken.
By the way, I'm not a huge fan of 'Trust my application, I use read only access, I swear."
I found the google api playground at https://developers.google.com/oauthplayground in which they use OAuth2.0 token with most of apis, including contact, toggling a webpage:
Google OAuth 2.0 Playground is requesting permission to:
Manage your contacts
According to this playground, it's possible to use OAuth2.0 with google contact api, but I have no idea how to add https:// www.google.com/m8/feeds to my scope, which doesn't appear on the list.
Is there an other way to do that ?
If this question is still open for you, here is some sample code how to use oauth2 and Google Contact API v3:
import gdata.contacts.client
from gdata.gauth import AuthSubToken
from oauth2client import tools
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
def oauth2_authorize_application(client_secret_file, scope, credential_cache_file='credentials_cache.json'):
"""
authorize an application to the requested scope by asking the user in a browser.
:param client_secret_file: json file containing the client secret for an offline application
:param scope: scope(s) to authorize the application for
:param credential_cache_file: if provided or not None, the credenials will be cached in a file.
The user does not need to be reauthenticated
:return OAuth2Credentials object
"""
FLOW = flow_from_clientsecrets(client_secret_file,
scope=scope)
storage = Storage(credential_cache_file)
credentials = storage.get()
if credentials is None or credentials.invalid:
# Run oauth2 flow with default arguments.
credentials = tools.run_flow(FLOW, storage, tools.argparser.parse_args([]))
return credentials
SCOPES = ['https://www.google.com/m8/feeds/', 'https://www.googleapis.com/auth/userinfo.email']
credentials = oauth2_authorize_application('client-secret.json', scope=SCOPES)
token_string = credentials.get_access_token().access_token
# deprecated!
# auth_token = AuthSubToken(token_string, SCOPES)
with open('client-secret.json') as f:
oauth2_client_secret = json.load(f)
auth_token = gdata.gauth.OAuth2Token(
client_id=oauth2_client_secret['web']['client_id'],
client_secret=oauth2_client_secret['web']['client_secret'],
scope=SCOPES,
user_agent='MyUserAgent/1.0',
access_token=credentials.get_access_token().access_token,
refresh_token=credentials.refresh_token)
client = gdata.contacts.client.ContactsClient(auth_token=auth_token)
query = gdata.contacts.client.ContactsQuery()
The request should look like:
https://accounts.google.com/o/oauth2/auth?
scope=https%3A%2F%2Fwww.google.com%2Fm8%2Ffeeds&
state=<myState>&
redirect_uri=<Redirect URI>&
response_type=code&
client_id=<my Client ID>&approval_prompt=force
This will obtain read/write access to the user's contacts.