I have a script to export text from a GDrive file using an OAuth client, which works perfectly well -
import googleapiclient.discovery as google
from apiclient.http import MediaIoBaseDownload
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import datetime, io, os, pickle
Scopes=" ".join(['https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive.metadata',
'https://www.googleapis.com/auth/drive.readonly'])
TokenFile="token.pickle"
def init_creds(clientfile,
scopes,
tokenfile=TokenFile):
token=None
if os.path.exists(tokenfile):
with open(tokenfile, 'rb') as f:
token=pickle.load(f)
if (not token or
not token.valid or
token.expiry < datetime.datetime.utcnow()):
if (token and
token.expired and
token.refresh_token):
token.refresh(Request())
else:
flow=InstalledAppFlow.from_client_secrets_file(clientfile, scopes)
token=flow.run_local_server(port=0)
with open(tokenfile, 'wb') as f:
pickle.dump(token, f)
return token
def export_text(id,
clientfile,
scopes=Scopes):
creds=init_creds(clientfile=clientfile,
scopes=scopes)
service=google.build('drive', 'v3', credentials=creds)
request=service.files().export_media(fileId=id,
mimeType='text/plain')
buf=io.BytesIO()
downloader, done = MediaIoBaseDownload(buf, request), False
while done is False:
status, done = downloader.next_chunk()
destfilename="tmp/%s.txt" % id
return buf.getvalue().decode("utf-8")
if __name__=='__main__':
print (export_text(id="#{redacted}"
clientfile="/path/to/oath/client.json"))
But it's a pain to have to go through the OAuth flow every time, and since it's only me using the script I want to simplify things and use a Service Account instead, following on from this post -
Google Drive API Python Service Account Example
My new Service Account script, doing exactly the same thing, is as follows -
import googleapiclient.discovery as google
from oauth2client.service_account import ServiceAccountCredentials
from apiclient.http import MediaIoBaseDownload
import io
Scopes=" ".join(['https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive.metadata',
'https://www.googleapis.com/auth/drive.readonly'])
def export_text(id,
clientfile,
scopes=Scopes):
creds=ServiceAccountCredentials.from_json_keyfile_name(clientfile,
scopes)
service=google.build('drive', 'v3', credentials=creds)
request=service.files().export_media(fileId=id,
mimeType='text/plain')
buf=io.BytesIO()
downloader, done = MediaIoBaseDownload(buf, request), False
while done is False:
status, done = downloader.next_chunk()
destfilename="tmp/%s.txt" % id
return buf.getvalue().decode("utf-8")
if __name__=='__main__':
print (export_text(id="#{redacted}",
clientfile="path/to/service/account.json"))
but when I run it for the same id, I get the following -
googleapiclient.errors.HttpError: <HttpError 404 when requesting https://www.googleapis.com/drive/v3/files/#{redacted}/export?mimeType=text%2Fplain&alt=media returned "File not found: #{redacted}.">
It feels like the Service Account script is passing the authentication step (ie Service Account creds are okay) but then failing when trying to fetch the file - weird as I can fetch it fine using the OAuth version :/
Any thoughts on what might be causing this 404 error in the Service Account version, given the OAuth client version clearly works for the same id?
Answer:
You need to share your file with the service account.
More Information:
As you would with any file, you need to give a user explicit permissions to be able to see it. As a service account is a separate entitiy to you, this goes for them as well.
Using the file sharing settings (you can just do this in the Drive UI by right-clicking the file and hitting Share), give the email address of the service account the correct permission (read/write). The email address of the service account is in the form:
service-account-name#project-id.iam.gserviceaccount.com
Before making your call do a File.list to see which files the service account has access to. Doing a file.get on a file that the service account doesn't have access to will result in a file not found error. Remember that the service account is not you, it has its own google drive account. Any files you want to access need to be uploaded to its account or shared with the service account.
If the file.list fails then it would suggest to me that there is something wrong with the authorization and you should ensure that the service account has access to client file maybe its that file it cant find.
Granting service account acccess
Create a directory on your personal google drive account. Take the service account email address, it can be found in the key file you downloaded it has a # in it. Then share that directory on your drive account with the service account like you would share with any other user.
Adding files to that directory may or may not give the service account access to them automatically permissions is a pain you may need to also share the file with the service account.
Remember to have the service account grant your personal account permissions to the file when it uploads it its going to be the owner.
Related
I've written the following code to upload an image to my own Google Drive using a service account.
My code is returning successfully, giving me an ID back but there's nothing appearing on my actual Google Drive.
from django.conf import settings
import os
from apiclient.discovery import build
from apiclient.http import MediaFileUpload
from oauth2client.service_account import ServiceAccountCredentials
def get_service(api_name, api_version, scope, key_file_location):
credentials = ServiceAccountCredentials.from_json_keyfile_name(key_file_location, scopes=scope)
service = build(api_name, api_version, credentials=credentials)
return service
def setup_upload():
scope = ['https://www.googleapis.com/auth/drive']
key_file_location = os.path.join(os.path.dirname(settings.BASE_DIR), 'common/my-json-file.json')
service = get_service('drive', 'v3', scope, key_file_location)
file_path = os.path.join(os.path.dirname(settings.BASE_DIR), 'common/apple.png')
file_metadata = {'name': 'apple.png'}
media = MediaFileUpload(file_path, mimetype="image/png")
file = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
print(file.get('id')) #this returns an actual ID
setup_upload()
I do get a long ID string back from the last line of setup_upload(). But nothing is appearing on my actual Google Drive. I'm expecting to see the apple.png file pop up in my home directory.
What am I missing here?
Quoting from the tutorial you linked to:
"You can take the service account email address and give it access to a directory on your Google drive. It will then be allowed to upload to that directory, but you wont have access to the files. You will need to complete a second step and give yourself personally permission to access those files by updating or patching the file permissions."
Have you given the service account access to the place where you want to write the file? If you haven't specified where to upload the file to, the service account may just be uploading the file into its own Drive.
In addition to #user2705223's answer, if you want to be able to access the files it uploads, then you must grant yourself access to them through the service account. Check if you had a successful login credentials and authorize the service account with right scope. You can try following this documentation to help you do the authorization to make API requests.
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()
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.
I am try to access dbm api , I am authenticating the url using service account please find the sample code below
from oauth2client.service_account import ServiceAccountCredentials
from apiclient.discovery import build
from httplib2 import Http
scopes =['https://www.googleapis.com/auth/doubleclickbidmanager']
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'path/to/key/.jsonfile', scopes=scopes)
http_auth = credentials.authorize(Http())
body={}
dbm = build('doubleclickbidmanager', 'v1', http=http_auth)
print dbm
request = dbm.lineitems().downloadlineitems(body=body).execute()
print request
If I use oauth mechanism to authenticate the url the code is running properly, since I don't want user interaction, I need server to server mechanism so I used service account
Steps which I tried:
I have created the service account and downloaded the json key file and used in the code but when I try to run my code it throws the following error:
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/doubleclickbidmanager/v1/lineitems/downloadlineitems?alt=json returned "You are not authorized to use DoubleClick Bid Manager API. Please contact dbm-support#google.com.">
Please help , thanks in advance.
As others have said here, you want to log in to the DBM site and add your service account as a user:
Then, per this documentation, you can set up service account credentials using your client secrets json file. If you want that service account to be able to access reports you've created in DBM under your user account (what you log in with) you need to delegate domain-wide authority:
delegated_credentials = credentials.create_delegated('user#example.org')
http_auth = delegated_credentials.authorize(Http())
dbm = build('doubleclickbidmanager', 'v1', http=http_auth)
queries = dbm.queries().listqueries().execute()['queries']
A service account isn't you its a dummy user it has its on Google drive account for example, and by default it doesn't have access to any DoubleClick Bid Manager APIs. Service accounts need to be pre authorized to be able to access private data. So for it to be able to access your double click data you are going to have to grant it access.
Normally with any other API I would say you take the service account email address and add it as a user. I don't have access to double click so I am not even sure if you can add other users manually. They don't have anything in the documentation about service accounts kind of makes me think its not supported. Let us know if you manage to get it to work.
Is it possible to use Google Calendar API v3 with service accounts without having a google apps domain?
Basically what i want to do is create an event on my own calendar with a python script without having the script to prompt me for the user and password... anyone has any idea?
I used this code, but no luck:
import pprint
import pytz
import httplib2
import requests
from datetime import datetime, timedelta
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
with open('/home/xpto/service_account_certificate.pem', 'rb') as f:
key = f.read()
service_account_name = '000000000000-j...#developer.gserviceaccount.com'
credentials = SignedJwtAssertionCredentials(
service_account_name,
key,
scope=['https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.readonly'],
sub='xpto#gmail.com')
http = httplib2.Http()
http = credentials.authorize(http)
service = build(serviceName='calendar', version='v3', http=http)
I get this error:
auth2client.client.AccessTokenRefreshError: unauthorized_client: Unauthorized client or scope in request
Yes - just create a service account and this will give you an email address for the account.
Then just share your calendar with that email address giving it whatever permissions it needs.
Granting access of google resources to the service account works. In my case, I was trying to use a service account to push files onto google drive. By granting access of the folder on my google drive to the service account (email), I was able to see files and create files on google drive under my account.