How do use the addEditor method on a protected google sheet - google-apps-script

Please help. This is all new to me.
I have a protected sheet in a google spreadsheet that I want to leave protected. However, my script deletes rows and adds rows to that protected sheet. It works great for me, but other other users cannot run the script.
I tried creating an onOpen trigger that calls a function which uses the addEditor method for those users. However, that did not work. To test my trigger, I changed it to the following which calls a function called UserInfoFunction, also shown below , wherein I get a messagebox telling be who the Effective user is and who the Active User is. I thought the Effective user should have been me since I created the script. But when others run this the message box shows they are the Effective user which, I'm assuming is why my addEditor function is not working since the Effective user wouldn't have permissions to change the protection...What am I doing wrong.
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.openById(".......");
ScriptApp.newTrigger(UserInfoFunction())
.forSpreadsheet(ss)
.onOpen()
.create();
}
function UserInfoFunction() {
var me = Session.getEffectiveUser();
var them = Session.getActiveUser();
SpreadsheetApp.getUi().alert('me is equal to...'+ me + ' user is equal to ' + them);
}

Related

Installable trigger needs to grab email of user who edited, but is only accessing mine

So I've got a spreadsheet that lists work to be done via an imported range from another file. Users have a dropdown of validated data that when they select the status of this work it searches the other sheet for the work they've selected and updates the status of this work. It's super simple, and works for everyone I've given editing privileges to.
I'd like alter it to also log the email of the user who edited the work status and therefore ran the script (we are all part of the same workspace domain). I've gotten it to pull my email and place it where required with repetition, but I cannot get it to access anyone else's. I tried deploying it, although I'm not entirely sure I understand how that works. I've looked into authorizing it, but the only place I can find to alter authorization is via the appscript.json in the editor, but that isn't showing the permissions that the documentation says to edit/add so I'm a little lost as to how to authorize this.
Not sure if it matters, but this script is attached to the sheet it picks up the edit from. I don't know if that means the sheet permissions need to change or what.
Here is the entirety of the code, minus identifying URL's/ID's:
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); //shortens calling to the current sheet
var dataSheet = SpreadsheetApp.openById("datafileID").getSheetByName("Data"); //shortens calling to the data file
const status = e.value; //validated user input
var erange = e.range; //range of edited cell
var ecolumn = erange.getColumn(); //range column of edited cell
var erow = erange.getRow(); //range row of edited cell
var snRow = erow; //identifies what row to look for the store number
var snColumn = ecolumn-2; //identifies what column to look for the store number
var sn = sheet.getRange(snRow, snColumn).getValue(); //declares the store number as variable
var user = e.user; //declares user as variable
if (!e.range.isBlank()) { //searches data sheet for store and updates status and user
var column = dataSheet.getRange("G:G").getValues();
var uRow;
for (var i = 0; i < column.length; i++){
if (column[i][0] === sn) {
uRow = i+1;
break;
}
}
dataSheet.getRange(uRow,6).setValue(status)
dataSheet.getRange(uRow,5).setValue(user)
}
sheet.getActiveCell().clearContent();
}
onEdit is a reserved name for simple triggers, you should not use call this function from an edit / change installable trigger because there will be two executions running in parallel.
When using an simple or installable trigger with Google Workspace accounts from the same domain, e.user should return the User object representing de active user.
As the script is working for your account there is no need of additional permissions.
As the script is not working, try the following:
Delete the installable trigger
Change the name of the function (i.e. respondToEdit)
Create the installable trigger again pointing to the new function name.
Double check that the spreadsheet sharing permissions are set to editors from your Google Workspace domain only.
References
https://developers.google.com/apps-script/guides/triggers/events#edit

Hiding google sheet tab from users [duplicate]

