Google Apps Platform

Writing your First Marketplace App using Python

Note: There's a new Google Apps Marketplace experience! Beginning November 19, 2013, new listings may only be created using the new version: existing developers may need to create a new Chrome Web Store account to publish new listings. Refer to the new documentation for more information.

Contents

  1. Audience
  2. Build the App
  3. Integrate the App
    1. Single Sign On with OpenID
    2. OAuth-enabled access to Google Calendar
  4. Create the Listing
    1. Creating the Manifest
    2. Listing the App on the Marketplace
    3. Retrieving the OAuth Key and Secret
  5. Launching the App in Production
  6. Appendix - Advanced Options
    1. Redirecting to your site during Installation

This tutorial will walk you through launching your first application on the Google Apps Marketplace. The tutorial assumes you already can create a basic "Hello World" web application and builds on top of such an application to demonstrate several key pieces of functionality -- a link in the Google universal navigation, Single Sign On using OpenID and OAuth-enabled access to Google Apps data. Instead of saying "Hello World", this application will greet the Google Apps user by name and shows them their next Google Calendar appointment.

To see how Marketplace applications are installed by domain administrators and consumed by end-users, take a look at the application lifecycle.

Audience

This tutorial assumes that you are a developer with a high level understanding of web technologies such as XML and HTTP. The code for this application is in Python, but the concepts demonstrated can easily be adapted to other languages.

Build the App

You may already have a cloud application you'd like to launch on the Google Apps Marketplace, or you may be building one from scratch. This tutorial starts with a basic "Hello World" application and demonstrates how it can be integrated with Google Apps.

Here's the basic application which will be integrated with Google Apps:

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util

