UiApp: Persistent login for non-gmail users - google-apps-script

I am attempting to implement a Google Apps Script web service which requires users to log in using an account set up on our system.
The users will not necessarily have a gmail account, and should not be required to create one.
The web service must run using as the script owner, as it is necessary for it to be able to write to a spreadsheet and other resources which do not have shared write permission.
I have managed to implement the login screen, with reasonably strong security -- but the problem I encounter now is that users must log back in every time they visit, and even if they hit the refresh button.
Any ideas on how to implement this?
Is there some way to store a cookie in the users browser, containing a session id?
Or is there some other method which can work?
Thanks in advance!
Josh

This is a very old post but as there is a solution, I think it is better to show it to help people with a similar need
Hi Josh,
I have developed such a system and there is indeed a way to do this.
You can indeed develop a cookie like system that is using the PrivateCache class: CacheService.getPrivateCache().
It works if the user reload the page or close it.
However with this solution when you close your browser it will not be possible to retrieve the information anymore.
Here are the functions that I use to prevent the problem you have underlined
Feel free to adapt them
function getCookie(){
var cache=CacheService.getPrivateCache();
var cached=cache.get("UserCookie");
if(cached!=null){
return Utilities.jsonParse(cached);
}
return -1;
}
function createCookie(data){
var cache=CacheService.getPrivateCache();
cache.put("UserCookie",Utilities.jsonStringify(data),1800);
}
function removeCookie(){
var cache=CacheService.getPrivateCache();
cache.remove("UserCookie");
}
Another way would be to use UserProperties. In this case it will work even if you close your browser... I just tried it
the functions to use are therefore:
function getCookie(){
var cached=UserProperties.getProperty('UserCookie');
if(cached!=null){
return Utilities.jsonParse(cached);
}
return -1;
}
function createCookie(data){
UserProperties.setProperty('UserCookie',Utilities.jsonStringify(data));
}
function removeCookie(){
UserProperties.deleteProperty("UserCookie");
}
I hope it will help anyone...
Cheers
Nicolas

Persistent login are not possible with Apps Script as Apps Script can not interact with browser objects like cookies etc. Apps Script is intended to work only with Google Accounts.

Related

Limiting Google Apps Script Web app to certain users only

Making a web app that makes changes to certain (Sheets) files on my Google Drive (i.e. user will use the app as me), but I would like to restrict the Web app access
only to certain users. When deploying app, I only have the options of making it private or public.
One solution would be to use the session class to see if a correct user is logged in.
function onAppBegin(){
if (Session.getActiveUser().getEmail() != "correctappuser#gmail.com") return null;
accessGranted();
}
However, I am concerned if this crude method is actually safe and is not hackable?
The method is too safe: nobody will have access. If your web app is deployed with the option "Execute the app as: me", then Session.getActiveUser().getEmail() will probably return the empty string. See documentation:
The circumstances in which the email address is available vary: for example, the user's email address is not available in any context that allows a script to run without that user's authorization, like [...] a web app deployed to "execute as me" (that is, authorized by the developer instead of the user). However, these restrictions generally do not apply if the developer and the user belong to the same G Suite domain.
The issue is that even though the user logged in to access the web app, they did not authorize it to do anything on their behalf, e.g., find their email address.
If the web app is deployed to be executed by "User accessing the web app", then they will be asked to authorize it, and so the web app can learn their identity. But then, it will only be able to modify those files that the user already can modify directly.
The way I get around this difficulty is by giving the authorized users a key to the web app (some long random string). They access the app by going to https://..../exec?key=mykey, and the app checks the key as follows:
function doGet(e) {
if (e.parameter.key == "mykey") {
var ss = SpreadsheetApp.openById("spreadsheet Id");
// modify the spreadsheet
}
}
Now, this is even more crude than your original approach, but it works. If a wrong person gets the key, they will be able to use the app but the damage will be limited to what the app can do. It's not nearly as bad as someone getting access to your Google Account.
Combining the two ideas works great.
Checking to make sure it's the right user, and requiring a key.
If not both are correct it displays a no access page, instead of the real page.
function doGet(e) {
if (Session.getActiveUser().getEmail() != "good#email.here") {
var t = HtmlService.createTemplateFromFile("pageNoKey");
return t.evaluate().setTitle("userDelegation No Access");
} else if (e.parameter.key == "SomeKeyHere") {
var t = HtmlService.createTemplateFromFile("page");
return t.evaluate().setTitle("PageTitle");
}
var t = HtmlService.createTemplateFromFile("pageNoKey");
return t.evaluate().setTitle("PageTitle No Access");
}
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
My code can probably be improved. I'm still learning. (Maybe validate both values in the first if loop?)
Anyway, this is what I'm using to restrict access to users managing Gmail delegation, using my free tool userDelegation. Since it's powered by a service account with domain wide rights, it needs to be locked down hard.
If you want a really flexible solution, check out this video, where the validation is done based on what type of access users have to a specific Google Doc! That was unnecessarily complex for my situation, as I only needed to restrict access to a single person accessing my web app. I will instead create separate versions of the same web app, and give each admin their own.
If you need an easy way to restrict your web app to several people, then that method might be just right for you.

