Narrowing OAuth scope from Drive to Drive.file fails in Google Apps Script add-on

83 views
Skip to first unread message

Darren D'Mello

unread,
Jan 12, 2024, 11:04:46 PM1/12/24
to Google Apps Script Community
I have a google slides addon in marketplace which lets users to create slides from Drive picked images
This addon currently uses the following scopes

    "https://www.googleapis.com/auth/presentations",
    "https://www.googleapis.com/auth/script.container.ui",
    "https://www.googleapis.com/auth/drive"



ISSUE: I am advised by Oauth Verification process to narrow down the scopes and use drive.file (narrower) instead using a drive scope (broader)

The email stated me - "Since your app services only require per-file selection, your use case does not meet the minimum scope requirement of the Google Drive API user data and developer policy for access to restricted Drive API scopes. You should migrate your application to use the non-sensitive drive.file scope instead of the restricted scope. "



THE ERROR/PROBLEM

After the scopes are narrowed, the app no longer works and gives the error
Error: Exception: You do not have permission to call DriveApp.getFileById. Required permissions: (https://www.googleapis.com/auth/drive.readonly || https://www.googleapis.com/auth/drive)


WHAT I AM ASKING FOR:  I am aware of scopes and their definitions https://developers.google.com/drive/api/guides/api-specific-auth. The https://www.googleapis.com/auth/drive.file scope can Create new Drive files, or modify existing files, that you open with an app or that the user shares with an app while using the Google Picker API or the app's file picker.

I could have used https://www.googleapis.com/auth/drive.readonly but this again is a restricted scope

Using this narrow scopes, How do I fix my below code so that it can place picked google drive images to slides? The OAuth team is not satisfied with the Drive scope and strictly suggest Drive.file scope

https://www.googleapis.com/auth/drive.file scope can Create new Drive files, or modify existing files, that you open with an app or that the user shares with an app while using the Google Picker API or the app's file picker.
Using this narrow scopes, how do I fix my below code so that it can place picked google drive images to slides?
The OAuth team is not satisfied with the Drive scope and strictly suggest Drive.file scope


TO MAKE A COPY OF THE CODE FILE: https://docs.google.com/presentation/d/1l1WvLlo3-4fLepWgvk9Bkj785NhxleY_c-0Lvv1rxyU/copy

//appsscript.json
{
  "timeZone": "Etc/GMT",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Drive",
        "version": "v3",
        "serviceId": "drive"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/presentations",
    "https://www.googleapis.com/auth/script.container.ui",
    "https://www.googleapis.com/auth/drive.file"
  ]
}

//Code.gs
var ANAME = "Create Slides from Images";
var PICKER_ORIGIN = "\"https://docs.google.com\"";

function onOpen(e) {
SlidesApp.getUi().createMenu("Create Slides from Images")
.addItem('Open', 'showPickerImages')
.addToUi();
}

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

function showAlert(msg) {
var ui = SlidesApp.getUi();
var result = ui.alert(
ANAME,
msg + '',
ui.ButtonSet.OK);
}

function showPickerImages() {
try {
var html = HtmlService.createTemplateFromFile("Picker")
.evaluate()
.setWidth(650).setHeight(500).setTitle("Select images folder")
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
SlidesApp.getUi().showModalDialog(html, "Create slides from Drive images");
} catch (e) {
showAlert("Error: " + e + ' ' + e.lineNumber);
}
}

function insertImages(imageIds) {
try {
var deck = SlidesApp.getActivePresentation();
var firstSlide = deck.getSlides()[0];
var title = firstSlide.getPageElements()[0];
var subtitle = firstSlide.getPageElements()[1];

title.asShape().getText().setText(ANAME);
subtitle.asShape().getText().setText("Google Apps Script\nSlides Service demo");

// Iterate through the imageIds and call addImageSlide for each imageId
imageIds.forEach(function(imageId) {

var slide = deck.appendSlide(SlidesApp.PredefinedLayout.BLANK);
slide.insertImage(DriveApp.getFileById(imageId).getBlob());

//I have tried with this, but this fails too saying image must be publicly accessible, the images are in users drive and cannot be shared publicly
//slide.insertImage(`https://drive.google.com/uc?export=download&id=${imageId}`);

});
} catch (e) {
showAlert("Error: " + e + ' ' + e.lineNumber);
}
}


//Picker.html

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<script type="text/javascript">
var pickerApiLoaded = false;
var fO;

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

function createPicker(token) {
    if (pickerApiLoaded && token) {
        var docsView = new google.picker.DocsView()
            .setOwnedByMe(true)
            .setIncludeFolders(true)
            .setSelectFolderEnabled(true)
            .setMimeTypes("image/png,image/jpeg,image/jpg,image/gif,image/heif");

        var docsViewSharedDrive = new google.picker.DocsView()
            .setOwnedByMe(true)
            .setIncludeFolders(true)
            .setEnableDrives(true)
            .setMimeTypes("image/png,image/jpeg,image/jpg,image/gif,image/heif");

        var docsViewSharedFiles = new google.picker.DocsView()
            .setOwnedByMe(false)
            .setIncludeFolders(true)
            .setMimeTypes("image/png,image/jpeg,image/jpg,image/gif,image/heif");

        var uploadView = new google.picker.DocsUploadView()
            .setIncludeFolders(true);

        var picker = new google.picker.PickerBuilder()
            .addView(docsView)
            .addView(docsViewSharedDrive)
            .addView(docsViewSharedFiles)
            .addView(uploadView)
            .hideTitleBar()
            .setSize(600 - 2, 425 - 2)
            .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
            .enableFeature(google.picker.Feature.SIMPLE_UPLOAD_ENABLED)
            .enableFeature(google.picker.Feature.SUPPORT_DRIVES)
            .setOAuthToken(token)
            .setCallback(fileSelected)
            .setOrigin(<?!=PICKER_ORIGIN?>)
            .build();
        picker.setVisible(true);
    } else {
        google.script.run.showAlert('Unable to load the file picker.');
    }
}

function fileSelected(images) {
    var files = [],
        folders = [],
        imgs = []
    var action = images[google.picker.Response.ACTION];

    if (action == google.picker.Action.PICKED) {
        var documents = images[google.picker.Response.DOCUMENTS];
        for (var i = 0; i < documents.length; i++) {
            imgs.push(documents[i].id);
        }
        google.script.run.insertImages(imgs);
    } else if (action == google.picker.Action.CANCEL) {
    }
}
</script>
</head>
<body>
<script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
<script>
  onApiLoad();
  </script>
</body>
</html>



DimuDesigns

unread,
Jan 13, 2024, 9:09:15 AM1/13/24
to Google Apps Script Community
I don't have any code to verify this approach but you can try getting the image data client-side directly in picker.html and then passing that back to your server-side GAS script instead of passing an id. Inspect the properties of the 'images' argument passed to your fileSelected handler, maybe there is something in there you can leverage.
Reply all
Reply to author
Forward
0 new messages