googlecloud 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +4 -0
  3. data/LICENSE +674 -0
  4. data/Manifest +111 -0
  5. data/README.md +4 -3
  6. data/bin/gcutil +53 -0
  7. data/googlecloud.gemspec +4 -3
  8. data/packages/gcutil-1.7.1/CHANGELOG +197 -0
  9. data/packages/gcutil-1.7.1/LICENSE +202 -0
  10. data/packages/gcutil-1.7.1/VERSION +1 -0
  11. data/packages/gcutil-1.7.1/gcutil +53 -0
  12. data/packages/gcutil-1.7.1/lib/google_api_python_client/LICENSE +23 -0
  13. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/__init__.py +1 -0
  14. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/discovery.py +743 -0
  15. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/errors.py +123 -0
  16. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/ext/__init__.py +0 -0
  17. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/http.py +1443 -0
  18. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/mimeparse.py +172 -0
  19. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/model.py +385 -0
  20. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/schema.py +303 -0
  21. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/__init__.py +1 -0
  22. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/anyjson.py +32 -0
  23. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/appengine.py +528 -0
  24. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/client.py +1139 -0
  25. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/clientsecrets.py +105 -0
  26. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/crypt.py +244 -0
  27. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/django_orm.py +124 -0
  28. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/file.py +107 -0
  29. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/locked_file.py +343 -0
  30. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/multistore_file.py +379 -0
  31. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/tools.py +174 -0
  32. data/packages/gcutil-1.7.1/lib/google_api_python_client/uritemplate/__init__.py +147 -0
  33. data/packages/gcutil-1.7.1/lib/google_apputils/LICENSE +202 -0
  34. data/packages/gcutil-1.7.1/lib/google_apputils/google/__init__.py +3 -0
  35. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/__init__.py +3 -0
  36. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/app.py +356 -0
  37. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/appcommands.py +783 -0
  38. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/basetest.py +1260 -0
  39. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/datelib.py +421 -0
  40. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/debug.py +60 -0
  41. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/file_util.py +181 -0
  42. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/resources.py +67 -0
  43. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/run_script_module.py +217 -0
  44. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/setup_command.py +159 -0
  45. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/shellutil.py +49 -0
  46. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/stopwatch.py +204 -0
  47. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/__init__.py +0 -0
  48. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper.py +140 -0
  49. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper_test.py +149 -0
  50. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth.py +130 -0
  51. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth_test.py +75 -0
  52. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds.py +128 -0
  53. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds_test.py +111 -0
  54. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base.py +1808 -0
  55. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base_test.py +1651 -0
  56. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta13.json +2851 -0
  57. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta14.json +3361 -0
  58. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds.py +342 -0
  59. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds_test.py +474 -0
  60. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds.py +344 -0
  61. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds_test.py +231 -0
  62. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/flags_cache.py +274 -0
  63. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil +89 -0
  64. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil_logging.py +69 -0
  65. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds.py +262 -0
  66. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds_test.py +172 -0
  67. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds.py +1506 -0
  68. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds_test.py +1904 -0
  69. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds.py +91 -0
  70. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds_test.py +56 -0
  71. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds.py +106 -0
  72. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds_test.py +59 -0
  73. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata.py +96 -0
  74. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_lib.py +357 -0
  75. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_test.py +84 -0
  76. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_api.py +420 -0
  77. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_metadata.py +58 -0
  78. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds.py +824 -0
  79. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds_test.py +307 -0
  80. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds.py +178 -0
  81. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds_test.py +133 -0
  82. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds.py +181 -0
  83. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds_test.py +196 -0
  84. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/path_initializer.py +38 -0
  85. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds.py +173 -0
  86. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds_test.py +111 -0
  87. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes.py +61 -0
  88. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes_test.py +50 -0
  89. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds.py +276 -0
  90. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds_test.py +260 -0
  91. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys.py +266 -0
  92. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys_test.py +128 -0
  93. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/table_formatter.py +563 -0
  94. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool.py +188 -0
  95. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool_test.py +88 -0
  96. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils.py +208 -0
  97. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils_test.py +193 -0
  98. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version.py +17 -0
  99. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker.py +246 -0
  100. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker_test.py +271 -0
  101. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds.py +151 -0
  102. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds_test.py +60 -0
  103. data/packages/gcutil-1.7.1/lib/httplib2/LICENSE +21 -0
  104. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/__init__.py +1630 -0
  105. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/cacerts.txt +714 -0
  106. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/iri2uri.py +110 -0
  107. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/socks.py +438 -0
  108. data/packages/gcutil-1.7.1/lib/iso8601/LICENSE +20 -0
  109. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/__init__.py +1 -0
  110. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/iso8601.py +102 -0
  111. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/test_iso8601.py +111 -0
  112. data/packages/gcutil-1.7.1/lib/python_gflags/AUTHORS +2 -0
  113. data/packages/gcutil-1.7.1/lib/python_gflags/LICENSE +28 -0
  114. data/packages/gcutil-1.7.1/lib/python_gflags/gflags.py +2862 -0
  115. data/packages/gcutil-1.7.1/lib/python_gflags/gflags2man.py +544 -0
  116. data/packages/gcutil-1.7.1/lib/python_gflags/gflags_validators.py +187 -0
  117. metadata +118 -5
  118. metadata.gz.sig +0 -0
