chromevox_webstore_util.py revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com#!/usr/bin/env python
2ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com
3ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com# Copyright 2014 The Chromium Authors. All rights reserved.
4ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com# Use of this source code is governed by a BSD-style license that can be
5ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com# found in the LICENSE file.
6ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com
7ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com'''A set of utilities to interface with the Chrome Webstore API.'''
8bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com
9bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comimport SimpleHTTPServer
10bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comimport SocketServer
11bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comimport httplib
12bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comimport json
13f523e25da069e3e1af2cb73d37073a34f3bb9ea2reed@android.comimport os
14bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comimport re
15b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.comimport sys
16b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.comimport thread
17b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.comimport urllib
18b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.comimport webbrowser
19b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com
20b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.comPROJECT_ARGS = {
21b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com  'client_id': ('937534751394-gbj5334v9144c57qjqghl7d283plj5r4'
22b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com      '.apps.googleusercontent.com'),
23b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com  'grant_type': 'authorization_code',
24b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com  'redirect_uri': 'http://localhost:8000'
25b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com}
26b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com
27b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.comPORT = 8000
28b8b92ea089a380e17984e4df7b49b379cb6bd929reed@google.com
29bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comAPP_ID = 'kgejglhpjiefppelpmljglcjbhoiplfn'
304bc1983e01d756ae9c91fd380758457f579d26eareed@android.comOAUTH_DOMAIN = 'accounts.google.com'
31bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comOAUTH_AUTH_COMMAND = '/o/oauth2/auth'
324e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.comOAUTH_TOKEN_COMMAND = '/o/oauth2/token'
334e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.comWEBSTORE_API_SCOPE = 'https://www.googleapis.com/auth/chromewebstore'
344e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.com
354e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.comAPI_ENDPOINT_DOMAIN = 'www.googleapis.com'
364e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.comCOMMAND_GET_UPLOAD_STATUS = (
374e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.com    '/chromewebstore/v1.1/items/%s?projection=draft' % APP_ID)
384e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.comCOMMAND_POST_PUBLISH = '/chromewebstore/v1.1/items/%s/publish' % APP_ID
39c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.comCOMMAND_POST_UPLOAD = '/upload/chromewebstore/v1.1/items/%s' % APP_ID
404e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.com
414e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.comclass CodeRequestHandler(SocketServer.StreamRequestHandler):
42bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com  def handle(self):
43bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com    content = self.rfile.readline()
4415e9d3e66e161ce23df30bc13f8a0c87d196b463robertphillips@google.com    self.server.code = re.search('code=(.*) ', content).groups()[0]
4515e9d3e66e161ce23df30bc13f8a0c87d196b463robertphillips@google.com    self.rfile.close()
46e9d0060f4d7b5a07a220182d83aae3a140784c4breed@android.com
474bc1983e01d756ae9c91fd380758457f579d26eareed@android.comdef GetAuthCode():
48bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com  Handler = CodeRequestHandler
49bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com  httpd = SocketServer.TCPServer(("", PORT), Handler)
5030e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com  query = '&'.join(['response_type=code',
5130e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com                    'scope=%s' % WEBSTORE_API_SCOPE,
5230e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com                    'client_id=%(client_id)s' % PROJECT_ARGS,
5330e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com                    'redirect_uri=%(redirect_uri)s' % PROJECT_ARGS])
5430e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com  auth_url = ' https://%s%s?%s' % (OAUTH_DOMAIN, OAUTH_AUTH_COMMAND, query)
5530e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com  print 'Navigating to %s' % auth_url
56bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com  webbrowser.open(auth_url)
5730e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com  httpd.handle_request()
5830e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com  httpd.server_close()
5930e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com  return httpd.code
6030e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com
6130e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.comdef GetOauthToken(code, client_secret):
6230e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com  PROJECT_ARGS['code'] = code
634bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  PROJECT_ARGS['client_secret'] = client_secret
644bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  body = urllib.urlencode(PROJECT_ARGS)
654bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  conn = httplib.HTTPSConnection(OAUTH_DOMAIN)
66fbfcd5602128ec010c82cb733c9cdc0a3254f9f3rmistry@google.com  conn.putrequest('POST', OAUTH_TOKEN_COMMAND)
674bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  conn.putheader('content-type', 'application/x-www-form-urlencoded')
684bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  conn.putheader('content-length', len(body))
694bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  conn.endheaders()
70fbfcd5602128ec010c82cb733c9cdc0a3254f9f3rmistry@google.com  conn.send(body)
7129348cb0612e19030d979156860946241e2ff4bdreed@android.com  content = conn.getresponse().read()
7229348cb0612e19030d979156860946241e2ff4bdreed@android.com  return json.loads(content)
7329348cb0612e19030d979156860946241e2ff4bdreed@android.com
74fbfcd5602128ec010c82cb733c9cdc0a3254f9f3rmistry@google.comdef GetPopulatedHeader(client_secret):
754e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.com  code = GetAuthCode()
764e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.com  access_token = GetOauthToken(code, client_secret)
774e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.com  url = 'www.googleapis.com'
78e9d0060f4d7b5a07a220182d83aae3a140784c4breed@android.com
799dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com  return {'Authorization': 'Bearer %(access_token)s' % access_token,
809dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com             'x-goog-api-version': 2,
819dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com             'Content-Length': 0
829dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com            }
839dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com
849dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.comdef SendGetCommand(command, client_secret):
859dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com  headers = GetPopulatedHeader(client_secret)
86ef77ec210416733e38072175cc948dcbdb831685reed@google.com  conn = httplib.HTTPSConnection(API_ENDPOINT_DOMAIN)
87ef77ec210416733e38072175cc948dcbdb831685reed@google.com  conn.request('GET', command, '', headers)
88ef77ec210416733e38072175cc948dcbdb831685reed@google.com  return conn.getresponse()
89ef77ec210416733e38072175cc948dcbdb831685reed@google.com
90ef77ec210416733e38072175cc948dcbdb831685reed@google.comdef SendPostCommand(command, client_secret, header_additions = {}, body=None):
91ef77ec210416733e38072175cc948dcbdb831685reed@google.com  headers = GetPopulatedHeader(client_secret)
92ef77ec210416733e38072175cc948dcbdb831685reed@google.com  headers = dict(headers.items() + header_additions.items())
93ef77ec210416733e38072175cc948dcbdb831685reed@google.com  conn = httplib.HTTPSConnection(API_ENDPOINT_DOMAIN)
94ef77ec210416733e38072175cc948dcbdb831685reed@google.com  conn.request('POST', command, body, headers)
95ef77ec210416733e38072175cc948dcbdb831685reed@google.com  return conn.getresponse()
96ef77ec210416733e38072175cc948dcbdb831685reed@google.com
97ef77ec210416733e38072175cc948dcbdb831685reed@google.comdef GetUploadStatus(client_secret):
98ef77ec210416733e38072175cc948dcbdb831685reed@google.com  '''Gets the status of a previous upload.
99ef77ec210416733e38072175cc948dcbdb831685reed@google.com  Args:
100c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com    client_secret ChromeVox's client secret creds.
101c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com  '''
102c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com  return SendGetCommand(COMMAND_GET_UPLOAD_STATUS, client_secret)
103c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com
104c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com# httplib fails to persist the connection during upload; use curl instead.
105c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.comdef PostUpload(file, client_secret):
106c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com  '''Posts an uploaded version of ChromeVox.
107c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com  Args:
108ef77ec210416733e38072175cc948dcbdb831685reed@google.com    file A string path to the ChromeVox extension zip.
109bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com    client_secret ChromeVox's client secret creds.
1100561a3ccd24e46259d825adf5507c7ee9531b9c5reed@google.com  '''
1114bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  header = GetPopulatedHeader(client_secret)
112bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com  curl_command = ' '.join(['curl',
11330e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com                           '-H "Authorization: %(Authorization)s"' % header,
114bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com                           '-H "x-goog-api-version: 2"',
11530e6d2c2054c15f1cb6c0637bee6756261291751bsalomon@google.com                           '-X PUT',
116f523e25da069e3e1af2cb73d37073a34f3bb9ea2reed@android.com                           '-T %s' % file,
117f523e25da069e3e1af2cb73d37073a34f3bb9ea2reed@android.com                           '-v',
1189dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com                           'https://%s%s' % (API_ENDPOINT_DOMAIN,
1199dc2713fc4a75e7fbee2f985057fde680a07c7f0tomhudson@google.com                                             COMMAND_POST_UPLOAD)])
120f523e25da069e3e1af2cb73d37073a34f3bb9ea2reed@android.com
1214bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  print 'Running %s' % curl_command
1224bc1983e01d756ae9c91fd380758457f579d26eareed@android.com  if os.system(curl_command) != 0:
1234bc1983e01d756ae9c91fd380758457f579d26eareed@android.com    sys.exit(-1)
12429348cb0612e19030d979156860946241e2ff4bdreed@android.com
1254e635f9eb218b15f656a02e71c60d4f787fe9306reed@android.comdef PostPublishTrustedTesters(client_secret):
126ef77ec210416733e38072175cc948dcbdb831685reed@google.com  '''Publishes a previously uploaded ChromeVox extension to trusted testers.
127c289743864e2ab926a95e617a5cd1d29b26d1825mtklein@google.com  Args:
12815e9d3e66e161ce23df30bc13f8a0c87d196b463robertphillips@google.com    client_secret ChromeVox's client secret creds.
12915e9d3e66e161ce23df30bc13f8a0c87d196b463robertphillips@google.com  '''
130bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com  return SendPostCommand(COMMAND_POST_PUBLISH,
131bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.com                         client_secret,
132bd6343b1d60d2a85e930f33f4b06b4502b3e8caamtklein@google.com                         { 'publishTarget': 'trustedTesters'})
133f523e25da069e3e1af2cb73d37073a34f3bb9ea2reed@android.com
134bd700c391dd45acd8ea1a40321789c9d92a14bb8reed@android.comdef PostPublish(client_secret):
135  '''Publishes a previously uploaded ChromeVox extension publically.
136  Args:
137    client_secret ChromeVox's client secret creds.
138  '''
139  return SendPostCommand(COMMAND_POST_PUBLISH, client_secret)
140