This question already has an answer here:
Google Spreadsheet - Show sheets depending on type of user
(1 answer)
Closed 2 years ago.
I have a google sheets document with two tabs one called called internal and the other called external. How can i hide the internal tab from other users? the lock function already avialble is not good enough I only want people from my company to be able to see both tabs, clients should only be able to see the external tab.
function validUsers() {
String[] adminUsers = {”email1#gmail.com”,”email2#gmail.com”,”email3#gmail.com”};
if (adminUsers.indexOf(Session.getEffectiveUser().getEmail()) >= 0) {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Internal').showSheet()
else
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Internal').hideSheet()
}
}
Issue:
You want to hide or show a sheet in your spreadsheet depending on which user is accessing the spreadsheet.
Solution:
You could do the following:
Install an onOpen trigger which executes a function (let's call it fireOnOpen) every time a user opens the spreadsheet.
The function fireOnOpen should check which user is accessing the spreadsheet, and hide or show a certain sheet (called Internal) depending on this.
In order to check the current user accessing the spreadsheet, you can use getActiveUser() (instead of getEffectiveUser(), which will return the user who installed the trigger).
Workflow:
The trigger can be installed either manually or programmatically. To do it programmatically, copy this function to your script editor and execute it once:
function createOnOpenTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("fireOnOpen")
.forSpreadsheet(ss)
.onOpen()
.create();
}
This will result in fireOnOpen being executed every time a user accessed the spreadsheet. The fireOnOpen function could be something like this:
function fireOnOpen() {
const adminUsers = ["email1#gmail.com","email2#gmail.com","email3#gmail.com"];
const currentUser = Session.getActiveUser().getEmail();
const internalSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Internal");
if (adminUsers.includes(currentUser)) internalSheet.showSheet();
else internalSheet.hideSheet();
}
Important notes:
You cannot hide sheets for some users but not for others. A hidden sheet is hidden for all users, and a visible sheet is visible for all users. Therefore, this will only work if internal and external users don't access the spreadsheet at the same time. If they do, external users might be able to access the Internal sheet.
getActiveUser() is not always populated, as you can see on this answer, so please make sure that all admin users are from the same G Suite domain. Otherwise, this won't work.
If the privacy of the Internal sheet is critical and there is a possibility of internal and external users accessing the spreadsheet at the time, I would not recommend this solution.
Edit:
As mentioned in comments, a possible workaround for the occasions when admin and non-admin users access the file at the time could be the following:
When an admin user accesses the file, store the time in which that happened.
Create a time-driven trigger to execute a function periodically (every 5 minutes, let's say), which will check if an admin accessed the file a short time ago (let's say 30 minutes). If the admin has done that, remove the Permissions for the different non-admin domains. If that's not the case, add these Permissions back.
Enabling the Drive Advanced Service would be required in this case.
Updated code sample:
function fireOnOpen() {
const adminUsers = ["email1#gmail.com","email2#gmail.com","email3#gmail.com"];
const currentUser = Session.getActiveUser().getEmail();
const internalSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Internal");
if (adminUsers.includes(currentUser)) {
internalSheet.showSheet();
const documentProperties = PropertiesService.getDocumentProperties();
documentProperties.setProperty("lastAdminAccess", new Date().getTime()); // Store time of admin access
} else internalSheet.hideSheet();
}
function createOnOpenTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("fireOnOpen")
.forSpreadsheet(ss)
.onOpen()
.create();
}
function updatePermissions() {
const fileId = SpreadsheetApp.getActive().getId();
const lastAdminAccess = PropertiesService.getDocumentProperties().getProperty("lastAdminAccess"); // Last time of admin access in ms
const now = new Date().getTime(); // Current time in milliseconds
const thirtyMinutes = 1000 * 60 * 30; // 30 minutes in milliseconds
if (now - lastAdminAccess < thirtyMinutes) {
const currentPermissions = Drive.Permissions.list(fileId)["items"];
const publicPermissionIds = currentPermissions.filter(permission => permission["type"] === "anyone")
.map(permission => permission["id"]);
publicPermissionIds.forEach(permissionId => Drive.Permissions.remove(fileId, permissionId));
} else {
const resource = {
type: "anyone",
role: "reader"
}
Drive.Permissions.insert(resource, fileId);
}
}
function createTimeTrigger() {
ScriptApp.newTrigger("updatePermissions")
.timeBased()
.everyMinutes(5)
.create();
}
As soon as you share a sheet you should assume that anyone can see the data in it. Even if someone shouldn't be able to see the internal tab, they can always e.g. make a copy of the sheet and thus get to the data.
You could try creating a separate sheet and using =IMPORTRANGE() to refer to the original one. But know that once you allow the connection between the two sheets, anyone with access to the second one might be able to access anything in the first one. Maybe get around that using three sheets:
Internal + External - your current sheet
A sheet-in-the-middle that only you can access. It has a single tab Internal that uses =IMPORTRANGE() to access data from 1)
The External sheet for clients. Linked to 2) through =IMPORTRANGE()
This way 3) only has access to the data in 2) which in turn only includes a link to 1).
I do not promise that this will make the data safe from those who shouldn't see it. But it will at least be safer.

My Script works for some people but not others