Onedrive API vs LiveSDK

I am developing for WP8.1 and probably will port on other platforms.
I want to integrate a OneDrive functionality, however, I can't understand what is the difference between LiveSDK (from NuGet packages) and OneDrive API.
It seems that Microsoft does not communicate at all regarding that, I really don't understand why. As far as I understood, the LiveSDK is the old one and will be replaced by OneDrive API, but the LiveSDK seems so much simpler to use that I can't understand their logic... On top of that both have been updated recently (in April for the LiveSDK).
So my question is
As long as my app has not been published yet, should I move to
OneDrive API, or keep on the LiveSDK?
Does anyone has already
tried both?
What are the limitations and benefits of both?
Based on a future-proof sight, I will go for the OneDrive API, but my main concern is:
Is is possible to login as easily on OneDrive API than with the Live SDK? (Working temporary solution below)
For now, I have been able to login with the LiveSDK using a single button and three lines of code, no user input required at all, where the AuthenticateAndContinue method used by the OneDrive API opens a Webcontrol and requires to enter manually the login and password.
At the end I have used the LiveSDK Authentication and use the access token provided with the OneDrive API. It is not a clean approach from my point of view, but I couldn't manage to get the AuthenticateAndContinue method to work (I get a 404 error answer after the login).
If anybody has a better solution, I am opened to suggestions :)
[Solution extracted from question to get out of "unanswered" stack]
In summary, my current method is the following:
Authenticate using LiveSDK API
Use the session cookie and the OneDrive API from there
The simplified code is the following:
The method below allows to log in silently, only the first time requires a manual validation from the user allowing the program to use its Live account.
It does not require any password from the user
var authClient = new LiveAuthClient();
var authResult = await authClient.LoginAsync(new string[] {
"wl.signin", "onedrive.readwrite", "onedrive.appfolder"});
if (authResult.Session == null)
throw new InvalidOperationException("You need to sign in and give consent to the app.");
var Connection = new ODConnection("https://api.onedrive.com/v1.0",
new MicrosoftAccountAuthenticationInfo() { TokenType = "Bearer",
AccessToken = odArgs.Session.AccessToken });
It is not as clean as I would like (using 2 different SDK), but it works :)

Security of GAS for G-sites?

