Accessing the gmail API with delegated domain-wide authority - python

I have a gmail account in my g-suite organization for which I want to automatically read its gmail messages.
Since this needs to be run periodically with an automated process, the regular OAuth flow is not useful, since someone needs to open a browser and give permission to the automated access.
So far I've created a service account with domain-wide authority, as documented here.
The code I am using is:
import httplib2
from apiclient import discovery
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', ]
CLIENT_SECRET_FILE = '/path/to/client_secrets.json'
def get_credentials():
from oauth2client.service_account import ServiceAccountCredentials
credentials = ServiceAccountCredentials.from_json_keyfile_name(CLIENT_SECRET_FILE, SCOPES)
delegated_credentials = credentials.create_delegated('myproject#myproject-123456.iam.gserviceaccount.com')
return delegated_credentials
def main():
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('gmail', 'v1', http=http)
results = service.users().messages().list(userId='user_to_impersonate#mycompany.com').execute()
if __name__ == '__main__':
main()
But I get a 400 Bad Request error:
Traceback (most recent call last):
File "gmail.py", line 77, in <module>
main()
File "gmail.py", line 65, in main
results = service.users().messages().list(userId='user_to_impersonate#mycompany.com').execute()
File "/usr/local/lib/python3.6/site-packages/oauth2client/_helpers.py", line 133, in positional_wrapper
return wrapped(*args, **kwargs)
File "/usr/local/lib/python3.6/site-packages/googleapiclient/http.py", line 842, in execute
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 400 when requesting https://www.googleapis.com/gmail/v1/users/user_to_impersonate%40mycompany.com/messages?alt=json returned "Bad Request">
Is it possible at all to access a specific gmail account without having to grant permission from the browser? Do I need to perform any special step first? Or, is there any way get more information for debugging?

Based on the docs, you need to use delegated_credentials = credentials.create_delegated('user_to_impersonate#mycompany.com') instead of delegated_credentials = credentials.create_delegated('myproject#myproject-123456.iam.gserviceaccount.com').

Related

I want to create google sheet from python using Pycharm but its not working

