2

I created a forms addon with scope:

https://www.googleapis.com/auth/drive.file

and created a picker:

function onOpen(e)
{
  FormApp.getUi().createAddonMenu()
  .addItem('Sync to Drive', 'showPicker')
  .addToUi();
}

function getOAuthToken() {
  return ScriptApp.getOAuthToken();
}

function showPicker() {
  var html = HtmlService.createHtmlOutputFromFile('Picker.html')
    .setWidth(600)
    .setHeight(425)
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);
  FormApp.getUi().showModalDialog(html, 'Select Folder');
}

// Use Advanced Drive Service - https://developers.google.com/apps-script/advanced/drive
function getFileWithAdvancedDriveService(fileId)
{
  var drivefl = Drive.Files.get(fileId);
  FormApp.getUi().alert('File: '+drivefl);
  return drivefl;
}

// Use Drive Service - https://developers.google.com/apps-script/reference/drive
function getFileWithDriveService(fileId)
{
  var drivefl = DriveApp.getFileById(fileId);
  FormApp.getUi().alert('File: '+drivefl);
  return drivefl;
}

After I open a file using this picker, I am trying to read it in the pickerCallback:

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css" />
    <script type="text/javascript">
      var DIALOG_DIMENSIONS = {
        width: 600,
        height: 425,
      };
      var authToken;
      var pickerApiLoaded = false;

      function onApiLoad() {
        gapi.load('picker', {
          callback: function () {
            pickerApiLoaded = true;
          },
        });
        google.script.run.withSuccessHandler(createPicker).withFailureHandler(showError).getOAuthToken();
      }

      function createPicker(token) {
        authToken = token;
        if (pickerApiLoaded && authToken) {
          var docsView = new google.picker.DocsView()
            .setIncludeFolders(true);

          var picker = new google.picker.PickerBuilder()
            .addView(docsView)
            .enableFeature(google.picker.Feature.NAV_HIDDEN)
            .hideTitleBar()
            .setOAuthToken(authToken)
            .setCallback(pickerCallback)
            .setOrigin('https://docs.google.com')
            .build();

          picker.setVisible(true);
        } else {
          showError('Unable to load the file picker.');
        }
      }

      function pickerCallback(data, ctx) {
        var action = data[google.picker.Response.ACTION];
        if(action == google.picker.Action.PICKED) {
          var doc = data[google.picker.Response.DOCUMENTS][0];
          var id = doc[google.picker.Document.ID];

          // Option 1 - Advanced Drive Service in apps script
          return google.script.run.withSuccessHandler(showMessage).withFailureHandler(showError).getFileWithAdvancedDriveService(id);

          // Option 2 - Drive Service in apps script
          return google.script.run.withSuccessHandler(showMessage).withFailureHandler(showError).getFileWithDriveService(id);

          // Option 3 - Drive Service in client
          return gapi.load('client', function () {
              gapi.client.load('drive', 'v2', function () {
                  gapi.client.setToken({ access_token: authToken });
                  var file = gapi.client.drive.files.get({ 'fileId': id });
                  file.execute(function (resp) {
                    showMessage(resp);
                  });
              });
          });
        } else if(action == google.picker.Action.CANCEL) {
          google.script.host.close();
        }
      }

      function showMessage(message) {
        document.getElementById('result').innerHTML = '<div>Message:</div> ' + JSON.stringify(message);
      }

      function showError(message) {
        document.getElementById('result').innerHTML = `<div>Error:</div> <div style="color:red;">${message}</div>`;
      }
    </script>
  </head>

  <body>
    <div>
      <p id="result"></p>
    </div>
    <script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
  </body>
</html>
  1. gapi.client.drive.files.get fails with the error: code: 404, message: "File not found: 1d0cqiT3aipgjMfLPolzgWVrnsl4xPxUJ1_7pH3ONVzU"

  2. I tried the same on the server side (apps script) and got similar error:

    DriveApp.getFolderById(fileId)

returns:

