Google App Scripts and FusionTable - google-apps-script

I am working on my first Google App Script (Script as Webapp) and trying to access a fusion table, but each time I try to access the fusion table I get back insufficient privileges. I am logged in as the owner of the app, so I am not sure why this is happening.
function getReports(){
var authToken = ScriptApp.getOAuthToken();
Logger.log(authToken);
var query = encodeURIComponent("Select * from " + tableId);
queryFusionTables(authToken, query);
}
function queryFusionTables(authToken, query) {
var URL = "https://www.googleapis.com/fusiontables/v1/query?sql="+query+"&key={myKey}";
Logger.log(query);
//Logger.log(authToken);
var response = UrlFetchApp.fetch(URL, {
method: "post",
headers: {
"Authorization": "Bearer " + authToken,
"X-JavaScript-User-Agent": "Google APIs Explorer",
},
});
Logger.log(response.getContentText());
return response.getContentText();
}
Does Anyone have any ideas as to why this is happening?

The OAuth token returned by ScriptApp.getOAuthToken() is only authorized for the scopes required by your script, which is determined by the Apps Script services you use. For instance, if you were to use DriveApp the Google Drive API scope would be requested.
The Apps Script code doesn't know you are attempting to use the Fusion Tables API, so it didn't request that scope. However, you are in luck! Apps Script has a built-in integration with Fusion Tables, using the Fusion Tables advanced service. Simply enable the service and Apps Script will take care of the authorization for you, plus provide you with auto-complete.

Related

What is the benefit of calling google web app with access token and what are the disadvantages of not using it