I want to create a script that runs on a Google site. The script would perform a specialized calculation for a given user and then display the answer for the user. The script would depend upon user input, but the code itself should not be viewable by the user. I want the code to exist in a "black box" so that the calculation formula can be kept secret. Is this possible?
I searched the documentation, but only found this, which does not address this question:
https://developers.google.com/apps-script/guides/services/authorization#permissions_and_types_of_scripts
Whether your App script is embedded in a site or written as a standalone script does not change a lot of things... the only difference will be the way you include it on a page as a gadget.
If it is embedded it will be available from a list of scripts in the page editing, if not you'll have to use the .exec url of the deployed webapp.
Anyway, that does not change the way people will have access to the app.
You can define these parameter when you deploy the app (which is mandatory in both cases), allowing for anonymous access or requiring to be logged in.
If I understood you correctly, you would like to restrict the access to some people but not share the code.
Depending on how you defined the access mode above and wether you are using a Google Apps inside a domain or not, you will be able to do it using 2 ways :
in a domain you can get the logged user identity and use that to accept/deny showing the app.
in a "normal" gmail account you will have to implement some sort of logging feature to request a user name and password to give access to the active part of the app.
In both case you never need to share the script itself, this sharing parameter is independent from the webapp access.
I hope I understood your question correctly... if not, feel free to comment.
You might check out the Private Functions section of the following page.....the example is similar to what you're referring to, I think.
https://developers.google.com/apps-script/guides/html/communication?hl=ru
I don't fully understand how they work and haven't used them myself, but I bookmarked it to figure out later. Maybe another user who understands Private Functions better can explain...

How to give write permissions only to UI app to write data to a Spreadsheet

I have developed a web application using Google scripts UIApp class which will collect the data of work done by each associate and writes data to my spreadsheet.
Problem I'm facing is I have to share my spreadsheet to all the associates with write permission where it enables them to see others data. I want to hide this sheet from all but they should also be able write data using the web application I shared.
Please let me know for more details or any code snippets
Note: I have Not used Google Form because I need change the list values dynamically based on the selection and the type of user.
You can deploy the app you have built with UiApp to be executed as "you" and allow access to anyone. The spreadsheet won't need to be shared anymore but as it is anyone (even anonymous if you are not in a domain) will be able to use it...
You'll have to implement an access control yourself, again this will be different in a domain or in a "normal" gmail account (in a domain you can get the user email, in a gmail account you can't)
(since apparently I can't comment.. but to follow up on this question.)
Is there a best practice for running as something other than really-yourself?
Say a team is managing it, or your a contractor who won't stay with the company, and so you're account and access is likely to go away.
I assume it results in creating a shared account, or perhaps a groups or something? You start getting into all sorts of ACL issues. And a simple ``run as yourself'' doesn't seem like a good long term solution.

Custom HTML5 Geolocation Prompt

As I am testing out one of the HTML5 features - geolocation for my project,
I realized that users can close the prompt without allowing or denying it,
that defeats the whole purpose of the prompt.
And because in my project I want to dynamically display data to users depending on user's location, this can't be done, simply because without knowing user's response,
it doesn't trigger any of the two callbacks - success / error.
so I started searching to see if there's any solution to this,
and a lot of suggestions to this is to set timeout,
I tried and everything works perfectly.
However, one small flaw here is tho, by the time it hits the timeout expiration,
all the data are already displayed, and when i say all, i mean EVERYTHING,
because there's no location detected.
So I came up with two solutions that might work,
1) create a custom geolocation prompt that forces users to allow/deny location to be shared,
and pass the response to browser to set the location preference
2) pause page-load (stop stuff from being rendered) and wait till it hits the timeout expiration or it gets response from users
Does anyone have any idea how to implement one of these two solutions?
PS: sorry if this isn't unclear to you, i know my english sucks, but I can explain in more details.
Thanks guys!
You shouldn't be able to use a custom Geolocation prompt if your project is browser based, because malicious developers could use the method to trick the user. Also, since the Geolocation API is an asynchronous event, it's going to continue loading the rest of the page while it waits.
What I recommend is to use a conditional statement instead with an else clause. This way, your script functions should only execute after location has been shared, and you have a fall back on what should happen if no geolocation information is provided (which I highly recommend as situations will occur when the data isn't provided).
Example of the conditional statement to check for geolocation information using JS:
if (navigator.geolocation) {
// code to run if there is geolocation information
}
else{
// code to run if no geolocation info is given to the browser
}