Search code examples
google-apps-scriptgoogle-drive-apigoogle-oauthhttp-status-code-404google-picker

Can I read a drive file opened using Google Picker with drive.file oauth scope?


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?


Solution

  • 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