client (imitating google sheets add-on) :
function getReqParam(type){
//DriveApp.getFiles() - commented row needed for the Drive scope request
let data = { param2: "value2" ,param1: Session.getActiveUser()};
var params = {
method: type,
contentType: "application/json",
headers: { Authorization: "Bearer " + ScriptApp.getOAuthToken() },
payload: JSON.stringify(data),
muteHttpExceptions: true,
};
return params;
}
function sendPostReq(){
let res = UrlFetchApp.fetch(url, getReqParam("POST"));
console.log("sendPostReq response: ", res.getResponseCode());
console.log("sendPostReq response: ", res.getContentText());
}
webapp:
function doPost(e){
console.log("doPost",e);
try{
saveRegToDB(JSON.parse(e.postData.contents));
e.method = "POST";
//return regisration status
e.regStatus = {status: "active"};
}catch(err){
e.regStatus = {status: "error"};
e.err = JSON.stringify(err);
e.errMSg = JSON.stringify(err.message);
console.error("doPost exception",err);
console.error("doPost exception",err.stack);
}
return ContentService.createTextOutput(JSON.stringify(e))
.setMimeType(ContentService.MimeType.JSON);
}
function saveRegToDB({param1,param2}={}){
let rowID = -1;
if (param1 && param2){
let ss = getSS();
let sheetName = "Registrations";
let s = ss.getSheetByName(sheetName);
let r = s.getLastRow() + 1;
rowID = r;
let rVals = [param1,param2,getTimeStamp()];
let vals = [rVals]; //2 dimensions array
console.log("reg saveToDB ",vals,r);
//write to sheet
s.getRange(r,1,1,rVals.length).setValues(vals);
}
console.log("saveRegToDB:" ,rowID);
return rowID;
}
function getSS(){
let ssid = getSSid();
let ss = SpreadsheetApp.openById(ssid);
SpreadsheetApp.setActiveSpreadsheet(ss);
return ss;
}
function getSSid(){
return PropertiesService.getScriptProperties()
.getProperty("regSSID");
}
function getTimeStamp() {
return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss");
}
I have a simple google web app. The web app is supposed to serve a google sheets addon, to store add-on related information to a private SS (owned by the add-on account), for example: users' registrations. I am wondering about the Who has access setting I should use in order to deploy the the web app, and the usage of access token that comes with it as explained here.
The Execute as (legacy editor: Execute the app as to Me
When deploying webapp with Who has access: Anyone with Google account (Anyone in legacy editor) web app should be called with access token
headers: { Authorization: "Bearer " + ScriptApp.getOAuthToken() }
There are 2 problems with that approach:
here it is claimed that webapp script project should be shared with all the users that might use it. In my case every user that will install the addon, that is: everyone. This is a disatvantage for itself. Despite that, another user (say, user2) can access successfully the web app.
In order the use access token the client has to request for Drive scopes, even though web app doesn't use any of the Drive API. More over, even of calling the web app with access code, it fails to access the user drive files, due to missing permissions Exception: You do not have permission to access the requested document.
So not only, client is enforced to ask for Drive scope web app does not need, even if trying to "use" Drive API to access client drive - it is blocked.
I have tested it by setting the regSSID script property read in getSSid() to spreadsheet owned by the user2, and also executing client from user2 account script editor
What is the benefit of deploying web app with Anyone with Google account (Anyone in legacy editor) and using access token for that?
When deploying webapp with Who has access: Anyone (Anyone, even anonymous in legacy editor) no access token is required, thus no need to ask for Drive scope. This is good thing. The question is what might the disadvantages of deploying the web app to Anyone, especially in terms of security. I guess web app can filtered out all calls from unwanted access by validating applicative post request parameter. Is this a real security issue?

Upload to Google Cloud storage from App Script

I've been bashing my head at this for over a day, but I can't figure out how to upload data to Google Cloud Storage via an app script attached to a google sheet. I've been running into issues with authorisation. I've copied the getService method from here (pasted below) but the service keeps failing to receive authorisation. service.hasAccess() always returns false.
function uploadFileToGCS(dataJSON) {
var service = getService();
if (!service.hasAccess()) {
Browser.msgBox("Failed to grant service access")
return;
}
var url = 'https://www.googleapis.com/upload/storage/v1/b/BUCKET/o?uploadType=media&name=FILE'
.replace("BUCKET", params.BUCKET_NAME)
.replace("FILE", encodeURIComponent(params.FILE_PATH));
var response = UrlFetchApp.fetch(url, {
method: "POST",
payload: dataJSON,
contentType: "application/json",
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
}
function getService() {
return OAuth2.createService('ctrlq')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId(params.CLIENT_ID)
.setClientSecret(params.CLIENT_SECRET)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('https://www.googleapis.com/auth/devstorage.read_write')
.setParam('access_type', 'offline')
.setParam('approval_prompt', 'force')
.setParam('login_hint', Session.getActiveUser().getEmail());
}
function authCallback(request) {
var service = getService();
var authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('Connected to Google Cloud Storage');
} else {
return HtmlService.createHtmlOutput('Access Denied');
}
}
I've created OAUTH credentials for a web-app on the Google Cloud Console. I've also enabled the Cloud Storage API and Google Cloud Storage JSON API. I'm unsure however on the redirect URL. (Ideally, I'd like to use a service account because I just want to take the values from the spreadsheet and upload them as a JSON file.)
Anyway, appreciate the help!
I think google app script project and google cloud project should be linked. You need to create a google cloud project to use this API.
I encountered a similar problem when I wanted to duplicate an object in Cloud Storage. Not sure if this is the solution for you, but I'm putting it here in case someone need it.
Just use the XML-formmatted REST API with OAuth2 token, and the code looks like this:
var objectSource = "SOURCE_BUCKET_NAME/SOURCE_OBJECT_NAME";
var url = "https://storage.googleapis.com/DESTINATION_BUCKET_NAME/NAME_OF_COPY";
var resp = UrlFetchApp.fetch(url, {
method: "PUT",
headers: {
Authorization: 'Bearer '+ OAUTH2_TOKEN,
"x-goog-copy-source": objectSource,
},
'muteHttpExceptions': true,
});
Check out the Cloud Storage's document for differences between copy and upload.

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.

Google Docs API in Google Apps Script as an external API (NOT as extended service)

I am trying to use the new Google Docs API using Google Apps Script. Since new API is not yet available as an extended service, I am trying to do it using UrlFetchApp() but failing.
Apologies for my naive attempt here:
function apiCall(){
var API_KEY = 'YOUR_API_KEY';
var username = 'YOUR_USERNAME';
var password = 'YOU_PASSWORD';
var DOC_ID = 'YOUR_DOC_ID';
var root = 'https://docs.googleapis.com/v1/documents/';
var endpoint = DOC_ID;
var query = '?key=' + API_KEY;
var params = {
'method': 'GET',
'muteHttpExceptions': true,
'headers': {
'Authorization': 'Basic ' + Utilities.base64Encode(username + ':' + password)
}
};
var response = UrlFetchApp.fetch(root + endpoint + query, params);
var data = response.getContentText();
var json = JSON.parse(data);
Logger.log(json);
}
I get the following response:
{error={code=401, message=Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or another valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project., status=UNAUTHENTICATED}}
Can someone point to the right direction, where I can find some documentation how to use Google Docs API in Google Apps Script.
If you own the document then you don't need to leverage an API key. Also, instead of using Basic authentication you can leverage the built-in Bearer OAuth token as follows:
/**
* Get `Document` resource object from Google Docs REST API.
*
* #param {String} docId - A Google Document Id
*
* #return {Document} A Document resource object.
*/
function getDocumentResouce(docId) {
return JSON.parse(UrlFetchApp.fetch(
"https://docs.googleapis.com/v1/documents/" + docId,
{
"headers": {
"Authorization":"Bearer " + ScriptApp.getOAuthToken()
}
}
)
);
}
Note: GET is the default HTTP request method used by UrlFetchApp.fetch() so you don't need to define it in the options object.
ADDENDUM
As Tanaike stated in the comments you'll need to manually add the relevant scopes (in addition to the ones you already have enabled) to your manifest JSON.
First check your project properties to get the list of existing scopes via the menu
File > Project Properties > Scopes. You need to add those scopes, as well as one of the relevant document scopes (listed in the documentation) to your manifest.
The following links provide the information you'll need to manage your manifest and scopes:
https://developers.google.com/apps-script/concepts/manifests
https://developers.google.com/apps-script/concepts/scopes

Getting Error Daily Limit for Unauthenticated Use Exceeded in Google Sheets for URL Shortener API

I am getting "Error Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup" in google sheets with using URL Shortener API (image as below)
Below is my code in Script Editor
function shortenURL(urlField) {
var toShorten = UrlShortener.newUrl().setLongUrl(urlField);
var shortened = UrlShortener.Url.insert(toShorten);
return shortened.getId();
}
I have bonded to the API through Google Sheets build-in setting from "Cloud Platform Project", enabled URL Shortener API in "Advanced Google Services", enabled it in Google API Console, and created both API and OAuth (image as below). Besides, I was just using it for less than 20 cells in the Google Sheets, and so I am sure it is way less than the quote given by Google.
Cloud Platform Project
Advanced Google Services
Enable in Google API Console
If I use the below code (mentioned here), it works fine.
However, I want the script to run automatically as the function instead of by clicking the button. Therefore, I still want to solve the error.
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Shorten")
.addItem("Go !!","rangeShort")
.addToUi()
}
function rangeShort() {
var range = SpreadsheetApp.getActiveRange(), data = range.getValues();
var output = [];
for(var i = 0, iLen = data.length; i < iLen; i++) {
var url = UrlShortener.Url.insert({longUrl: data[i][0]});
output.push([url.id]);
}
range.offset(0,1).setValues(output);
}
I found some post mentioned the solution to this error is to apply authentication to the requests to Google. However, I have already created API key and OAuth and bond them to Google Sheets through those Google Sheets build-in setting.
Is there any solution to the error?
If the error occurs due to authentication issue, how I can apply the authentication in addition to those setting I have already done?
Try sending a POST request to UrlShortener API endpoint instead of using the UrlShortener service in Apps Script. With UrlShortener.Url.insert(), I quickly hit the usage quota.
Append the API url with the 'key' parameter and set it equal to the API key you obtained from Developer console.
In the body of your POST request, set 'muteHttpExceptions' to 'true' to log all error messages as beautified JSON.
var API_KEY = "YOUR_API_KEY";
function shortenUrl(longUrl) {
var apiEndpoint = "https://www.googleapis.com/urlshortener/v1/url?key=" + API_KEY;
var data = {
"longUrl": longUrl
}
var options = {
"method": "post",
"contentType": "application/json",
"muteHttpExceptions": true,
"payload": JSON.stringify(data)
};
var response = UrlFetchApp.fetch(apiEndpoint, options).getContentText();
var data = JSON.parse(response);
return data.id;
}