I am setting up a spreadsheet whose users do not necessarily have a Google account nor should be required to log in.
I would like to interact with users in two ways that are possible with the Browser class. One is displaying an alert in a pop-up window, in order to warn users not to edit a field. This is done with Browser.msgBox or ̀€ui.alert. The second thing is requesting an input in a window, which is done nicely with Browser.inputBox().
While my code works fine when I am logged in as owner, it is useless when edited by an unregistered user.
Can I make those function work for unregistered users and if so, how?
Otherwise, are there workarounds to achieve similar functionalities?
Here is a minimal example of what I did:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSheet();
var eventRange = event.range;
var eventRow = eventRange.getRow();
var eventCol = eventRange.getColumn();
if(eventCol == 1){
protectDateField(ss, eventRow, eventCol)
}
}
function protectDateField(ss, eventRow, eventCol){
var ui = SpreadsheetApp.getUi();
ui.alert("Do Not Edit This Field!");
ss.getRange(eventRow,eventCol).clearContent();
}
Short answer
What you want to do it's not possible on Google Sheets.
Explanation
To run a script bounded to a spreadsheet the user should be an editor for that spreadsheet.
Simple triggers can't access services that require authorization to run.
Some additional restrictions apply when triggers are triggered by anonymous users.
NOTES:
The required authorization scopes by each method are included on the documentation section for each method.
Anonymous users can't authorize scripts to be ran by installable triggers as the authorization process requires a usar account to log the respective authorization.
For further details see:
https://developers.google.com/apps-script/guides/sheets
https://developers.google.com/apps-script/guides/triggers
Workarounds
There is no way to achieve the desired functionality by solely using Google Sheets, but you could
create a web app by using Apps Script and embed on it a spreadsheet
create a web app by using another platform and embed on it spreadsheet
Related
I'm writing a signoff script in Google Apps Script for GSheets that allows users to select their name from a dropdown list in a cell, then if the name matches the currently active account, another function will run to lock the row etc. Everything is working perfectly on my gmail account, but when the client tries to run the script using an account managed by google but with a non-gmail domain, the email address check fails. I'm waiting on the client to send me the log info as I don't have permissions to access it in their file, but wanted to see if anyone else had run into this issue.
In the example code with modified company and username, the code does NOT work, but if I set atDomainString to "#gmail.com" and name to my gmail address and run under my account, it does.
const atDomainString = "#redactedcompany.com";
const name = "JSmith";
function userMatchesSignature(name) {
let user = Session.getEffectiveUser().toString().toLowerCase();
let signUser = (name + atDomainString).toLowerCase();
Logger.log("User: <"+user + "> vs <" + signUser +">");
return user === signUser;
}
Try to grab their error message to confirm what's going on. But most likely they're blocking access to the client ID. Since the scripts that try to access user data might be blocked.
Depending on the scopes you're using on your project, they might need to verify the app https://support.google.com/cloud/answer/7454865
Best practice
Create the project using a domain account instead of a free Gmail account. Because they might run into scopes issues. In their domain, they can mark the app as internal.
Our supplier maintains a Googlesheet and we are trying to add a form on our website that accesses that sheet and pulls data from it. Very simple task but we are having trouble with OAuth. The sheet is not public and only shared with certain people (including my Google ID).
Now, I used Google Developer Console to setup the Googlesheets API library. I added a simple form which is here: https://alcocovers.com/knowledge-base/track-your-order/. When I first used the form, I clicked on the "sign in" button (currently hidden), it showed me the consent form in a popup, I signed in using my Google ID and it asked "alcocovers.com wants access to my profile info" and I gave access. The form started to work and I could pull information from the sheet and show on our web page.
But the problem is, the authentication I did to link the website to access my Google account (and the google sheet) doesn't work for everyone. If I use the form in incognito mode, it fails to access the sheet that means everyone who uses the form has to give consent. That doesn't make sense because I thought when I signed in first time and gave the website access to my account and sheet, it will work for everyone. It's the website making the access not the individual user. We want the users of our site to be able to use the form and pull data from the sheet. How can I achieve this?
Below is the code I am using to initialize and authenticate the client. This code is copy pasted from the Google Sheet API documentation. I am assuming this needs to change so the OAuth only happens once which is already done linking the site to the sheet and on future form use, no consent should be needed.
function initClient() {
var API_KEY = ''; // I added api key here
var CLIENT_ID = ''; // I added client id here
var SCOPE = 'https://www.googleapis.com/auth/spreadsheets.readonly';
gapi.client.init({
'apiKey': API_KEY,
'clientId': CLIENT_ID,
'scope': SCOPE,
'discoveryDocs': ['https://sheets.googleapis.com/$discovery/rest?version=v4'],
}).then(function() {
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSignInStatus);
updateSignInStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
});
}
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
function updateSignInStatus(isSignedIn) {
if (isSignedIn) {
makeApiCall();
}
}
function handleSignInClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
function handleSignOutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
Update #1:
After trying the solution suggested by #Jescanellas, I am getting following error. Note, I kept the same API key, only changed the CLIENT ID.
The other users cannot use your credentials to access the Sheet. In order to do that you need to use a Service Account. Once you create it you can use your Google Account to make the authorized calls to the API with Domain-Wide Delegation, by using the Service Account credentials instead of yours. Follow the steps from the documentation:
1 - Create the service account and credentials
Open the Service accounts page. If prompted, select a project.
Click add Create Service Account, enter a name and description for
the service account. You can use the default service account ID, or
choose a different, unique one. When done click Create.
The Service account permissions (optional) section that follows is
not required. Click Continue.
On the Grant users access to this service account screen, scroll
down to the Create key section. Click add Create key.
In the side panel that appears, select the format for your key: JSON
is recommended.
Click Create. Your new public/private key pair is generated and
downloaded to your machine; it serves as the only copy of this key.
For information on how to store it securely, see Managing service
account keys.
Click Close on the Private key saved to your computer dialog, then
click Done to return to the table of your service accounts.
2 - To enable G Suite domain-wide delegation:
Locate the newly-created service account in the table. Under
Actions, click show more, then Edit.
In the service account details, click Show domain-wide
delegation, then ensure the Enable G Suite Domain-wide Delegation
checkbox is checked.
If you haven't yet configured your app's OAuth consent screen, you
must do so before you can enable domain-wide delegation. Follow the
on-screen instructions to configure the OAuth consent screen, then
repeat the above steps and re-check the checkbox.
Click Save to update the service account, and return to the table of
service accounts. A new column, Domain-wide delegation, can be seen.
Click View Client ID, to obtain and make a note of the client ID.
As said before, use the Service Account credentials to access the Sheet.
I have a custom button which is to query and possibly update an Administration App in Quickbase, which the current user doesn't require access to.
I have JS code which is executed on a button click by the user to check the admin app, etc...
my API call to check the app has the appropriate apptoken and usertoken. However, the browser still has the current user's session cached, so the API call errors out with an access denied error message.
I'm looking for either a way to make a hidden incognito window, to then execute this code, or a way to problematically force the usertoken to supersede the current user access/permissions.
I've seen where chrome extensions can use chrome.windows.create... but I have no experience with extensions, and Ideally, I don't want to have to have an extension for just this functionality, and have to possibly install it on every user's PC for this to work...
Here is a snippet of my current code... This code does work if someone has permissions to the Administration App... but this code is residing in a different application:
PreProcURL = "https://<domain>.quickbase.com/db/<dbid>?a=API_DoQuery&apptoken=<>&usertoken=<>&query={'3'.EX.'1'}";
PreProcQuery.open('GET', PreProcURL, 'async');
PreProcQuery.send();
PreProcQuery.onload = function(){
console.log(PreProcQuery.responseXML);
RunBit = (PreProcQuery.responseXML.documentElement.getElementsByTagName("runbit"))[0].innerHTML;
SupportData = (PreProcQuery.responseXML.documentElement.getElementsByTagName("supportdata"))[0].innerHTML;
if(RunBit != "1"){
$.get("https://<domain>.quickbase.com/db/<dbid>?a=API_EditRecord&rid=1&_fid_6=1&_fid_7="+rid+"&apptoken=<>&usertoken=<>");
}else{
if(SupportData == rid){
alert("This PreProc File is already in progress... please wait.");
}else{
alert("Another PreProc is already in progress... please wait.");
}
}
};
Thanks in advance for any assistance on this.
API calls executed in JavaScript that is hosted within quickbase.com (button, pages, etc.) will run as that logged in user that triggered the script. The usertoken gets ignored.
The most common way to accomplish what you are after is to write the API_DoQuery code on a server side location and then trigger it from your JS code.
I coach a sports team and set up a website for it. I would like to add a button to an admin page which I can click to quickly send an email to everyone on the team. This email will say something like: "Today's schedule has changed, please visit the website for more info."
I am sure this could be achieved easier though a distribution list in outlook or something, but I want to use, and get a better understanding of, Google Apps Script.
I have a Google Spreadsheet with the email addresses and a simple script function which sends the email.
This works great when I click to run it inside the script editor, but is there a way to call this from an external website with a button and some JavaScript?
You need to create a Google UI object that you can embed in your site - you'll do this by creating a Script that you will deploy as a web app. (Refer to Google Apps Script - Web Apps.) That object will contain a button that will invoke your existing script function to send the email.
Publish your web app to execute as you. For your specific situation, you will also want to have access to the app limited to you. That will mean that the UI Blob you create will not function for the general public. This is very incomplete, but produces a blob with a single button that will trigger your script:
function doGet() {
var app = UiApp.createApplication();
var button = app.createButton('Send Schedule Change Email');
app.add(button);
var handler = app.createServerHandler('myClickHandler');
button.addClickHandler(handler);
return app;
}
function myClickHandler(e) {
var app = UiApp.getActiveApplication();
// Call your library function, e.g.
TeamSpreadsheet.sendScheduleChanged();
var label = app.createLabel('Message Sent')
.setId('statusLabel')
.setVisible(false);
label.setVisible(true);
app.close();
return app;
}
Your web app will need to have access to your existing script, which I assume is embedded in your spreadsheet. This is accomplished by first saving a version of your existing script (File - Manage Versions), then adding it as a library to your new web app script (Resources - Manage Libraries). Read more about libraries here. Since your web app will run as you, you can keep your spreadsheet private.
Caveats
The utility script in your library will need to open your spreadsheet in a way that will work from outside of the sheet; use SpreadsheetApp.openById(), rather than SpreadsheetApp.getActiveSpreadsheet(). Unfortunately, openById pukes when you use it to open the host spreadsheet, so you'll want something like this:
var ss = null;
try {
// This works for scripts running in the host spreadsheet
ss = SpreadsheetApp.getActiveSpreadsheet();
} catch(err) {
try {
// This works for web apps
ss = SpreadsheetApp.openById("SPREADSHEET ID");
} catch(err) {
Logger.log("Could not open source spreadsheet.");
// Well, then, not much sense carrying on, is there?
return;
}
}
SpreadsheetApp.setActiveSpreadsheet(ss);
...
In fact, watch for any reliance on calls to get info from "active" objects. You may need to do some gymnastics to get around them.
I've found debugging this type of arrangement to be painful, since the app-script debugger often reports "Server Errors" when you try to trace into a library routine.
I hope that helps gets you started!
As a complement to Mogsdad's answer (that was quite complete and interesting) I'd say that your case could be a bit simpler since you have already a working script.
Take your script and add a doGet() function like in Mogsdad example, define a handler on the button that will call your existing function you wrote to send mails, in this function replace the getActiveSpreadsheet() by SpreadsheetApp.OpenById("the ID of the SS") and the getActiveSheet() by OpenByName() and you're done with the modifications.
After that follow Mogsdad instructions : deploy the script as a webapp running as you and use the provided url to access it from a link on your site.
If you want not to take any risk, do all these changes on a copy of your original spreadsheet so you always keep a working model at hand.
If you want more accurate advice (or if you meet some difficulties) feel free to show your existing script to get some help with the modifications.
PS : please consider this as a simple comment, written in an answer for the comfort of formating
Just publish your script as web application and upon a button click, excecute:
window.open("https://script.google.com/macros/s/<id>/exec");
Don't know if this was possible a year ago.
Using Google Sites, How can I connect to another user spreadsheet using username and password, alike one usually does with mysql?
I tried the following, but it did not work :
var opts = {dataType:'json',username:'xxxxx',password:'xxxxxx'};
var query = new google.visualization.Query(urlTable, opts);
yes i need to connect to a spreadsheet, who owner is other user(spreadsheet is in other account), from code JS with top two lines that i posted at the first post in this thread
using username and password that i have
i see this https://developers.google.com/gadgets/docs/oauth#examples but i dont understand well
thanks
The error in site is Access denied because the owner of document is other gmail account...