Blog Posts

JSONP and Google Apps Script (Part2)

Blog: Dave Thinking Aloud - David French

In an earlier post, I explored the use of the Google Apps Script Content Service supplying data to a web page in an effort to provide a reasonable degree of separation between a service view of Google Apps and a presentation layer. There were also considerable side-benefits in performance and freeing up of the way the web page can be built.

Following the documentation for the content service, I got to this dramatic warning. 
Warning: Be very careful when using this JSONP technique in your scripts. Because it’s possible for anyone to embed the script tag in their web page, you may be “tricked” into executing the script when you visit a malicious website, which can then capture the data returned. It’s a best practice to make sure that your JSONP scripts are read-only and only return non-sensitive information.
The issue with JSONP is the exposure of script which can then be captured by a third party who may then induce an authorised user to load another page which includes the script and provides the third party with access to restricted data and controls. Note this does require an active attacker (information cannot accidentally leak).
With any information storage, you do have to consider the value and sensitivity of the information and the effort that a third party will go through to get it and match your response accordingly.
A simple method of providing assurance that the server is sending its data response to the right page (not some other party copy) involves a number of steps.

  • The request from the browser should contain a ‘hard to guess’ additional factor – a ‘session key’ used by the server script to verify the validity of the request in concert with the Google Account.
  • The session key is stored in a cookie on the local machine. The browser operation will prevent a page from a foreign site from obtaining cookies associated with your site.
  • Session key is generated by apps script and stored in a User Property. 
  • The new session key is returned to the user by an alternate path (email, SMS, UIApp) and entered into the original web page to be stored in cookie.
This strategy should be sufficient to negate the exposure of the script that initiates the JSONP exchange. Of course, it does not protect against all other attacks.

Example apps script code

function doGet(request) {
Logger.log(JSON.stringify(request));
var start = new Date().getTime();
// get user to return in content
var user = Session.getActiveUser().getEmail();
// get session property to check with incoming session parameter
var sessionValue = UserProperties.getProperty(‘session’);
Logger.log(“got property sessionValue = “+sessionValue + ” parameter = “+request.parameter.session);
if (request.parameter.session == sessionValue && request.parameter.session !== undefined ) {
// get the data into an array
var sourceSpreadSheet = SpreadsheetApp.openById(“0At0FkhjjhjhjjhjhjjhkhjkhkjZYi15SEpjTkE”);
var dataArray = sourceSpreadSheet.getDataRange().getValues();
Logger.log(new Date().getTime() – start);
var aadata = [];
var aoColumns = [];
var col = {};
var html = “”;
var headers = dataArray[0];
// all except header for DataTable
for ( var i=1;i<dataArray.length;i++) {
aadata.push(dataArray[i]);
}
// headers for DataTable
for (i=0;i<headers.length;i++) {
col = { “sTitle”: headers[i]};
aoColumns.push(col);
}
// result object return by content service
var result ={
“aaData”: aadata,
“aoColumns”: aoColumns,
“user”:user,”success”:true
}
}
else {
// not a valid session parameter create new and pass to user through alt path
sessionValue = generateGUID();
// send a mail message (SMS is alternative and arguably more secure but would use a random number for ease of use)
MailApp.sendEmail(user,
“Session Second Factor Security”,
“Copy and paste this code into the Session Code Entry Boxn”+sessionValue);
// save it in the userproperties
UserProperties.setProperty(‘session’,sessionValue);
// set return object
var result ={
“aaData”: aadata,
“aoColumns”: aoColumns,
“user”:user, “success”:false
}
}
Logger.log(new Date().getTime() – start);
// formatted for JSONP
html = ContentService.createTextOutput(request.parameters.prefix + ‘(‘ + JSON.stringify(result) + ‘)’)
.setMimeType(ContentService.MimeType.JSON);
return html;
}
function generateGUID () {
// rfc4122 version 4 compliant solution from broofa
//http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript

return ‘xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx’.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == ‘x’ ? r : (r&0x3|0x8);
return v.toString(16);
});
}


HTML Code

<html>
<head>
<meta http-equiv=”content-type” content=”text/html; charset=utf-8″ />
<title>JQUERY DataTables Page Hosted in Google Drive with cookie for added security</title>
<link rel=”stylesheet” type=”text/css” href=”main.css” />
<link rel=”stylesheet” type=”text/css” href=”//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css”>
<script type=”text/javascript” charset=”utf8″ src=”//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.min.js”></script>
<script type=”text/javascript” charset=”utf8″ src=”//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js”></script>
<script type=”text/javascript” charset=”utf8″>
$(document).ready(function() {
$(‘#demo’).html( ‘<table cellpadding=”0″ cellspacing=”0″ border=”0″ class=”display” id=”example”></table>’ );
var proxyJsonp = “https://script.google.com/a/macros/example.com/s/AKfylkjghffdvN2tXR6fo_c/exec”;
var sessionparam = “session=”+Cookies[‘ppkcookie1’];
$.getJSON(proxyJsonp + “?” +sessionparam + “&prefix=?”, null, function(data) {
$(‘#example’).dataTable( {
“aaData”: data.aaData ,
“aoColumns”: data.aoColumns
} );
document.getElementById(“user”).innerHTML=data.user;
});
} );
</script>
<script type=”text/javascript” src=”cookies.js”></script>
<script type=”text/javascript”>
function saveIt(name) {
var x = document.forms[‘cookieform’].cookievalue.value;
if (!x)
alert(‘Please fill in a value in the input box.’);
else {
Cookies.create(name,x,7);
alert(‘Cookie created’);
}
}
</script>
</head>
<body>
<h1>JQUERY DataTables Page Hosted in Google Drive with cookie for added security</h1>
<div id=”header”>
</div>
<div id=”content”>
<h3>
<form name=”cookieform” action=”#”><p>
Session Code <input name=”cookievalue” />
</p></form>
<p><a href=”javascript:saveIt(‘ppkcookie1’)” class=”page”>Save Session Code</a><br />
</h3>
</div>
<p>Session cookie contains a hard to guess value to be passed in request and validated at server</p>
<p>Data returns from Google Apps Script JSONP</p>
<p>Includes sorting, paging and filtering by default.</p>
<p id=”user”>User ???????</p>
<p>User must be logged on to Google Apps Account for this data table to appear (by design!) </p>
<p>Entire data table loaded in one hit.</p>
<div id=”demo”>This iss where the table goes</div>
</body>
</html>


Cookies usage was based on http://www.quirksmode.org/js/cookies.html

Leave a Comment

Get the BPI Web Feed

Using the HTML code below, you can display this Business Process Incubator page content with the current filter and sorting inside your web site for FREE.

Copy/Paste this code in your website html code:

<iframe src="http://www.businessprocessincubator.com/content/jsonp-and-google-apps-script-part2/?feed=html" frameborder="0" scrolling="auto" width="100%" height="700">

Customizing your BPI Web Feed

You can click on the Get the BPI Web Feed link on any of our page to create the best possible feed for your site. Here are a few tips to customize your BPI Web Feed.

Customizing the Content Filter
On any page, you can add filter criteria using the MORE FILTERS interface:

Customizing the Content Filter

Customizing the Content Sorting
Clicking on the sorting options will also change the way your BPI Web Feed will be ordered on your site:

Get the BPI Web Feed

Some integration examples

BPMN.org

XPDL.org

×