You do not have permission to call DriveApp.getFolderById. Required permissions: (https://www.googleapis.com/auth/drive.readonly || https://www.googleapis.com/auth/drive)
  1. Same with advanced drive api:

    Drive.Files.get(fileId)

returns:

GoogleJsonResponseException: API call to drive.files.get failed with error: File not found: 1d0cqiT3aipgjMfLPolzgWVrnsl4xPxUJ1_7pH3ONVzU

Do I need drive.readonly scope to read the file opened by the user using Google Picker?

TheMaster
  • 45,448
  • 6
  • 62
  • 85
Réti Opening
  • 1
  • 1
  • 6
  • 21
  • What is `gapi`? Is this client or server? – TheMaster May 31 '22 at 12:06
  • @TheMaster Its in the client - Javascript callback for Google Picker. – Réti Opening May 31 '22 at 12:21
  • @TheMaster *gapi* is for loading google's javascript libraries on the client: which loads Google Picker: gapi.load('picker', { }); – Réti Opening May 31 '22 at 12:43
  • So this: `DriveApp.getFolderById` is a server side error(There is no `DriveApp` on the client). If it's the client, you need to quote the error on the client side. You also need to say where you got that server side error or how you made the connection between `gapi.client.drive.files.get` and `DriveApp.getFolderById` or what made you think they're related? – TheMaster May 31 '22 at 12:49
  • @TheMaster Sorry! I copy/pasted the error when I tried the server side option with DriveApp. I have rewritten the question with all the 3 options I tried. It works if I use drive.readonly oauth scope though. – Réti Opening May 31 '22 at 13:07
  • Where is the `authtoken` created from? Provide [mcve]. Technically only the `drive.file` scope should be enough. But I'm thinking picker project and the accessing project are somehow different, making Google think the accessing project doesn't have access. – TheMaster May 31 '22 at 15:56
  • @TheMaster The authtoken is created from apps script. I have created a minimal reproducible example, please take a look - https://docs.google.com/forms/d/1jpEa-mp8ccCZhEGlgBN52AML2i1oHIShFpY8Oe3GC44/edit?usp=sharing If you switch oauth scope from drive.file to drive.readonly, it works fine. – Réti Opening May 31 '22 at 18:13
  • Sorry, external links are not mcve. You should [edit] your question instead. If you used `authtoken` from server side apps script, I believe they are different from client side `gapi`. For eg, I think there are client id, secrets on the client side that won't match server side apps script project. Also, you're setting origin on the client side on the first picker call. I don't think they will match, when the client makes a second call through `gapi`. Anyway, those are my educated guesses, which may or may not be the real issue. – TheMaster May 31 '22 at 18:20
  • @TheMaster Yes. Thats why I am passing the authtoken from apps script to client side. Edited the question with the code. Please see if you find any issues. In any case, thanks for the help. – Réti Opening May 31 '22 at 19:01
  • Related: https://stackoverflow.com/questions/17508212/how-do-i-use-google-picker-to-access-files-using-the-drive-file-scope – TheMaster May 31 '22 at 19:22
  • In addition to enabling drive sdk, I'm guessing origin is a issue, could you try removing `.setOrigin(..)` on the picker or see if `gapi.client` has a `.setOrigin` method, so that origins match? – TheMaster May 31 '22 at 19:24
  • Related: Origin of a Google webapp iframe: https://stackoverflow.com/questions/63551837/where-is-my-iframe-in-the-published-web-application-sidebar/63551838#63551838 – TheMaster Jun 01 '22 at 10:30
  • @TheMaster Unfortunately, the addon sandbox does not allow any origin other than docs.google.com. It throws this error: Incorrect origin value. Expected 'https://docs.google.com' but was 'https://script.google.com' – Réti Opening Jun 01 '22 at 13:13

2 Answers2

1

Yes, as the https://www.googleapis.com/auth/drive.file scope only allows you to:

View and manage Google Drive files and folders that you have opened or created with this app

And https://www.googleapis.com/auth/drive.readonly:

See and download all your Google Drive files

You can review the full list of scopes here.

Emel
  • 2,283
  • 1
  • 7
  • 18
  • Thanks! drive.file scope says "View and manage Google Drive files and folders that you have opened or created with this app". When I open a file using the addon's file picker, is that not considered opened with this app? – Réti Opening May 31 '22 at 12:39
  • From what I know, you only can view files created by this app. – Emel May 31 '22 at 12:46
  • I just wrestled this for hours. If you only want to use drive.file ( which is a far better user experience ), you need to make sure you include your app id when creating the picker. – Jason Wilmot Jul 08 '23 at 17:20
0

I found the problem. It works if I move this gapi.load code from pickerCallback:

      return gapi.load('client', function () {
          gapi.client.load('drive', 'v2', function () {
              gapi.client.setToken({ access_token: authToken });
              var file = gapi.client.drive.files.get({ 'fileId': id });
              file.execute(function (resp) {
                showMessage(resp);
              });
          });
      });

to javascript onload:

  function onGsiLoad()
  {
    return gapi.load('client', function () {
      return gapi.client.load('drive', 'v2', function () {
        gsiLoaded = true;
        maybeEnablePicker();
      });
    });
  }

And only have gapi.client.drive.files.get in pickerCallback:

      var file = gapi.client.drive.files.get({ 'fileId': fileId });
      file.execute(function (resp) {
        showMessage(resp);
      });

Working test case is here: https://docs.google.com/forms/d/1h3FWKVpGbCApg1_2unD3L86QlOmh9CIwK-W1Q97UTYQ/edit?usp=sharing

Buggy one is here: https://docs.google.com/forms/d/1jpEa-mp8ccCZhEGlgBN52AML2i1oHIShFpY8Oe3GC44/edit?usp=sharing

Réti Opening
  • 1
  • 1
  • 6
  • 21