There is a sheet that is shared with writing permissions to users A and B.
Column X is protected and can be edited only by user A.
User B should use a script with user A permissions to edit column X.
From reading the various documentation (https://developers.google.com/apps-script/guides/services/authorization), I understand that there are two ways to run a script on a google sheet as a different user.
Using the python api (https://developers.google.com/sheets/api/quickstart/python). The disadvantages: either launches from the cmdline, or Google's App Engine which is not free.
Using a WebApp. My problem with that solution, and perhaps this is what I am missing, is how to edit a cell with the doGet and doPost. All the examples that I saw were using the google script to edit the cells, which slammed me back to the permissions issue.
A possible solution: User A should deploy the web app with settings "execute as me [that is, A]", and choose access level as "anyone" [so that user B can access] or "anyone, even anonymous" to save user B an extra step and allow them to access the app programmatically. To prevent users other than B from editing, A gives B an access token, which B uses in their GET or POST requests. For example: user B accesses the app via GET request to https://..../exec?key=mykey, and the app has code like this:
function doGet(e) {
if (e.parameter.key == "mykey") {
var ss = SpreadsheetApp.openById("spreadsheet Id"); // can't use getActiveSpreadsheet in doGet/doPost
// do something with the spreadsheet
}
}
Documentation: doGet/doPost event object.
Related
Project_1 is a container-bound script. A container is a readable spreadsheet (Template).
Code_1:
function doPost(e) {
return HtmlService.createHtmlOutput(JSON.stringify(e));
}
The user makes a copy of the Template, deploys the script (Project_1) as a webapp with permissions: "Execute as: Me, Who has access: Anyone". The user is the owner of Project_1.
Project_2 is a script deployed as an add-on. The user from point 1 is not the owner of Project_2.
Code_2:
function sendPost() {
var sheetURL = SpreadsheetApp.getActiveSpreadsheet().getUrl();
var webAppUrl = "https://script.google.com/macros/s/###/exec"; // 7: Part_1 - WebApp: Tester
// var auth = ScriptApp.getOAuthToken();
// var header = { 'Authorization': 'Bearer ' + auth };
var payload = { scriptName: 'updateData', sheetURL: 'sheetURL' };
var options = {
method: 'post',
// headers: header,
muteHttpExceptions: true,
payload: payload
};
var resp = UrlFetchApp.fetch(webAppUrl, options);
var respTxt = resp.getContentText();
console.log('resp: ' + respTxt);
}
function doPost(ev) {
var respWebapp = func(ev);
}
The user installs an add-on (Project_2).
The flow in the direction of addon -> webapp is fine: when sendPost() starts, it sends a request to the webapp and receives a response with the necessary data_1 in response.
The flow in the direction of "someone on the web" -> webapp also flows well: when requesting a webapp_url receives the transferred data_2.
I am trying to transfer data_2 to an addon.
I read a lot about scripts.run, but it seems that this option is not applicable in such a situation.
There is also nowhere to add an eventListener.
I would not want to deploy webapp from my account, so as not to spend my quota for simultaneous executions (<= 30).
Also I would not like to do a sidebar, etc. in the spreadsheet and try to screw eventListener to html. I assume that with this approach, the listener (if it is possible to add it there at all) will be active only when ui is active (the spreadsheet is open and the sidebar is active). Data can come at any time of the day and must be immediately transferred to the addon.
Added:
I feel like I'm stumped. Therefore I reaches out to the community in the hope that someone would suggest a workaround or a new strategy for this initial data. By initial data I mean provide the opportunity for more than 30 users to exchange messages in both directions Spreadsheet <--> External service (for example, Telegram) and at the same time not fall under the limit of 30 simultaneous script executions.
Added_2:
I'm going to assign a bounty, so I'm transferring here from the comments what is missing in the post and updating the question itself.
I rejected the option with immediate entry into the sheet, because this will cause constant calls to the spreadsheet and slow down the performance of the system as a whole.
I am aware of the existence of Google cloud functions and Google compute engine, but would like to stay within the free quotas.
QUESTION: How to transfer data from webapp to addon and execute func () or which workaround to use to achieve the goals?
Here is a list of your requirements:
Trigger add-on code to run from some external request, not using the add-on user interface or time based trigger.
Code runs from the user's account, using their quota
Run the add-on code regardless of whether the user is using the add-on or not. For example, their Google Sheet is closed, and the user may even be signed out.
I only know of one way to do that, and it's with a Sheet's add-on by triggering the "On Change" event by setting a value in a Sheet cell using the Sheets API. The Sheets API must use a special option to set the value "As the User."
The special setting is:
valueInputOption=USER_ENTERED
That option will trigger the "On Change" event even if the Sheet is closed.
Obviously the script making the request needs authorization from the user to set a value in a cell of the Sheet.
If the script sending the request is outside of the user's account then you'd need to use OAuth.
The add-on would need to install an "On Change" trigger for the Sheet and the function that the trigger is bound to would need to determine whether the change was from the special cell designated for this special functionality.
If the request to set a value in the users Sheet is from outside of that users Google account, then the user of the Sheet would need to somehow authorize the OAuth credentials for the Sheets API to make a change to the Sheet.
Depending upon the programming language being used with the Google Sheets API, there may be a Sheets API Library specifically for that language. You can also use the Sheets REST API.
There is an example here on StackOverflow of using the Sheets REST API from Apps Script, but if the external request is from some code that isn't Apps Script, it won't be exactly the same.
I understand that the solutions proposed in the comments, by others and myself, can't work in your scenario because it can't stand an average delay of 30 seconds. In that case I strongly advise you to set up a Cloud project that can be used as an instant server, as opposed to triggers/apps/etc.
In "Code_1" and "Code_2" use a shared data store. In other words, instead of directly passing the data from "Code_1" to "Code_2", make that Code_1 write to the datastore and "Code_2" read from it.
One possibility among many is to use a spreadsheet as a database. In this case you might use on change triggers to do some action when the spreadsheet is changed by one of the "Code_1" scripts and/or use time-driven triggers to do some action with certain frequency or at certain datetime.
I have a working GAS web application that is deployed
execute as - me
who has access - anyone
now I need to separate data that the users are using, storing, accessing. I was thinking of using Google Sign-In but then I thought that I might somehow get user's info when they are logged into their Google account if I deploy with the who has access option set to Anyone with Google account. But I am not able to find anywhere how or if even I am able to get any details of the logged in user.
The only info that is related is this SO question Get user info when someone runs Google Apps Script web app as me . The solutions proposed https://stackoverflow.com/a/59974388/250422 seemed to me too complex if I can get details of the logged user in my GAS code.
Or is there any simple way how to protect my GAS web application with login? I want to save user related data to a dedicated spreadsheet for that particular user. So I need to somehow identify the user.
UPDATE
To be clear what I want to achieve
have a GAS web application with login authentication
each user of such application got separate Google spreadsheet file stored under my account or somewhere where I got access to it but NOT the end user
preferably I would use an existing facility for the login part
UPDATE2
as requested the code that I am currently using is
function doGet() {
Logger.log("let us start");
var htmlTemplate = HtmlService.createTemplateFromFile('index');
// htmlTemplate.dataFromServerTemplate = { fromServer: zzlib.settings.toHTML};
var htmlOutput = htmlTemplate.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
var email = Session.getActiveUser().getEmail();
Logger.log(email);
tmplibX.saveFile(email);
HtmlService.createTemplateFromFile('index').evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) //SAMEORIGIN
return htmlOutput;
}
and the tmplibX.saveFile(email); looks like
function saveFile(fileName){
console.log("from library");
Logger.log("from library");
console.log(fileName);
var fileName=Session.getActiveUser().getEmail();
console.log(fileName);
DriveApp.createFile("FROM-LIB-"+fileName, 'Hello, world!');
console.log("finished");
}
Bismillah... Hii I'm actually glad to see someone is doing what I do..
So what I do is: I CREATE TWO WEB APPS.
First is Execute as User accessing the web app and Accessible by Anyone with Google account
Second is Execute as Me and Accessible by Anyone
And what I write in the First Web App is:
function doGet(e) {
var em = Utilities.base64EncodeWebSafe(Session.getActiveUser().getEmail(), Utilities.Charset.UTF_8)
return HtmlService.createHtmlOutput('<script>window.top.location.replace("https://script.google.com/macros/s/mySecondWebAppDeploymentID/exec?e='+em+'")</script>')
.setTitle('myTitle').setFaviconUrl('https://i.ibb.co/xxx/myLogo.png')
}
This will redirect to the Second Web App with a parameter called e which is the users email encoded in Base 64. Which is the only data we can get.
Then in the Second Web App, u can write whatever u want, like redirect to a prefilled gform or getData from a Spreadsheet then match it with users email than do whatever according to the data...
The essential code of the second Web App is:
function doGet(e){
var email = Utilities.newBlob(Utilities.base64DecodeWebSafe(e.parameter.e,Utilities.Charset.UTF_8)).getDataAsString()
//Do whatever u want with that email string variable
}
Actually I was hoping to find someone else doing this, because I want to know what is the limit of this 'get Email' feature from App Script.
I was planning to use this for an event registration with Estimated Users up to 1000 users. Of course it would be troublesome if this feature is limited and ended up collapsing.
Hope this help, and I can know what exactly is the limit of this feature.
In order to add the user information such as their email address from your web app to a specific Spreadsheet in your Drive you should follow these steps:
Create the web app. Then you can use a simple function in your code.gs like the one below to insert a row with the user's email to the Spreadsheet you have chosen in your Drive to store this data:
function doSomething() {
SpreadsheetApp.openById('SPREADSHEET ID').getSheetByName('SHEET NAME').appendRow([Session.getActiveUser().getEmail()])
}
Once you have your HTML and script ready, you can deploy this web app as Execute as : me and Who has access to this web app : anyone with a Google Account. In this way the script will execute only on your account and will only modify your prive Spreadsheet while being able to retrieve the user information. Note that in this way user's won't be asked for authorization as you will be the only one running the scripts behind this web app (however, all users must have a Google Account).
Reference
Permissions execute as me vs as the user
getEmail
i wrote code for getting all viewers of spreadsheet in app script .
i have used getViewers() method to get viewers names who actually viewed it. but that method is returning me the names of people to whom i actually shared the spreadsheet....
is there any other way that i can get all viewers of spreadsheet.?
is there any web automation tools that can solve my problem?
Answer:
It is not possible to get a list of people that have opened your a Google Drive file using Google Apps Script - a method that returns this list does not exist. The getViewers() method returns the list of people with view and comment permissions for a file, while getEditors() retrieves the list of people that have edit permissions.
The Issue:
is there any other way that i can get all viewers of spreadsheet.? is there any web automation tools that can solve my problem?
There is no way of getting viewers of a Google Sheet as this is a huge security issue. This information is not stored and is therefore not retrievable.
Workaround:
You can make a custom function which stores the username of a person when they open the file - though be aware that triggers have restrictions and will only run if the person opening the file has edit access. I have included a list of Apps Script Trigger Restrictions below for you to look through and decide what is the best approach for you.
Code:
function onOpen(e) {
var user = e.user.getEmail();
// do some code to store or save this parameter
// for example save it to a hidden sheet or email it to yourself
// though an email would require an installable trigger to be made
}
Simple Trigger Restrictions:
These are not all of the restrictions (full restrictions are available here), but these are the ones that I believe to be most relevant for you.
As per the Google Apps Script Simple Triggers documentation:
They do not run if a file is opened in read-only (view or comment) mode.
They cannot access services that require authorization. For example, a simple trigger cannot send an email because the Gmail service requires authorization, but a simple trigger can translate a phrase with the Language service, which is anonymous.
They can modify the file they are bound to, but cannot access other files because that would require authorization.
They may or may not be able to determine the identity of the current user, depending on a complex set of security restrictions.
This last point is important - getting information about the current user is possible depending on the security policies of the the G Suite domain. A detailed explanation of this can be found in the getActiveUser() method documentation:
If security policies do not allow access to the user's identity, User.getEmail() returns a blank string. The circumstances in which the email address is available vary: for example, the user's email address is not available in any context that allows a script to run without that user's authorization, like a simple onOpen(e) or onEdit(e) trigger, a custom function in Google Sheets, or a web app deployed to "execute as me" (that is, authorized by the developer instead of the user). However, these restrictions generally do not apply if the developer runs the script themselves or belongs to the same G Suite domain as the user.
There are big security issues with getting a list of people that have viewed a file, for good reason, and so what you are looking to do it highly restricted by Google.
References:
Class File of Google Apps Script
getViewers() method of Class File
getEditors() method of Class File
Simple Triggers
onOpen(e) Trigger
Simple Trigger Restrictions
Installable Triggers
Event objects
Class User of Apps Script
User.getEmail() method
Class Session
getActiveUser() method of Session Class
I'd like to pull data into a Google doc from a MySQL database (quote / invoice data) via a Google Apps Script. The doc will be used by several people (in a small agency). However these project managers should not be able to see the login details that are embedded in the Apps Script Code. I don't care if they see the Code. So is there a trick to hide or lock the login details for the database? Any advice??
Google apps script has a concept called properties services. You can use the Script properties or the user properties to stored your credentials:
Script properties are shared among all users of a script, add-on, or web app. They are typically used for app-wide configuration data, like the username and password for the developer's external database.
Usage:
var scriptProperties = PropertiesService.getScriptProperties();
//setting
scriptProperties.setProperty('SERVER_URL', 'http://www.example.com/');
//retrieving
var units = userProperties.getProperty('DISPLAY_UNITS');
User properties are shared among the current user of a script, add-on, or web app and typically used for User-specific settings, like metric or imperial units.
Usage:
var userProperties = PropertiesService.getUserProperties();
//setting
userProperties.setProperty('DISPLAY_UNITS', 'metric');
//retrieving
var units = userProperties.getProperty('DISPLAY_UNITS');
You can also manually set, view or remove these properties by going to File->Properties and the relevant tab.
There is also the third kind of property called document properties about which you can find out here.
As some of you mentioned, the problem is that a user could read out the login details stored externally with an appropriate script. So my solution for now is to run the SQL select in a standalone script embedded as web app and to let the standalone script write the query result into a spreadsheet. The editable doc will then Import the data from there.
i have an application (called:leave management) for my company that created from google sites(for UI), google spreadsheet(for Database) and integrated with google apps script. This application handled request for leaving, so, employee that login with company email can read and write the spreadsheet from google sites.
And here's the problem...i don't want the employee could edit the spreadsheet "DIRECT"...they should be write and read on the google sites...so the sharing settings for the spreadsheet must be "PRIVATE"...but it's impossible for read or write by other employee if the spreadsheet was "PRIVATE"...
in my mind...we can access the spreadsheet but we must have email spreadsheet owner, and the password too...it should be like this
var Sheet = SpreadsheetApp.openById("SpreadsheetID", "emailSpreadsheetOwner", "passEmailSpreadsheetOwner")
but yeah...it's doesn't work...
could you help me??
thanks :)
I use the same setup in an app, the spreadsheet is set to 'anyone with the link can edit' but no user knows the name or the ID of the sheet so they can only write and read through the UI and they'll never open the ss itself.
Isn't that what you want ?
There are two ways of deploying your application. Given your requirements, have your application run as the owner of the script and not as the 'User accessing the web app'
This way, you (the owner of the script) can keep the spreadsheet private, yet be able to write and read from it when a different user accesses the application.