11320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#!/usr/bin/env python 21320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 31320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci# Copyright 2014 The Chromium Authors. All rights reserved. 41320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci# Use of this source code is governed by a BSD-style license that can be 51320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci# found in the LICENSE file. 61320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 71320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci'''A set of utilities to interface with the Chrome Webstore API.''' 81320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 91320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport SimpleHTTPServer 101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport SocketServer 111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport httplib 121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport json 131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport os 141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport re 151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport sys 161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport thread 171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport urllib 181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport webbrowser 191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciPROJECT_ARGS = { 211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'client_id': ('937534751394-gbj5334v9144c57qjqghl7d283plj5r4' 221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '.apps.googleusercontent.com'), 231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'grant_type': 'authorization_code', 241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'redirect_uri': 'http://localhost:8000' 251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci} 261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciPORT = 8000 281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciAPP_ID = 'kgejglhpjiefppelpmljglcjbhoiplfn' 301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciOAUTH_DOMAIN = 'accounts.google.com' 311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciOAUTH_AUTH_COMMAND = '/o/oauth2/auth' 321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciOAUTH_TOKEN_COMMAND = '/o/oauth2/token' 331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciWEBSTORE_API_SCOPE = 'https://www.googleapis.com/auth/chromewebstore' 341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciAPI_ENDPOINT_DOMAIN = 'www.googleapis.com' 361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciCOMMAND_GET_UPLOAD_STATUS = ( 371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '/chromewebstore/v1.1/items/%s?projection=draft' % APP_ID) 381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciCOMMAND_POST_PUBLISH = '/chromewebstore/v1.1/items/%s/publish' % APP_ID 391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciCOMMAND_POST_UPLOAD = '/upload/chromewebstore/v1.1/items/%s' % APP_ID 401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciclass CodeRequestHandler(SocketServer.StreamRequestHandler): 421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci def handle(self): 431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci content = self.rfile.readline() 441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci self.server.code = re.search('code=(.*) ', content).groups()[0] 451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci self.rfile.close() 461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef GetAuthCode(): 481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci Handler = CodeRequestHandler 491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci httpd = SocketServer.TCPServer(("", PORT), Handler) 501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci query = '&'.join(['response_type=code', 511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'scope=%s' % WEBSTORE_API_SCOPE, 521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'client_id=%(client_id)s' % PROJECT_ARGS, 531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'redirect_uri=%(redirect_uri)s' % PROJECT_ARGS]) 541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci auth_url = ' https://%s%s?%s' % (OAUTH_DOMAIN, OAUTH_AUTH_COMMAND, query) 551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci print 'Navigating to %s' % auth_url 561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci webbrowser.open(auth_url) 571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci httpd.handle_request() 581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci httpd.server_close() 591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return httpd.code 601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef GetOauthToken(code, client_secret): 621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci PROJECT_ARGS['code'] = code 631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci PROJECT_ARGS['client_secret'] = client_secret 641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci body = urllib.urlencode(PROJECT_ARGS) 651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn = httplib.HTTPSConnection(OAUTH_DOMAIN) 661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn.putrequest('POST', OAUTH_TOKEN_COMMAND) 671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn.putheader('content-type', 'application/x-www-form-urlencoded') 681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn.putheader('content-length', len(body)) 691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn.endheaders() 701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn.send(body) 711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci content = conn.getresponse().read() 721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return json.loads(content) 731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef GetPopulatedHeader(client_secret): 751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci code = GetAuthCode() 761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci access_token = GetOauthToken(code, client_secret) 771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci url = 'www.googleapis.com' 781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return {'Authorization': 'Bearer %(access_token)s' % access_token, 801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'x-goog-api-version': 2, 811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'Content-Length': 0 821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef SendGetCommand(command, client_secret): 851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci headers = GetPopulatedHeader(client_secret) 861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn = httplib.HTTPSConnection(API_ENDPOINT_DOMAIN) 871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn.request('GET', command, '', headers) 881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return conn.getresponse() 891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef SendPostCommand(command, client_secret, header_additions = {}, body=None): 911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci headers = GetPopulatedHeader(client_secret) 921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci headers = dict(headers.items() + header_additions.items()) 931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn = httplib.HTTPSConnection(API_ENDPOINT_DOMAIN) 941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci conn.request('POST', command, body, headers) 951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return conn.getresponse() 961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef GetUploadStatus(client_secret): 981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '''Gets the status of a previous upload. 991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci Args: 1001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci client_secret ChromeVox's client secret creds. 1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci ''' 1021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return SendGetCommand(COMMAND_GET_UPLOAD_STATUS, client_secret) 1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci# httplib fails to persist the connection during upload; use curl instead. 1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef PostUpload(file, client_secret): 1061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '''Posts an uploaded version of ChromeVox. 1071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci Args: 1081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci file A string path to the ChromeVox extension zip. 1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci client_secret ChromeVox's client secret creds. 1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci ''' 1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci header = GetPopulatedHeader(client_secret) 1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci curl_command = ' '.join(['curl', 1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '-H "Authorization: %(Authorization)s"' % header, 1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '-H "x-goog-api-version: 2"', 1151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '-X PUT', 1161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '-T %s' % file, 1171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '-v', 1181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 'https://%s%s' % (API_ENDPOINT_DOMAIN, 1191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci COMMAND_POST_UPLOAD)]) 1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci print 'Running %s' % curl_command 1221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if os.system(curl_command) != 0: 1231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci sys.exit(-1) 1241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef PostPublishTrustedTesters(client_secret): 1261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '''Publishes a previously uploaded ChromeVox extension to trusted testers. 1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci Args: 1281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci client_secret ChromeVox's client secret creds. 1291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci ''' 1301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return SendPostCommand(COMMAND_POST_PUBLISH, 1311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci client_secret, 1321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci { 'publishTarget': 'trustedTesters'}) 1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 1341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef PostPublish(client_secret): 1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci '''Publishes a previously uploaded ChromeVox extension publically. 1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci Args: 1371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci client_secret ChromeVox's client secret creds. 1381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci ''' 1391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return SendPostCommand(COMMAND_POST_PUBLISH, client_secret) 140