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