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