15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from google.appengine.ext import webapp
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from google.appengine.ext.webapp import util
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from google.appengine.api import users
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from google.appengine.api import urlfetch
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from google.appengine.ext.webapp import template
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from google.appengine.api.urlfetch import DownloadError
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import oauth2
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from django.utils import simplejson
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Configuration
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CONFIG = {
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'oauth_consumer_key': 'anonymous',
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'oauth_consumer_secret': 'anonymous',
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'license_server': 'https://www.googleapis.com',
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'license_path': '%(server)s/chromewebstore/v1/licenses/%(appid)s/%(userid)s',
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'oauth_token': 'INSERT OAUTH TOKEN HERE',
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'oauth_token_secret': 'INSERT OAUTH TOKEN SECRET HERE',
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  'app_id': 'INSERT APPLICATION ID HERE',
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Check to see if the server has been deployed.  In the dev server, this
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# env variable will start with 'Development', in production, it will start with
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 'Google App Engine'
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)IS_PRODUCTION = os.environ['SERVER_SOFTWARE'].startswith('Google App Engine')
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Valid access levels that may be returned by the license server.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)VALID_ACCESS_LEVELS = ['FREE_TRIAL', 'FULL']
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def fetch_license_data(userid):
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Fetches the license for a given user by making an OAuth signed request
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  to the license server.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    userid OpenID of the user you are checking access for.
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The server's response as text.
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  url = CONFIG['license_path'] % {
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'server': CONFIG['license_server'],
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'appid': CONFIG['app_id'],
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'userid': urllib.quote_plus(userid),
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  oauth_token = oauth2.Token(**{
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'key': CONFIG['oauth_token'],
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'secret': CONFIG['oauth_token_secret']
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  })
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  oauth_consumer = oauth2.Consumer(**{
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'key': CONFIG['oauth_consumer_key'],
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'secret': CONFIG['oauth_consumer_secret']
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  })
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.debug('Requesting %s' % url)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  client = oauth2.Client(oauth_consumer, oauth_token)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  resp, content = client.request(url, 'GET')
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.debug('Got response code %s, content %s' % (resp, content))
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return content
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def parse_license_data(userid):
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Returns the license for a given user as a structured object.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    userid: The OpenID of the user to check.
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    An object with the following parameters:
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      error:  True if something went wrong, False otherwise.
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      message: A descriptive message if error is True.
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      access: One of 'NO', 'FREE_TRIAL', or 'FULL' depending on the access.
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  license = {'error': False, 'message': '', 'access': 'NO'}
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response_text = fetch_license_data(userid)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.debug('Attempting to JSON parse: %s' % response_text)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      json = simplejson.loads(response_text)
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.debug('Got license server response: %s' % json)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except ValueError:
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.exception('Could not parse response as JSON: %s' % response_text)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      license['error'] = True
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      license['message'] = 'Could not parse the license server response'
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except DownloadError:
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.exception('Could not fetch license data')
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    license['error'] = True
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    license['message'] = 'Could not fetch license data'
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if json.has_key('error'):
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    license['error'] = True
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    license['message'] = json['error']['message']
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  elif json['result'] == 'YES' and json['accessLevel'] in VALID_ACCESS_LEVELS:
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    license['access'] = json['accessLevel']
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return license
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class MainHandler(webapp.RequestHandler):
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Request handler class."""
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def get(self):
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Handler for GET requests."""
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    user = users.get_current_user()
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if user:
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if IS_PRODUCTION:
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # We should use federated_identity in production, since the license
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # server requires an OpenID
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        userid = user.federated_identity()
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # On the dev server, we won't have access to federated_identity, so
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # just use a default OpenID which will never return YES.
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # If you want to test different response values on the development
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # server, just change this default value (e.g. append '-yes' or
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # '-trial').
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        userid = ('https://www.google.com/accounts/o8/id?'
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  'id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      license_data = parse_license_data(userid)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      template_data = {
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'license': license_data,
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'user_name': user.nickname(),
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'user_id': userid,
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'user_logout': users.create_logout_url(self.request.uri),
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Force the OpenID login endpoint to be for Google accounts only, since
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # the license server doesn't support any other type of OpenID provider.
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      login_url = users.create_login_url(dest_url='/',
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      federated_identity='google.com/accounts/o8/id')
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      template_data = {
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'user_login': login_url,
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Render a simple template
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    path = os.path.join(os.path.dirname(__file__), 'templates', 'index.html')
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.response.out.write(template.render(path, template_data))
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  application = webapp.WSGIApplication([
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ('/', MainHandler),
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ], debug=False)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  util.run_wsgi_app(application)
154