1

I've searched for hours for an answer to this, so please bear with me!

I'm using Apps Script to connect to the Google Drive API. I have created a service account in Console, and collected the relevant credentials. I've got the following code:

function test()
{


function getOAuthService(user) {
var JSON = {
    "private_key": "-----BEGIN PRIVATE KEY-----\nMY KEY HERE\n",
    "client_email": "clientemail@clientemail.iam.gserviceaccount.com",
    "client_id": "11111111111111",
    "user_email": "myemail@myemail.com"
};
    return OAuth2.createService("Service Account")
        .setTokenUrl('https://accounts.google.com/o/oauth2/token')
        .setPrivateKey(JSON.private_key)
        .setIssuer(JSON.client_email)
        .setSubject(JSON.user_email)
        .setPropertyStore(PropertiesService.getScriptProperties())
        .setParam('access_type', 'offline')
        .setScope('https://www.googleapis.com/auth/drive');
}

function getUserFiles() {
    var service = getOAuthService();
    service.reset();

        var url = 'https://www.googleapis.com/drive/v2/files?pageSize=1';
        var response = UrlFetchApp.fetch(url, {
            headers: {
                Authorization: 'Bearer ' + service.getAccessToken()
            }, muteHttpExceptions: true
        });
        Logger.log(response.getContentText());
    
}

function reset() {
    var service = getOAuthService();
    service.reset();
}

getUserFiles()
}

I have also added the OAuth2 library into the script.

Every time I run this I am met with the following error though: Error: Access not granted or expired. (line 454, file "Service")

Does anybody have any ideas?

Thanks!

sitestem
  • 21
  • 4

3 Answers3

1

How about the following modification?

From:

return OAuth2.createService("Service Account")
    .setTokenUrl('https://accounts.google.com/o/oauth2/token')
    .setPrivateKey(JSON.private_key)
    .setIssuer(JSON.client_email)
    .setSubject(JSON.user_email)
    .setPropertyStore(PropertiesService.getScriptProperties())
    .setParam('access_type', 'offline')
    .setScope('https://www.googleapis.com/auth/drive');

To:

return OAuth2.createService("Service Account")
    .setTokenUrl('https://accounts.google.com/o/oauth2/token')
    .setPrivateKey(JSON.private_key)
    .setIssuer(JSON.client_email)
    // .setSubject(JSON.user_email)  // <--- Removed
    .setPropertyStore(PropertiesService.getScriptProperties())
    .setParam('access_type', 'offline')
    .setScope('https://www.googleapis.com/auth/drive');

Note:

  • As additional information, when the file list is retrieved by the service account, the Drive is different from your Google Drive of your Google account. Please be careful this. If you want to retrieve the file list of your Google Drive, for example, when a folder in your Google Drive is shared with the email of service account, the file list in the folder can be retrieved by the service account.

  • From Iamblichus's comments. Ref and Ref

    • The original code would have been all right if the service account had been granted domain-wide authority and the OP wanted to use the SA to impersonate another user in the domain (this is done via .setSubject, but won't work if domain-wide delegation is not set). But since the OP wanted to access the service account's Drive, and not impersonate another account, there was no reason for using .setSubject.

Reference:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • 1
    Thank you for your answer. While the OP didn't clarify their situation on the question, I think it would be appropriate to clarify this a bit more, for other people facing this same issue: the original code would have been all right if the service account had been granted [domain-wide authority](https://developers.google.com/identity/protocols/oauth2/service-account) and the OP wanted to use the SA to impersonate another user in the domain (this is done via `.setSubject`, but won't work if domain-wide delegation is not set). – Iamblichus Nov 17 '20 at 09:12
  • 1
    But since the OP wanted to access the service account's Drive, and not impersonate another account, there was no reason for using `.setSubject`. – Iamblichus Nov 17 '20 at 09:12
  • 1
    @Iamblichus Thank you for your always support. I could add your additional information to the Node section. I also think that it will be useful for other users. Thank you so much. – Tanaike Nov 17 '20 at 09:23
0

You can use the Advanced Drive Service to list files. You don't need an OAuth library or to even get an OAuth token. You will need to enable the Advanced Drive Service from the "Resources" menu, and the "Advanced Google services" menu option.

function listFiles() {
  var file;
  
  var query = 'trashed = false';
  var files;
  var pageToken;
  do {
    files = Drive.Files.list({
      q: query,
      maxResults: 100,
      pageToken: pageToken
    });
    if (files.items && files.items.length > 0) {
      for (var i = 0; i < files.items.length; i++) {
        file = files.items[i];
        Logger.log('%s (ID: %s)', file.title, file.id);
      }
    } else {
      Logger.log('No files found.');
    }
    pageToken = files.nextPageToken;
  } while (pageToken);
}
Alan Wells
  • 30,746
  • 15
  • 104
  • 152
0

The note at the end of Tanalike's answer worked here - my problem was not adding my service account email to Google Drive. I replaced the user_email with my service email (and shared this email address to a folder in Drive) and it worked perfectly. Thanks for your help!

sitestem
  • 21
  • 4
  • `.setSubject` is for impersonating other accounts (will only work if the service account has been granted [domain-wide authority](https://developers.google.com/identity/protocols/oauth2/service-account)). In order to access the service account's Drive, this method is not needed (and actually should not be used). Also, I don't see any reason for using `service.reset();`, I'd consider removing that line. – Iamblichus Nov 17 '20 at 09:15