Marking a file as viewed by a user - google-drive-api

I created a file using the create API
f_id = files().create(body={'name': 'Dummy', 'mimeType': '*/*'}, media_body='/abc.txt', fields='id')
Then I share this with everyone in my domain, by link(so marking allowFileDiscovery as False)
permissions().create(fileId=f_id, body={'type': 'domain', 'domain': ,'allowFileDiscovery': false, 'role': 'reader'}, fields='id').execute()
After this with a different user's service account, I do this to mark the file as viewed by the user
files().update(fileId=f_id, body={'viewedByMeTime': (datetime.now() - timedelta(seconds=5)).isoformat("T") + "Z"}).execute()
This used to work until yesterday, but now I get this error
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/drive/v3/files/<f_id>?alt=json returned "The user does not have sufficient permissions for this file.">
This still works for a folder I created with the same permissions, but not restricted to domain. I am using GDrive API version v3. Has there been any recent changes here which could cause this issue?

Related

Google API to create/update files on 'Shared with me' folders

I have been trying to use the Google API to create files on a folder that's been shared with me by another user (I made sure I have edit permissions on it). When I was using the files.create module with supportsAllDrives=True I got the following error message:
{
"errorMessage": "<HttpError 404 when requesting https://www.googleapis.com/upload/drive/v3/files?supportsTeamDrives=true&alt=json&uploadType=multipart returned "File not found: 1aLcUoiiI36mbCt7ZzWoHr8RN1nIPlPg7.". Details: "[{'domain': 'global', 'reason': 'notFound', 'message': 'File not found: 1aLcUoiiI36mbCt7ZzWoHr8RN1nIPlPg7.', 'locationType': 'parameter', 'location': 'fileId'}]">",
"errorType": "HttpError",
"requestId": "fc549b9e-9590-4ab4-8aaa-f5cea87ba4b6",
"stackTrace": [
" File "/var/task/lambda_function.py", line 154, in lambda_handler\n upload_file(service, download_path, file_name, file_name, folder_id, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')\n",
" File "/var/task/lambda_function.py", line 78, in upload_file\n file = service.files().create(\n",
" File "/opt/python/googleapiclient/_helpers.py", line 131, in positional_wrapper\n return wrapped(*args, **kwargs)\n",
" File "/opt/python/googleapiclient/http.py", line 937, in execute\n raise HttpError(resp, content, uri=self.uri)\n"
]
}
After a bit of digging in, I found that 'Shared Drives' is different from 'Shared with me' and all the APIs I found so far apply to the 'Shared Drives' only. The supportsTeamDrives=True has been deprecated and I was not able to find a related replacement parameter in the docs. There is a parameter sharedWithMe=True for the file.list api and I'm not sure how I can use this in my code because file.create doesn't see the folderID for a 'Shared with me' folder anyway. Any suggestions are appreciated in advance!
My current code:
def upload_file(service, file_name_with_path, file_name, description, folder_id, mime_type):
media_body = MediaFileUpload(file_name_with_path, mimetype=mime_type)
body = {
'name': file_name,
'title': file_name,
'description': description,
'mimeType': mime_type,
'parents': [folder_id]
}
file = service.files().create(
supportsAllDrives=True,
supportsTeamDrives=True,
body=body,
media_body=media_body).execute()
Modified answer to include more details:
You are correct 'Shared Drives' are different from 'Shared With Me'. First off, you need to get the ID from the shared with you folder, for this you can use files:list. To upload files to that folder or any type of folder you can use the modified code below:
from __future__ import print_function
import pickle
import os.path
from googleapiclient.http import MediaFileUpload
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2 import credentials, service_account
# Scopes required by this endpoint -> https://developers.google.com/drive/api/v3/reference/files/create
SCOPES = ['https://www.googleapis.com/auth/drive']
"""
To upload/create a file in to a 'Shared with me' folder this script has the following configured:
1. Project:
* Create project
* Enable the Google Workspace API the service account will be using: https://developers.google.com/workspace/guides/create-project
2.Consent screen:
* Configure the consent screen for the application
* Create credentials for your service account depending on the type of application to be used with https://developers.google.com/workspace/guides/create-credentials#create_a_service_account
Once your Service Account is created you are taken back to the credentials list (https://console.cloud.google.com/apis/credential) click on the created Service Account, next click on ‘Advanced settings’ and copy your client ID
3. Scopes
* Collect the scopes needed for your service account/application
https://developers.google.com/identity/protocols/oauth2/scopes
4. Grant access to user data to a service account in Google Workspace https://admin.google.com/ac/owl/domainwidedelegation
* In the "Client ID" field, paste the client ID from your service account
* In the "OAuth Scopes" field, enter a comma-delimited list of the scopes required by your application. This is the same set of scopes you defined when configuring the OAuth consent screen.
* Click Authorize.
5. In your code you need to impersonate the account the folder was shared with, if it was your account, you add your account here:
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_creds = credentials.with_subject('user#domain.info')
"""
def main():
SERVICE_ACCOUNT_FILE = 'drive.json' #Service Account credentials from Step 2
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_creds = credentials.with_subject('user#domain.xyz')
service = build('drive', 'v3', credentials=delegated_creds)
media = MediaFileUpload(
'xfiles.jpg',
mimetype='image/jpeg',
resumable=True
)
request = service.files().create(
media_body=media,
body={'name': 'xfile new pic', 'parents': ['1Gb0BH1NFz30eau8SbwMgXYXDjTTITByE']} #In here 1Gb0BH1NFz3xxxxxxxxxxx is the 'Shared With ME'FolderID to upload this file to
)
response = None
while response is None:
status, response = request.next_chunk()
if status:
print("Uploaded %d%%." % int(status.progress() * 100))
print("Upload Complete!")
if __name__ == '__main__':
main()
Where:
parents is the ID of the folder shared with you.
See here for more documentation details
After a chat with a Google Workspace API specialist, turns out there is no API available to perform the above task. For clarity, refer the picture where my target folder lies.
Difference between 'Shared Drive' and 'Shared with me' (image)
Here's the response from the Support Agent:
I reviewed your code and everything was done perfectly, so I spoke to
our Drive Specialists, and they have explained to me that "Shared with
me" it's more than anything a label, and because you are not the owner
of the file, (like you would be if they were in "My Drive" )nor the
co-owner (if they were located in "Shared Drive") it does not allow
you to use any type of API in order to automate file creation or
deletion or anything for that matter.
In this case you can either make a copy on your Drive and automate it
there, and just update it every now and then in the file that was
shared with you, or just ask the user to move it to the "Shared Drive"
and access it from there.
I confess I'm a little disappointed that there is no API way to add/delete/edit in another user's folder in spite of having permissions to do so. My understanding as a developer is that the CLI is the ultimate most powerful way to interact with any service. GUI comes second to CLI, it's just a more visually appealing medium. Often times, when we are not able to perform a task using the GUI, we turn to CLI and manage high granularity and precision.
But this was a completely upside down scenario! I'm failing to understand how I'm able to access the 'shared folder' and make adds and deletes through the GUI but unable to do the same using a script. I understand now that 'Shared with me' is just a label and not a 'location' for me to access the folder but surely I would have assumed there was another API way to access a folder that belonged to another user (using the person's username/ID for identification, folder path as target, verifying if I have permissions to make said changes for authentication, returning an error if I don't, lastly executing the API).
If someone's able to explain to me if there is a specific reason why this is not made available to end users, I would love to learn about it please.
EDIT
I'm a bit late posting the solution here, but the issue turned out to be that the google workspace service account that was being used by my API did not have write permissions to the Shared Drive I was trying to query. Once the service account was given the required edit permissions, my code worked perfectly.

