Thursday, March 12, 2015
New Apps Script features at Google I O—again!
Scripts in Google Docs
Many of you have told us that you want to be able to extend Google Docs just like Google Sheets, with custom menus, dialogs, and triggers. Starting today, you can do just that (plus custom sidebars, too). To learn more about Apps Script in Docs—including a couple of secret features that we can’t tell you about yet!—please tune into the live stream with me and Jonathan Rascher on Thursday at 3:30pm PT.
Forms Service / Scripts in Google Forms
In response to another top request, you can now use the Forms Service to programmatically create and modify Google Forms, including triggers and a better way to respond to form submissions. (We’ve created a new 5-minute quickstart to get you going.) You can also extend the Google Forms editor with the same custom menus, dialogs, and sidebars as Google Docs. If you’re at I/O, learn how to build Forms with Apps Script by joining Eric Koleda and Matthew Ziegelbaum on Wednesday at 1:55pm PT.Drive Service
For those of you who use the DocsList Service to automate your Google Drive, a newer version is now available. Drive Service comes with new features like setting the owner of a file or folder or changing the sharing settings. We designed the new service from the ground up to make it easier to work with large numbers of files and also fixed a lot of bugs. If you’re at I/O, Arun Nagarajan and John McGowan will give you more insight into Drive integration on Thursday at 1:40pm PT.Faster HtmlService
At Google I/O 2012, we launched HtmlService to let you build custom user interfaces with secure client-side scripting. Starting today, you can enable an experimental version of the client-side sandbox that runs significantly faster in any browser that supports ECMAScript 5 strict mode.Improved Authorization Flow and API Console Integration
You’ve also told us that authorizing a script takes too many steps. Now, you can opt in to an experimental new authorization flow that requires fewer clicks. In addition, every script that uses the new flow automatically creates a project in the Google APIs Console. This makes it much easier to use Google APIs that aren’t built in to Apps Script. To upgrade a script to the new flow, select File > Upgrade authorization experience. If you’re at I/O, Arun Nagarajan and Christoph Schwab-Ganser will demonstrate the new flow in their session on using the YouTube Analytics API with Apps Script on Wednesday at 1:55pm PT.As you can see, we’ve been working hard to improve Apps Script for you. We hope you enjoy the new features!
![]() | Saurabh Gupta profile | twitter | blog As the product manager for Google Apps Script, Saurabh is responsible for Apps Script’s overall vision and direction. |
How to use Google Apps APIs to handle large files
Retrieving and Storing User Profile Entries
The User Profile API makes it easy to divide retrieval of all profile entries into multiple fetch operations. The one area to tweak for this call is the size of results that can reliably be returned within 10 seconds on Google App Engine. In our testing, 100 entries per page is about the right size. Once configured, we will retrieve the feed containing the first set of entries, parse the response, and persist the results to cache. If that feed contains a link to another page containing more entries, we queue up a task to handle the next page and repeat until all pages are processed:ContactQuery query = null;//if this is the first page, there is no next link. Construct initial page urlif (nextLink == null) {String domain = getDomain(loggedInEmailAddress);String initialLink = PROFILES_FEED + domain + PROJECTION_FULL;query = new ContactQuery(new URL(initialLink));query.setMaxResults(GAE_OPTIMAL_PAGE_SIZE);} else {query = new ContactQuery(new URL(nextLink));}query.setStringCustomParameter(TWO_LEGGED_OAUTH_PARAM, loggedInEmailAddress);//fetch next profile feed containing entriesProfileFeed feed = contactsService.query(query, ProfileFeed.class);List> currentMaps = (List >)memcacheService.get(memcacheKey); for(ProfileEntry entry:feed.getEntries()){//secret sauce: convert entry into csv column header/value mapcurrentMaps.add(converter.flatten(entry));}//store updated list of converted entry maps back into memcachememcacheService.put(memcacheKey, currentMaps);if(feed.getNextLink()!=null){//start task to get next page of entriestasksService.fetchUserProfilesPageTask(spreadsheetTitle, loggedInEmailAddress, feed.getNextLink().getHref(), memcacheKey);}else{//no more pages to retrieve, start task to publish csvtasksService.exportMapsToSpreadsheet(spreadsheetTitle,loggedInEmailAddress,memcacheKey);}}
Exporting Profiles as a Google Docs Spreadsheet
One of the trickiest obstacles to work around in this effort is generating the Spreadsheet file since GAE restricts the ability to write to the File System. The Spreadsheets Data API was one possibility we considered, but we ended up feeling it was a bit of overkill, having to first create the Spreadsheet using the Docs List API and then populate the Spreadsheet one record per request. This could have generated thousands of requests to populate the entire Spreadsheet. Instead, we leveraged an open source library to write csv file data directly to a byte array, and then sent the file data as the content of the newly created Docs List entry in a single request:Once completed, this method results in a newly created, populated Google Docs Spreadsheet viewable in the logged-in users Google Docs.public void saveRowsAsSpreadsheet(String spreadsheetTitle, String loggedInEmailAddress, String memcacheKey) {//get list of csv column header/value maps from cache:List> rows = (List >)memcacheService.get(memcacheKey); //secret sauce: convert csv maps into a byte arrraybyte[] csvBytes = converter.getCsvBytes(rows);SpreadsheetEntry newDocument = new SpreadsheetEntry();MediaByteArraySource fileSource = new MediaByteArraySource(csvBytes,"text/csv");MediaContent content = new MediaContent();content.setMediaSource(fileSource);content.setMimeType(new ContentType( "text/csv"));//add MIME-Typed byte array as content to SpreadsheetEntrynewDocument.setContent(content);//set title of SpreadsheetEntrynewDocument.setTitle(new PlainTextConstruct(docTitle));URL feedUri = new URL(new StringBuilder(DOCS_URL).append(?).append(TWO_LEGGED_OAUTH_PARAM).append(=).append(userEmail).toString());docsService.insert(feedUri,newDocument);//we are done, time to delete the data stored in cachememcacheService.delete(memcacheKey);}
Conclusion
Rich signatures for your domain using the Email Settings and the Profiles APIs
A recent addition to the Email Settings API allows domain administrators to use HTML-encoded strings when configuring the default signature for their users.
Updating all signatures to make them adopt the same visually appealing style sounds like a perfect task to automate, however we’d still need to collect various pieces of information for each user, such as phone number or job title, and the Email Settings API has no knowledge of them.
The Google Apps Profiles API provides exactly what we are looking for and in the rest of this article we’ll see how to have the two APIs interact to reach our goal.
Let’s assume we want our signatures to look like the one in the screenshot below, with a bold name, italic job title and clickable link for the email address. Of course you can edit the style as you like with a bit of HTML skills:

Python is the programming language of our choice for this small script and we use the Google Data APIs Python Client Library to send requests to the Email Settings and Profiles APIs.
The first few lines of the script import the required libraries and set the values of the credentials that will be used to authorize our requests. You can find the consumer key and secret for your domain in your Control Panel, under Advanced Tools - Manage OAuth domain key. Remember to replace the dummy values in the script below with yours before running it:
import gdata.apps.emailsettings.client
import gdata.contacts.client
# replace these values with yours
CONSUMER_KEY = mydomain.com
CONSUMER_SECRET = my_consumer_secret
company_name = ACME Inc.
admin_username = admin
We’ll use 2-legged OAuth as the authorization mechanism and set the administrator’s email address as the value of the
xoauth_requestor_id parameter, identifying the user we are sending the requests on behalf of.The consumer key and secret plus the requestor id are the only parameters needed to create an OAuth token that we can pass to the Email Settings and Profiles clients:
# request a 2-legged OAuth token
requestor_id = admin_username + @ + CONSUMER_KEY
two_legged_oauth_token = gdata.gauth.TwoLeggedOAuthHmacToken(
CONSUMER_KEY, CONSUMER_SECRET, requestor_id)
# Email Settings API client
email_settings_client = gdata.apps.emailsettings.client.EmailSettingsClient(
domain=CONSUMER_KEY)
email_settings_client.auth_token = two_legged_oauth_token
# User Profiles API client
profiles_client = gdata.contacts.client.ContactsClient(
domain=CONSUMER_KEY)
profiles_client.auth_token = two_legged_oauth_token
Let’s define a class that generates the signatures for our users on the basis of a set of optional attributes (occupation, phone number, email, etc). This is the class you need to edit or extend if you want to change the style of the signatures for your domain. In the example below, the
HtmlSignature() method simply concatenates some strings with hard-coded styling, but you may want to use a more elaborate templating system instead:# helper class used to build signatures
class SignatureBuilder(object):
def HtmlSignature(self):
signature = %s % self.name
if self.occupation:
signature += %s % self.occupation
if self.company:
signature += %s % self.company
signature += Email: <a href=mailto:%s>%s</a> - Phone: %s % (
self.email, self.email, self.phone_number)
return signature
def __init__(
self, name, company=, occupation=, email=, phone_number=):
self.name = name
self.company = company
self.occupation = occupation
self.email = email
self.phone_number = phone_number
Let’s use profiles_client to retrieve a feed containing all profiles for the domain. Each call to
GetProfilesFeed() only returns a page of users, so we need to follow the next links until we get all users:# get all user profiles for the domain
profiles = []
feed_uri = profiles_client.GetFeedUri(profiles)
while feed_uri:
feed = profiles_client.GetProfilesFeed(uri=feed_uri)
profiles.extend(feed.entry)
feed_uri = feed.FindNextLink()
At this point profiles will contain the list of users we want to process. For each of them, we instantiate a
SignatureBuilder object and set its properties name, company, occupation, email and phone_number with the data for that user.A call to the HtmlSignature() method of the SignatureBuilder instance will provide us with a properly formatted HTML-encoded signature.
# extract relevant pieces of data for each profile
for entry in profiles:
builder = SignatureBuilder(entry.name.full_name.text)
builder.company = company_name
if entry.occupation:
builder.occupation = entry.occupation.text
for email in entry.email:
if email.primary and email.primary == true:
builder.email = email.address
for number in entry.phone_number:
if number.primary and number.primary == true:
builder.phone_number = number.text
# build the signature
signature = builder.HtmlSignature()
The Email Settings API client exposes a method called
UpdateSignature to set the signature for a target user. This methods accepts two parameters, the username of the user to be affected and a string containing the signature. We just built the latter, so we only need the retrieve the unique username that identifies each user and that can be easily inferred from the entry identifier returned by the Profiles API, as described in the code and the comment below.It is worth mentioning that you can also retrieve usernames with the Provisioning API, but for the sake of simplicity we’ll rely on this small hack:
# entry.id has the following structure:
# http://www.google.com/m8/feeds/profiles/domain/DOMAIN_NAME/full/USERNAME
# the username is the string that follows the last /
username = entry.id.text[entry.id.text.rfind(/)+1:]
It’s time to send the requests to the Email Settings API and update the signature:
# set the users signature using the Email Settings API
email_settings_client.UpdateSignature(username=username,
signature=signature)
For further details on what can be accomplished with the Google Apps APIs, please check our documentation and don’t hesitate to reach out to us on our forums if you have any questions.
![]() | Claudio Cherubino profile | twitter | blog Claudio is a Developer Programs Engineer working on Google Apps APIs and the Google Apps Marketplace. Prior to Google, he worked as software developer, technology evangelist, community manager, consultant, technical translator and has contributed to many open-source projects, including MySQL, PHP, Wordpress, Songbird and Project Voldemort. |
Wednesday, March 11, 2015
Building a Rails based app for Google Apps Marketplace
Editors note: This is a guest post by Benjamin Coe. Benjamin shares tips and best practices on using Ruby on Rails for integrating with Google Apps and launching on the Marketplace. — Arun Nagarajan
- Replacing OpenID with OAuth 2.0 for Single-Sign-On.
- Replacing 2-legged OAuth with OAuth 2.0 Service Accounts, for delegated account access.
- Releasing a Gmail Contextual Gadget that worked within these new authentication paradigms.
OAuth 2.0 for SSO
In the revamped Google Apps Marketplace, OAuth 2.0 replaces OpenID for facilitating Single-Sign-On. The flow is as follows:- OAuth 2.0 credentials are created in the Cloud Console, within the same project that has the Google Apps Marketplace SDK enabled.
- When accessing your application, a user is put through the standard OAuth 2.0 authentication flow using these credentials.
- If the user has the Google Apps Marketplace App installed they will be logged directly into your application, skipping the authorization step.
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["GAM_OAUTH_KEY"], ENV["GAM_OAUTH_SECRET"]
end
Yesware already had a Google OAuth 2.0 authentication strategy, so we opted to subclass the Google OAuth 2.0 OmniAuth Strategy. This allowed us to continue supporting our existing OAuth 2.0 credentials, while adding support for Google Apps Marketplace SSO. Our subclassed strategy looked like this:
# Subclass the GoogleOauth2 Omniauth strategy for
# Google Apps Marketplace V2 SSO.
module OmniAuth
module Strategies
class GoogleAppsMarketplace < OmniAuth::Strategies::GoogleOauth2
option :name, google_apps_marketplace
end
end
end
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["OAUTH_KEY"],
ENV["OAUTH_SECRET"],
{:scope => ENV["OAUTH_SCOPE"]}
provider :google_apps_marketplace, ENV["GAM_OAUTH_KEY"],
ENV["GAM_OAUTH_SECRET"],
{ :scope => ENV["GAM_OAUTH_SCOPE"],
:access_type => online }end
Note that :access_type is set to online. This is necessary to prevent the authorization prompt from being presented to a SSO user. Omniauth defaults to an :access_type of offline.
Thats all it takes. With this OmniAuth strategy in place, when a domain administrator installs your application SSO will be available across the domain.
OAuth 2.0 Service Accounts
To support Yeswares reminder functionality, we needed offline access to a users email account. In the past, this functionality was supported through 2-legged OAuth. In the new Google Apps Marketplace paradigm, OAuth 2.0 Service Accounts are the replacement.- In the Cloud Console, generate a private key for the OAuth 2.0 Service Account associated with your Google Apps Marketplace project.
- Download the .p12 private key generated.
- Place this key somewhere that will be accessible by your production servers, e.g., a certificates folder in your codebase.
Gmail.connect!(:xoauth, ben@example.com, {
token: authentication.token,
secret: authentication.secret,
consumer_key: google.key,
consumer_secret: google.secret,
read_only: true
})
Using the new Service Account Based Approach, it was as follows: key = Google::APIClient::PKCS12.load_key(With OAuth 2.0 Service Accounts, the underlying libraries we used to interact with Gmail remained the same. There were simply a few extra steps necessary to obtain an access token.
google_apps.service.p12path, # this is a constant value Google uses
# to password protect the key.
notasecret
)service_account = Google::APIClient::JWTAsserter.new(
google_apps.service.email,
https://mail.google.com/,
key
)client = Google::APIClient.new(
:application_name => APPLICATION_NAME,
:version => APPLICATION_VERSION
).tap do |client|
client.authorization = service_account.authorize(ben@example.com)end
Google.connect!(:xoauth2, ben@example.com, {
:oauth2_token => client.authorization.access_token,
})
Contextual Gadgets and SSO
Yesware provides a Gmail Contextual Gadget, for scheduling email reminders. To facilitate this, its necessary that the gadget interact with a users email account. To make this a reality, we needed to implement SSO through our contextual gadget. Google provides great reading material on this topic. However, the approach outlined concentrates on the deprecated OpenID-based SSO approach. We used a slightly modified approach.Rather than OpenID, we used OAuth 2.0 for associating the opensocial_viewer_id with a user. To do this, we needed to modify our OmniAuth strategy to store the opensocial_viewer_id during authentication:
# Subclass the GoogleOauth2 Omniauth strategy forOnce an opensocial_viewer_id was connected to a Yesware user, we could securely make API calls from our contextual gadget. To cut down on the ritual surrounding this, we wrote a Devise Google Apps OpenSocial Strategy for authenticating the OpenSocal signed requests.
# Google Apps Marketplace V2 SSO.
module OmniAuth
module Strategies
class GoogleAppsMarketplace < OmniAuth::Strategies::GoogleOauth2
option :name, google_apps_marketplace
def request_phase
# Store the opensocial_viewer_id in the session.
# this allows us to bind the Google Apps contextual
# gadget to a user account.
if request.params[opensocial_viewer_id]
session[:opensocial_viewer_id] = request.params[opensocial_viewer_id]
end
super
end
end
end
end
Now Go Forth
Once we figured out all the moving parts, we were able to use mostly off the shelf mature libraries for building our Google Apps Marketplace Integration. I hope that this retrospective look at our development process helps other Rails developers hit the ground running even faster than we did.| | Benjamin Coe profile Benjamin Coe cofounded the email productivity company Attachments.me, which was acquired by Yesware, Inc., in 2013. Before starting his own company, Ben was an engineer at FreshBooks, the world’s #1 accounting solution. Ben’s in his element when writing scalable cloud-based infrastructure, and loves reflecting on the thought-process that goes into this. A rock-climber, amateur musician, and bagel aficionado, Ben can be found roaming the streets of San Francisco. ben@yesware.com — https://github.com/bcoe— @benjamincoe |
Supporting the growing Google Drive developer community Google Drive Workshops
Since the public unveiling of the Google Drive SDK in April, companies like Lucidchart or HelloFax have built powerful, slick, useful Google Drive apps, and many more companies are launching compelling integrations every day. During this time, our developer community — especially on Stack Overflow — has grown substantially.
To help support our growing developer community and all the interest in integrating with Google Drive, we’re starting a series of Google Drive developer workshops. For the inaugural event, we are hosting several companies — including Shutterfly, Fedex, Autodesk, Twisted Wave, 1DollarScan and Manilla — to participate in a two-day workshop this week at the Googleplex in Mountain View, California.
During this workshop, Google engineers will be on hand to assist attendees with various parts of their Google Drive integration: things like design and implementation of features, authorization flow, and Android integration. Companies have shown that the Google Drive SDK allows for deep integration in just a couple days and we really hope that attendees of this workshop will enjoy a similar experience. Tune back in later this week to find out more about what we learned and accomplished in our workshop.
If you are interested in attending similar Google Drive workshops near you or if you want to contact the Google Drive team about a potential integration with your product, let us know.
| Nicolas Garnier Google+ | Twitter Nicolas Garnier joined Google’s Developer Relations in 2008 and lives in Zurich. He is a Developer Advocate for Google Drive and Google Apps. Nicolas is also the lead engineer for the OAuth 2.0 Playground. |

