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.
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
I have been able to move some data from google classroom to a spreadsheet using google script. Specifically anything in classroom courses or course work. I get a permission error when I try to access and thing in coursework.
(method listAssigments works fine needs refinement works slow)
(method listGrades gives and permission error at var submissions)
I have set up the API as describe in getting started, but have not added any other authorization.
I originally thought it was because I created it in Google classroom, but when I use the site https://developers.google.com/classroom/reference/rest/v1/courses.courseWork.studentSubmissions/list The try this works fine. When I put the request directly to the web I get the error again.
I know it can work several add-ons get in information, but I can not find any example code that is not python. I am trying to make a simple spreadsheet that will sort the grade as I need them for my grade book. Any work around would be fine. Though I would like to be able to push grades back to google as well. Adding participation points is really slow in classroom, I would rather just copy down and change any anomalies or paste in grade for an another spreadsheet.
I will add the code in case it helps, but I believe it must be some sort of additional authentication or permission needed to provide or add.
function listGrades(courseWorkId, course) {
var courseId = course.id
var ss = SpreadsheetApp.getActive().getSheetByName(course.name);
var submissions = Classroom.Courses.CourseWork.StudentSubmissions.list(4140802199, 4801051201);
var grade = submissions.studentSubmissions[0].assignedGrade
}
function listAssigments(course) {
var courseId = course.id
var courseWork = Classroom.Courses.CourseWork.list(courseId).courseWork
var ss = SpreadsheetApp.getActive().getSheetByName(course.name);
for(var i = 0; i < courseWork.length; i++) {
var tittle = courseWork[i].title
var points = courseWork[i].creationTime
var id = courseWork[i].id
var id = courseWork[i]
ss.getRange(2, i+5).setValue(tittle);
ss.getRange(3, i+5).setValue(points);
ss.getRange(1, i+5).setValue(id);
}
OK, I think this a bug with Google Apps Script.
Google Apps Script is supposed to determine the correct permissions automatically by scanning your code before it is executed. Fetching a student's course work using Classroom.Courses.CourseWork.list requires the https://www.googleapis.com/auth/classroom.coursework.students scope.
However, if you add this request to your code and then go to File, Project properties, Scopes, you'll see that only https://www.googleapis.com/auth/classroom.coursework.me is granted.
I was able to workaround the issue by adding a random call to Classroom.Courses.CourseWork.remove(courseId, id) in my code (you can comment it out so that it isn't actually called... Google Apps Script checks commented out code to determine the correct scopes as well). Google Apps Script correctly detected this request for me and granted the correct permission.
I'll report a bug to Google to see if they can reproduce the same issue we are experiencing...
I found a workaround from another post. I did not have a high enough score to post there.
function createCoursework (id) {
Classroom.Courses.CourseWork.create(id,
{ // doesn't work but triggers permissions correctly
"courseId": id,
"title": 'foo',
"description": 'desc',
});
}
As Tom Hinkle said. It does not work but it does trigger the authorization you need to access the data.
Hopefully this is quite a simple question!
I've made a Google Script that writes to cells in a separate sheet "MasterSheet" (helped by several useful Q&As from here). This will ultimately be deployed embedded to multiple different sheets that I'm giving to individual users.
It works perfectly when the user has edit permissions on "MasterSheet", but I need that to remain private - i.e. not even viewable to anyone but me.
As background: In each 'user sheet', IMPORTRANGE is used to import the columns from 'MasterSheet' that that user is allowed to view, and then the script allows the user to add a comment to the MasterSheet.
I can view MasterSheet to see all the columns with comments from various users on one unified sheet, but the individual users shouldn't be able to view this.
The specific script for writing to the sheet is fairly generic:
function saveCommentToMasterSheet(form){
var company = form.company,
contact = form.contactselect,
comment = form.message ,
ss = SpreadsheetApp.openById("MASTERSHEET_ID").getSheetByName('MasterSheet');
if(company=="all"){
var row = findCell(contact);
} else {
var row = findCell2(contact,company);
}
//^The above finds the specific row number relating to the entry the user wants to comment on.
var cell2 = ss.getRange(row,11);
// ^In this case '11' is the column related to this sheet's specific user, I've made separate sheets for each user that are identical except this column number
cell2.setValue(comment);
}
I believe that I could make MasterSheet editable to anyone with the link, but I'd rather avoid that, particularly as the script is embedded in each spreadsheet so if the users just looked at it they'd find the MasterSheet id.
I understand that it's possible to run a script as me using the execute API, but if I'm honest, I struggled a little to figure out make that work.
Sorry if I'm asking a simple question - I've given it a good search and can't figure it out.
Many thanks!
Alex
N.B. This Running a google script from within a spreadsheet, but as a different user? looks like a similar question, but I'd really like to keep the comment system within the user's spreadsheet.
You can make a POST request to the master spreadsheet from the spreadsheets distributed to the users:
Apps Script Documentation - UrlFetchApp.fetch()
function saveCommentToMasterSheet(form) {//Function in the
//spreadsheets distributed to the users
var options,responseCode,url;
url = "https://script.google.com/macros/s/File_ID/exec";//Get from publishing
options = {};
options.method = 'post';
options.payload = form;
responseCode = UrlFetchApp.fetch(url, options).getResponseCode();
Logger.log('responseCode: ' + responseCode);//View the Logs
};
The above code will trigger the doPost(e) function in the master spreadsheet, and put the data into the event object e.
Then you can get the data out of e and write the data directly to what is the active spreadsheet, which is the master spreadsheet. Publish the Web App to run as "Me".
There are two versions of the published Web App; the "dev" version and the "exec" version. The "dev" version is always live with the latest changes, but should never be used in production. The "exec" version has a new version every time that you publish a the script again. To use the latest "exec" version in production, you must keep publishing the latest code.
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 ?
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