Getting "The provided image is in an unsupported format" error when trying to insert an image in a Google Slides presentation

I am trying to insert an image (PNG) in a Google Slide presentation using the Slides API. I do this by first uploading the image to the user's Drive, obtaining the url, passing that along to the Slide API via the correct request and then deleting the image file.
What used to work as of a few weeks ago:
image_url = '%s&access_token=%s' % (
drive_service.files().get_media(fileId=image_file_id).uri,
creds.token)
However, there have been changes to the Drive API, such that URLS constructed this way no longer work.
I am having difficulty figuring out the new correct URL to use here. The options as per the doc that describes the change are:
Use webContentLink -- Downloads
Use webViewLink -- View
Use exportLinks -- Export
I use code that looks like this to get these links:
upload = drive_service.files().create(
body={'name': 'My Image File'},
media_body=media_body,
fields='webContentLink, id, webViewLink').execute()
image_url = upload.get('webContentLink')
I have tried both #1 and #2 and get the following error:
"Invalid requests[0].createImage: The provided image is in an unsupported format."
I have also been receiving the following error intermittently:
"Invalid requests[0].createImage: Access to the provided image was forbidden."
I verified that I am able to download / view the image from the URLs generated in #1 and #2. I didn't try #3 since I am not trying to export to a different format.
What would be the best way to go about figuring out the correct URL to use?
From your script, I think that the reason of your issue is due to this. By this, the query parameter of access_token cannot be used. Under this situation, when image_url = '%s&access_token=%s' % (drive_service.files().get_media(fileId=image_file_id).uri,creds.token) is used, the login page is returned. By this, such error occurs. So as a workaround, how about the following flow?
Flow:
Upload a PNG file.
Publicly share the PNG file by creating a permission.
Insert the PNG file to Slides.
Close the shared PNG file by deleting the permission.
When the image file is put to the Slides, even when the permission of file is deleted, the image is not removed from the Slides. This workaround uses this.
Sample script:
For above flow, the sample script of python is as follows. Please set the variables of uploadFilename, presentation_id and pageObjectId
uploadFilename = './sample.png' # Please set the filename with the path.
presentation_id = '###' # Please set the Google Slides ID.
pageObjectId = '###' # Please set the page ID of the Slides.
drive = build('drive', 'v3', credentials=creds)
slides = build('slides', 'v1', credentials=creds)
# 1. Upload a PNG file from local PC
file_metadata = {'name': uploadFilename}
media = MediaFileUpload(uploadFilename, mimetype='image/png')
upload = drive.files().create(body=file_metadata, media_body=media, fields='webContentLink, id, webViewLink').execute()
fileId = upload.get('id')
url = upload.get('webContentLink')
# 2. Share publicly the uploaded PNG file by creating permissions.
drive.permissions().create(fileId=fileId, body={'type': 'anyone', 'role': 'reader'}).execute()
# 3. Insert the PNG file to the Slides.
body = {
"requests": [
{
"createImage": {
"url": url,
"elementProperties": {
"pageObjectId": pageObjectId
}
}
}
]
}
slides.presentations().batchUpdate(presentationId=presentation_id, body=body).execute()
# 4. Delete the permissions. By this, the shared PNG file is closed.
drive.permissions().delete(fileId=fileId, permissionId='anyoneWithLink').execute()
Note:
I thought that from your script, you might be using google-api-python-client with python. So I proposed the sample script for python.
In this case, the scopes for using Slides API and Drive API are required. Please be careful this.
In the case of Google Apps Script, you can see the sample script at here.
References:
Upcoming changes to the Google Drive API and Google Picker API
Permissions: create
Permissions: delete
If I misunderstood your question and this was not the direction you want, I apologize.
I was running into the same error even when using the flow involving granting temporary permissions access then removing the permissions after calling .createImage() or .replaceAllShapesWithImage()
I also ran into this error when creating permissions for a folder containing those images: "Invalid requests[0].replaceAllShapesWithImage: Access to the provided image was forbidden." Not sure why the permissions are not propagating to the images...
Following Kos' comment, switching to jpg file type worked for me.
Edit:
It appears I am also required to set the scope to 'https://www.googleapis.com/auth/drive' in order for it to work, which isn't ideal, but is sufficient for now.
Edit 2:
Nevermind it appears to be inconsistent. I am running into the permissions access error again. Deleting my token.pickle does not seem to fix either

