Google Execution API does not work with non-"script owner" user - google-apps-script

I call a Google Apps Script project from my website with the Apps Script API, but when I am logged in with another account instead of the owner, the Apps Script function receives no parameter values.
When logged in my website with the account that created the script, everything works properly.
i adjusted "dev mode" to false with no change in behavior.
Here is my Apps Script project code
function test(mail){
var s =mail
Logger.log(s)
var sheets = "anything";
return Logger.log(sheets);
}
my website code (call)
function test() {
var scriptId = "MYSCRIPT";
var request = {
'function': 'test',
'parameters': [mail],
//'devMode': false // Optional
};
No scopes are required.

Related

How to handle authorization for Apps Script from Chrome extension

I try to execute a Google Apps Script from my Chrome extension. My Google Apps Script uses Spreadsheet Service - it reads data from the user's spreadsheet, url of which is provided by user to my extension and then sent by extension to GAS. So, I've deployed Google Apps Script as web app, which is executed by a user accessing the web app and can be accessed by anyone. The problem is that when the script is run for the first time by a new user, the user can't see the authorization request so the script can't access the spreadsheet and send data to the extension. Everything works fine only if a user runs the script manually in the browser - only then the authorization request appears.
My question is how extension can trigger the authorization request to appear automatically?
I have read this question with answers Running Apps Script from Chrome extension requires authorization but there are only instructions on how to avoid authorization, which in my case can't be avoided. I would appreciate any help with this.
Update: I'm trying to use getAuthorizationUrl() as advised in the comments and now I'm stuck again. Here is the code from the Google Apps Script:
function doGet(e) {
var url=e.parameter.url;
var ss = SpreadsheetApp.openByUrl(url);
var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
var status=authInfo.getAuthorizationStatus();
if (status=="NOT_REQUIRED") {
var sheet = ss.getSheets()[0];
var data = getData(sheet);
if(!data) {
data = '';
}
return ContentService.createTextOutput(JSON.stringify({'result': data})).setMimeType(ContentService.MimeType.JSON);
} else {
var authUrl=authInfo.getAuthorizationUrl();
return authUrl;
}
}
In both cases responseType of my XMLHttpRequest will be "", so how can my extension distinguish between 2 possible responses? I need something like this in the content script (don't know what to put inside if):
xhr.onreadystatechange = function(response) {
if (xhr.readyState === 4 && xhr.status == 200) {
try {
if (??response is authUrl) {
//do redirect;
}
else {
//process the data from the spreadsheet
}

Calling Google Apps Script in another Project File

I am trying to call a Google Apps Script file that is in another project file following the sample here using UrlFetchApp.fetch.
I'm getting the same error that the original poster mentions but I am not having an success with my sample.
Did Google change something in the last 4 years that prevents me from calling the other script file?
See script below.
Below is the function that I am using to call the other project file
function makeRequest()
{
var webAppUrl = "https://script.google.com/macros/s/***/exec";
var auth = ScriptApp.getOAuthToken();
var header = { 'Authorization': 'Bearer ' + auth };
var options = { 'method':'post', 'headers':header };
var resp = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(resp);
}
Below is the function that I am trying to call. Additionally, I have ran the authorizeDrive function and published as a webapp.
function authorizeDrive()
{
var forScope = DriveApp.getRootFolder();
}
function doPost()
{
var ss = SpreadsheetApp.openById('ssID');
var name = ss.getName();
Logger.log('called');
return ContentService.createTextOutput(name);
}
You want to run the Google Apps Script in the GAS project A by accessing to Web Apps from the GAS project B.
In your case, Web Apps is deployed by Who has access to the app: of Only myself or Anyone.
You want to access to Web Apps using the access token.
The GAS project A and B are in your Google Drive.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
I think that in your case, the scope is required to be added to the project including makeRequest(). So in order to add the scope for accessing to Web Apps using the access token, how about the following modification?
Modified script:
function makeRequest()
{
var webAppUrl = "https://script.google.com/macros/s/***/exec";
var auth = ScriptApp.getOAuthToken();
var header = { 'Authorization': 'Bearer ' + auth };
var options = { 'method':'post', 'headers':header };
var resp = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(resp);
}
// DriveApp.getFiles() // This comment line is used for automatically detecting the scope.
Please add the // DriveApp.getFiles() of the comment line. This comment line is used for automatically detecting the scope.
In this case, https://www.googleapis.com/auth/drive.readonly is added to the scopes. If this didn't resolve your issue, please add the comment line of // DriveApp.createFile(blob). In this case, https://www.googleapis.com/auth/drive is added.
Note:
When the script of Web Apps side is modified, please redeploy it as new version. By this, the latest script is reflected to Web Apps. Please be careful this.
If the owner of GAS project of Web Apps is not your account which has the script of makeRequest(), at first, please share the GAS project file of Web Apps with your account. Then, please test it. This specification has added at April 11, 2018. Also, please be careful this.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
If I misunderstood your question and this was not the result you want, I apologize.

Apps Script API not pasting data into Google Sheets

I'm trying to execute the function by calling it from my Chrome Extension. I can see in my "My Executions" that the chrome extension called the function, but it's not pasting data into Google Sheets.
When I try to run that function from the Script Editor directly it's working.
Function Call from Chrome Extension
post({ 'url': 'https://script.googleapis.com/v1/scripts/' + SCRIPT_ID + ':run',
'callback': executionAPIResponse,
'token': token,
'request': {
'function': 'setData',
'parameters': { 'data': JSON.parse(exec_data.value) },
}
});
Script Editor Code
function setData(parameters) {
try {
var doc = SpreadsheetApp.openById(DOC_ID);
var sheet = doc.getSheets()[0];
var data = parameters.data;
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
return {"status":"ok", 'doc':doc.getUrl()};
} catch(e){
// if error return this
Logger.log(e);
return {"status": JSON.stringify(e)};
}
}
I deployed the Apps Script project as "API Executable." I am wondering, is there something wrong with the way I deployed the app in Script Editor?
getActiveSpreadsheet() only works on scripts projects bounded to a spreadsheet and only to access the the spreadshet that contains the script project.
Instead of using the above method use openByUrl(url) or openById(id)
I found the mistake i was making. Actually the code was correct but while deploying as API Executable you have to always change the version to new one when you made changes to your code

Missing publish to Chrome Web Store option for web application

I'm trying to make an inbox-listener element to automatically pull message content with a specific subject line into a Google Spreadsheet for reporting. I'm hoping this GitHub Project will do the trick, and I've made the Google Apps Script project that should handle it. Part of the process is to verify the project ownership by publishing it as draft on the Chrome web store. According to Google's documentation, that should be under the publish function, but I don't see it there, or anywhere in the script editor. Could anyone tell me what I'm doing wrong, or if this feature is disabled?
Here is what the menu looks like in my IDE.
Adapted from a OP's comment to the accepted answer
The script is definitely not contained in a Google Doc or Spreadsheet, but it is in a workspace tied to a Google Site. Interestingly, I've tried following the link in the KENdi's answer to edit Apps Scripts under my work account, and I'm told I don't have >permission to generate app scripts on that level. I'll send an email to my Admin to see if I can elevate privileges so I can publish through this channel.
GAS Code
main.gs
<meta name="google-site-verification" content="eA0WbBgImGB_wcsnSADjvwnCBaNyrSifyyxuNhHSXf8" />
var PROJECTID = 'api-project-...';
var WEBHOOK_URL = 'My custom project URL'
function doPost(e){
var postBody = JSON.parse(e.postData.getDataAsString());
var messageData = Utilities.newBlob(Utilities.base64Decode(postBody.message.data)).getDataAsString();
var ss = SpreadsheetApp.openById('...').getSheetByName("Log");
ss.appendRow([new Date(), messageData, JSON.stringify(postBody,undefined,2)])
return 200;
}
function setupPubSub(){
var newTopic = CreateTopic("mailTrigger");
newTopic.setIamPolicy(addGmailPolicy());
Logger.log(newTopic.getName());
var newSub = CreateSubscription("mailTrigger",newTopic.getName(),WEBHOOK_URL);
}
function disEnrollEmail(email){
var email = email || "me";
var res = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/"+email+"/stop",{method:"POST",headers:{authorization:"Bearer "+ScriptApp.getOAuthToken()}});
Logger.log(res.getContentText());
}
function enrollEmail(email){
var email = email || "me";
PubSubApp.setTokenService(getTokenService())
var topicName = PubSubApp.PublishingApp(PROJECTID).getTopicName("mailTrigger")
Logger.log(watchEmail(topicName,{labelIds:["INBOX"], email:email}));
}
helper.gs
function addGmailPolicy(Policy){
return PubSubApp.policyBuilder()
[(Policy)?"editPolicy":"newPolicy"](Policy)
.addPublisher("SERVICEACCOUNT", 'gmail-api-push#system.gserviceaccount.com')
.getPolicy();
}
function addDomainSubs(Domain,Policy){
return PubSubApp.policyBuilder()
[(Policy)?"editPolicy":"newPolicy"](Policy)
.addPublisher("DOMAIN", Domain)
.getPolicy();
}
function getSubscriptionPolicy(){
return PubSubApp.policyBuilder()
.newPolicy()
.addSubscriber("DOMAIN","ccsknights.org")
}
function watchEmail(fullTopicName,watchOptions){
var options = {email:"me",token:ScriptApp.getOAuthToken(),labelIds:[]};
for(var option in watchOptions){
if(option in options){
options[option] = watchOptions[option];
}
}
Logger.log(options);
var url = "https://www.googleapis.com/gmail/v1/users/"+options.email+"/watch"
var payload = {
topicName: fullTopicName,
labelIds: options.labelIds
}
var params = {
method:"POST",
contentType: "application/json",
payload: JSON.stringify(payload),
headers:{Authorization: "Bearer "+ options.token
},
muteHttpExceptions:true
}
var results = UrlFetchApp.fetch(url, params);
if(results.getResponseCode() != 200){
throw new Error(results.getContentText())
}else{
return JSON.parse(results.getContentText());
}
}
function CreateTopic(topicName) {
var topic;
PubSubApp.setTokenService(getTokenService());
var pubservice = PubSubApp.PublishingApp(PROJECTID);
try{topic = pubservice.newTopic(topicName)}
catch(e){topic = pubservice.getTopic(topicName);}
return topic;
}
function CreateSubscription(subscriptionName,topicName,webhookUrl){
var sub;
PubSubApp.setTokenService(getTokenService());
var subService = PubSubApp.SubscriptionApp(PROJECTID);
try{sub = subService.newSubscription(subscriptionName,topicName,webhookUrl)}
catch(e){sub = subService.getSubscription(subscriptionName,topicName,webhookUrl)}
return sub;
}
function getTokenService(){
var jsonKey = JSON.parse(PropertiesService.getScriptProperties().getProperty("jsonKey"));
var privateKey = jsonKey.private_key;
var serviceAccountEmail = jsonKey.client_email;
var sa = GSApp.init(privateKey, ['https://www.googleapis.com/auth/pubsub'], serviceAccountEmail);
sa.addUser(serviceAccountEmail)
.requestToken();
return sa.tokenService(serviceAccountEmail);
}
function requestGmailScope_(){GmailApp.getAliases()}
The "Register in Chrome Web Store" option is only available in the standalone scripts.
Meaning, if this script is bound to a Google Sheets, Docs, Forms or scripts appear among your files in Google Drive, then this "Register in Chrome Web Store" is not available.
You can verify it by checking the publish in this "https://www.google.com/script/start/". This one will have an option of "Register in Chrome Web Store" in the publish.
While by checking the script editor of a spreadsheet, created in your Drive, the "Register in Chrome Web Store" will not be found in the Publish section.
Hope this information helps you.
It turns out I couldn't build Apps Scripts under my work account because my browser was defaulting to my personal Gmail account. Resetting the browser and connecting as my work account first fixed the problem. For future reference, you have to make your scripts under Script.Google.com in order for them to be shareable via the web store. You can bypass an authentication problems like this by resetting your browser history or running Chrome in incognito mode if you have to.
Great catch, KENDi!

Google Apps Script Execution API - Calling app is Apps Script - Apps Script to Apps Script

I have two scripts in different domains, I would like to fetch data from one to the other.
I've found Script Execution API to be useful, however they don't provide any example GAS to GAS. https://developers.google.com/apps-script/guides/rest/quickstart/target-script
Any suggestion/guidance on how to accomplish such thing?
This answer might be helpful. what i have done is that created a cloud platform project and created two standalone project, one contains the calling script and other has the method you want to call and must be deployed as a API Executable.
IMPORTANT: Both these project must be associated with the Cloud Platform Project.
For associating the projects to Cloud Platform's project , in script editor do this, Resources --> Cloud Platform Project. You'll see the dialog box which has input box having placeholder Enter Project Number Here, here put the Cloud Platform's project number and do the same for other project as well.
Now as i have followed google's quick start tutorial for EXECUTION API. i have a target script (Script containing your method) as follows:
/**
* The function in this script will be called by the Apps Script Execution API.
*/
/**
* Return the set of folder names contained in the user's root folder as an
* object (with folder IDs as keys).
* #return {Object} A set of folder names keyed by folder ID.
*/
function getFoldersUnderRoot() {
var root = DriveApp.getRootFolder();
var folders = root.getFolders();
var folderSet = {};
while (folders.hasNext()) {
var folder = folders.next();
folderSet[folder.getId()] = folder.getName();
}
return folderSet;
}
NOTE: This script must be deployed as a API Executable. In project you must have enabled the Google Execution API.
Now the calling script,
function makeRequest(){
// DriveApp.getRootFolder();
var access_token=ScriptApp.getOAuthToken();
var url = "https://script.googleapis.com/v1/scripts/{YourScriptId}:run";
var headers = {
"Authorization": "Bearer "+access_token,
"Content-Type": "application/json"
};
var payload = {
'function': 'getFoldersUnderRoot',
devMode: true
};
var res = UrlFetchApp.fetch(url, {
method: "post",
headers: headers,
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
Logger.log(res);
}
On first line inside function you can see i have commented out the Drive API call. I did that because the calling script and target script must have the same scope.
Yes you can use Execution API to fetch data from one domain to other. From the documentation, it consists of a single scripts resource, which has a single method, run, that makes calls to specific Apps Script functions.
The run method must be given the following information when called:
The ID of the script being called.
The name of the function within the script to execute.
The list of parameters the function requires (if any).
Here is an example from GitHub:
var script = google.script('v1');
var key = require('./creds.json');
var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, ['https://www.googleapis.com/auth/spreadsheets'], null);
jwtClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
script.scripts.run({
auth: jwtClient,
scriptId: 'script-id',
resource: {
function: 'test',
parameters: []
}
}, function(err, response) {
if (err) {
throw err;
}
console.log('response', response);
})
});
You can also check this related SO question on how to use Google Apps Script Execution API and Javascript to make the interaction between website and Google Drive.