How to use a service account to authorize google sheets? - python

I am trying to open a private google sheet using python. The end goal here is to read that private sheet data into a json object. I have made sure to create a google cloud project, enable the API's, and service account. The service account email has been shared and added as an editor. I also created OAuth keys for a desktop application. This is required since the file is private.
I know I need to somehow request a token to use for access to the sheets API, but I am at a loss for how to create a request, and utilize the client_secret file generated from OAuth keys. I figured the googleAPI would have a function where you can pass this file directly, but I am lost in documentation.
Any insight would be appreciated!

All you need to do is supply the library with the location of the clientSecret.json file you should have downloaded from Google cloud console. This method should build the service for you and you can make the requests to the api. It will handle all the authorization.
from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
def get_service(api_name, api_version, scopes, key_file_location):
"""Get a service that communicates to a Google API.
Args:
api_name: The name of the api to connect to.
api_version: The api version to connect to.
scopes: A list auth scopes to authorize for the application.
key_file_location: The path to a valid service account JSON key file.
Returns:
A service that is connected to the specified API.
"""
credentials = ServiceAccountCredentials.from_json_keyfile_name(
key_file_location, scopes=scopes)
# Build the service object.
service = build(api_name, api_version, credentials=credentials)
return service
The best example I know of for service account authentication with python is the Google analytics quickstart If you have any issues altering it for google sheets let me know i can try and help.
Calling it should be something like this.
def main():
# Define the auth scopes to request.
scope = 'https://www.googleapis.com/auth/spreadsheets'
key_file_location = '<REPLACE_WITH_JSON_FILE>'
# Authenticate and construct service.
service = get_service(
api_name='sheets',
api_version='v4',
scopes=[scope],
key_file_location=key_file_location)
data = your_method_to_call_sheets(service)
How to create clientSecret.json remember to enable the google sheets api under libary

Related

Access Google Analytics API without generating service account client_secrets