class MainHandler(webapp.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>%s</body></html>' % 'Hello world')

def main():
  application = webapp.WSGIApplication([('/', MainHandler)],
                                       debug=True)
  util.run_wsgi_app(application)

if __name__ == '__main__':
  main()

This "Hello World" sample application uses Google App Engine and the Python runtime. Of course, any language and hosting platform can be used to build Marketplace applications.

Integrate the App

Although any language can be used to build Marketplace applications, using a language with available OpenID libraries supporting the Google Apps discovery extensions and available Google Data API client libraries makes developing integrations easier.

This application uses the following libraries to integrate with Google Apps:

Download:

  • Self-contained ZIP Download
  • To make it easier for you to get started, we've included the sample app as a self-contained download. After you download the referenced file, create a new project with the App Engine Launcher and unzip the content into that directory. Adjust the variables in main.py and app.yaml, and then deploy the project to App Engine. Finally, create your listing using the steps below.

Single Sign On with OpenID

The OpenID protocol enables users to login to applications around the web without the need to create a new set of credentials on each site. Using the Single Sign On functionality availability to Marketplace applications, Google Apps users can seamlessly login to third-party web applications as if the applications are natively part of Google Apps.

Many Google Apps users login to Google Apps first thing in the morning to check their e-mail. With the universal navigation bar links created by Marketplace applications, users get fingertip access to all the applications they regularly use-- right from within their e-mail and the other Google Apps.

Discovery

OpenID discovery is the process of finding the correct OpenID endpoint to use for users on a given domain. To start the process, applications supporting OpenID typically ask the user for their identifier or allow them to pick from a set of popular providers. For users navigating from Google Apps, the information needed to start the discovery process can be supplied in the link to the application, making the process transparent to users. For example, if we define the link to our app in the manifest as http://www.googlecodesamples.com/helloworld?from=google&domain=${DOMAIN_NAME} and log in as john@example.com, then the URL would appear in the universal navigation bar as http://www.googlecodesamples.com/helloworld?from=google&domain=example.com. This allows the application to immediately make an OpenID authentication request without prompting the user.

Typically OpenID requires that each domain host metadata on a web server. Because Google Apps doesn't require every customer to host a website, a slightly modified version of the discovery prototocol is used for Google Apps domains.

Authentication and Whitelisting

After the library finds the correct Google Apps OpenID endpoint for the given domain, the "Hello World" application redirects the user's web browser to the endpoint. Since the user accessed the application from the Google universal navigation, they're already logged in to their Google Apps account. Google does a comparison of the openid.realm value specified in the request to those registered by applications installed on their domain. If the realm matches exactly, the user is immediately redirected back to the "Hello World" application seamlessly without any interstitial "access granting" page typically associated with OpenID.

Code

The code for the OpenID portion of "Hello World" is presented below:

  • main.py is the page the user first visits when accessing the application from the universal navigation bar. This page kicks off the OpenID discovery process and login flow based on the domain query parameter.
  • app.yaml controls the application's configuration. It specifies that a login is required on all pages, and defines the handler for OpenID login requests (as per the Google App Engine documentation). Make sure you've also set your application to use OpenID in the application's dashboard.
import re
from urlparse import urlparse
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util

class MainHandler(webapp.RequestHandler):
  def get(self):
    user = users.get_current_user()
    if user and self.check_email(user):
      greeting = ('Hello %s (%s)!)' %
                 (user.email(), user.nickname()))
    else:
      greeting = 'You need to log in!'

    self.response.out.write('<html><body>%s</body></html>' % greeting)

  def check_email(self, user):
    """Performs basic validation of the supplied email address as outlined
    in http://code.google.com/googleapps/marketplace/best_practices.html
    """
    domain = urlparse(user.federated_identity()).hostname
    m = re.search('.*@' + domain, user.email())
    if m:
      return True
    else:
      return False

class OpenIDHandler(webapp.RequestHandler):
    def get(self):
      """Begins the OpenID flow and begins Google Apps discovery for the supplied domain."""
      self.redirect(users.create_login_url(dest_url='http://my-app.appspot.com/',
                                           _auth_domain=None,
                                           federated_identity=self.request.get('domain')))

def main():
  application = webapp.WSGIApplication([('/', MainHandler),
                                        ('/_ah/login_required', OpenIDHandler)],
                                       debug=True)
  util.run_wsgi_app(application)

if __name__ == '__main__':
  main()
application: my-app
version: 1
runtime: python
api_version: 1

handlers:
- url: /_ah/login_required
  script: main.py

- url: .*
  script: main.py
  login: required

OAuth-enabled access to Google Calendar

When a user visits the "Hello World" application, they are greeted by name and presented with their next upcoming calendar appointment from Google Calendar using the Calendar Data API and 2-legged OAuth.

Each listing in the Marketplace has an OAuth key and secret which can be used to access the authorized data for any user in any domain which has installed the app, based on the granting of privileges by the domain administrator during the install process. This key and secret can be obtained after listing the app on the Marketplace. The key and secret pair authorize the application, but in order to specify which user's data the application needs access to, a query parameter called xoauth_requestor_id is added to all API requests indicating which user's data is needed.

Because of the potential sensitivity of this information, applications need to ensure that they're presenting the data to the correct user and thus it uses OpenID to determine the user's identity. Since Google does not allow a user to directly modify their e-mail address of record, the e-mail address retrieved via OpenID attribute exchange may be used, but you should be sure to read the best practices and security considerations which cover some precautions you should take. The general Google Data APIs documentation contains more information on 2-legged OAuth including samples in other languages.

Code

The following expands on the above code, adding the display of the user's next Calendar event:

  • main.py contains the code to request the next event, and populates values to be used within an HTML template.
  • index.html contains the HTML output displayed to the user.
import os, re
from datetime import datetime
from urlparse import urlparse
import gdata.auth
import gdata.calendar.service
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp import util

class MainHandler(webapp.RequestHandler):
  def get(self):
    template_values = {}

    user = users.get_current_user()
    if user and self.check_email(user):
      greeting = ('Hello %s (%s)!)' %
                 (user.email(), user.nickname()))
      event = self.get_next_event(user)
      if event:
        template_values['title'] = event.title.text
        template_values['start'] = event.when[0].start_time
        template_values['end'] = event.when[0].end_time
        template_values['where'] = event.where[0].value_string
        template_values['desc'] = event.content.text
    else:
      greeting = 'You need to log in!'

    template_values['greeting'] = greeting
    path = os.path.join(os.path.dirname(__file__), 'templates/index.html')
    self.response.out.write(template.render(path, template_values))

  def check_email(self, user):
    """Performs basic validation of the supplied email address as outlined
    in http://code.google.com/googleapps/marketplace/best_practices.html
    """
    domain = urlparse(user.federated_identity()).hostname
    m = re.search('.*@' + domain, user.email())
    if m:
      return True
    else:
      return False

  def get_next_event(self, user):
    """Uses two-legged OAuth to retrieve the user's next Calendar event."""
    CONSUMER_KEY = "replace_me.apps.googleusercontent.com"
    CONSUMER_SECRET = "replace_me"
    SIG_METHOD = gdata.auth.OAuthSignatureMethod.HMAC_SHA1

    client = gdata.calendar.service.CalendarService(source='myCompany-helloworld')
    client.SetOAuthInputParameters(SIG_METHOD, CONSUMER_KEY, consumer_secret=CONSUMER_SECRET,
                                   two_legged_oauth=True, requestor_id=user.email())

    query = gdata.calendar.service.CalendarEventQuery('default', 'private', 'full')
    query.start_min = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
    query.sortorder = 'ascending'
    query.orderby = 'starttime'
    query.max_results = 1
    feed = client.CalendarQuery(query)

    if len(feed.entry):
      return feed.entry[0]
    else:
      return None

class OpenIDHandler(webapp.RequestHandler):
    def get(self):
      """Begins the OpenID flow and begins Google Apps discovery for the supplied domain."""
      self.redirect(users.create_login_url(dest_url='http://my-app.appspot.com/',
                                           _auth_domain=None,
                                           federated_identity=self.request.get('domain')))

def main():
  application = webapp.WSGIApplication([('/', MainHandler),
                                        ('/_ah/login_required', OpenIDHandler)],
                                       debug=True)
  util.run_wsgi_app(application)

if __name__ == '__main__':
  main()
<html>
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <h3>{{ greeting }}</h3>
    {% if title %}
    <div>
      <p>Your next event is:</p>
      <p>Title: {{ title }}</p>
      <p>When: {{ start }} - {{ end }}</p>
      <p>Where: {{ where }}</p>
      <p>Description: {{ desc }}</p>
    </div>
    {% endif %}
  </body>
</html>

Defining the App Structure

Creating the Manifest

An installable application the Marketplace is defined by the Marketplace listing and an application manifest. The Marketplace listing generally defines the marketing and business components of an app while the manifest describes the technical details and data access requirements. The manifest is an XML documented uploaded by the developer during the process of creating a listing.

Manifest Structure

A manifest is composed of meta-data about the application (including a name, description and URLs), one or more extensions (such as a navigation link or OpenID) and zero or more data access requirements.

This "Hello World" application is defined using the following elements:

  • Name: "Hello World"
  • Description: "Demonstrates a simple Google Apps Marketplace application."
  • Support URL: End-users can learn how to get support for this application at https://my-app.appspot.com/
  • Universal navigation link: The application will be accessed from the Google universal navigation using the URL https://my-app.appspot.com?from=google&domain=${DOMAIN_NAME}. The ${DOMAIN_NAME} variable is automatically replaced by the customer's domain name and this information is used to enable OpenID-based Single Sign On.
  • OpenID realm: The supplied OpenID realm will be automatically whitelisted during the install process so that the identity of the application user is automatically passed to the application without the typical OpenID authorization page being presented, as the domain administrator has approved the installation of the application. This realm must exactly match the openid.realm used in OpenID requests.
  • Data access scope: Since this application needs access to Google Calendar to present the next upcoming Calendar appointment, the scope for Google Calendar must be listed in the manifest, and referenced by one of the other extensions (in this case, the navigation link which represents the web application). A list of the valid scopes can be found in the manifest documentation. A <Reason /> is also supplied for each scope, indicating the reason the application needs access to the specified data. This is presented to the administrator during the install process.
<?xml version="1.0" encoding="UTF-8" ?>
<ApplicationManifest xmlns="http://schemas.google.com/ApplicationManifest/2009">
  <Name>Hello World</Name>
  <Description>Demonstrates a simple Google Apps Marketplace application</Description>

  <!-- Administrators and users will be sent to this URL for application support -->
  <Support>
    <Link rel="support" href="https://my-app.appspot.com/" />
  </Support>

  <!-- Show this link in Google's universal navigation for all users -->
  <Extension id="navLink" type="link">
    <Name>Hello World</Name>
    <Url>https://my-app.appspot.com?from=google&domain=${DOMAIN_NAME}</Url>
    <Scope ref="calendarAPI"/>
  </Extension>

  <!-- Declare our OpenID realm so our app is white listed -->
  <Extension id="realm" type="openIdRealm">
    <Url>https://my-app.appspot.com</Url>
  </Extension>

  <!-- Need access to the Calendar API -->
  <Scope id="calendarAPI">
    <Url>https://www.google.com/calendar/feeds/</Url>
    <Reason>This app displays the user's next upcoming Google Calendar appointment.</Reason>
  </Scope>
</ApplicationManifest>

More information about application manifests, including additional configuration options, is available in the manifest documentation.

Listing the App

The Google Apps Marketplace is located at http://www.google.com/appsmarketplace. On the Marketplace, you can create private listings and use them for development and testing purposes.

After you visit the Marketplace, you'll need to Sign In and then Become a Vendor before creating your first listing. These links are available at the top-right of the home page. Every valid Google account and Google Apps account can create a vendor profile, but listings cannot be shared between accounts.

The next step is to create your first listing. Be sure to check the checkbox at the top that says "My product may be directly installed into Google Apps domains" as this (along with the application category) cannot be changed after the listing is created and listings cannot currently be deleted.

Copy and paste the manifest from above, modifying the https://my-app.appspot.com URLs to point to your server, and then click on the "Save and Preview" button and you're done!

Retrieving the OAuth Key and Secret

Every listing for an installable application on the Marketplace is associated with an OAuth key and secret. This key and secret is used to access the APIs specified as <Scope /> elements in the manifest file using 2-legged OAuth. The specified data for all users on all domains which have installed the application can be accessed, based on the permissions granted by the domain administrator during the install process. This enables your application to seamlessly integrate with a user's data to do things like create calendar appointments, utilize a user's Google Apps contacts within your application, upload documents, spreadsheets or PDFs to Google Docs and more.

The OAuth key and secret can be accessed by visiting your Vendor Profile using the link at the top right of the Marketplace. Each installable listing will have a separate OAuth key and secret. Please note where this information is accessed as you'll need to replace the key and secret with your generated value.

Launching

Launch your application is easy! Simply create a new listing on the Google Apps Marketplace, supplying the same information as you supplied in your development listing. After you complete the new listing form, you'll need to submit your listing for publishing. After it is reviewed and approved, it will be live on the Marketplace.

You should remember to grab the new OAuth key and secret for your production listing. This key and secret will be different from the key and secret used by your development listing.

Appendix

Redirecting to your site during Installation

The application install flow has an optional step 3 which allows your application to collect additional information from the administrator during the installation process. This can include contact information, billing information or other information required to setup the account for the business.

The optional 3rd step can be specified by supplying a link in the manifest file such as: <Link rel="setup" href="https://my-app.appspot.com/setup?domain=${DOMAIN}"> along with the other <Link /> elements. The ${DOMAIN_NAME} substitution is important as it enables the OpenID discovery process discussed above. An additional query parameter named callback will automatically be added to the end of your setup URL. After the administrator has completed the setup process in your application, you should redirect them to the callback URL so they can enable the application.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.