I've developed a web app with google script and code this in my gs.
var email = Session.getActiveUser().getEmail();
Logger.log(email);
// then do something to render the Email address on the page
After publishing the script, I log in with another account and execute the script.
Then the page display this:
This application was created by another user, not by Google.
This application has access to the following personal information: email address.
But still nothing in Log and the page display nothing.
I just don't get it...
Although it's not stated explicitly in the reference documentation for Session.GetActiveUser(), my testing confirmed that your web app needs to execute as the user accessing the web app to have access to getActiveUser(). I used the code below and the logging library described here.
Depending on the use case of your application, perhaps you could create a library containing a centralized ScriptDB instance to capture the active user email. Then, create another project that works with your private spreadsheet and the same scriptDB library.
var LOG_FILENAME = 'your-log-doc'
var LOG_PATH = 'folder/subfolder/third-folder'
function doGet() {
//Change variable above to a good path and filename for you
var x = LogLibrary.InitializeLogging(LOG_PATH, LOG_FILENAME)
Logger.log(x)
var email = Session.getActiveUser().getEmail();
Logger.log("Start email logging")
Logger.log(email);
LogLibrary.fnSaveLog() //Write the save and flush the logger content
// then do something to render the Email address on the page
var HTMLToOutput = '<html><h1>A header</h1><p>' + email + '</p></html>';
return HtmlService.createHtmlOutput(HTMLToOutput);
}
I've made some tests because I am looking for a solution about the same problem,and I conclude that :
so that function can works, the web app should be executed as "user accessing the web app", and for that, the web app should be configured so that can be executed as user accessing the web app
Note that : the user will be asked to authorise the script to access, edit and
manage it's drive also receiving and sending mails (which is not evident !! for all most of users of corse").
In the end that it depend of what you will use it for.
And in google documentation nothing is mentioned about this issue!!
For a simple function that returns the email address as a text to use as script or in google html forms.. etc :
function onOpen(){
var emailName = Session.getActiveUser().getEmail();
var optionsHtml = emailName.toString().split(",");
return optionsHtml;
}
Look in the gas issues forum (google). It hax been addressed many times. Basically you need to set it as run as user accessing the app if u expect users from outside your domain. Changing that setting will qlikely break your app depending on the permissions it needs
If your app is from before the 25th of June 2013, and you are using getEffectiveUser() or getUser() methods you will need to re-authorize the script because of the new authorization experience.
You can see the documentation here.
https://developers.google.com/apps-script/scripts_google_accounts#understandingPermissions
And do not forget to execute the script before deploying.
If you want to access the user's email, make sure you have added the "https://www.googleapis.com/auth/userinfo.email" email access policy in the appsscript .json
You can see the documentation here.
https://developers.google.com/apps-script/reference/base/session#authorization
Related
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
Is it possible to process multiple selected emails by an addon?
The code from Google shows how to process only one selected email. I suppose 'e' would be some kind of array of accesstokens but I don't know how to access it.
function getContextualAddOn(e) {
var accessToken = e.messageMetadata.accessToken;
Logger.log("token:"+ accessToken);
GmailApp.setCurrentMessageAccessToken(accessToken);
var messageId = e.messageMetadata.messageId;
var card = createCard();
return [card.build()];
}
Thanks.
Your Apps Script code never runs on the client device, where the messages are selected in the Gmail UI - it runs on Google's servers. You can't access UI information from add-ons (there's no GmailApp.getSelectedThreads(), CardService.getActiveEmails(), etc.). You are only able to access the opened message / draft, and others in the same thread (or mailbox, depending on your add-on's scopes).
The event object received by your triggered callback function will only have a single access token. Until additional manifest triggers are added (currently only contextual and compose exist), this won't change.
As this is not Google, no one here can tell you if or when other triggers may or may not be added. You best bet is to request the feature.
I want my UiApp (GAS) application accessible only by people I explicitely invite.
I don't want to publish my application for public access, but prefer to provide some people a link (desktop shortcut) to an html file on a shared disk (so I only need to update the link to my application if I update it).
While trying to achieve that, I experience several problems.
Problem-1
I don't want the user being able to see my code.
How can I achieve that?
Problem-2
If I execute the application as me I can't retrieve the actual user (Session.getActiveUser() and Session.getEffectiveUser() both return 'me') and because of that I don't know what emails nor calendar to display.
How can I retrieve info that allows to display emails and calendar of the actual user (so NOT 'me') ?
Even if this requires an extra parameter while starting the application, it's acceptable to me.
Problem-3
I noticed that if the project uses a library, the application won't run unless the user explicitely defines the library himself. To me it seems the user should have nothing to do with that. How can avoid this problem (apart from not using libraries)?
In my case, I use a library for logging some info. This library is not located on a shared disk. Even if I disable the library (so logs will go to the system default logger) the application fails to run as a message appears stating the library is missing (and reffered in 'Resources').
What can I do to avoid this problem?
This question is rather long to answer but I'll try to make it short :
You don't need to share the code, keep it private.
The webapp should be deployed as "the user executing the app" so that all calendars, docs etc will be those of the user. You should allow anybody to access the app and create a login identification in your app itself : when the user tries to run the app you get his email (getActiveUser()) and compare this email with a list stored in scriptProperties. If the mail is not in the list then return a message saying this user is not allowed to run the app and let the script send you an alert so you know who has tried.
The Library should not be an issue, I have script using libraries that work for other users without them having to do anything except authorize.
To illustrate, here is a piece of code I use to control access of a webapp :
function doGet() {
var clientOK = false;
var client = PropertiesService.getScriptProperties().getProperty('clients').split();
var email = Session.getActiveUser().getEmail();
var app = UiApp.createApplication().setTitle('Order form').setStyleAttribute('background', 'beige');
var mainPanel = app.createAbsolutePanel().setStyleAttribute('padding','25');
var reject = app.createHTML("Hello,<BR><BR>You are connected with the address <B>"+email+"</B> that is not allowed to access this form,<BR><BR>"+
"Please contact the webMaster if you think this is an error.<BR><BR>Thank you.").setStyleAttribute('padding','25');
if(clients.join().match(email) == email){ clientOK = true ; var nom = email };
if(!clientOK){
app.add(reject)
MailApp.sendEmail('myEmail#gmail.com', 'attempt to log in form', email+' tried to access the form and has been rejected. Think about asking him what happened !')
return app
}
// continue normal code...
EDIT about problem 3 : if the Library you use writes to a spreadsheet (as a logger destination), this spreadsheet has to be shared with these users as well. Even if they don't need to open that spreadsheet from their drive, they will be able to do it as it will appear in their 'shared with me' view in their drive. That said, maybe you can get rid of that as soon as the development phase is over ?
In a little over my head. Trying to use UrlFetchApp.fetch(url, options) from a container-bound script to run code in a web app.
The problem: Container-bound doc script can only run as the activeUser. doc script creates a new doc by copying a template. I would like the doc newly created from the template to be stored in a centralized folder owned by the developer. I see two solutions.
I give all domain users view/edit access to the developer's folder.
I create a web app from a standalone script which runs as the effectiveUser (developer) who has access to the folder. In this case the doc script calls the web app using UrlFetchApp passing in the parameters (folder, doc). However, to quite able to figure out how to do this, if possible.
var unitId = DocumentApp.getActiveDocument().getId();
var unit = DriveApp.getFileById(unitId);
var folderId = unit.getDescription() //FoldId for unit is stored in description
var folder = DriveApp.getFolderById(folderId);
var lesson = DriveApp.getFileById(UNIT.LESSON_TEMPLATE_ID).makeCopy('Lesson - ' + result.getResponseText());
folder.addFile(lesson);//Currently I have the folder shared/edit with domain users.
//I would prefer to share/view. However, since the
//container-bound doc script runs only as active user, no
//can do. Is it possible to build a small web app which
//runs as effective user and saves lesson to folder.
showLessonSidebar(folderId);
Any hints out there?
Yes, it is possible to create a web app that runs as effective user.If the developer - effective user has access to folder and template file.
You could pass the folderId and Name of the file to be copied to the WebApp, rather than passing folder and Doc object (passing the folder Object and Doc object as URL parameter may be quite tricky and if content is too large may not be even possible as there are constraints on the possible length of URL)
1) Write a Apps Script
function doGet(e) {
var folderId = e.parameters.folderId;
var copyFileName = e.parameters.name;
var userEmail = e.parameters.email;
var folder = DriveApp.getFolderById(folderId);
var lesson = DriveApp.getFileById(UNIT.LESSON_TEMPLATE_ID).makeCopy(copyFileName);
folder.addFile(lesson);
//share file to domain or share folder with domain - folder.setSharing(....);
file.setSharing(DriveApp.Access.DOMAIN, DriveApp.Permission.VIEW);
}
2) While deploying the Web App, developer can set Execute as me(developer#...) and set "Who has access to Web App" as Anyone.
3) Make sure developer has authorized the web App.
4) Share the Web App Script file with all your domain users.
Also, if you want to pass the content of file for some reason from container-script to Web App, you may consider implementing the logic in doPost() as you would have the ability to pass large data using POST in UrlFetch.
As far as I understand, the only way to use UrlFetch in a bound script, is using an installed trigger. (UrlFetch is not authorized to run when used in a bound script's simple triggers: https://developers.google.com/apps-script/add-ons/lifecycle#authorization_modes.)
An installed trigger always "runs with the authorization of the user who created the trigger, even if another user with edit access opens the spreadsheet". Thus, simply use installed triggers:
function setupTrigger(){
var doc = DocumentApp.getActiveDocument();
ScriptApp.newTrigger('open').forDocument(doc).onOpen().create();
}
This sample code creates an installable trigger for opening the document bound to the script.
I have a gradebook web app script which looks at a logged in student's email address, finds the email in the gradebook, and then displays the student's grades based on the column the email is in. The only problem is this only works if the spreadhseet is made public. How can I keep the spreadhseet private and still make this script work? I know that if I choose "Anyone with the link" it is unlikely someone will find the spreadsheet, but I'd prefer it to stay private. In addition, from the "Deploy as Web App" interface, the app must be executed as the user, not myself. Any ideas?
var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheet/ccc?key=ID');
var sh1 = ss.getSheetByName('StudentGrades');
var logsheet = ss.getSheetByName('logsheet');
var data = sh1.getDataRange().getValues();
var user = Session.getEffectiveUser()
Logger.log(user)
function doGet() {
var app = UiApp.createApplication();
if(!getcol(user)){
var warn = app.createTextBox().setWidth('500').setValue("Your results are not available or you don't have permission to view these data");// if user is not in the list, warning + return
app.add(warn)
return app
}
var grid = app.createGrid(data.length, 2).setWidth('300px').setBorderWidth(1).setCellPadding(0).setCellSpacing(0).setStyleAttribute('borderCollapse','collapse').setId('grid');
var text = app.createLabel(user).setWidth('300px');
var col = getcol(user)
grid.setWidget(0,1,text).setText(0, 0, 'Results for');
grid.setStyleAttribute('textAlign','center')
for(n=1;n<data.length;++n){
grid.setText(n, 0, string(data[n][0]));
grid.setText(n, 1, string(data[n][col]));
grid.setStyleAttributes(n-1, 0, {'fontWeight':'bold','background':'#fff','border':'1px solid #000'});//left column css attributes
grid.setStyleAttributes(n-1, 1, {'fontWeight':'bold','background':'#fff','border':'1px solid #000'});//right column css attributes
}
app.add(grid);
return app
}
function string(value){
Logger.log(typeof(value))
if (typeof(value)=='string'){return value};// if string then don't do anything
if (typeof(value)=='number'){return Utilities.formatString('%.1f / 20',value)};// if number ther format with 1 decimal
if (typeof(value)=='object'){return Utilities.formatDate(value, Session.getTimeZone(), "MM-dd")};//object >> date in this case, format month/day
return 'error'
}
function getcol(mail){
if(data[0].toString().indexOf(mail.toString())!=-1){
for(zz=1;zz<data[0].length;++zz){
if(data[0][zz] == mail){var colindex=zz;break}
}
return colindex
}
return false
}
As I was suggesting in another of your posts you could setup a 'manual login' that will allow you to keep the spreadsheet private while running the webapp as yourself.
That implies that you'll have to create a list of user/password keys/values and that each student will have to enter his user name and password (you'll have to send them that information by email along with the link to the webapp) in a front end login screen before they gain access to the results display part.
I guess the best place to hold that list would be in script properties in the form of key:value.
If you need more that this to implement that solution (and if you think you'll go that way) feel free to ask.
EDIT following your comment :
Ok, I understand it can be unpleasant :)
Just wondering, wouldn't it be easier to store these data in the script itself in a scriptDB that you could automatically update with the spreadsheet values ?
The update would have to be executed by you (or by a timer trigger you create so that it runs on your account) and so the spreadsheet would remain private.
The students could then access the webapp with their accounts without accessing the spreadsheet.
From that service accessed as the user, call through urlget another apps script service published as yourself. That one does the ss-related work.
Might get timeouts on the service call though.
Why not setup a google group for each of your classes? Then add the google group to the SHARE permissions for the appropriate spreadsheet(s). This would have the added benefit of allowing you to easily send emails to your various classes via the appropriate google group. Under this scenario, your grade spreadsheets remain private and are only accessible by you and those email addresses listed in your google group.
I am a domain admin running the Google Apps for Education suite with many similar applications in place. You don't mention if you have administration rights for the domain or not, but I assume you do?
I keep all spreadsheets private and run the web apps as myself (giving the script the necessary access) which I think is precisely what you are aiming for?
I think your main issue stems from using .getEffectiveUser() which isn't providing you with what you need. I use Session.getActiveUser.getEmail() and then iterate through student objects (this is essentially just a sheet of student details returned as objects using the standard getRowsData function) to match the email value (if not found user = unknown).
This works perfectly for me with a student base of over 2000, all the data is private and the apps respond quickly serving personalised information.
If you'd like to see an example app please let me know, it's a little large to post here.