How to run a Google Apps Script in a read-only spreadsheet? - google-apps-script

I'm new to the world of Google Apps Script, and I found myself faced with a problem. I've created a script that runs as an onOpen() trigger, but if the spreadsheet file is read-only, this script does not run. I read that, being read-only, it is not able to run it. Is there a way to remedy this problem?
I gave the permissions in edit, blocking all the cells from scripts. I wish people can not see the script, as this allows them to re-run the script and take "power" on the sheet. If I create an API can I keep the sheet read-only?
Any suggestions?

All scripts must run under someone's authority. Under whose authority, a script is run determines whose data is accessible to the script and whether such authority can run the script.
Authorization Concepts:
Scripts which are run from the script editor run under the authorization of user at the keyboard¹. Custom functions runs anonymously. Installable triggers runs under the user created the trigger. WebApps run as per the options selected during deployment.
Simple triggers fire automatically and anonymously under these restrictions²:
The script must be bound to a Google Sheets, Slides, Docs, or Forms file, or else be an add-on that extends one of those applications.They do not run if a file is opened in read-only (view or comment) mode.They cannot access services that require authorization.
Installable triggers must be set up and run under the user who set up the trigger³.
They do not run if a file is opened in read-only (view or comment) mode.Installable triggers always run under the account of the person who created them.A given account cannot see triggers installed from a second account, even though the first account can still activate those triggers.
You can restrict access to spreadsheet, sheets or ranges⁴,⁵.
Deductions:
Mr.A(Owner) has a spreadsheet. The spreadsheet has two sheets 1.Main Sheet and 2. Secret Sheet(Sheet is protected and hidden to be edited only by Mr.A). It also has the following scripts: 1. A simple trigger script(onEdit) to set timestamp as a note in every cell that is edited. 2. A installed trigger (AonEdit()) to send email from MrA's email on every edit. 3. A function(summary()) to create a summary of Main Sheet and send email from user's account to himself(to run manually from script editor). 4. A function to protect the secret sheet(protect()) 5. A simple onOpen() trigger logging Someone opened your sheet. And He gives edit access to Mr.B(a editor). What can Mr.B do?
Mr.B edits cell A1 in Main Sheet:
Simple trigger onEdit is fired anonymously and a timestamp is set on A1 as a note.
Installable trigger AonEdit is fired under Mr.A's authority and a email is sent from Mr.A's gmail.This is not known to Mr.B,though he can see the script itself.
Mr.B,being a cunning person as he is, unhides the secret sheet successfully and attempts to modify it:
Mr.B could not modify the secret sheet,even though he can fully unhide/view it
Mr.B finds the script editor and tries to run the function summary(). This function needs gmail permission. Mr.B is greeted with authorization[¹] for Mr.B's gmail account,so that the function may run. Mr.B grants authorization and the summary is sent from Mr.B's gmail account.
Mr.B cunningly modifies the protect() function to grant himself edit access to the secret sheet. The user at the keyboard is Mr.B. Mr.B's authority is not enough. He's greeted with the following error:
You are trying to edit/remove a protected cell or object. Please contact the spreadsheet owner to remove protection if you need to edit.
Mr.A (Owner) knows about this unauthorized access attempt by Mr.B, He restricts edit access to All sheets. Now, Mr.B even though has permission to edit the spreadsheet, He cannot edit any sheet in the spreadsheet. Whenever he opens the sheet, the simple onOpen() is triggered logging Someone opened your sheet. Mr.B however can run the function summary() even now(If he has the script editor link) to get the summary of the sheet.
Solutions:
as this allows them to re-run the script and take "power" on the sheet.
A user with write permission can enter the script and modify the permissions.
As explained above, That wont be possible. The sheet/range edit permission is maintained even at the script level. However, A potential loop-hole is the AonEdit() function. If Mr.B were to know that Mr.A had set up a installable trigger for AonEdit() function, He can modify the AonEdit() function to say protection.remove(),which will run under Mr.A's authority(Installable triggers run under the person who created it) and thus the protection is removed.
I wish people can not see the script.
You can use a standalone script⁶. You can also use installable triggers[³] with standalone scripts subject to the restrictions of those triggers(such as It'll only run under your authority). Since the script is not bound to the spreadsheet, Editors on the spreadsheet do not have edit/view permission on the script. Alternatively, You can publish a addon⁷/webapp⁸.

Related

Allow script to edit locked cells

I have a complex spreadsheet where most of the sheet is locked and the user can only edit a handful of cells which triggers a bunch of calculations. This used to work fine but the problem now is I have added a drawing which I attached a script to so it acts as a button. Doing this forces the user to have to authorize and now the scripts run as that user so when the script tries to update cells that are locked to the user it fails.
How can I make it so a user can't type into cells, but my scripts can still update them. Basically I want the script to have full access to the sheet, not restricted by user permissions.
Workaround#1 -Service account:
Create a service account
Share your spreadsheet with edit permissions to the service account's email
Install and Use the Google oauth2 library to get Bearer token with necessary scopes(Drive/Sheets/Both). This token can be used to impersonate the service account.
Using the bearer token above, You can directly access the
google-sheets-api using urlfetch
OR use a published webapp(set to execute as "User accessing the app" and "Anyone") to use inbuilt services such as SpreadsheetApp. See Second related answer linked below.
In this case, PRIVATE_KEY of the service account acts as a password to access the spreadsheet with edit privileges. So, exposing it directly in the script editor will give access to any of the editors to access protected areas of the spreadsheet and all service account resources. So, in a way, protected areas are not protected in a absolute way. If protected areas need to be absolutely protected, You may be able to bypass this limitation
using two script projects: a bound one posting data to a unbound one, which is published as a web app and holds the private key. Here, editors can be supplied with passwords to access the unbound script.
Another way is to simply publish a addon, as a addon's source code is never visible to end users.
Workaround#2 - Installable triggers:
Use a installable edit trigger with a checkbox. Users click a checkbox in the unprotected area and script modifies the protected area.
Installable triggers run under the authority of the user who installed it and not as the current user.
They can bypass permission restrictions of the sheet. But this is a double edged sword. Anyone with edit permission will be able to trigger the script. Not only that, they may also be able to access the script editor and modify the script as they see fit. To limit foul usage,
Set the script to run only at a specified version: This can be done by setting the edit trigger manually in Tools > Script editor> Edit > Current project triggers > Add trigger > Select version. Script must have a saved version and be deployed as a webapp(doesn't need to be working).
Avoid providing unnecessary scopes to the script. Limit oauthScopes by editing manifest file. Preferably the only scope provided should be https://www.googleapis.com/auth/spreadsheets.currentonly
Related:
Is there a way to let a user edit another spreadsheet with a script and hide it from him at the same time?
Google App Script execute function when a user selects a cell in a range

Google Apps Script AddOn: onChange trigger programatically with misleading permission scope text

By Google documentation, an onChange trigger for a Spreadsheet addon has to be created programmatically via ScriptApp.newTrigger(...)....
newtrigger requires the scope :
https://www.googleapis.com/auth/script.scriptapp
When the user is asked for granting permissions, the text presented is:
Allow this application to run when you are not present
I understand this function can create time based trigger, but for a simple onChange in a Spreadsheet, I would understand if the user declines it due to that text.
Is there a way to have an onChange trigger without this scope for changes (not onEdit) in spreadsheets?
Answer:
The only way to create a trigger on behalf of a user programmatically is using ScriptApp.newTrigger(), and this method requires the https://www.googleapis.com/auth/script.scriptapp scope. No other scope will allow this to run.
You can however set up an onChange() trigger for the Spreadsheet, which will run regardless of who makes the change to the sheet.
More Information:
When you set up a trigger, you are allowing an app/script/function to run, on behalf of you, without you manually running the function. Asking a user to create a trigger will require them to authorise that they consent to the script doing something on their behalf.
You can however, set up the trigger yourself. As long as the function that will run on the trigger doesn't violate any of the installable trigger restrictions then this will run on the Sheet as long as it has been authorised to run by you.
Remember though, that if you set up the trigger yourself, the script will always execute as you, not as the user. So if the function, for example, sends an email, then the email will be sent on behalf of you - as you were the one that authorised the script.
Setting up an Installable Trigger manually:
If setting up the trigger to run as you is an acceptable solution, you can do so by following these steps:
Save the script with the save icon, press the run button (►), and confirm the authentication of running the script.
From here, following the Edit > Current project's triggers menu item, you will have a new page open in the G Suite Developer Hub. Click the + Add Trigger button in the bottom right and set up the trigger settings as follows:
Choose which function to run: <your-function-name>
Choose which deployment should run: Head
Select event source: From Spreadsheet
Select event type: On change
And press save.
This will now run on all changes made to the sheet - regardless of who made the change. Just remember that as far as execution goes, you're the script runner.
References:
Class ScriptApp | Apps Script - .newTrigger(functionName) method
Installable Triggers | Apps Script - Restrictions
Event Objects | Apps Script - Google Sheets events: Change

How to refer to my tasklist in a standalone script that will be used by other user

I have a spreadsheet linked to a standalone script that imports task from my tasklist. It works fine when I am the user but does not work in share user account.
The codes in the standalone script is
function getTasks() {
var tasklistID="mytasklistid";
var tasks=Tasks.Tasks.list(tasklistID);
return tasks
}
Code in Bound script is
function getTask(){
var tasks = TaskManagerScript.getTasks()
Browser.msgBox(tasks)
}
When I run the code in my account I get the tasks from tasklist with the specified id as expected. But when I run it from a different user account I get the the tasklist of the other user.
How do I make the code return the tasks from the list with the specified id when other users run it.
You are using a container-bound script of Spreadsheet, and the bound script installs a library.
The Spreadsheet is shared by users. You are an owner of Spreadsheet.
You want to make users use your task ID.
You want to run the script by the OnEdit event trigger.
If my understanding is correct, how about this answer?
OnEdit event trigger is run as owner of Spreadsheet. So when users edit the Spreadsheet, the script is run with your task list ID. But from your question, it's when I run it from a different user account I get the the tasklist of the other user.. I could confirm that in my environment, when users edit to the shared Spreadsheet, the script is run as owner (me), and my task list ID could be used. Unfortunately, I couldn't replicate your situation. So, in order to confirm this situation, can you test the following flow?
Sample flow for testing:
Create new Spreadsheet.
Open the script editor and install the library of TaskManagerScript.
Enable Tasks API at Advanced Google Services.
Put the script to the script editor. The script is as follows.
function getTask(e) {
var tasks = TaskManagerScript.getTasks();
e.source.appendRow([JSON.stringify(tasks)]);
}
Install OnEdit trigger to getTasks().
In order to authorize, please run getTasks() by the script editor and authorize the scopes.
In this case, an error occurs. But don't worry. This action is used only for authorizing.
This authorization might not be required. But this is just in case.
Share the Spreadsheet to user who is not your account.
The user edits the sheet.
By above flow, when the user edits the sheet, the result retrieved with your task list ID is returned.
Note:
When OnEdit event trigger runs the script by editing the cells by users, the script is run as owner of Spreadsheet. But when the script is run by clicking a button on the sheet and the custom menu, the script is run as each user. Please be careful this.
Reference:
Installable Triggers
Run your scripts as webapps that are always run as you. As far as I know tasks doesn't allow sharing or let you manipulate other peoples tasks.

How to run a spreadsheet bound script as the creator (not viewer/editor)

I made some script with my spreadsheet, which uses some Trigger and SendEmail functionalities. I have menu items in spreadsheet to control these triggers and sendemails.
Now when I share this spreadsheet with someone other, when he tried to access the trigger or sendemail functions from menu, the script asks for authorization as that user. IF authorized it will function as that user. e.g. send email as that user or make a new trigger as that user. This makes things double and useless
I want that any user accessing the script can use those functionalities, but won't require authorization to run as that user. The script should run as the creator of the sheet, so that no double triggering occurs. How should I do it?
You could create a web-app. Web-apps have the ability to run either as the user himself, or as the developer/publisher.
Under Execute the app as, select whose authorization the app should
run with: your account (the developer's) or the account of the user
who visits the app (see permissions).
https://developers.google.com/apps-script/guides/web
This web-app wouldn't have a sheet linked to it, but if you only use 1 sheet you can have the web-app access the sheet through the ID of the sheet. You could use your existing menu-items to trigger the webapp
Would this be a possible solution for your problem?

Script triggered onEdit runs into permissions error when trying to edit protected sheet

I have a problem with a Google script for a spreadsheet. I have a script installed onEdit through the Resources menu. The script copies some cells from an unprotected sheet to a protected sheet. The script works fine when I'm logged in to my account (I'm the owner of the spreadsheet), but runs into permission errors when run from a collaborator's account who has no access to the protected sheet. Shouldn't the script run as me (the account owner) and not run into permission issues?
Another script in the same spreadsheet that runs onFormSubmit is able to edit a protected sheet when a collaborator submits a form, even though he has no access to the protected sheet. This suggests the problem is not with permissions per se, but that the problem is specific to the onEdit trigger, right?
This is expected behavior. The onEdit trigger is one of three 'simple triggers'.
From the docs:
"These simple triggers run in response to actions in Google Spreadsheets, and they run as the active user. For example, if Bob opens the Spreadsheet, then the onOpen function runs as Bob, irrespective of who added the script to the Spreadsheet. For this reason, the simple triggers are restricted in what they are permitted to do:
They cannot execute when the Spreadsheet is opened in read-only mode.
They cannot determine the current user.
They cannot access any services that require authentication as that
user. For example, the Google Translate service is anonymous and can
be accessed by the simple triggers. Google Calendar, Gmail, and Sites
are not anonymous and the simple triggers cannot access those
services.
They can only modify the current Spreadsheet. Access to other
Spreadsheets is forbidden.
Have to use installed trigger for it to work