How to get an attachment's data with the openproject API

I'm currently trying to show the data about an openproject workpackage in my website using the openproject api v3 (I'm using PHP with symfony.).
I have no issue getting the general data (subject, description, priority, etc), but I'm not sure I know how to show the attachments to the user.
I tried using "/api/v3/work_packages/".$id."/attachments", but there is no image in the json returned:
"_type":"Collection",
"total":1,
"count":1,
"_embedded":{
"elements":[{
"_type":"Attachment",
"id":1888,"fileName":"128-128-logo.png",
"fileSize":9583,
"description":{"format":"plain","raw":null,"html":""},
"contentType":"image/png",
"digest":{"algorithm":"md5","hash":"/*-hash-here-*/"},
"createdAt":"2018-07-09T16:49:26Z",
"_links":{
"self":{"href":"/api/v3/attachments/1888","title":"128-128-logo.png"},
"author":{"href":"/api/v3/users/7","title":"User Name"},
"container":{"href":"/api/v3/work_packages/1697","title":"Subject -\u003E Test Query OpenProject"},
"downloadLocation":{"href":"/attachments/1888/128-128-logo.png"},
"delete":{"href":"/api/v3/attachments/1888","method":"delete"}
}
}]
},
"_links":{
"self":{"href":"/api/v3/work_packages/1697/attachments"}
}
I also tried with a direct link to the attachment, but got
"_type":"Attachment",
"id":1888,
"fileName":"128-128-logo.png",
"fileSize":9583,
"description":{"format":"plain","raw":null,"html":""},
"contentType":"image/png",
"digest":{"algorithm":"md5","hash":"/*-hash-here-*/"},
"createdAt":"2018-07-09T16:49:26Z",
"_links":{
"self":{"href":"/api/v3/attachments/1888","title":"128-128-logo.png"},
"author":{"href":"/api/v3/users/7","title":"User Name"},
"container":{"href":"/api/v3/work_packages/1697","title":"Subject -\u003E Test Query OpenProject"},
"downloadLocation":{"href":"/attachments/1888/128-128-logo.png"},
"delete":{"href":"/api/v3/attachments/1888","method":"delete"}
}
The download location is not an API url and return 406 - unauthorised if I try to access it with an img tag ( <img src="https://XXXXXX.openproject.com/attachments/1888/128-128-logo.png"> )
So I guess my question is: How do I show my users the attachments even if they don't have an account on my openproject?
To show the attachment on your website you will have to:
Grant anonymous access to your openproject instance.
Have the attachment in a public project, meaning that it will have to be attached to a work package, wiki page ... of a public project
Grant anonymous access to your openproject instance
Uncheck "Authentication required" in the system settings ("Administration" -> "System settings" -> "Authentication" (Tab)") and save.
Turn project public
Check "Public" in the project settings (After navigating to a project -> "Project settings" -> "Information" (Tab)) and save.
Only attachments attached to a resource of that now public project will be accessible by your website.
Consequences
Everyone will be able to access your OpenProject instance, at least the part with the public project.
Alternative 1
If it is a private website and all visitors of that website will also have an account on the OpenProject instance and if it is assured, that they will always be logged into OpenProject when visiting the website, their browser will send the session cookie to the OpenProject instance automatically upon fetching the image which will result in them being authorized to get the attachment.
Alternative 2
Once OP 8.0 is released, API clients will be able to download attachments. Then a script with an API key could download the attachments of interest and store them in a location accessible to your website. The website visitors would then get the attachment from the website directly.

Google Drive API - Watch notifies "sync", but nothing after that

I'm trying to subscribe to changes in a Team Drive folder or its direct-children files. I get a notification with X-Goog-Resource-State: sync, but no notifications after that. I've tried renaming files, editing file contents, adding/removing files. Nothing triggers additional notifications.
I'm doing this with the Ruby API:
drive = Google::Apis::DriveV3::DriveService.new
drive.authorization = ... # json auth for a service account with access to the drive
file_id = ... # id of the folder to watch
channel = {
id: 'test',
type: 'web_hook',
address: 'https://example.com/notifications',
}
drive.watch_file(file_id, channel, supports_team_drives: true)
I know that this auth can list and access files within the team drive without problem. I get no errors from the watch_file call. I get the "sync" event successfully, but no "update" or similar events later.
Am I missing something? How can I debug this?

Inserting images in a GCE account

I am trying to register/insert an image in a gce account. This raw image source is shared publicly. However, I see this error when making the insert call
{u'status': u'DONE', u'kind': u'compute#operation', u'name': u'operation-1413287109771-505608c24bef9-5c02ac49-1dbd219b', u'startTime': u'2014-10-14T04:45:10.142-07:00', u'httpErrorMessage': u'FORBIDDEN', u'insertTime': u'2014-10-14T04:45:09.871-07:00', u'targetLink': u'https://www.googleapis.com/compute/v1/projects/qubole-gce-test/global/images/image-v1-36', u'operationType': u'insert', u'error': {u'errors': [{u'message': u"Required 'read' permission for 'rawDisk.source'", u'code': u'PERMISSIONS_ERROR'}]}, u'progress': 100, u'endTime': u'2014-10-14T04:45:11.625-07:00', u'httpErrorStatusCode': 403, u'id': u'15732625722022858454', u'selfLink': u'https://www.googleapis.com/compute/v1/projects/qubole-gce-test/global/operations/operation-1413287109771-505608c24bef9-5c02ac49-1dbd219b', u'user': u'964307357192-smkpef2g0v8q3oopq44tvh1d3h1lplgk#developer.gserviceaccount.com'}
I googled and from the posts I found, it says thay you have to share the image publicly - which I have already done.
I am using this API https://cloud.google.com/compute/docs/reference/latest/images/insert
The rawDisk.source that I am using here is the GCS URL which I have made public, yet I am getting the error I pasted.
As discussed on the gce-disussion mailing list this is a known regression in GCE that the engineering team is working on. As a workaround you can get this API working by adding the GCS read-write scope (https://www.googleapis.com/auth/devstorage.read_write) to the scopes you request when performing OAuth2 authentication.