@@ -0,0 +1,1139 @@
1
+ # Copyright (C) 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """An OAuth 2.0 client.
16
+
17
+ Tools for interacting with OAuth 2.0 protected resources.
18
+ """
19
+
20
+
21
+
22
+ import base64
23
+ import clientsecrets
24
+ import copy
25
+ import datetime
26
+ import httplib2
27
+ import logging
28
+ import os
29
+ import sys
30
+ import time
31
+ import urllib
32
+ import urlparse
33
+
34
+ from anyjson import simplejson
35
+
36
+ HAS_OPENSSL = False
37
+ try:
38
+ from oauth2client.crypt import Signer
39
+ from oauth2client.crypt import make_signed_jwt
40
+ from oauth2client.crypt import verify_signed_jwt_with_certs
41
+ HAS_OPENSSL = True
42
+ except ImportError:
43
+ pass
44
+
45
+ try:
46
+ from urlparse import parse_qsl
47
+ except ImportError:
48
+ from cgi import parse_qsl
49
+
50
+ logger = logging.getLogger(__name__)
51
+
52
+ # Expiry is stored in RFC3339 UTC format
53
+ EXPIRY_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
54
+
55
+ # Which certs to use to validate id_tokens received.
56
+ ID_TOKEN_VERIFICATON_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
57
+
58
+ # Constant to use for the out of band OAuth 2.0 flow.
59
+ OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
60
+
61
+
62
+ class Error(Exception):
63
+ """Base error for this module."""
64
+ pass
65
+
66
+
67
+ class FlowExchangeError(Error):
68
+ """Error trying to exchange an authorization grant for an access token."""
69
+ pass
70
+
71
+
72
+ class AccessTokenRefreshError(Error):
73
+ """Error trying to refresh an expired access token."""
74
+ pass
75
+
76
+ class UnknownClientSecretsFlowError(Error):
77
+ """The client secrets file called for an unknown type of OAuth 2.0 flow. """
78
+ pass
79
+
80
+
81
+ class AccessTokenCredentialsError(Error):
82
+ """Having only the access_token means no refresh is possible."""
83
+ pass
84
+
85
+
86
+ class VerifyJwtTokenError(Error):
87
+ """Could on retrieve certificates for validation."""
88
+ pass
89
+
90
+
91
+ def _abstract():
92
+ raise NotImplementedError('You need to override this function')
93
+
94
+
95
+ class MemoryCache(object):
96
+ """httplib2 Cache implementation which only caches locally."""
97
+
98
+ def __init__(self):
99
+ self.cache = {}
100
+
101
+ def get(self, key):
102
+ return self.cache.get(key)
103
+
104
+ def set(self, key, value):
105
+ self.cache[key] = value
106
+
107
+ def delete(self, key):
108
+ self.cache.pop(key, None)
109
+
110
+
111
+ class Credentials(object):
112
+ """Base class for all Credentials objects.
113
+
114
+ Subclasses must define an authorize() method that applies the credentials to
115
+ an HTTP transport.
116
+
117
+ Subclasses must also specify a classmethod named 'from_json' that takes a JSON
118
+ string as input and returns an instaniated Credentials object.
119
+ """
120
+
121
+ NON_SERIALIZED_MEMBERS = ['store']
122
+
123
+ def authorize(self, http):
124
+ """Take an httplib2.Http instance (or equivalent) and
125
+ authorizes it for the set of credentials, usually by
126
+ replacing http.request() with a method that adds in
127
+ the appropriate headers and then delegates to the original
128
+ Http.request() method.
129
+ """
130
+ _abstract()
131
+
132
+ def refresh(self, http):
133
+ """Forces a refresh of the access_token.
134
+
135
+ Args:
136
+ http: httplib2.Http, an http object to be used to make the refresh
137
+ request.
138
+ """
139
+ _abstract()
140
+
141
+ def apply(self, headers):
142
+ """Add the authorization to the headers.
143
+
144
+ Args:
145
+ headers: dict, the headers to add the Authorization header to.
146
+ """
147
+ _abstract()
148
+
149
+ def _to_json(self, strip):
150
+ """Utility function for creating a JSON representation of an instance of Credentials.
151
+
152
+ Args:
153
+ strip: array, An array of names of members to not include in the JSON.
154
+
155
+ Returns:
156
+ string, a JSON representation of this instance, suitable to pass to
157
+ from_json().
158
+ """
159
+ t = type(self)
160
+ d = copy.copy(self.__dict__)
161
+ for member in strip:
162
+ if member in d:
163
+ del d[member]
164
+ if 'token_expiry' in d and isinstance(d['token_expiry'], datetime.datetime):
165
+ d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
166
+ # Add in information we will need later to reconsistitue this instance.
167
+ d['_class'] = t.__name__
168
+ d['_module'] = t.__module__
169
+ return simplejson.dumps(d)
170
+
171
+ def to_json(self):
172
+ """Creating a JSON representation of an instance of Credentials.
173
+
174
+ Returns:
175
+ string, a JSON representation of this instance, suitable to pass to
176
+ from_json().
177
+ """
178
+ return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
179
+
180
+ @classmethod
181
+ def new_from_json(cls, s):
182
+ """Utility class method to instantiate a Credentials subclass from a JSON
183
+ representation produced by to_json().
184
+
185
+ Args:
186
+ s: string, JSON from to_json().
187
+
188
+ Returns:
189
+ An instance of the subclass of Credentials that was serialized with
190
+ to_json().
191
+ """
192
+ data = simplejson.loads(s)
193
+ # Find and call the right classmethod from_json() to restore the object.
194
+ module = data['_module']
195
+ try:
196
+ m = __import__(module)
197
+ except ImportError:
198
+ # In case there's an object from the old package structure, update it
199
+ module = module.replace('.apiclient', '')
200
+ m = __import__(module)
201
+
202
+ m = __import__(module, fromlist=module.split('.')[:-1])
203
+ kls = getattr(m, data['_class'])
204
+ from_json = getattr(kls, 'from_json')
205
+ return from_json(s)
206
+
207
+ @classmethod
208
+ def from_json(cls, s):
209
+ """Instantiate a Credentials object from a JSON description of it.
210
+
211
+ The JSON should have been produced by calling .to_json() on the object.
212
+
213
+ Args:
214
+ data: dict, A deserialized JSON object.
215
+
216
+ Returns:
217
+ An instance of a Credentials subclass.
218
+ """
219
+ return Credentials()
220
+
221
+
222
+ class Flow(object):
223
+ """Base class for all Flow objects."""
224
+ pass
225
+
226
+
227
+ class Storage(object):
228
+ """Base class for all Storage objects.
229
+
230
+ Store and retrieve a single credential. This class supports locking
231
+ such that multiple processes and threads can operate on a single
232
+ store.
233
+ """
234
+
235
+ def acquire_lock(self):
236
+ """Acquires any lock necessary to access this Storage.
237
+
238
+ This lock is not reentrant.
239
+ """
240
+ pass
241
+
242
+ def release_lock(self):
243
+ """Release the Storage lock.
244
+
245
+ Trying to release a lock that isn't held will result in a
246
+ RuntimeError.
247
+ """
248
+ pass
249
+
250
+ def locked_get(self):
251
+ """Retrieve credential.
252
+
253
+ The Storage lock must be held when this is called.
254
+
255
+ Returns:
256
+ oauth2client.client.Credentials
257
+ """
258
+ _abstract()
259
+
260
+ def locked_put(self, credentials):
261
+ """Write a credential.
262
+
263
+ The Storage lock must be held when this is called.
264
+
265
+ Args:
266
+ credentials: Credentials, the credentials to store.
267
+ """
268
+ _abstract()
269
+
270
+ def locked_delete(self):
271
+ """Delete a credential.
272
+
273
+ The Storage lock must be held when this is called.
274
+ """
275
+ _abstract()
276
+
277
+ def get(self):
278
+ """Retrieve credential.
279
+
280
+ The Storage lock must *not* be held when this is called.
281
+
282
+ Returns:
283
+ oauth2client.client.Credentials
284
+ """
285
+ self.acquire_lock()
286
+ try:
287
+ return self.locked_get()
288
+ finally:
289
+ self.release_lock()
290
+
291
+ def put(self, credentials):
292
+ """Write a credential.
293
+
294
+ The Storage lock must be held when this is called.
295
+
296
+ Args:
297
+ credentials: Credentials, the credentials to store.
298
+ """
299
+ self.acquire_lock()
300
+ try:
301
+ self.locked_put(credentials)
302
+ finally:
303
+ self.release_lock()
304
+
305
+ def delete(self):
306
+ """Delete credential.
307
+
308
+ Frees any resources associated with storing the credential.
309
+ The Storage lock must *not* be held when this is called.
310
+
311
+ Returns:
312
+ None
313
+ """
314
+ self.acquire_lock()
315
+ try:
316
+ return self.locked_delete()
317
+ finally:
318
+ self.release_lock()
319
+
320
+
321
+ class OAuth2Credentials(Credentials):
322
+ """Credentials object for OAuth 2.0.
323
+
324
+ Credentials can be applied to an httplib2.Http object using the authorize()
325
+ method, which then adds the OAuth 2.0 access token to each request.
326
+
327
+ OAuth2Credentials objects may be safely pickled and unpickled.
328
+ """
329
+
330
+ def __init__(self, access_token, client_id, client_secret, refresh_token,
331
+ token_expiry, token_uri, user_agent, id_token=None):
332
+ """Create an instance of OAuth2Credentials.
333
+
334
+ This constructor is not usually called by the user, instead
335
+ OAuth2Credentials objects are instantiated by the OAuth2WebServerFlow.
336
+
337
+ Args:
338
+ access_token: string, access token.
339
+ client_id: string, client identifier.
340
+ client_secret: string, client secret.
341
+ refresh_token: string, refresh token.
342
+ token_expiry: datetime, when the access_token expires.
343
+ token_uri: string, URI of token endpoint.
344
+ user_agent: string, The HTTP User-Agent to provide for this application.
345
+ id_token: object, The identity of the resource owner.
346
+
347
+ Notes:
348
+ store: callable, A callable that when passed a Credential
349
+ will store the credential back to where it came from.
350
+ This is needed to store the latest access_token if it
351
+ has expired and been refreshed.
352
+ """
353
+ self.access_token = access_token
354
+ self.client_id = client_id
355
+ self.client_secret = client_secret
356
+ self.refresh_token = refresh_token
357
+ self.store = None
358
+ self.token_expiry = token_expiry
359
+ self.token_uri = token_uri
360
+ self.user_agent = user_agent
361
+ self.id_token = id_token
362
+
363
+ # True if the credentials have been revoked or expired and can't be
364
+ # refreshed.
365
+ self.invalid = False
366
+
367
+ def authorize(self, http):
368
+ """Authorize an httplib2.Http instance with these credentials.
369
+
370
+ The modified http.request method will add authentication headers to each
371
+ request and will refresh access_tokens when a 401 is received on a
372
+ request. In addition the http.request method has a credentials property,
373
+ http.request.credentials, which is the Credentials object that authorized
374
+ it.
375
+
376
+ Args:
377
+ http: An instance of httplib2.Http
378
+ or something that acts like it.
379
+
380
+ Returns:
381
+ A modified instance of http that was passed in.
382
+
383
+ Example:
384
+
385
+ h = httplib2.Http()
386
+ h = credentials.authorize(h)
387
+
388
+ You can't create a new OAuth subclass of httplib2.Authenication
389
+ because it never gets passed the absolute URI, which is needed for
390
+ signing. So instead we have to overload 'request' with a closure
391
+ that adds in the Authorization header and then calls the original
392
+ version of 'request()'.
393
+ """
394
+ request_orig = http.request
395
+
396
+ # The closure that will replace 'httplib2.Http.request'.
397
+ def new_request(uri, method='GET', body=None, headers=None,
398
+ redirections=httplib2.DEFAULT_MAX_REDIRECTS,
399
+ connection_type=None):
400
+ if not self.access_token:
401
+ logger.info('Attempting refresh to obtain initial access_token')
402
+ self._refresh(request_orig)
403
+
404
+ # Modify the request headers to add the appropriate
405
+ # Authorization header.
406
+ if headers is None:
407
+ headers = {}
408
+ self.apply(headers)
409
+
410
+ if self.user_agent is not None:
411
+ if 'user-agent' in headers:
412
+ headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
413
+ else:
414
+ headers['user-agent'] = self.user_agent
415
+
416
+ resp, content = request_orig(uri, method, body, headers,
417
+ redirections, connection_type)
418
+
419
+ if resp.status == 401:
420
+ logger.info('Refreshing due to a 401')
421
+ self._refresh(request_orig)
422
+ self.apply(headers)
423
+ return request_orig(uri, method, body, headers,
424
+ redirections, connection_type)
425
+ else:
426
+ return (resp, content)
427
+
428
+ # Replace the request method with our own closure.
429
+ http.request = new_request
430
+
431
+ # Set credentials as a property of the request method.
432
+ setattr(http.request, 'credentials', self)
433
+
434
+ return http
435
+
436
+ def refresh(self, http):
437
+ """Forces a refresh of the access_token.
438
+
439
+ Args:
440
+ http: httplib2.Http, an http object to be used to make the refresh
441
+ request.
442
+ """
443
+ self._refresh(http.request)
444
+
445
+ def apply(self, headers):
446
+ """Add the authorization to the headers.
447
+
448
+ Args:
449
+ headers: dict, the headers to add the Authorization header to.
450
+ """
451
+ headers['Authorization'] = 'Bearer ' + self.access_token
452
+
453
+ def to_json(self):
454
+ return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
455
+
456
+ @classmethod
457
+ def from_json(cls, s):
458
+ """Instantiate a Credentials object from a JSON description of it. The JSON
459
+ should have been produced by calling .to_json() on the object.
460
+
461
+ Args:
462
+ data: dict, A deserialized JSON object.
463
+
464
+ Returns:
465
+ An instance of a Credentials subclass.
466
+ """
467
+ data = simplejson.loads(s)
468
+ if 'token_expiry' in data and not isinstance(data['token_expiry'],
469
+ datetime.datetime):
470
+ try:
471
+ data['token_expiry'] = datetime.datetime.strptime(
472
+ data['token_expiry'], EXPIRY_FORMAT)
473
+ except:
474
+ data['token_expiry'] = None
475
+ retval = OAuth2Credentials(
476
+ data['access_token'],
477
+ data['client_id'],
478
+ data['client_secret'],
479
+ data['refresh_token'],
480
+ data['token_expiry'],
481
+ data['token_uri'],
482
+ data['user_agent'],
483
+ data.get('id_token', None))
484
+ retval.invalid = data['invalid']
485
+ return retval
486
+
487
+ @property
488
+ def access_token_expired(self):
489
+ """True if the credential is expired or invalid.
490
+
491
+ If the token_expiry isn't set, we assume the token doesn't expire.
492
+ """
493
+ if self.invalid:
494
+ return True
495
+
496
+ if not self.token_expiry:
497
+ return False
498
+
499
+ now = datetime.datetime.utcnow()
500
+ if now >= self.token_expiry:
501
+ logger.info('access_token is expired. Now: %s, token_expiry: %s',
502
+ now, self.token_expiry)
503
+ return True
504
+ return False
505
+
506
+ def set_store(self, store):
507
+ """Set the Storage for the credential.
508
+
509
+ Args:
510
+ store: Storage, an implementation of Stroage object.
511
+ This is needed to store the latest access_token if it
512
+ has expired and been refreshed. This implementation uses
513
+ locking to check for updates before updating the
514
+ access_token.
515
+ """
516
+ self.store = store
517
+
518
+ def _updateFromCredential(self, other):
519
+ """Update this Credential from another instance."""
520
+ self.__dict__.update(other.__getstate__())
521
+
522
+ def __getstate__(self):
523
+ """Trim the state down to something that can be pickled."""
524
+ d = copy.copy(self.__dict__)
525
+ del d['store']
526
+ return d
527
+
528
+ def __setstate__(self, state):
529
+ """Reconstitute the state of the object from being pickled."""
530
+ self.__dict__.update(state)
531
+ self.store = None
532
+
533
+ def _generate_refresh_request_body(self):
534
+ """Generate the body that will be used in the refresh request."""
535
+ body = urllib.urlencode({
536
+ 'grant_type': 'refresh_token',
537
+ 'client_id': self.client_id,
538
+ 'client_secret': self.client_secret,
539
+ 'refresh_token': self.refresh_token,
540
+ })
541
+ return body
542
+
543
+ def _generate_refresh_request_headers(self):
544
+ """Generate the headers that will be used in the refresh request."""
545
+ headers = {
546
+ 'content-type': 'application/x-www-form-urlencoded',
547
+ }
548
+
549
+ if self.user_agent is not None:
550
+ headers['user-agent'] = self.user_agent
551
+
552
+ return headers
553
+
554
+ def _refresh(self, http_request):
555
+ """Refreshes the access_token.
556
+
557
+ This method first checks by reading the Storage object if available.
558
+ If a refresh is still needed, it holds the Storage lock until the
559
+ refresh is completed.
560
+
561
+ Args:
562
+ http_request: callable, a callable that matches the method signature of
563
+ httplib2.Http.request, used to make the refresh request.
564
+
565
+ Raises:
566
+ AccessTokenRefreshError: When the refresh fails.
567
+ """
568
+ if not self.store:
569
+ self._do_refresh_request(http_request)
570
+ else:
571
+ self.store.acquire_lock()
572
+ try:
573
+ new_cred = self.store.locked_get()
574
+ if (new_cred and not new_cred.invalid and
575
+ new_cred.access_token != self.access_token):
576
+ logger.info('Updated access_token read from Storage')
577
+ self._updateFromCredential(new_cred)
578
+ else:
579
+ self._do_refresh_request(http_request)
580
+ finally:
581
+ self.store.release_lock()
582
+
583
+ def _do_refresh_request(self, http_request):
584
+ """Refresh the access_token using the refresh_token.
585
+
586
+ Args:
587
+ http_request: callable, a callable that matches the method signature of
588
+ httplib2.Http.request, used to make the refresh request.
589
+
590
+ Raises:
591
+ AccessTokenRefreshError: When the refresh fails.
592
+ """
593
+ body = self._generate_refresh_request_body()
594
+ headers = self._generate_refresh_request_headers()
595
+
596
+ logger.info('Refreshing access_token')
597
+ resp, content = http_request(
598
+ self.token_uri, method='POST', body=body, headers=headers)
599
+ if resp.status == 200:
600
+ # TODO(user) Raise an error if loads fails?
601
+ d = simplejson.loads(content)
602
+ self.access_token = d['access_token']
603
+ self.refresh_token = d.get('refresh_token', self.refresh_token)
604
+ if 'expires_in' in d:
605
+ self.token_expiry = datetime.timedelta(
606
+ seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
607
+ else:
608
+ self.token_expiry = None
609
+ if self.store:
610
+ self.store.locked_put(self)
611
+ else:
612
+ # An {'error':...} response body means the token is expired or revoked,
613
+ # so we flag the credentials as such.
614
+ logger.info('Failed to retrieve access token: %s' % content)
615
+ error_msg = 'Invalid response %s.' % resp['status']
616
+ try:
617
+ d = simplejson.loads(content)
618
+ if 'error' in d:
619
+ error_msg = d['error']
620
+ self.invalid = True
621
+ if self.store:
622
+ self.store.locked_put(self)
623
+ except:
624
+ pass
625
+ raise AccessTokenRefreshError(error_msg)
626
+
627
+
628
+ class AccessTokenCredentials(OAuth2Credentials):
629
+ """Credentials object for OAuth 2.0.
630
+
631
+ Credentials can be applied to an httplib2.Http object using the
632
+ authorize() method, which then signs each request from that object
633
+ with the OAuth 2.0 access token. This set of credentials is for the
634
+ use case where you have acquired an OAuth 2.0 access_token from
635
+ another place such as a JavaScript client or another web
636
+ application, and wish to use it from Python. Because only the
637
+ access_token is present it can not be refreshed and will in time
638
+ expire.
639
+
640
+ AccessTokenCredentials objects may be safely pickled and unpickled.
641
+
642
+ Usage:
643
+ credentials = AccessTokenCredentials('<an access token>',
644
+ 'my-user-agent/1.0')
645
+ http = httplib2.Http()
646
+ http = credentials.authorize(http)
647
+
648
+ Exceptions:
649
+ AccessTokenCredentialsExpired: raised when the access_token expires or is
650
+ revoked.
651
+ """
652
+
653
+ def __init__(self, access_token, user_agent):
654
+ """Create an instance of OAuth2Credentials
655
+
656
+ This is one of the few types if Credentials that you should contrust,
657
+ Credentials objects are usually instantiated by a Flow.
658
+
659
+ Args:
660
+ access_token: string, access token.
661
+ user_agent: string, The HTTP User-Agent to provide for this application.
662
+
663
+ Notes:
664
+ store: callable, a callable that when passed a Credential
665
+ will store the credential back to where it came from.
666
+ """
667
+ super(AccessTokenCredentials, self).__init__(
668
+ access_token,
669
+ None,
670
+ None,
671
+ None,
672
+ None,
673
+ None,
674
+ user_agent)
675
+
676
+
677
+ @classmethod
678
+ def from_json(cls, s):
679
+ data = simplejson.loads(s)
680
+ retval = AccessTokenCredentials(
681
+ data['access_token'],
682
+ data['user_agent'])
683
+ return retval
684
+
685
+ def _refresh(self, http_request):
686
+ raise AccessTokenCredentialsError(
687
+ "The access_token is expired or invalid and can't be refreshed.")
688
+
689
+
690
+ class AssertionCredentials(OAuth2Credentials):
691
+ """Abstract Credentials object used for OAuth 2.0 assertion grants.
692
+
693
+ This credential does not require a flow to instantiate because it
694
+ represents a two legged flow, and therefore has all of the required
695
+ information to generate and refresh its own access tokens. It must
696
+ be subclassed to generate the appropriate assertion string.
697
+
698
+ AssertionCredentials objects may be safely pickled and unpickled.
699
+ """
700
+
701
+ def __init__(self, assertion_type, user_agent,
702
+ token_uri='https://accounts.google.com/o/oauth2/token',
703
+ **unused_kwargs):
704
+ """Constructor for AssertionFlowCredentials.
705
+
706
+ Args:
707
+ assertion_type: string, assertion type that will be declared to the auth
708
+ server
709
+ user_agent: string, The HTTP User-Agent to provide for this application.
710
+ token_uri: string, URI for token endpoint. For convenience
711
+ defaults to Google's endpoints but any OAuth 2.0 provider can be used.
712
+ """
713
+ super(AssertionCredentials, self).__init__(
714
+ None,
715
+ None,
716
+ None,
717
+ None,
718
+ None,
719
+ token_uri,
720
+ user_agent)
721
+ self.assertion_type = assertion_type
722
+
723
+ def _generate_refresh_request_body(self):
724
+ assertion = self._generate_assertion()
725
+
726
+ body = urllib.urlencode({
727
+ 'assertion_type': self.assertion_type,
728
+ 'assertion': assertion,
729
+ 'grant_type': 'assertion',
730
+ })
731
+
732
+ return body
733
+
734
+ def _generate_assertion(self):
735
+ """Generate the assertion string that will be used in the access token
736
+ request.
737
+ """
738
+ _abstract()
739
+
740
+ if HAS_OPENSSL:
741
+ # PyOpenSSL is not a prerequisite for oauth2client, so if it is missing then
742
+ # don't create the SignedJwtAssertionCredentials or the verify_id_token()
743
+ # method.
744
+
745
+ class SignedJwtAssertionCredentials(AssertionCredentials):
746
+ """Credentials object used for OAuth 2.0 Signed JWT assertion grants.
747
+
748
+ This credential does not require a flow to instantiate because it
749
+ represents a two legged flow, and therefore has all of the required
750
+ information to generate and refresh its own access tokens.
751
+ """
752
+
753
+ MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
754
+
755
+ def __init__(self,
756
+ service_account_name,
757
+ private_key,
758
+ scope,
759
+ private_key_password='notasecret',
760
+ user_agent=None,
761
+ token_uri='https://accounts.google.com/o/oauth2/token',
762
+ **kwargs):
763
+ """Constructor for SignedJwtAssertionCredentials.
764
+
765
+ Args:
766
+ service_account_name: string, id for account, usually an email address.
767
+ private_key: string, private key in P12 format.
768
+ scope: string or list of strings, scope(s) of the credentials being
769
+ requested.
770
+ private_key_password: string, password for private_key.
771
+ user_agent: string, HTTP User-Agent to provide for this application.
772
+ token_uri: string, URI for token endpoint. For convenience
773
+ defaults to Google's endpoints but any OAuth 2.0 provider can be used.
774
+ kwargs: kwargs, Additional parameters to add to the JWT token, for
775
+ example prn=joe@xample.org."""
776
+
777
+ super(SignedJwtAssertionCredentials, self).__init__(
778
+ 'http://oauth.net/grant_type/jwt/1.0/bearer',
779
+ user_agent,
780
+ token_uri=token_uri,
781
+ )
782
+
783
+ if type(scope) is list:
784
+ scope = ' '.join(scope)
785
+ self.scope = scope
786
+
787
+ self.private_key = private_key
788
+ self.private_key_password = private_key_password
789
+ self.service_account_name = service_account_name
790
+ self.kwargs = kwargs
791
+
792
+ @classmethod
793
+ def from_json(cls, s):
794
+ data = simplejson.loads(s)
795
+ retval = SignedJwtAssertionCredentials(
796
+ data['service_account_name'],
797
+ data['private_key'],
798
+ data['private_key_password'],
799
+ data['scope'],
800
+ data['user_agent'],
801
+ data['token_uri'],
802
+ data['kwargs']
803
+ )
804
+ retval.invalid = data['invalid']
805
+ return retval
806
+
807
+ def _generate_assertion(self):
808
+ """Generate the assertion that will be used in the request."""
809
+ now = long(time.time())
810
+ payload = {
811
+ 'aud': self.token_uri,
812
+ 'scope': self.scope,
813
+ 'iat': now,
814
+ 'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
815
+ 'iss': self.service_account_name
816
+ }
817
+ payload.update(self.kwargs)
818
+ logger.debug(str(payload))
819
+
820
+ return make_signed_jwt(
821
+ Signer.from_string(self.private_key, self.private_key_password),
822
+ payload)
823
+
824
+ # Only used in verify_id_token(), which is always calling to the same URI
825
+ # for the certs.
826
+ _cached_http = httplib2.Http(MemoryCache())
827
+
828
+ def verify_id_token(id_token, audience, http=None,
829
+ cert_uri=ID_TOKEN_VERIFICATON_CERTS):
830
+ """Verifies a signed JWT id_token.
831
+
832
+ Args:
833
+ id_token: string, A Signed JWT.
834
+ audience: string, The audience 'aud' that the token should be for.
835
+ http: httplib2.Http, instance to use to make the HTTP request. Callers
836
+ should supply an instance that has caching enabled.
837
+ cert_uri: string, URI of the certificates in JSON format to
838
+ verify the JWT against.
839
+
840
+ Returns:
841
+ The deserialized JSON in the JWT.
842
+
843
+ Raises:
844
+ oauth2client.crypt.AppIdentityError if the JWT fails to verify.
845
+ """
846
+ if http is None:
847
+ http = _cached_http
848
+
849
+ resp, content = http.request(cert_uri)
850
+
851
+ if resp.status == 200:
852
+ certs = simplejson.loads(content)
853
+ return verify_signed_jwt_with_certs(id_token, certs, audience)
854
+ else:
855
+ raise VerifyJwtTokenError('Status code: %d' % resp.status)
856
+
857
+
858
+ def _urlsafe_b64decode(b64string):
859
+ # Guard against unicode strings, which base64 can't handle.
860
+ b64string = b64string.encode('ascii')
861
+ padded = b64string + '=' * (4 - len(b64string) % 4)
862
+ return base64.urlsafe_b64decode(padded)
863
+
864
+
865
+ def _extract_id_token(id_token):
866
+ """Extract the JSON payload from a JWT.
867
+
868
+ Does the extraction w/o checking the signature.
869
+
870
+ Args:
871
+ id_token: string, OAuth 2.0 id_token.
872
+
873
+ Returns:
874
+ object, The deserialized JSON payload.
875
+ """
876
+ segments = id_token.split('.')
877
+
878
+ if (len(segments) != 3):
879
+ raise VerifyJwtTokenError(
880
+ 'Wrong number of segments in token: %s' % id_token)
881
+
882
+ return simplejson.loads(_urlsafe_b64decode(segments[1]))
883
+
884
+ def credentials_from_code(client_id, client_secret, scope, code,
885
+ redirect_uri = 'postmessage',
886
+ http=None, user_agent=None,
887
+ token_uri='https://accounts.google.com/o/oauth2/token'):
888
+ """Exchanges an authorization code for an OAuth2Credentials object.
889
+
890
+ Args:
891
+ client_id: string, client identifier.
892
+ client_secret: string, client secret.
893
+ scope: string or list of strings, scope(s) to request.
894
+ code: string, An authroization code, most likely passed down from
895
+ the client
896
+ redirect_uri: string, this is generally set to 'postmessage' to match the
897
+ redirect_uri that the client specified
898
+ http: httplib2.Http, optional http instance to use to do the fetch
899
+ token_uri: string, URI for token endpoint. For convenience
900
+ defaults to Google's endpoints but any OAuth 2.0 provider can be used.
901
+ Returns:
902
+ An OAuth2Credentials object.
903
+
904
+ Raises:
905
+ FlowExchangeError if the authorization code cannot be exchanged for an
906
+ access token
907
+ """
908
+ flow = OAuth2WebServerFlow(client_id, client_secret, scope, user_agent,
909
+ 'https://accounts.google.com/o/oauth2/auth',
910
+ token_uri)
911
+
912
+ # We primarily make this call to set up the redirect_uri in the flow object
913
+ uriThatWeDontReallyUse = flow.step1_get_authorize_url(redirect_uri)
914
+ credentials = flow.step2_exchange(code, http)
915
+ return credentials
916
+
917
+
918
+ def credentials_from_clientsecrets_and_code(filename, scope, code,
919
+ message = None,
920
+ redirect_uri = 'postmessage',
921
+ http=None):
922
+ """Returns OAuth2Credentials from a clientsecrets file and an auth code.
923
+
924
+ Will create the right kind of Flow based on the contents of the clientsecrets
925
+ file or will raise InvalidClientSecretsError for unknown types of Flows.
926
+
927
+ Args:
928
+ filename: string, File name of clientsecrets.
929
+ scope: string or list of strings, scope(s) to request.
930
+ code: string, An authroization code, most likely passed down from
931
+ the client
932
+ message: string, A friendly string to display to the user if the
933
+ clientsecrets file is missing or invalid. If message is provided then
934
+ sys.exit will be called in the case of an error. If message in not
935
+ provided then clientsecrets.InvalidClientSecretsError will be raised.
936
+ redirect_uri: string, this is generally set to 'postmessage' to match the
937
+ redirect_uri that the client specified
938
+ http: httplib2.Http, optional http instance to use to do the fetch
939
+
940
+ Returns:
941
+ An OAuth2Credentials object.
942
+
943
+ Raises:
944
+ FlowExchangeError if the authorization code cannot be exchanged for an
945
+ access token
946
+ UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
947
+ clientsecrets.InvalidClientSecretsError if the clientsecrets file is
948
+ invalid.
949
+ """
950
+ flow = flow_from_clientsecrets(filename, scope, message)
951
+ # We primarily make this call to set up the redirect_uri in the flow object
952
+ uriThatWeDontReallyUse = flow.step1_get_authorize_url(redirect_uri)
953
+ credentials = flow.step2_exchange(code, http)
954
+ return credentials
955
+
956
+
957
+ class OAuth2WebServerFlow(Flow):
958
+ """Does the Web Server Flow for OAuth 2.0.
959
+
960
+ OAuth2Credentials objects may be safely pickled and unpickled.
961
+ """
962
+
963
+ def __init__(self, client_id, client_secret, scope, user_agent=None,
964
+ auth_uri='https://accounts.google.com/o/oauth2/auth',
965
+ token_uri='https://accounts.google.com/o/oauth2/token',
966
+ **kwargs):
967
+ """Constructor for OAuth2WebServerFlow.
968
+
969
+ Args:
970
+ client_id: string, client identifier.
971
+ client_secret: string client secret.
972
+ scope: string or list of strings, scope(s) of the credentials being
973
+ requested.
974
+ user_agent: string, HTTP User-Agent to provide for this application.
975
+ auth_uri: string, URI for authorization endpoint. For convenience
976
+ defaults to Google's endpoints but any OAuth 2.0 provider can be used.
977
+ token_uri: string, URI for token endpoint. For convenience
978
+ defaults to Google's endpoints but any OAuth 2.0 provider can be used.
979
+ **kwargs: dict, The keyword arguments are all optional and required
980
+ parameters for the OAuth calls.
981
+ """
982
+ self.client_id = client_id
983
+ self.client_secret = client_secret
984
+ if type(scope) is list:
985
+ scope = ' '.join(scope)
986
+ self.scope = scope
987
+ self.user_agent = user_agent
988
+ self.auth_uri = auth_uri
989
+ self.token_uri = token_uri
990
+ self.params = {
991
+ 'access_type': 'offline',
992
+ }
993
+ self.params.update(kwargs)
994
+ self.redirect_uri = None
995
+
996
+ def step1_get_authorize_url(self, redirect_uri=OOB_CALLBACK_URN):
997
+ """Returns a URI to redirect to the provider.
998
+
999
+ Args:
1000
+ redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
1001
+ a non-web-based application, or a URI that handles the callback from
1002
+ the authorization server.
1003
+
1004
+ If redirect_uri is 'urn:ietf:wg:oauth:2.0:oob' then pass in the
1005
+ generated verification code to step2_exchange,
1006
+ otherwise pass in the query parameters received
1007
+ at the callback uri to step2_exchange.
1008
+ """
1009
+
1010
+ self.redirect_uri = redirect_uri
1011
+ query = {
1012
+ 'response_type': 'code',
1013
+ 'client_id': self.client_id,
1014
+ 'redirect_uri': redirect_uri,
1015
+ 'scope': self.scope,
1016
+ }
1017
+ query.update(self.params)
1018
+ parts = list(urlparse.urlparse(self.auth_uri))
1019
+ query.update(dict(parse_qsl(parts[4]))) # 4 is the index of the query part
1020
+ parts[4] = urllib.urlencode(query)
1021
+ return urlparse.urlunparse(parts)
1022
+
1023
+ def step2_exchange(self, code, http=None):
1024
+ """Exhanges a code for OAuth2Credentials.
1025
+
1026
+ Args:
1027
+ code: string or dict, either the code as a string, or a dictionary
1028
+ of the query parameters to the redirect_uri, which contains
1029
+ the code.
1030
+ http: httplib2.Http, optional http instance to use to do the fetch
1031
+
1032
+ Returns:
1033
+ An OAuth2Credentials object that can be used to authorize requests.
1034
+
1035
+ Raises:
1036
+ FlowExchangeError if a problem occured exchanging the code for a
1037
+ refresh_token.
1038
+ """
1039
+
1040
+ if not (isinstance(code, str) or isinstance(code, unicode)):
1041
+ if 'code' not in code:
1042
+ if 'error' in code:
1043
+ error_msg = code['error']
1044
+ else:
1045
+ error_msg = 'No code was supplied in the query parameters.'
1046
+ raise FlowExchangeError(error_msg)
1047
+ else:
1048
+ code = code['code']
1049
+
1050
+ body = urllib.urlencode({
1051
+ 'grant_type': 'authorization_code',
1052
+ 'client_id': self.client_id,
1053
+ 'client_secret': self.client_secret,
1054
+ 'code': code,
1055
+ 'redirect_uri': self.redirect_uri,
1056
+ 'scope': self.scope,
1057
+ })
1058
+ headers = {
1059
+ 'content-type': 'application/x-www-form-urlencoded',
1060
+ }
1061
+
1062
+ if self.user_agent is not None:
1063
+ headers['user-agent'] = self.user_agent
1064
+
1065
+ if http is None:
1066
+ http = httplib2.Http()
1067
+
1068
+ resp, content = http.request(self.token_uri, method='POST', body=body,
1069
+ headers=headers)
1070
+ if resp.status == 200:
1071
+ # TODO(user) Raise an error if simplejson.loads fails?
1072
+ d = simplejson.loads(content)
1073
+ access_token = d['access_token']
1074
+ refresh_token = d.get('refresh_token', None)
1075
+ token_expiry = None
1076
+ if 'expires_in' in d:
1077
+ token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
1078
+ seconds=int(d['expires_in']))
1079
+
1080
+ if 'id_token' in d:
1081
+ d['id_token'] = _extract_id_token(d['id_token'])
1082
+
1083
+ logger.info('Successfully retrieved access token: %s' % content)
1084
+ return OAuth2Credentials(access_token, self.client_id,
1085
+ self.client_secret, refresh_token, token_expiry,
1086
+ self.token_uri, self.user_agent,
1087
+ id_token=d.get('id_token', None))
1088
+ else:
1089
+ logger.info('Failed to retrieve access token: %s' % content)
1090
+ error_msg = 'Invalid response %s.' % resp['status']
1091
+ try:
1092
+ d = simplejson.loads(content)
1093
+ if 'error' in d:
1094
+ error_msg = d['error']
1095
+ except:
1096
+ pass
1097
+
1098
+ raise FlowExchangeError(error_msg)
1099
+
1100
+ def flow_from_clientsecrets(filename, scope, message=None):
1101
+ """Create a Flow from a clientsecrets file.
1102
+
1103
+ Will create the right kind of Flow based on the contents of the clientsecrets
1104
+ file or will raise InvalidClientSecretsError for unknown types of Flows.
1105
+
1106
+ Args:
1107
+ filename: string, File name of client secrets.
1108
+ scope: string or list of strings, scope(s) to request.
1109
+ message: string, A friendly string to display to the user if the
1110
+ clientsecrets file is missing or invalid. If message is provided then
1111
+ sys.exit will be called in the case of an error. If message in not
1112
+ provided then clientsecrets.InvalidClientSecretsError will be raised.
1113
+
1114
+ Returns:
1115
+ A Flow object.
1116
+
1117
+ Raises:
1118
+ UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
1119
+ clientsecrets.InvalidClientSecretsError if the clientsecrets file is
1120
+ invalid.
1121
+ """
1122
+ try:
1123
+ client_type, client_info = clientsecrets.loadfile(filename)
1124
+ if client_type in [clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
1125
+ return OAuth2WebServerFlow(
1126
+ client_info['client_id'],
1127
+ client_info['client_secret'],
1128
+ scope,
1129
+ None, # user_agent
1130
+ client_info['auth_uri'],
1131
+ client_info['token_uri'])
1132
+ except clientsecrets.InvalidClientSecretsError:
1133
+ if message:
1134
+ sys.exit(message)
1135
+ else:
1136
+ raise
1137
+ else:
1138
+ raise UnknownClientSecretsFlowError(
1139
+ 'This OAuth 2.0 flow is unsupported: "%s"' * client_type)