3

I would like to upgrade the following code from oauth2client to google-auth. Yes, this code does work and is a copy paste from the Google demo on their site.

from __future__ import print_function
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools

# If modifying these scopes, delete the file token.json.
SCOPES = 'https://www.googleapis.com/auth/presentations.readonly'

# The ID of a sample presentation.
PRESENTATION_ID = '<some id>'

def main():
    """Shows basic usage of the Slides API.
    Prints the number of slides and elments in a sample presentation.
    """
    # 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.
    # delete the file to for authentication and authorization again
    store = file.Storage('token.json')
    creds = store.get()

    if not creds or creds.invalid:
        # credentials.json is issued by Google for the application
        flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
        creds = tools.run_flow(flow, store)

    service = build('slides', 'v1', http=creds.authorize(Http()))

    # 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()

I was able to upgrade most of it (I think) but could not find the equivalent for tools.run_flow using google-auth. Below is my upgraded version, and it has everything (I think) except the mechanism to do tools.run_flow. How is tools.run_flow accomplished using google-auth?

from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
from googleapiclient.discovery import build

SCOPES = ['https://www.googleapis.com/auth/presentations.readonly']
PRESENTATION_ID = '<some id>'

credentials = service_account.Credentials.from_service_account_file(
    "the-json-file.json",
    scopes=SCOPES
)    

service = build('slides', 'v1', credentials=credentials)
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'))))

When I run the above code using the google-auth approach, I get the following result:

googleapiclient.errors.HttpError: <HttpError 403 when requesting https://slides.googleapis.com/v1/presentations/<some id>?alt=json returned "The caller does not have permission">

Additional context, I am trying to build a Python script which accesses Google Slides to perform some processing on slide content. I am reading only, not writing to Google Slides. The code above is my attempt to handle the authentication and authorization part of accessing the Google Slides content for my GSuite account (I am the admin of my organization's GSuite). The first example using the older oauth2client works great, but since oauth2client is deprecated and google-auth is recommended I would like to do it the "right" way using the latest library.

UPDATE

I resolved part of my problem with the following code:

from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
from googleapiclient.discovery import build

from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

import os
import json


def get_credentials():

    CLIENT_SECRET_FILE = '<some file>'
    SCOPES = ['https://www.googleapis.com/auth/presentations.readonly']

    credentials_path = '<some path>/token.json'

    if os.path.exists(credentials_path):
        # expect these to be valid. may expire at some point, but should be refreshed by google api client...
        return Credentials.from_authorized_user_file(credentials_path, scopes=SCOPES)
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
            CLIENT_SECRET_FILE,
            scopes=SCOPES,
            redirect_uri='urn:ietf:wg:oauth:2.0:oob')

        auth_url, _ = flow.authorization_url(prompt='consent')

        print('Please go to this URL and finish the authentication flow: {}'.format(auth_url))
        code = input('Enter the authorization code: ')

        flow.fetch_token(code=code)
        credentials = flow.credentials

        credentials_as_dict = {
            'token': credentials.token,
            'refresh_token': credentials.refresh_token,
            'id_token': credentials.id_token,
            'token_uri': credentials.token_uri,
            'client_id': credentials.client_id,
            'client_secret': credentials.client_secret
        }

        with open(credentials_path, 'w') as file:
            file.write(json.dumps(credentials_as_dict))

        return credentials

# The ID of a sample presentation.
PRESENTATION_ID = '<some id>'

service = build('slides', 'v1', credentials=get_credentials())
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'))))

My challenge now is causing the web browser to open automatically. I can copy and paste the link and get the code manually and it all works. Ideally, I would like the automatic opening of the web browser and capturing of the token like it did in the older library.

SOLVED

Update a section code in the previous code sample to this:

    flow = InstalledAppFlow.from_client_secrets_file(
        CLIENT_SECRET_FILE,
        scopes=SCOPES,
        redirect_uri='urn:ietf:wg:oauth:2.0:oob')

    credentials = flow.run_local_server()

    credentials_as_dict = {
        'token': credentials.token,
        'refresh_token': credentials.refresh_token,
        'id_token': credentials.id_token,
        'token_uri': credentials.token_uri,
        'client_id': credentials.client_id,
        'client_secret': credentials.client_secret
    }

Web browser opens automatically and captures the token value. All is good.

0 Answers0