I was try to create google sheet using python in Pycharm but I try very had didn't out bugs.
here I submit my full code and show my error result.
# [START sheets_create]
from __future__ import print_function
import google.auth
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
def create(title):
"""
Creates the Sheet the user has access to.
Load pre-authorized user credentials from the environment.
TODO(developer) - See https://developers.google.com/identity
for guides on implementing OAuth2 for the application.\n"
"""
creds = 'conof.json'
# pylint: disable=maybe-no-member
try:
service = build('sheets', 'v4', credentials=creds)
spreadsheet = {
'properties': {
'title': title
}
}
spreadsheet = service.spreadsheets().create(body=spreadsheet,
fields='spreadsheetId') \
.execute()
print(f"Spreadsheet ID: {(spreadsheet.get('spreadsheetId'))}")
return spreadsheet.get('spreadsheetId')
except HttpError as error:
print(f"An error occurred: {error}")
return error
if __name__ == '__main__':
# Pass: title
create("mysheet1")
# [END sheets_create]
This is default code I found from google sheet development site I just added here my json file from google cloud console generated. I was hard to manage to solve library error's. I am using windows platform.
And my error's
C:\Users\pc\PycharmProjects\createShee\venv\Scripts\python.exe C:/Users/pc/PycharmProjects/createShee/mumu.py
Traceback (most recent call last):
File "C:\Users\pc\PycharmProjects\createShee\mumu.py", line 37, in <module>
create("mysheet1")
File "C:\Users\pc\PycharmProjects\createShee\mumu.py", line 19, in create
service = build('sheets', 'v4', credentials=creds)
File "C:\Users\pc\PycharmProjects\createShee\venv\lib\site-packages\googleapiclient\_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
File "C:\Users\pc\PycharmProjects\createShee\venv\lib\site-packages\googleapiclient\discovery.py", line 298, in build
service = build_from_document(
File "C:\Users\pc\PycharmProjects\createShee\venv\lib\site-packages\googleapiclient\_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
File "C:\Users\pc\PycharmProjects\createShee\venv\lib\site-packages\googleapiclient\discovery.py", line 604, in build_from_document
http = _auth.authorized_http(credentials)
File "C:\Users\pc\PycharmProjects\createShee\venv\lib\site-packages\googleapiclient\_auth.py", line 124, in authorized_http
return credentials.authorize(build_http())
AttributeError: 'str' object has no attribute 'authorize'
Process finished with exit code 1
Please help me to solve this project.
The credential parameter of build() method can only accept the following objects:
credentials: oauth2client.Credentials or
google.auth.credentials.Credentials, credentials to be used for
authentication.
An easy way to create a credential object is to use the authentication in the Python Quickstart of Google Sheets API.
Just follow the Quickstart guide, change the scope to 'https://www.googleapis.com/auth/spreadsheets' and replace the try-except block with the try-except block in the Create a spreadsheet.
Your code should look like this:
from __future__ import print_function
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
def create(title):
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
try:
service = build('sheets', 'v4', credentials=creds)
spreadsheet = {
'properties': {
'title': title
}
}
spreadsheet = service.spreadsheets().create(body=spreadsheet,
fields='spreadsheetId') \
.execute()
print(f"Spreadsheet ID: {(spreadsheet.get('spreadsheetId'))}")
return spreadsheet.get('spreadsheetId')
except HttpError as error:
print(f"An error occurred: {error}")
return error
if __name__ == '__main__':
create("mysheet1")
Output:
Notes: Make sure to install the necessary package included in the Quickstart guide, download the credential json file, save it in the same directory as your script and rename it to credentials.json
Reference:
Module discovery
Try replacing creds = 'conof.json' with
credentials = google.oauth2.credentials.Credentials.from_authorized_user_file('conof.json)
and adding the import as necessary.
You are currently passing a string to the credentials parameter in build; however, build expects you to pass a specific type of credentials object to it. You can obtain this credential object from a JSON file using the line of code above. See this answer for more detail.

Google Sheet API access with Application Default credentials using scopes giving Insufficient Scopes error while running on GCP VM

I am trying to access Google Sheet (read write mode) from Python (runs in GKE). I have tried both outh2client as well as google-auth approach but it gives the same error every time:
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://sheets.googleapis.com/v4/spreadsheets/1kvHv1OBCzr9GnFxRu9RTJC7jjQjc9M4rAiDnhyak2Sg/values/vm_metrics%21A10?alt=json returned "Request had insufficient authentication scopes.". Details: "[{'#type': 'type.googleapis.com/google.rpc.ErrorInfo', 'reason': 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', 'domain': 'googleapis.com', 'metadata': {'method': 'google.apps.sheets.v4.SpreadsheetsService.GetValues', 'service': 'sheets.googleapis.com'}}]">
This is my code using outh2client:
from googleapiclient.discovery import build
from oauth2client import client
creds=client.GoogleCredentials.get_application_default().create_scoped(
['https://www.googleapis.com/auth/spreadsheets'])
service = build('sheets', 'v4', credentials=creds)
sheet = service.spreadsheets()
sheet.values().get(spreadsheetId='whatev', range='Sheet1!A:C').execute()
This is my code using google-auth:
import google.auth
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
creds, project=google.auth.default(scopes=SCOPES)
service = build('sheets', 'v4', credentials=creds)
sheet = service.spreadsheets()
sheet.values().get(spreadsheetId='XXXXXXXXXX', range='Sheet1!A:C').execute()
AFAICT, this is the same question as Google Sheet API access with Application Default credentials. The solution (from that SO post) is
from googleapiclient.discovery import build
from oauth2client import client
creds = client.GoogleCredentials.get_application_default().create_scoped(
['https://www.googleapis.com/auth/spreadsheets.readonly']
)
response = service.spreadsheets().values().get(
spreadsheetId='XXXXXXXXXX',
range='Sheet1!A:C'
).execute()
rows = response['values']
However, this will not work with a human user's application default credentials. You cannot run gcloud auth application-default login and use your user credentials. You'll get this error:
Traceback (most recent call last):
File "<ipython-input-2-2a9ba7e9e38f>", line 8, in <module>
sheet.values().get(spreadsheetId='1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', range='Class Data!A2:E''....').execute()
File "/Users/dking/miniconda3/lib/python3.7/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
File "/Users/dking/miniconda3/lib/python3.7/site-packages/googleapiclient/http.py", line 855, in execute
raise HttpError(resp, content, uri=self.uri)
HttpError: <HttpError 403 when requesting https://sheets.googleapis.com/v4/spreadsheets/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/values/Class%20Data%21A2%3AE....?alt=json returned "Request had insufficient authentication scopes.">
AFAICT, there is no way to use a human user's application default credentials, which is tremendously annoying.

Insufficient authentication scopes error using Google Slides API

I'm trying to understand how to work with Google Slides API,
I did everything as it was described here.
In "enable Google Slides API" I tried to choose "Desktop app" or "Web Server", but in both ways every time when I try to run quickstart.py file, I get same error:
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://slides.googleapis.com/v1/presentations/1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc?alt=json returned "Request had insufficient authentication scopes.">
This is what i have inside my quickstart.py:
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/presentations.readonly']
# The ID of a sample presentation.
PRESENTATION_ID = '1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc'
def main():
"""Shows basic usage of the Slides API.
Prints the number of slides and elments in a sample presentation.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('slides', 'v1', credentials=creds)
# Call the Slides API
presentation = service.presentations().get(
presentationId=PRESENTATION_ID).execute()
slides = presentation.get('slides')
print('The presentation contains {} slides:'.format(len(slides)))
for i, slide in enumerate(slides):
print('- Slide #{} contains {} elements.'.format(
i + 1, len(slide.get('pageElements'))))
if __name__ == '__main__':
main()
Updated:
Whole error msg looks like this:
Traceback (most recent call last):
File "/Users/A/PycharmProjects/PyProjects/quickstart.py", line 54, in <module>
main()
File "/Users/A/PycharmProjects/PyProjects/quickstart.py", line 44, in main
presentationId=PRESENTATION_ID).execute()
File "/Users/A/PycharmProjects/PyProjects/venv/lib/python3.7/site-packages/googleapiclient/_helpers.py", line 134, in positional_wrapper
return wrapped(*args, **kwargs)
File "/Users/A/PycharmProjects/PyProjects/venv/lib/python3.7/site-packages/googleapiclient/http.py", line 907, in execute
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://slides.googleapis.com/v1/presentations/1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Vuc?alt=json returned "Request had insufficient authentication scopes.">
Hello I have stumbled upon the same issue it is easily solvable, you just have to approve it manually.
https://developers.google.com/people/quickstart/python#step_1_turn_on_the
So press the Enable the People API button and it should open a new window where you have to login manually first time for the approval hope it helps.

Working with Gmail API from Google AppEngine

In GAE standard environment, I'm struggling with registering the watch() call against Gmail API for the Pub/Sub push notification using google-api-python-client.
Here is the relevant excerpt from my code:
import googleapiclient.discovery
from oauth2client import service_account
SCOPES = ['https://www.googleapis.com/auth/gmail.modify']
SERVICE_ACCOUNT_FILE = '<My-project>-<short-ID>.json'
credentials = service_account.ServiceAccountCredentials.from_json_keyfile_name(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
gmail = googleapiclient.discovery.build('gmail', 'v1', credentials=credentials)
watchRequest = {
'labelIds' : ['INBOX'],
'topicName' : 'projects/<my-project>/topics/<my-topic>'
}
gmail.users().watch(userId='<email-I-need-to-watch>', body=watchRequest).execute()
After firing-off this part of the code I get:
Traceback (most recent call last):
File "/base/alloc/tmpfs/dynamic_runtimes/python27/54c5883f70296ec8_unzipped/python27_lib/versions/1/google/appengine/runtime/wsgi.py", line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File "/base/alloc/tmpfs/dynamic_runtimes/python27/54c5883f70296ec8_unzipped/python27_lib/versions/1/google/appengine/runtime/wsgi.py", line 299, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File "/base/alloc/tmpfs/dynamic_runtimes/python27/54c5883f70296ec8_unzipped/python27_lib/versions/1/google/appengine/runtime/wsgi.py", line 85, in LoadObject
obj = __import__(path[0])
File "/base/data/home/apps/e~<my-project>-191008/20180124t154459.407164278206739455/main.py", line 68, in <module>
gmail.users().watch(userId='<email-I-need-to-watch>', body=watchRequest).execute()
File "/base/data/home/apps/e~<my-project>/20180124t154459.407164278206739455/lib/oauth2client/_helpers.py", line 133, in positional_wrapper
return wrapped(*args, **kwargs)
File "/base/data/home/apps/e~<my-project>/20180124t154459.407164278206739455/lib/googleapiclient/http.py", line 844, in execute
raise HttpError(resp, content, uri=self.uri)
HttpError: <HttpError 400 when requesting https://www.googleapis.com/gmail/v1/users/<email-I-need-to-watch>/watch?alt=json returned "Bad Request">
In regards to the authentication and authorization, here is what I have done so far:
I've created a Pub/Sub topic and this is the one I'm passing into the watch() request
I use G-Suite and the email inbox I intend to watch is part of my G-Suite business domain.
For this task I use a service account with enabled G-Suite Domain-wide Delegation. I've downloaded the .json service account file which I'm supplying in order to acquire the oauth2client.service_account.Credentials object (I see the access and refresh tokens being exchanged successfully in the logs). The json service file is placed in the same folder as my main.py script (root of my project).
In my G-Suite administration panel I've enabled the api access to the service account from 2. with the scope of https://www.googleapis.com/auth/gmail.modify. I'm using the gmail.modify access level as I intend to read, write and send both emails and drafts.
Is there something I'm missing out in my code or in the authentication and authorization steps?
Problem solved. I was missing the part of the code for impersonating a user from my domain in order to read his/her mailbox (as explained here).
The corrected code looks like this:
import googleapiclient.discovery
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/gmail.modify']
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
credentials = credentials.with_subject('<email-I-need-to-watch>')
gmail = googleapiclient.discovery.build('gmail', 'v1', credentials=credentials)
watchRequest = {
'labelIds' : ['INBOX'],
'topicName' : 'projects/<my-project>/topics/<my-topic>'
}
gmail.users().watch(userId='me', body=watchRequest).execute()

Trouble with oauth2client.client.Storage

I'm trying to use OAuth2 with my App Engine application, but I keep getting the following error:
Encountered unexpected error from ProtoRPC method implementation: IOError ([Errno 30] Read-only file system: 'credentials.dat')
Traceback (most recent call last):
File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/protorpc-1.0/protorpc/wsgi/service.py", line 181, in protorpc_service_app
response = method(instance, request)
File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/endpoints-1.0/endpoints/api_config.py", line 1332, in invoke_remote
return remote_method(service_instance, request)
File "/base/data/home/runtimes/python27/python27_lib/versions/third_party/protorpc-1.0/protorpc/remote.py", line 412, in invoke_remote_method
response = method(service_instance, request)
File "/base/data/home/apps/s~art-everywhere/7.385356767964097452/main.py", line 438, in upload_putphoto
gd_client = PicasaWA.login()
File "/base/data/home/apps/s~art-everywhere/7.385356767964097452/main.py", line 1653, in login
storage.put(credentials)
File "/base/data/home/apps/s~art-everywhere/7.385356767964097452/oauth2client/client.py", line 325, in put
self.locked_put(credentials)
File "/base/data/home/apps/s~art-everywhere/7.385356767964097452/oauth2client/file.py", line 113, in locked_put
f = open(self._filename, 'wb')
IOError: [Errno 30] Read-only file system: 'credentials.dat'
It looks like there's a problem with the write used in the library.
Here's a snippet of the code I used:
def login(cls):
scope = 'https://picasaweb.google.com/data/'
user_agent = 'picasawebuploader'
# credential_store = os.path.join(os.path.split(__file__)[0], "credentials.dat")
storage = Storage("credentials.dat")
# storage = Storage(credential_store)
credentials = storage.get()
# user = users.get_current_user()
# storage = StorageByKeyName(CredentialsModel, user.user_id(), 'credentials')
# credentials = storage.get()
if credentials is None or credentials.invalid:
flow = flow_from_clientsecrets("client_secrets.json", scope=scope, redirect_uri='urn:ietf:wg:oauth:2.0:oob')
uri = flow.step1_get_authorize_url()
logging.info("uri: %s", uri)
webbrowser.open(uri)
code = "Here I posted the code retrived by the autentication"
credentials = flow.step2_exchange(code)
storage.put(credentials)
if (credentials.token_expiry - datetime.utcnow()) < timedelta(minutes=5):
http = httplib2.Http()
http = credentials.authorize(http)
credentials.refresh(http)
gd_client = gdata.photos.service.PhotosService(source=user_agent,
email=USERNAME,
additional_headers={'Authorization' : 'Bearer %s' % credentials.access_token})
return gd_client
What am I doing wrong?
Thanks everyone for the help!
You can't create files in AppEngine like you would on a regular filesystem, it's read-only. You need to use special AppEngine credentials and storage objects.
Ran into the same issue running the devserver on my local machine. The solution was not pretty, but this change solved it for me. I'm not sure if you'd be able run storage.put(credentials) from within appengine. This might be where a lot of the issue came from. Doing the oauth flow as part of a separate script, and then loading the credentials from the file actually worked for me after modifying the devserver.
I highly doubt that this would actually work in production though, so you might want to look into Delegating domain-wide authority to the service account

Categories