how to prevent xss in GmailMessage.getBody() print - google-apps-script

I'm building a Google Script Web App that uses the user's Gmail messages
(I'm using the built-in gmail api), when the user chooses a message, I want to show him the message's content, to asure that's the right message.
I'm afraid the mail might contain xss, but I don't want to loose the styling of the message, it's ugly, so I can't write
var output=HtmlService.createHtmlOutput("");
output.appendUntrusted(message.getPlainBody());
my current Code is:
function getMessageContent(message)
{
var output=HtmlService.createHtmlOutput("");
output.append(message.getBody());
return output;
}
does a mail really might include xss?
If the answer is yes, how can I prevent it?
thanks Noam.

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.

How to edit Gmail messages as they arrive?

Ultimately my objective is to prevent email tracking via auto-loaded images with unique links. I'm aware that Google uses a proxy to load the images so at least they won't reveal my IP address, but there are certain individuals and organizations that annoyingly embed trackers into their emails -- and they actively check whether or not I read their emails.
In the Gmail app specifically, I can turn off auto-loading of images, but I'd like to have similar protection for other apps that don't have this setting. For example, apparently there isn't a way to do this with Google Inbox.
My current thought is to write a back-end script that can run on new mail (received either from an event or frequent polling) to turn embedded HTML images into hyperlinks links to those images -- only used if I really need the image. That way, no matter what app I use to open the email, I'm in control of how/when I'm tracked. Editing emails is something that I've done with the MS Exchange Server APIs, and I'm looking for a way to do this with Gmail -- by whatever means available.
I found a couple of threads from 2010 on how to modify the subject line using Google Apps Script and Gmail itself. At the time, you couldn't do that, but it seems possible that these have been updated since then or that there are solutions using the Gmail API or IMAP.
tl;dr
For my Gmail account, how can I programmatically modify (and save changes to) received emails?
Possible solutions:
Google Apps scripts
Gmail API
IMAP
Other?
I think the Gmail API would suit your needs perfectly.
Let's say I'm polling my Inbox every minute for new messages, with the Users.messages.list()-request. I'm careful to use the after-parameter in my query with the value of the last time I checked my Inbox, as seconds since the epoch. I only ask for Ids of the potential new messages. You could also subscribe to push events to mitigate the risk of your user pressing the message before you poll and alter the messages, as mentioned by #Max in the comments. Probably not an issue if the script is just for you.
q = after:<TIME_IN_SECONDS_SINCE_EPOCH_OF_LAST_POLL>
fields = messages/id
GET https://www.googleapis.com/gmail/v1/users/me/messages?fields=messages%2Fid&q=after%3A1437677475478&access_token={YOUR_API_KEY}
Response:
{
"messages": [
{
"id": "14ebc16800d1fdc0"
}, ...
]
}
Ha! I have a new message. I get it raw, decode its URL safe base64-encoded content, and have a look.
format = raw
fields = raw
GET https://www.googleapis.com/gmail/v1/users/me/messages/14eb68cb028163ba?fields=raw&format=raw&access_token={YOUR_API_KEY}
Response:
{
"raw": "RGVsaXZlcmVk..."
}
Let's do the aforementioned base64-decoding. Replace all the "-" with "+", and "_" with "/" to transform it from URL safe base64 data to regular base64-encoded data.
atob("RGVsaXZlcmVk...".replace(/\-/g, '+').replace(/\_/g, '/'));
Result:
<html lang="en">
<head>
<title>
Computerphile just uploaded a video
</title>
.
.
.
<img class="open_tracking_img" src="http://www.youtube.com/attribution_link?a=vi-KC3YA0Qc&u=/gen_204%3Fa%3Dem-uploademail" width="1" height="1">
.
.
.
</html>
Contains a lot of img-tags, for sure.
I just extract the img-tags, get the URLs, and remove all the img-tags in the mail with my favourite XML Parser.
After the tags are removed, I just insert the URLs in the mail where I see fit, and encode it back to the URL safe base64-encoded data it was retrieved in.
btoa("<html lang="en">...".replace(/\+/g, '-').replace(/\//g, '_'));
Finally, I delete the original mail and insert the modified one.
DELETE https://www.googleapis.com/gmail/v1/users/me/messages/14eb68cb028163ba?access_token={YOUR_API_KEY}
POST https://www.googleapis.com/gmail/v1/users/me/messages?access_token={YOUR_API_KEY}
{
"raw": "RGVsaXZlcmVkLVRvO..."
}
My new, modified mail is now in the inbox instead!

google app script - authenticate website for accessing its data

I am building a sidebar with the appscript. People who use this sidebar, are already users of my website abc.com. When he opens sidebar, I need to show him some content from my main website accessible only to his account. Lets say his profile in my website should be filled in the sidebar. For that, I need to authenticate that 'CALL TO SERVER' somehow so that I can send his data. In appscript, getActiveUser().getEmail() (or related) returns email, so we can fetch that persons data, but no authentication is done like this as anyone can send his email. Can you tell me the best way for this ?
You can use ContentService to GAS (as REST web-service), which will respond according to the parameter (for example you can send username to server and get reply)
doGet(e){
var user = e.parameter.username;
var reply = getReplyFor(user); //get some reply for concrete user
return ContentService.createTextOutput(reply).setMimeType(ContentService.MimeType.JSON);
}
I hope this helps
sorry for bad english =)

Keeping Email Message from Grouping into Conversation View in Gmail

I'm working on a feature for a client to send them email updates whenever a specific event occurs on their site. When the message shows up in Gmail, the messages get grouped together in conversation view even through they aren't the same conversation. It appears that this is due to the fact that Gmail groups based only on the subject. The client is adamant that we not change the subject line (don't get me started).
Does anyone know how I can disable this by sending a special header in the mail or am I out of luck?
There appears to be no way to prevent this, short of turning off conversation view (have you considered that?).
My guess is that Gmail is actually threading based on its own Thread-Topic header field, which it adds (overwriting any value you pass; it just copies the Subject field) - there's no way of telling, though, unless you can change that field after the fact. Which leads to the suggestion of writing an IMAP application to download the message, edit the headers, and re-upload it again. You'd need to investigate the feasibility of this, though.

UiApp: Persistent login for non-gmail users

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.