1
votes

I have been trying to access some simple information on Google Shared Drive files from a Python 3.7 script:

The last time a Google Sheets file on a shared drive was modified.

I have created a service account in the GCP Drive API menu and it can access/edit/etc Google Sheets without any problem the via the Sheets API.

However, when I use the same service account for the Drive API, it does not return any info on files outside its own folder (which contains only one file: "Getting Started"). The account has access to all Cloud APIs, has Domain-wide Delegation with all scopes related to Drive API included in the API control menu in GSuite.

The email address of the service account has been properly added to all folders in the shared drive.

Any idea? Basically all I need is to know when is the last time a sheet was modified by any given user.

secret_cred_file = ...
SCOPES = ['https://www.googleapis.com/auth/drive']
credentials = service_account.Credentials.from_service_account_file(secret_cred_file, scopes=SCOPES)
service = discovery.build('drive', 'v3', credentials=credentials)
results = service.files().list(pageSize=10, fields="nextPageToken, files(id, name,modifiedTime)").execute()
items = results.get('files', [])

PS: I have seen this: Getting files from shared folder but it does not help

2
Why do you have nextPageToken in fields? To get a next page token, you would usually set pageToken='nextPageToken'. If you don't use any field arguments, do you get the same result? - iansedano
I tried without any field arguments and I still have only one document available. - Paul
I think you need to look into account impersonation. You would need to get a list of all your users impersonating an admin account, then impersonating each user to get a list of all files. - iansedano

2 Answers

1
votes

You need to impersonate your users.

It is not possible to make an API call to get all the files in your domain in one go.

In the Service Accounts article it says:

Service accounts are not members of your Google Workspace domain, unlike user accounts. For example, if you share assets with all members in your Google Workspace domain, they will not be shared with service accounts...This doesn't apply when using domain-wide delegation, because API calls are authorized as the impersonated user, not the service account itself.

So unfortunately you can't just share a file with a service account. To get all the files in your domain you would need to:

  1. Impersonate an admin account and get a list of all the users.
  2. Impersonate each user and make Drive API request for each.

Here is a good quick start for the Python Library, specifically this section

Remember to set permissions in both the GCP console and the Admin console though it seems like you have done this correctly.

Example script

from google.oauth2 import service_account
from googleapiclient.discovery import build

def main():

    SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly',
        'https://www.googleapis.com/auth/admin.directory.user.readonly']
    SERVICE_ACCOUNT_FILE = 'credentials.json'

    credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)

    # Admin SDK to get users
    admin_delegated_credentials = credentials.with_subject('[ADMIN_EMAIL]')
    admin_service = build(
        'admin',
        'directory_v1',
        credentials=admin_delegated_credentials
        )

    admin_results = admin_service.users().list(customer='my_customer', maxResults=10,
                                orderBy='email').execute()
    users = admin_results.get('users', [])

    if not users:
        print('No users in the domain.')
    else:
        for user in users:
            print(u'{0} ({1})'.format(user['primaryEmail'],
                user['name']['fullName']))

            # Drive to get files for each user
            delegated_credentials = credentials.with_subject(user['primaryEmail'])

            drive_service = build(
                'drive',
                'v3',
                credentials=delegated_credentials
                )
            drive_results = drive_service.files().list(
                pageSize=10,
                fields="nextPageToken, files(id, name,modifiedTime)"
                ).execute()
            items = drive_results.get('files', [])

            if not items:
                print('No files found.')
            else:
                print('Files:')
                for item in items:
                    print(u'{0} ({1})'.format(item['name'],
                        item['id']))

if __name__ == '__main__':
    main()

Explanation

This script has two scopes:

  • 'https://www.googleapis.com/auth/drive.metadata.readonly'
  • 'https://www.googleapis.com/auth/admin.directory.user.readonly'

The project initialized in the GCP Cloud console has also been granted these scopes from within the Admin console > Security > API Controls > Domain wide delegation > Add new

The first thing the script does is build the credentials using from_service_account_file:

credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)

Then it builds the delegated credentials, that is, the user to be impersonated:

admin_delegated_credentials = credentials.with_subject('[ADMIN_EMAIL]')

From there it can build the service as normal. It gets a list of the users, loops through the users and lists their files. You could adapt this to your needs.

References

0
votes

I was able to list shared drive files without impersonating a user by adding some parameters to the list method as stated on google documentation:

Implement shared drive support

Shared drives follow different organization, sharing, and ownership models from My Drive. If your app is going to create and manage files on shared drives, you must implement shared drive support in your app. To begin, you need to include the supportsAllDrives=true query parameter in your requests when your app performs these operations:

files.get, files.list, files.create, files.update, files.copy, files.delete, changes.list, changes.getStartPageToken, permissions.list, permissions.get, permissions.create, permissions.update, permissions.delete

Search for content on a shared drive

Use the files.list method to search for shared drives. This section covers shared drive-specific fields in the files.list method. To search for shared drive, refer to Search for files and folders.

The files.list method contains the following shared drive-specific fields and query modes:

driveId — ID of shared drive to search.

includeItemsFromAllDrives — Whether shared drive items should be included in results. If not present or set to false, then shared drive items are not returned.

corpora — Bodies of items (files/documents) to which the query applies. Supported bodies are user, domain, drive, and allDrives. Prefer user or drive to allDrives for efficiency.

supportsAllDrives — Whether the requesting application supports both My Drives and shared drives. If false, then shared drive items are not included in the response.

Example

service.files().list(includeItemsFromAllDrives=True, supportsAllDrives=True, pageSize=10, fields="nextPageToken, files(id, name,modifiedTime)").execute()

It is nice to remember that the folder or files needs to be shared with the service account.