18ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen#!/usr/bin/python
28ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# Copyright (c) 2010 The Chromium Authors. All rights reserved.
38ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# Use of this source code is governed by a BSD-style license that can be
48ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# found in the LICENSE file.
58ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
68ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfrom google.appengine.ext import webapp
78ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfrom google.appengine.ext.webapp import util
88ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfrom google.appengine.api import users
98ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfrom google.appengine.api import urlfetch
108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfrom google.appengine.ext.webapp import template
118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfrom google.appengine.api.urlfetch import DownloadError
128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport oauth2
138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport urllib
148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport logging
158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport os
168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport time
178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfrom django.utils import simplejson
188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# Configuration
208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
218ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenCONFIG = {
228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  'oauth_consumer_key': 'anonymous',
238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  'oauth_consumer_secret': 'anonymous',
248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  'license_server': 'https://www.googleapis.com',
258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  'license_path': '%(server)s/chromewebstore/v1/licenses/%(appid)s/%(userid)s',
268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  'oauth_token': 'INSERT OAUTH TOKEN HERE',
278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  'oauth_token_secret': 'INSERT OAUTH TOKEN SECRET HERE',
288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  'app_id': 'INSERT APPLICATION ID HERE',
298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# Check to see if the server has been deployed.  In the dev server, this
328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# env variable will start with 'Development', in production, it will start with
338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# 'Google App Engine'
348ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenIS_PRODUCTION = os.environ['SERVER_SOFTWARE'].startswith('Google App Engine')
358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen# Valid access levels that may be returned by the license server.
378ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenVALID_ACCESS_LEVELS = ['FREE_TRIAL', 'FULL']
388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef fetch_license_data(userid):
408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  """Fetches the license for a given user by making an OAuth signed request
418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  to the license server.
428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  Args:
448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    userid OpenID of the user you are checking access for.
458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  Returns:
478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    The server's response as text.
488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  """
498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  url = CONFIG['license_path'] % {
508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    'server': CONFIG['license_server'],
518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    'appid': CONFIG['app_id'],
528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    'userid': urllib.quote_plus(userid),
538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  oauth_token = oauth2.Token(**{
568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    'key': CONFIG['oauth_token'],
578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    'secret': CONFIG['oauth_token_secret']
588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  })
598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  oauth_consumer = oauth2.Consumer(**{
618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    'key': CONFIG['oauth_consumer_key'],
628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    'secret': CONFIG['oauth_consumer_secret']
638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  })
648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  logging.debug('Requesting %s' % url)
668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  client = oauth2.Client(oauth_consumer, oauth_token)
678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  resp, content = client.request(url, 'GET')
688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  logging.debug('Got response code %s, content %s' % (resp, content))
698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return content
708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef parse_license_data(userid):
728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  """Returns the license for a given user as a structured object.
738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  Args:
758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    userid: The OpenID of the user to check.
768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  Returns:
788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    An object with the following parameters:
798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      error:  True if something went wrong, False otherwise.
808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      message: A descriptive message if error is True.
818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      access: One of 'NO', 'FREE_TRIAL', or 'FULL' depending on the access.
828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  """
838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  license = {'error': False, 'message': '', 'access': 'NO'}
848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  try:
858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    response_text = fetch_license_data(userid)
868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    try:
878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      logging.debug('Attempting to JSON parse: %s' % response_text)
888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      json = simplejson.loads(response_text)
898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      logging.debug('Got license server response: %s' % json)
908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    except ValueError:
918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      logging.exception('Could not parse response as JSON: %s' % response_text)
928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      license['error'] = True
938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      license['message'] = 'Could not parse the license server response'
948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  except DownloadError:
958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    logging.exception('Could not fetch license data')
968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    license['error'] = True
978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    license['message'] = 'Could not fetch license data'
988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if json.has_key('error'):
1008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    license['error'] = True
1018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    license['message'] = json['error']['message']
1028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  elif json['result'] == 'YES' and json['accessLevel'] in VALID_ACCESS_LEVELS:
1038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    license['access'] = json['accessLevel']
1048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return license
1068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass MainHandler(webapp.RequestHandler):
1088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  """Request handler class."""
1098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  def get(self):
1108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Handler for GET requests."""
1118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    user = users.get_current_user()
1128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if user:
1138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      if IS_PRODUCTION:
1148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # We should use federated_identity in production, since the license
1158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # server requires an OpenID
1168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        userid = user.federated_identity()
1178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      else:
1188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # On the dev server, we won't have access to federated_identity, so
1198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # just use a default OpenID which will never return YES.
1208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # If you want to test different response values on the development
1218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # server, just change this default value (e.g. append '-yes' or
1228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # '-trial').
1238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        userid = ('https://www.google.com/accounts/o8/id?'
1248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                  'id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
1258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      license_data = parse_license_data(userid)
1268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      template_data = {
1278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        'license': license_data,
1288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        'user_name': user.nickname(),
1298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        'user_id': userid,
1308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        'user_logout': users.create_logout_url(self.request.uri),
1318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
1328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    else:
1338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      # Force the OpenID login endpoint to be for Google accounts only, since
1348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      # the license server doesn't support any other type of OpenID provider.
1358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      login_url = users.create_login_url(dest_url='/',
1368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                      federated_identity='google.com/accounts/o8/id')
1378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      template_data = {
1388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        'user_login': login_url,
1398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
1408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    # Render a simple template
1428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    path = os.path.join(os.path.dirname(__file__), 'templates', 'index.html')
1438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    self.response.out.write(template.render(path, template_data))
1448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenif __name__ == '__main__':
1468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  application = webapp.WSGIApplication([
1478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    ('/', MainHandler),
1488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  ], debug=False)
1498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  util.run_wsgi_app(application)
150