In Google apps script documentation, there is a page about Private functions on server side. That should explain that without private functions, the server code is visible from the user browser.
Can anybody explain how you can see such server side functions in a browser ?
Thanks
See : https://developers.google.com/apps-script/guides/html/communication#private_functions
The server code is never visible on the user's browser, only the functions names. Private functions hides those names, but more importantly they remove the ability from the frontend to call them directly.
In other words, private functions allow you to define your backend entry-points, preventing a malicious user to bypass some checks you might have and call your "internal" functions directly.
To showcase how easy it is to see the name and call any non-private backend function, I've put up this example where we inspect the google.script.run object:
function myFunction() {}
function anotherFunction() {}
function privateFunction_() {}
function doGet() {
return HtmlService.createHtmlOutput(
'<p id="output"></p>'+
"<script>var s = ''; for( var prop in google.script.run ) s+=prop+'<br>';"+
"document.getElementById('output').innerHTML = s;</script>"
);
}
Here's this example published:
https://script.google.com/macros/s/AKfycbzk0d03iB1O3vVYVD_U7eONM357iOPlAn7RFxAeZKx34q1Ones/exec
And its source code (same as above):
https://script.google.com/d/1WMY5jWblGl8U84WvVU_mZjHDg-6rGOoOPnKMF6m2bS_V-2g6IChBVDrg/edit
-- to address a question in the comments
The doGet function cannot be made private since its name is fixed/predefined. But that is not really a problem as this function is supposed to be an entry point anyways, and since you expect it to be called from the users' browsers and can do your parameters checks and such accordingly.
Related
I suppose my question is twofold: doGet() in the following context will just fail after 0.1~0.2 seconds without posting logs, so I have no idea how to troubleshoot it by myself. Additionally, if I'm having the script execute on my behalf, do I have to push a request with my authorization token to a more "pertinent" area than just the sheet name, such as within the iteration itself? Read further for more details:
I have a source spreadsheet where I am cross-referencing user inputted data to validate the information we have "on file". Most of our clients are over the age of 55, so I am trying to reduce end-user complexity by having the script run on my behalf whenever they need to use it (to bypass the Authorization screen, with the big scary "This application could be unsafe!" message). The way I've read to accomplish this seems to be with doGet(), so I set up a low-level HTTP Get request that just pushes a doGet() with my OAuth token, returning the sheet name. I also set up a masking function specifically to do this, and linked it to the button originally used for the iteration logic. The doGet() looks like this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
and the button that uses UrlFetchApp looks like:
const runMask = () => {
const active = SpreadsheetApp.getActiveSheet().getSheetName();
const v4 = 'https://script.google.com/macros/s/<scriptid>/dev' // ScriptApp.getService().getUrl() posts 404
UrlFetchApp.fetch(`${v4}?sheetName='${active}'`, {
headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` },
});
I have some logs set up within the real runMask() that proceed all the way to the end of the program, giving me real URLs and OAuth tokens, so I know it's making it through runMask() without an issue. However, the doGet() log doesn't post anything, even at the top of the function. I can see that it's executing the trigger in my execution log, but the log itself remains empty.
I've tried:
using ScriptApp.getService().getUrl() in place of v4: posts 404 in the log w/ truncated server response
replacing ${active} with the name of the sheet: same issue; logging ${active} also returns the correct name of the sheet.
Beyond this, I'm not even sure what to do. I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email), and I have no issues about operational security (as both the spreadsheet and script are written by me, the clients have no need to grant access to their entire drive). Before trying to implement doGet() and bypass the authorization screen, the iterator itself worked just fine. As such, I have chosen not to include it here, as it's hardly relevant (the function that executes the iteration function never makes it to that point).
I understand this has been quite the deluge of information; I'd be happy to provide more information or context as needed.
Getting ReferenceError: iterator is not defined (line 12, file "ag2")
With this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
Issued with url/exec?option=A
It runs with
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(JSON.stringify(e));
Logger.log(content);
return content;
}
and returns the appropriate stringified object
Only use the test URL (/dev) for testing the web app from a web browser.
Before doGet from a web browser using a versioned deployment (/exec) remember to publish a new version.
Assign a Google Cloud Project to your Google Apps Script project. For details see https://developers.google.com/apps-script/guides/cloud-platform-projects.
To make it easier to debug your avoid calling functions from a Google Apps Script method like createTextOutput, instead, assign the function result to a variable and use it as the method parameter, i.e. replace
const content = ContentService.createTextOutput(iterator(e));
by
const something = iterator(e);
const content = ContentService.createTextOutput(something);
For debugging purposes, create a function to call your doGet function, and check that it hasn't any problem to run, i.e.
function __test__doGet(){
const e = {
parameter: {}
}
doGet(e);
}
Related
Exception handling in google apps script web apps
Issue:
When I saw your question, I'm worried about I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email).
If you are using only the following scopes at oauthScopes of appsscript.json,
https://www.googleapis.com/auth/spreadsheets.currentonly
https://www.googleapis.com/auth/script.external_request
https://www.googleapis.com/auth/userinfo.email
Unfortunately, these scopes cannot be used for access to Web Apps. Although I'm not sure about the method for running your function of runMask, I thought that this might be the reason for your issue.
Solution:
If you want to access Web Apps of https://script.google.com/macros/s/<scriptid>/dev using the access token retrieved by ScriptApp.getOAuthToken(), please include the following scope.
https://www.googleapis.com/auth/drive.readonly
or
https://www.googleapis.com/auth/drive
After you include the above scope, please reauthorize the scopes, and test it again. When your function of iterator has already been declared and the script worked, by running runMask, you can see the log of Logger.log(Recieved HTTP request.) and Logger.log(content) at the log.
Reference:
Taking advantage of Web Apps with Google Apps Script
This is related to this thread. I don't know what the problem is since I'm just new with this coding thing. But I found the error when I clicked the latest code below the URL.
This is the error TypeError: this[e.parameter.run] is not a function (line 2, file "Code").
And, this is the code:
function doGet(e) {
this[e.parameter.run](e.parameter.sheetName || null); //this is line 2
return ContentService.createTextOutput();
}
Honestly, I don't know what that function does since I just copy it on this solution. But as far as I understand, it helps for the script to be accessible to other users. What seems to be the problem here?
Explanation
You function doGet(e) expects a parameter run to which the name of an (existing) function is assigned.
This paramter has to be appended to the WebApp URL
If you call the WebApp without assigning it any paramater (by pressing on latest code) the function will error
Instead, you need to copy the Current web app URL and append to it ?run=NameOfFunction
For example:
https://script.google.com/macros/s/XXXXXXXX/exec?run=myFunction
If your script contains a function called myFunction() - this function will be executed on pasting the full Web App URL including parameter run into the browser address bar.
Recomendation
Modify
return ContentService.createTextOutput();
to return ContentService.createTextOutput("It worked");
This will give you some feedback about the fact that the Web App has been executed correctly.
In this post, I suggested to use the ScriptDB as an intermediate storage for global data of a Container Extension code. I wrote a sample code for my answer but the sample throws the error: You do not have permission to call query (line X) exception in a ScriptDb.getMyDb().query(...); line. I created the following simpler example demonstrating the problem. The code, both getDBSize and getSource functions, is permitted to use the ScriptDB by running it in the editor. The getDBSize function is executed without any problem by pressing the Run button in the Spreadsheet Script Manager Dialog. The getSource function works everywhere.
I published the Spreadsheet for the example - link. It is impossible to share the code for view, but it is possible to output it in a cell, the cell B3 contains exactly bellow code.
How is possible to permit the Spreadsheet Code to have access to the ScriptDB?
function getDBSize() {
var db = ScriptDb.getMyDb();
var result = db.query({});
var count = result.getSize();
return count;
}
function getSource() {
return this.toSource();
}
The problem is that you're trying to run this function as a spreadsheet custom function, and custom functions are way more limited than all examples on the Container Extension page you linked.
But, from a theoretical point of view, custom functions as well as simple event handlers (e.g. onEdit, onOpen), can not access anything that requires the user account or is associated with the user, only "generic" methods. For example, UrlFetchApp, open the current spreadsheet, or read ScriptProperties.
So, in thesis, querying a ScriptDb should be possible, since it's a generic call and has nothing to do with the active user, it's analogous to ScriptProperties. I don't see a workaround that would actually let you query the db, but you could use ScriptProperties instead. You can easily save and retrieve any object you would save on ScriptDb by using JSON.stringify and .parse. Of course, I'm not comparing ScriptDb capabilites with ScriptProperties, it's just a workaround.
Anyway, this seems like a good candidate for an enhancement request on our issue tracker.
I'd like a little sanity check if I may.
Can Javascript code in an HtmlOutput window floated over a Google spreadsheet alter variables and call methods in the gs code that created the HtmlOutput in the first place?
I have a Google spreadsheet with a floating form created like this:
someCode.gs
var theForm;
var theSpreadsheet;
function makeForm() {
:
theForm = HtmlService.createTemplateFromFile('aForm').evaluate();
theSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
theSpreadsheet.show(theForm);
:
}
function recordTheForm(jsonFormData) {
:
theForm.clear();
:
}
Attached to a button in the HTML of "aForm.html" I have a function call back into the file "someCode.gs".
aForm.html
:
google.script.run.recordTheForm(jsonTheForm);
:
Am I right that, even though it was instantiated from someCode.gs, the caller of recordTheForm() can have no knowledge of the contents of that originating memory space?
Is there anyway to get it? such as passing a "context" back and forth?
Variables in GAS:
Browser button clicks and other browser events produce a call to GAS. Every call from the browser to GAS causes your GAS code to re-run, re-initializing all variables. This is a problem for global variables. Global variables need to be passed back from the Browser to your GAS code or reloaded from the Database or Script Properties.
Consider this code:
function SwapColumns() {
chrome.tabs.executeScript(null,
{code:"SwapNameAndSurname();"});
}
The code above is in popup.html, the method SwapNameAndSurname() is in a content script named js.js that contains nothing but a library full of functions. When I call the SwapColumns function from popup.html, the SwapNameAndSurname() function in js.js is called changing something in the DOM of the original page via the power of content scripts.
However my problem is here. I have prepared an object named employee in popup.html that I need to send to the SwapNameAndSurname() function as a parameter:
var employee = {'Name' : 'John', 'Surname' : 'Doe', 'Qualifications' : [{'Title':'MCAD', 'Date':'Jan 2008'},{'Title':'MCSA', 'Date':'Feb 2008'}]};
function SwapColumns() {
chrome.tabs.executeScript(null,
{code:"SwapNameAndSurname(**???employee???**);"});
}
How can I do that? Since in chrome.tabs.executeScript you have to write code directly into a string, you cannot really send parameters. I've read Chrome's tabs.executeScript - passing parameters and using libraries? but still can't figure out what's happening.
I found an answer to my own question. From the extension pages you have to call the chrome.tabs.sendRequest method and from the the content scripts you have to add a listener using the chrome.extension.onRequest.addListener method. The send request method will trigger the event found in the content scripts enabling you to send any form of data to the content script.
For further reference see http://aarongusman.wordpress.com/2011/03/30/communication-between-chrome-extension-content-scripts-and-extension-pages/