I created an audit template in Google sheets that uses several scripts. The script is designed to populate a cell with the active user's email and a time date stamp when they click on a check box. The script works, except for one person, the script does not recognize his active user email. Can someone tell me why the script works for some but not others and how I might fix it?
I've tested the script in a blank test spreadsheet and it works for this person. We had this same issue in another Google Sheet, but we remedied it my creating a new spreadsheet and copying all of the tabs and script. However, attempts to do the same with this spreadsheet have failed.
Here's my script:
function onEdit(e) { //this is the event Google Sheets fires when cells are changed
var email = Session.getActiveUser().getEmail(); // Get the email address of the person running the script.
var date = new Date();
var spreadsheet = SpreadsheetApp.getActive();
if (e.source.getActiveSheet().getName() == "InterimTestsOfControls") { //only do this if the changed cell is on the specific worksheet you care about
switch (e.range.getA1Notation()) { //This gets the cell that was edited
case "E5": //and this switch statement allows you to only respond to the cells you care about
if (e.value == "TRUE") {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Prepared by " + email + " " + date);
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
else {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Click box to sign-off as preparer");
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue("");
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
}
}
}
From the official Session class documentation:
getActiveUser()
Gets information about the current user. If security policies do not allow access to the user's identity, User.getEmail() returns a blank string.
This situation may happen under many, different circumstances:
The user's email address is not available in any context that allows a script to run without that user's authorisation, 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).
These restrictions generally do not apply if the developer runs the script themselves or belongs to the same G Suite domain as the user.
In your situation I see that you are using a simple onEdit(e) trigger - so it looks like this may be the issue. You may want to check out Installable triggers instead.
function onEdit(e) {
var sh=e.range.getSheet();
var email = Session.getActiveUser().getEmail(); //This is a permissions issue. You may have to use an installable trigger and even then it's not guaranteed
var date = new Date();//you might want to use Utilities.formatDate()
//var spreadsheet = SpreadsheetApp.getActive();this is than same as e.source
if (sh.getName()=="InterimTestsOfControls") {
//you can selection your cells with information that's already in the event block rather than having to run another function
if(e.range.columnStart==6 && e.range.rowStart==5) {
if (e.value=="TRUE") {
sh.getRange("F5").setValue("Prepared by " + email + " " + date);
e.source.getSheetByName('Audit Planning').getRange('C7').setValue(email);
} else {
e.source.getRange("F5").setValue("Click box to sign-off as preparer");
e.source.getSheetByName('Audit Planning').getRange('C7').setValue("");
}
}
}
}
Getting and setting the active range is a remnant of the Macro Recorder following your every step and in general is not required in your scripts. And in fact it will slow down your scripts so please try to avoid using that approach in general for most scripting.

Writing into another Spreadsheet with SetValue fails without errors/exceptions

I'm trying to update a spreadsheet from a script running on another spreadsheet.
Nothing seems to have any effect on the table (SetValue(), SetBackgroundRGB(), etc.).
I've checked the scope, it includes "https://www.googleapis.com/auth/spreadsheets" permission; besides, this same script has no problem writing to another spreadsheet that it creates in runtime.
function updateAnotheSpreadsheet() {
var targetSpreadsheet = SpreadsheetApp.openById('<target spreadsheet id>');
var sheet = targetSpreadsheet.getSheetByName('<sheet name>');
Browser.msgBox(sheet.getRange("A1").getValue()); // Here I see that my getSheetByName worked
sheet.getRange("A1").setValue('Test value'); // But this does nothing
}
There are no errors but also no effect: nothing changes in the target spreadsheet.
Hi I was testing and was able to do what you are trying to do. You can try to declare a variable for the message, here is what I was able to do, I hope this resolves your inquiry.
function updateAnotherSpreadsheet() {
//Opening the second Spreadsheet by ID
var targetSpreadSheet = SpreadsheetApp.openById("<SpreadSheetId>");
var sheet = targetSpreadSheet.getSheetByName("<SheetName>");
//Getting the range to show on msgBox.
var msg = sheet.getRange("A1:A1").getValue();
//Displaying old data on A1:A1 from the secondary Spreadsheet.
Browser.msgBox("Old data on secondary spreadsheet: " + msg);
//Getting the range and setting new values
sheet.getRange("A1:A1").setValue('Test value').setBackground("teal").setFontColor("white");
}
I would suggest to check for more information here https://developers.google.com/apps-script/guides/sheets.
I hope this helps, greetings.
I found the problem.
A function called from OnEdit() can't ask for permissions. I needed to first update that other spreadsheet from any function called by something "active", like a button; then, after the script asks for permission once, it can do its thing from OnEdit() the next time it runs.

Time-driven triggers scope in Google Script

UPD:
I have a published application as Spreadsheets addon and a few spreadsheets, that use this addon and execute time driven trigger:
ScriptApp.newTrigger("myFunction")
.timeBased()
.everyHours(1)
.create();
All works fine, but I'm a little confused, in what scope does this trigger work?
Since this is a published addon, the script is one and the time driven function is also one, right? As Diego said: "It works with the user scopes of the user who initiated the trigger". Okay, but what about spreadsheets?
I need to write (if trigger sees only this spreadsheet):
function myFunction(){
var ss = SpreadsheetApp.getActive();
}
Or (if it runs without binding to a specific spreadsheet):
function myFunction(){
var ss = SpreadsheetApp.openById('<some id>');
}
In other words, when I set the trigger, do I set it only for the current document or for all?
Thanks!
It works with the user scopes of the user who initiated the trigger. Here's how you can check:
Create a User property value i and set it to zero
In a new function, get the user property i, write it to a sheet, increment i and save it back to the property.
Create a trigger so that function runs every minute.
View the spreadsheet as a different user.
As the different user, repeat steps 1 & 2 to see that now i has started again from zero, but the 1-minute trigger is still iterating the original i value (because the different user can't access the first user's properties).
Here's the code:
function getAndPrintUserProperty() {
var userProperties = PropertiesService.getUserProperties();
var i = userProperties.getProperty("i");
i++;
SpreadsheetApp.getActive().getSheetByName("Sheet1").appendRow([i]);
userProperties.setProperty("i", i);
}
function addUserProperty() {
PropertiesService.getUserProperties().setProperty("i", 0);
}