I am trying to access the Google Analytics report (v4) of a client, so I cannot generate my own client_secrets.json file. In the past I have used the oauth2client library to create a flow and storage object to authenticate via http. Current google documentation still uses the oauth2client library, which according to pypi.org (and google) was deprecated in September of 2018.
Here is the current code we are using to build the service:
import argparse
from apiclient.discovery import build
import httplib2
from oauth2client import client
from oauth2client import file
from oauth2client import tools
def get_service(api_name, api_version, scope, client_secrets_path):
"""Get a service that communicates to a Google API.
Args:
api_name: string The name of the api to connect to.
api_version: string The api version to connect to.
scope: A list of strings representing the auth scopes to authorize for the
connection.
client_secrets_path: string A path to a valid client secrets file.
Returns:
A service that is connected to the specified API.
"""
# 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=scope,
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(api_name + '.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(api_name, api_version, http=http)
return service
The current google documentation can be found here:
https://developers.google.com/analytics/devguides/config/mgmt/v3/quickstart/installed-py
How do I create this build without receiving specific client_secrets from the client and without using oauth2client?
If you use the service account flow, all you need to do is ask your client to add an additional user (which is just a api account connection) to their analytics account/property/view that you require access to, and it also means you don't need to construct an oauth2 flow.
There's step by step instructions of how to set this up and use it with the python library here:
https://developers.google.com/analytics/devguides/reporting/core/v4/quickstart/service-py

Authentication issue accessing Google Sheets Python API via cloud functions

I am trying to access a Google sheet using a cloud function and the sheets API in python, but get a 403 permissions error. I have created a service account, exported the key (included in the cloud function zip I am uploading) and have given this service account the API scopes necessary to access sheets (I've tried both https://www.googleapis.com/auth/spreadsheets and https://www.googleapis.com/auth/drive). Here is the code:
import httplib2
from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
def main(request):
scope = ['https://www.googleapis.com/auth/spreadsheets','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('client_secrets.json', scope)
service = build('sheets', 'v4', http=credentials.authorize(httplib2.Http()))
spreadsheet_id = 'id-of-the-google-sheet'
range = 'Sheet1!A:D'
response = service.spreadsheets().values().get(spreadsheetId=spreadsheet_id, range=range).execute()
cols = response.get('values', [])[0]
return cols[0]
requirements.txt:
google-api-python-client
oauth2client
google-auth-httplib2
google-auth
If I explicitly share the Google sheet with the service account, the authentication does work and I can access the sheet data. The question is why doesn't this work by applying the proper API scopes as the Google accounts admin?
Google Drive permissions are on top of scopes. Scopes are needed for authentication of the request, but Drive permissions (and Sheets by extension) are what determines who has access to which file.
If the calling account (the serviceaccount in this case) doesn't have access to the Sheet in question you'll get the 403 you've been getting.
Setting up the scopes in the Admin Console doesn't have direct correlation to the permissions.
One thing to note as well, if you have documents shared to everyone in the G Suite account by default, you could use Domain Wide Delegation (1) and using the serviceaccount impersonate a user (like the superadmin) inside the G Suite domain. You wouldn't need to share each document with the serviceaccount itself.
(1) This one is for the Admin SDK, but this is the article that contains the best example. You're interested in the credentials object there. You can also create the object with a .json IIRC.

Using Google Admin to view Drive files Domain-wide

I'm trying to list all Google Drive files Domain-wide, both users that still work here, and those that have moved on. With that, we can grep the output for certain terms (former customers) to delete customer-related files.
I believe I have a successful way to list all users using the Admin SDK Quickstart, since we have only about 200 total users (max is 500). I also have a way to list all files for a user using the Drive REST API's files.list() method. What I need to know is how to impersonate each user iteratively, in order to run the file listing script.
I have found the blurb .setServiceAccountUser(someone#domain.com) but I'm not really sure where to implement this, either in the service account authorization step, or in a separate middle-man script.
Have a look at https://github.com/pinoyyid/googleDriveTransferOwnership/blob/master/src/couk/cleverthinking/tof/Main.java
Specifically lines 285-299 which deal with generating a credential for an impersonated user.
GoogleCredential.Builder builder = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(serviceAccountEmailAddress)
.setServiceAccountPrivateKeyFromP12File(f)
.setServiceAccountScopes(Collections.singleton(SCOPE));
// if requested, impresonate a domain user
if (!"ServiceAccount".equals(impersonatedAccountEmailAddress)) {
builder.setServiceAccountUser(impersonatedAccountEmailAddress);
}
// build the Drive service
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, null)
.setApplicationName("TOF")
.setHttpRequestInitializer(builder.build()).build();
This is Java, but should at least tell you what the steps are.
You need to implement the authorization flow for Service Accounts.
Once you create a service account in a GCP project (console.developers.google.com), enable DWD (domain-wide delegation), then authorize that service account in your G Suite admin console, that key can then be used to "impersonate" any account in the G Suite instance:
Create the credentials object from the json file
from oauth2client.service_account import ServiceAccountCredentials
scopes = ['https://www.googleapis.com/auth/gmail.readonly']
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'/path/to/keyfile.json', scopes=scopes)
Create a credential that can impersonate user#example.org (could be any user in the domain though)
delegated_credentials = credentials.create_delegated('user#example.org')
Authorize the credential object (i.e. get an access_token)
from httplib2 import Http
http_auth = credentials.authorize(Http())
Call the Gmail API:
from apiclient import discovery
service = discovery.build('gmail', 'v1', http=http)
response = service.users().messages().list(userId='user#example.org').execute()

Gmail API server to server application (Bad Request error)

I'm following this tutorial Using OAuth 2.0 for Server to Server Applications. I am trying to connect to the Gmail API using a service account.
The code I end up with looks like this:
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
from apiclient.discovery import build
import json
scopes = ['https://www.googleapis.com/auth/gmail.readonly']
credentials = ServiceAccountCredentials.from_json_keyfile_name('******.json', scopes)
http_auth = credentials.authorize(Http())
service = build('gmail', 'v1', http=http_auth)
request = service.users().messages().list(userId='me')
response = request.execute()
print json.dumps(response, sort_keys=True, indent=2)
However, when I run this code, I get the following error:
googleapiclient.errors.HttpError:https://www.googleapis.com/gmail/v1/users/me/messages?alt=json returned "Bad Request">
Can someone help me understand where this error is coming from?
Think of a service account as a dummy user. It has a Google Drive account a google calendar account. What it doesn't to my knowlage is have a Gmail account.
Normally when you request data using a service account you have to grant the service account access to that data manually. In the case of google drive you can share a folder with the service account enabling it to access google drive. (you can also upload to its drive account but that's out of scope for this question)
There is no way to grant another user access to your Gmail account so there is no way to use a service account with a normal user Gmail account.
Note: If this is Not a normal user Gmail account and is in fact one based on google domains then you can grant the service account access to all the emails of the other users on the domain via the admin section.
Other wise you need to look into using Oauth2 to access gmail.

Google Contact API - Auth2.0

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.

Categories

Resources