1

I'm trying to replace a Google slides shape filled with a placeholder text with an image via API. The image in question is allocated in a team Drive unit. I've read several solutions like this and this, and I have tried to set the permissions in the image as "reader" and "anyone with link", both via API and manually, and nothing works. I get a "Access to the provided image was forbidden" error everytime. However, I can download the image wihout problems via API and without giving extra permissions. I'm using v3 of the Drive API and v1 for Slides API.

Below is the code I'm using:

def replace_shape_with_image(slides_service, url, presentation_id, contains_text):
    requests = [
        {
            "replaceAllShapesWithImage": {
                "imageUrl": url,
                "replaceMethod": "CENTER_INSIDE",
                "containsText": {
                    "text": "{{" + contains_text + "}}",
                }
            }
        }]
    body = {
        'requests': requests
    }
        
    response = slides_service.presentations().batchUpdate(presentationId=presentation_id,body=body).execute()
    return response


def create_public_permissions(drive_service, file_id):
    request = drive_service.permissions().create(
        fileId=file_id,
        supportsAllDrives=True,
        fields='id, type, role',
        body={
            'role':'reader',
            'type':'anyone'
        }
    )
    response = request.execute()
    return response


file_id = '123'
presentation_id = '789'
# drive_service = etc
# slides_service = etc
create_public_permissions(drive_service, file_id) # Here I put the actual service object and file ID. Manually verified and it works
url = f'https://drive.google.com/uc?export=download&id={file_id}'
# also tried url = https://drive.google.com/uc?id={file_id}&export=download
replace_shape_with_image(slides_service, url, presentation_id, 'test') # shape in slide has "test" as text

The following error is returned:

googleapiclient.errors.HttpError: <HttpError 400 when requesting https://slides.googleapis.com/v1/presentations/1cDSov4IKFHSyzaXjFYNYPB7EYlMMtInamYv0AwXiazw:batchUpdate?alt=json returned "Invalid requests[0].replaceAllShapesWithImage: Access to the provided image was forbidden.">

For downloading the file I use this code, which works without problem:

import io
from googleapiclient.http import MediaIoBaseDownload


tmp_file_path = r'C:\Users\me\tmp\testimage.png'
fh = io.FileIO(tmp_file_path, 'wb')
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
    status, done = downloader.next_chunk()

I know there have been changes in the Drive API recently (over the last year), and most questions about this are old and the solutions are deprecated, and more recent ones doesn't seem to work for me. Any ideas in how to solve this issue, if possible?

Thanks in advance!

jcf
  • 602
  • 1
  • 6
  • 26
  • I see you're using two services (drive_service and slides_service) - how are you authenticating these? Are they part of the same GCP project with the Drive API enabled? Also what are your scopes for each service? – Rafa Guillermo Jun 26 '20 at 10:21
  • Yes, they are part of the same project. Each service has service_account mode. Drive app has 'drive' scope and slides one has 'presentations'. Both are using OAUTH2. This configurations are handled by another person so I'm not fully aware of all of them at the moment. – jcf Jun 26 '20 at 10:44
  • Gotcha. Are you using impersonation or is this being run as the service account itself? – Rafa Guillermo Jun 26 '20 at 11:03
  • It is being run as the service account itself – jcf Jun 26 '20 at 11:11
  • Curious, I can't seem to replicate this in my environment. How are you modifying the permissions of the Drive file on your account if you aren't using impersonation? Also, what happens if you add `https://www.googleapis.com/auth/drive.readonly` to the scopes of `slides_service`? – Rafa Guillermo Jun 26 '20 at 11:53

1 Answers1

2

It's known issue: https://issuetracker.google.com/issues/150933939

As it was happening with one of our projects, we figured out that error is thrown not for all Google Drive images, but for random ones when using replaceAllShapesWithImage Slides API request.

Only possible workaround is to wrap your requests code in try / catch and use some placeholder image in case replaceAllShapesWithImage requests are keep failing.

Example code (in Google Apps Script):

var slideImageNotFoundPlaceholder = 'https://example.com/../placeholder.png';

try {
  Slides.Presentations.batchUpdate({'requests': slidesReqs}, presentationFileId);
}
catch(err)
{
    // loop all requests
    for (var i = 0; i < slidesReqs.length; i++)
    {
      // update replaceAllShapesWithImage requests
      if (slidesReqs[i].replaceAllShapesWithImage)
      {
        // replace with image not found placeholder
        slidesReqs[i].replaceAllShapesWithImage.imageUrl = slideImageNotFoundPlaceholder;
      }

    }

    Slides.Presentations.batchUpdate({'requests': slidesReqs}, presentationFileId);
}

It is not perfect solution, but all other requests are getting executed, so you won't have your presentations being totally failed.

P.S. hopefully Google will fix Google Drive images being used in Slides API, so "star" the issue linked in the beginning of the answer to make it happen faster.

Kos
  • 4,890
  • 9
  • 38
  • 42