When I want to get file changes, I get the following error:
The user does not have sufficient permissions for file ***
My code:
SCOPES = ['https://www.googleapis.com/auth/drive']
credentials = service_account.Credentials.from_service_account_file(
'service.json', scopes=SCOPES)
service = build('drive', 'v3', credentials=credentials)
service.revisions().list(fileId=DOC_ID)
IAM:
P.S.: This file was created by me in docs.google.com.
Answering the question, since it was added in the comment. The error message "The user does not have sufficient permissions for file ***" usually refers to a problem with the permissions in the file. To solved the issue you should share the file with the services account. This is base in the Google Documentation here
Related
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.
Google Drive API docs are not super great at helping determine best way to authenticate using a service account that I can then upload a .png file to the Drive. My end goal it so upload a .png file, copy a template doc, batch update that doc using text replace, and insert the newly uploaded .png image into that doc.
Sample code below:
from dotenv import load_dotenv
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
def credentials_from_file():
credentials = service_account.Credentials.from_service_account_file(
os.getenv('SERVICE_ACCOUNT_FILE'),
scopes=os.getenv('SCOPES')
)
drive_service = build('drive', 'v3', credentials=credentials)
return drive_service
def google_upload(drive_service, metadata_name, parents, permissions, file_path, mime_type):
file_metadata = {'kind':'drive#file', 'name':metadata_name, 'parents':parents, 'permissions':permissions}
media = MediaFileUpload(file_path, mimetype=mime_type)
file = drive_service.files().create(body=file_metadata, media_body=media, fields='id', supportsAllDrives=True).execute()
print('File ID: %s' % file.get('id'))
Implementation of Code
credentials = credentials_from_file()
drive_service = build('drive', 'v3', credentials=credentials)
metadata_name = custom_variables_png_table_img
parents = ['xxxx']
permissions = [{'kind':'drive#permission', 'emailAddress':os.getenv('EMAIL_ACCOUNT'), 'role':'owner'}]
file_path = custom_variables_png_table_img
mime_type = 'image/png'
google_upload(drive_service, metadata_name, parents, permissions, file_path, mime_type)
EDIT:
Looks like I forgot to actually write was the problem is. It's two fold.
I keep getting 2 errors when trying to run the google_upload() function which looks like an authentication error with the service account.
Error #1: jwt_grant access_token = response_data["access_token"] KeyError: 'access_token'
The above exception was the direct cause of the following exception:
Error #2: google.auth.exceptions.RefreshError: ('No access token in response.', {'id_token': 'xxx'})
Permissions being properly set on the recently uploaded image file.
The code you are using currently seams to be the same as what I have seen before.
from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
SCOPES = ['https://www.googleapis.com/auth/drive.readonly']
KEY_FILE_LOCATION = '<REPLACE_WITH_JSON_FILE_PATH_TO_FILE>'
def initialize_drive():
"""Initializes an Google Drive API V3 service object.
Returns:
An authorized Google Drive API V3 service object.
"""
credentials = ServiceAccountCredentials.from_json_keyfile_name(
KEY_FILE_LOCATION, SCOPES)
# Build the service object.
driveService = build('drive', 'v4', credentials=credentials)
return driveService
You haven't mentioned what is wrong with your code however i can make a few guesses.
The thing is that you mention you want to upload an image and the insert it into a document. You need to remember that the Google drive api is just a file storage api it can do more then that store files.
When you upload the file using the service account you need to remember that the service account is not you. So when you are uploading this file to this directory parents = ['xxxx'] where ever that directory is, either on the service accounts drive account or if this directory is one of your persona directories which you have shared with the service account. You may not have permissions to see this file.
By calling permissions create after uploading your file you can grant your own personal account permissions to access the file as well.
As for adding the image to a document. well the only way google can help you with that is if it is a Google doc type document. Then you would need to go though the Google docs api which would then give you access to add things programmaticlly to a document. I haven't used this API much so im not sure if it has the ability to add images to a document.
You should be able to use the google docs api with your service account you will just need to create a docs service using the same creds you already have from google drive.
service = build('docs', 'v1', credentials=creds)
I have started to develop some apis to create users in my G suite directory.
I followed the service account tutorials along with the Directory tutorials for python. The code I have is very simple just to test out how it will work.
from google.oauth2 import service_account
from googleapiclient.discovery import build
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']
SERVICE_ACCOUNT_FILE = 'file'
creds = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('admin', 'directory_v1', credentials=creds)
results = service.users().list(customer='i am not sure what customer is', maxResults=10, orderBy='email').execute()
#this line produces the error.
#Vscode also states the service has no member users. But I did install all #the libraries
users = results.get('users', [])
print(users)
The documentation to me is unclear about most things. When I run this I get
googleapiclient.errors.HttpError: <HttpError 400 when requesting https://www.googleapis.com/admin/directory/v1/users?customer=students&maxResults=10&orderBy=email&alt=json returned "Bad Request">
When I change customer from my_customer to something else I get Invalid Input.
Any suggestions to what may cause this error and preferably how to work with this api via a service account?
Now I did enable the directory api and create the service account and downloaded the service account file as well. Am I missing a step?
I would also prefer if someone has a better documentation that I was unable to find.
Finally I resolved this issue by setting another parameter "subject" when calling "service_account.Credentials.from_service_account_file".
Ensure that the service account has domain-wide-delegation enabled and has the proper scopes.
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 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)