Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: third_party/google_api_python_client/MODIFICATIONS.diff

Issue 1085893002: Upgrade 3rd packages (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: update readme Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 diff --git a/third_party/google_api_python_client/apiclient/__init__.py b/third_ party/google_api_python_client/apiclient/__init__.py
2 index 5efb142..acb1a23 100644
3 --- a/third_party/google_api_python_client/apiclient/__init__.py
4 +++ b/third_party/google_api_python_client/apiclient/__init__.py
5 @@ -3,7 +3,7 @@
6 import googleapiclient
7
8 try:
9 - import oauth2client
10 + from third_party import oauth2client
11 except ImportError:
12 raise RuntimeError(
13 'Previous version of google-api-python-client detected; due to a '
14 diff --git a/third_party/google_api_python_client/googleapiclient/channel.py b/t hird_party/google_api_python_client/googleapiclient/channel.py
15 index 68a3b89..4626094 100644
16 --- a/third_party/google_api_python_client/googleapiclient/channel.py
17 +++ b/third_party/google_api_python_client/googleapiclient/channel.py
18 @@ -60,7 +60,7 @@ import datetime
19 import uuid
20
21 from googleapiclient import errors
22 -from ...oauth2client import util
23 +from third_party.oauth2client import util
24
25
26 # The unix time epoch starts at midnight 1970.
27 diff --git a/third_party/google_api_python_client/googleapiclient/discovery.py b /third_party/google_api_python_client/googleapiclient/discovery.py
28 index 3ddac57..0e9e5cf 100644
29 --- a/third_party/google_api_python_client/googleapiclient/discovery.py
30 +++ b/third_party/google_api_python_client/googleapiclient/discovery.py
31 @@ -47,9 +47,9 @@ except ImportError:
32 from cgi import parse_qsl
33
34 # Third-party imports
35 -from ... import httplib2
36 +from third_party import httplib2
37 +from third_party.uritemplate import uritemplate
38 import mimeparse
39 -from ... import uritemplate
40
41 # Local imports
42 from googleapiclient.errors import HttpError
43 @@ -65,9 +65,9 @@ from googleapiclient.model import JsonModel
44 from googleapiclient.model import MediaModel
45 from googleapiclient.model import RawModel
46 from googleapiclient.schema import Schemas
47 -from oauth2client.client import GoogleCredentials
48 -from oauth2client.util import _add_query_parameter
49 -from oauth2client.util import positional
50 +from third_party.oauth2client.client import GoogleCredentials
51 +from third_party.oauth2client.util import _add_query_parameter
52 +from third_party.oauth2client.util import positional
53
54
55 # The client library requires a version of httplib2 that supports RETRIES.
56 diff --git a/third_party/google_api_python_client/googleapiclient/errors.py b/th ird_party/google_api_python_client/googleapiclient/errors.py
57 index a1999fd..18c52e6 100644
58 --- a/third_party/google_api_python_client/googleapiclient/errors.py
59 +++ b/third_party/google_api_python_client/googleapiclient/errors.py
60 @@ -24,7 +24,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
61
62 import json
63
64 -from ...oauth2client import util
65 +from third_party.oauth2client import util
66
67
68 class Error(Exception):
69 diff --git a/third_party/google_api_python_client/googleapiclient/http.py b/thir d_party/google_api_python_client/googleapiclient/http.py
70 index 8638279..d2ce70c 100644
71 --- a/third_party/google_api_python_client/googleapiclient/http.py
72 +++ b/third_party/google_api_python_client/googleapiclient/http.py
73 @@ -25,7 +25,6 @@ import StringIO
74 import base64
75 import copy
76 import gzip
77 -import httplib2
78 import json
79 import logging
80 import mimeparse
81 @@ -49,7 +48,8 @@ from errors import ResumableUploadError
82 from errors import UnexpectedBodyError
83 from errors import UnexpectedMethodError
84 from model import JsonModel
85 -from ...oauth2client import util
86 +from third_party import httplib2
87 +from third_party.oauth2client import util
88
89
90 DEFAULT_CHUNK_SIZE = 512*1024
91 diff --git a/third_party/google_api_python_client/googleapiclient/sample_tools.p y b/third_party/google_api_python_client/googleapiclient/sample_tools.py
92 index cbd6d6f..cc0790b 100644
93 --- a/third_party/google_api_python_client/googleapiclient/sample_tools.py
94 +++ b/third_party/google_api_python_client/googleapiclient/sample_tools.py
95 @@ -22,13 +22,13 @@ __all__ = ['init']
96
97
98 import argparse
99 -import httplib2
100 import os
101
102 from googleapiclient import discovery
103 -from ...oauth2client import client
104 -from ...oauth2client import file
105 -from ...oauth2client import tools
106 +from third_party import httplib2
107 +from third_party.oauth2client import client
108 +from third_party.oauth2client import file
109 +from third_party.oauth2client import tools
110
111
112 def init(argv, name, version, doc, filename, scope=None, parents=[], discovery_ filename=None):
113 diff --git a/third_party/google_api_python_client/googleapiclient/schema.py b/th ird_party/google_api_python_client/googleapiclient/schema.py
114 index af41317..92543ec 100644
115 --- a/third_party/google_api_python_client/googleapiclient/schema.py
116 +++ b/third_party/google_api_python_client/googleapiclient/schema.py
117 @@ -63,7 +63,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
118
119 import copy
120
121 -from oauth2client import util
122 +from third_party.oauth2client import util
123
124
125 class Schemas(object):
126 diff --git a/third_party/oauth2client/README.chromium b/third_party/oauth2client /README.chromium
127 index ea15b96..bfc4a01 100644
128 --- a/third_party/oauth2client/README.chromium
129 +++ b/third_party/oauth2client/README.chromium
130 @@ -1,7 +1,7 @@
131 Name: oauth2client
132 Short Name: oauth2client
133 -URL: https://pypi.python.org/packages/source/o/oauth2client/oauth2client-1.2.ta r.gz
134 -Version: 1.2
135 +URL: https://pypi.python.org/packages/source/o/oauth2client/oauth2client-1.4.7. tar.gz
136 +Version: 1.4.7
137 License: Apache License 2.0
138
139 Description:
140 diff --git a/third_party/oauth2client/__init__.py b/third_party/oauth2client/__i nit__.py
141 index ac84748..fea30ca 100644
142 --- a/third_party/oauth2client/__init__.py
143 +++ b/third_party/oauth2client/__init__.py
144 @@ -1,5 +1,8 @@
145 -__version__ = "1.2"
146 +"""Client library for using OAuth2, especially with Google APIs."""
147 +
148 +__version__ = '1.4.7'
149
150 GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
151 +GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code'
152 GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke'
153 -GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
154 +GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'
155 \ No newline at end of file
156 diff --git a/third_party/oauth2client/anyjson.py b/third_party/oauth2client/anyj son.py
157 deleted file mode 100644
158 index ae21c33..0000000
159 --- a/third_party/oauth2client/anyjson.py
160 +++ /dev/null
161 @@ -1,32 +0,0 @@
162 -# Copyright (C) 2010 Google Inc.
163 -#
164 -# Licensed under the Apache License, Version 2.0 (the "License");
165 -# you may not use this file except in compliance with the License.
166 -# You may obtain a copy of the License at
167 -#
168 -# http://www.apache.org/licenses/LICENSE-2.0
169 -#
170 -# Unless required by applicable law or agreed to in writing, software
171 -# distributed under the License is distributed on an "AS IS" BASIS,
172 -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
173 -# See the License for the specific language governing permissions and
174 -# limitations under the License.
175 -
176 -"""Utility module to import a JSON module
177 -
178 -Hides all the messy details of exactly where
179 -we get a simplejson module from.
180 -"""
181 -
182 -__author__ = 'jcgregorio@google.com (Joe Gregorio)'
183 -
184 -
185 -try: # pragma: no cover
186 - # Should work for Python2.6 and higher.
187 - import json as simplejson
188 -except ImportError: # pragma: no cover
189 - try:
190 - import simplejson
191 - except ImportError:
192 - # Try to import from django, should work on App Engine
193 - from django.utils import simplejson
194 diff --git a/third_party/oauth2client/appengine.py b/third_party/oauth2client/ap pengine.py
195 index 5cd3f4b..4131513 100644
196 --- a/third_party/oauth2client/appengine.py
197 +++ b/third_party/oauth2client/appengine.py
198 @@ -1,4 +1,4 @@
199 -# Copyright (C) 2010 Google Inc.
200 +# Copyright 2014 Google Inc. All rights reserved.
201 #
202 # Licensed under the Apache License, Version 2.0 (the "License");
203 # you may not use this file except in compliance with the License.
204 @@ -19,14 +19,14 @@ Utilities for making it easier to use OAuth 2.0 on Google Ap p Engine.
205
206 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
207
208 -import base64
209 import cgi
210 -import httplib2
211 +import json
212 import logging
213 import os
214 import pickle
215 import threading
216 -import time
217 +
218 +import httplib2
219
220 from google.appengine.api import app_identity
221 from google.appengine.api import memcache
222 @@ -41,7 +41,6 @@ from oauth2client import GOOGLE_TOKEN_URI
223 from oauth2client import clientsecrets
224 from oauth2client import util
225 from oauth2client import xsrfutil
226 -from oauth2client.anyjson import simplejson
227 from oauth2client.client import AccessTokenRefreshError
228 from oauth2client.client import AssertionCredentials
229 from oauth2client.client import Credentials
230 @@ -159,15 +158,20 @@ class AppAssertionCredentials(AssertionCredentials):
231 Args:
232 scope: string or iterable of strings, scope(s) of the credentials being
233 requested.
234 + **kwargs: optional keyword args, including:
235 + service_account_id: service account id of the application. If None or
236 + unspecified, the default service account for the app is used.
237 """
238 self.scope = util.scopes_to_string(scope)
239 + self._kwargs = kwargs
240 + self.service_account_id = kwargs.get('service_account_id', None)
241
242 # Assertion type is no longer used, but still in the parent class signature .
243 super(AppAssertionCredentials, self).__init__(None)
244
245 @classmethod
246 - def from_json(cls, json):
247 - data = simplejson.loads(json)
248 + def from_json(cls, json_data):
249 + data = json.loads(json_data)
250 return AppAssertionCredentials(data['scope'])
251
252 def _refresh(self, http_request):
253 @@ -186,11 +190,22 @@ class AppAssertionCredentials(AssertionCredentials):
254 """
255 try:
256 scopes = self.scope.split()
257 - (token, _) = app_identity.get_access_token(scopes)
258 - except app_identity.Error, e:
259 + (token, _) = app_identity.get_access_token(
260 + scopes, service_account_id=self.service_account_id)
261 + except app_identity.Error as e:
262 raise AccessTokenRefreshError(str(e))
263 self.access_token = token
264
265 + @property
266 + def serialization_data(self):
267 + raise NotImplementedError('Cannot serialize credentials for AppEngine.')
268 +
269 + def create_scoped_required(self):
270 + return not self.scope
271 +
272 + def create_scoped(self, scopes):
273 + return AppAssertionCredentials(scopes, **self._kwargs)
274 +
275
276 class FlowProperty(db.Property):
277 """App Engine datastore Property for Flow.
278 @@ -434,6 +449,7 @@ class StorageByKeyName(Storage):
279 entity_key = db.Key.from_path(self._model.kind(), self._key_name)
280 db.delete(entity_key)
281
282 + @db.non_transactional(allow_existing=True)
283 def locked_get(self):
284 """Retrieve Credential from datastore.
285
286 @@ -456,6 +472,7 @@ class StorageByKeyName(Storage):
287 credentials.set_store(self)
288 return credentials
289
290 + @db.non_transactional(allow_existing=True)
291 def locked_put(self, credentials):
292 """Write a Credentials to the datastore.
293
294 @@ -468,6 +485,7 @@ class StorageByKeyName(Storage):
295 if self._cache:
296 self._cache.set(self._key_name, credentials.to_json())
297
298 + @db.non_transactional(allow_existing=True)
299 def locked_delete(self):
300 """Delete Credential from datastore."""
301
302 @@ -553,16 +571,14 @@ class OAuth2Decorator(object):
303 Instantiate and then use with oauth_required or oauth_aware
304 as decorators on webapp.RequestHandler methods.
305
306 - Example:
307 + ::
308
309 decorator = OAuth2Decorator(
310 client_id='837...ent.com',
311 client_secret='Qh...wwI',
312 scope='https://www.googleapis.com/auth/plus')
313
314 -
315 class MainHandler(webapp.RequestHandler):
316 -
317 @decorator.oauth_required
318 def get(self):
319 http = decorator.http()
320 @@ -650,8 +666,9 @@ class OAuth2Decorator(object):
321 provided to this constructor. A string indicating the name of the field
322 on the _credentials_class where a Credentials object will be stored.
323 Defaults to 'credentials'.
324 - **kwargs: dict, Keyword arguments are be passed along as kwargs to the
325 - OAuth2WebServerFlow constructor.
326 + **kwargs: dict, Keyword arguments are passed along as kwargs to
327 + the OAuth2WebServerFlow constructor.
328 +
329 """
330 self._tls = threading.local()
331 self.flow = None
332 @@ -798,14 +815,18 @@ class OAuth2Decorator(object):
333 url = self.flow.step1_get_authorize_url()
334 return str(url)
335
336 - def http(self):
337 + def http(self, *args, **kwargs):
338 """Returns an authorized http instance.
339
340 Must only be called from within an @oauth_required decorated method, or
341 from within an @oauth_aware decorated method where has_credentials()
342 returns True.
343 +
344 + Args:
345 + *args: Positional arguments passed to httplib2.Http constructor.
346 + **kwargs: Positional arguments passed to httplib2.Http constructor.
347 """
348 - return self.credentials.authorize(httplib2.Http())
349 + return self.credentials.authorize(httplib2.Http(*args, **kwargs))
350
351 @property
352 def callback_path(self):
353 @@ -824,7 +845,8 @@ class OAuth2Decorator(object):
354 def callback_handler(self):
355 """RequestHandler for the OAuth 2.0 redirect callback.
356
357 - Usage:
358 + Usage::
359 +
360 app = webapp.WSGIApplication([
361 ('/index', MyIndexHandler),
362 ...,
363 @@ -858,7 +880,7 @@ class OAuth2Decorator(object):
364 user)
365
366 if decorator._token_response_param and credentials.token_response:
367 - resp_json = simplejson.dumps(credentials.token_response)
368 + resp_json = json.dumps(credentials.token_response)
369 redirect_uri = util._add_query_parameter(
370 redirect_uri, decorator._token_response_param, resp_json)
371
372 @@ -887,24 +909,23 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
373 Uses a clientsecrets file as the source for all the information when
374 constructing an OAuth2Decorator.
375
376 - Example:
377 + ::
378
379 decorator = OAuth2DecoratorFromClientSecrets(
380 os.path.join(os.path.dirname(__file__), 'client_secrets.json')
381 scope='https://www.googleapis.com/auth/plus')
382
383 -
384 class MainHandler(webapp.RequestHandler):
385 -
386 @decorator.oauth_required
387 def get(self):
388 http = decorator.http()
389 # http is authorized with the user's Credentials and can be used
390 # in API calls
391 +
392 """
393
394 @util.positional(3)
395 - def __init__(self, filename, scope, message=None, cache=None):
396 + def __init__(self, filename, scope, message=None, cache=None, **kwargs):
397 """Constructor
398
399 Args:
400 @@ -917,17 +938,20 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
401 decorator.
402 cache: An optional cache service client that implements get() and set()
403 methods. See clientsecrets.loadfile() for details.
404 + **kwargs: dict, Keyword arguments are passed along as kwargs to
405 + the OAuth2WebServerFlow constructor.
406 """
407 client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
408 if client_type not in [
409 clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
410 raise InvalidClientSecretsError(
411 - 'OAuth2Decorator doesn\'t support this OAuth 2.0 flow.')
412 - constructor_kwargs = {
413 - 'auth_uri': client_info['auth_uri'],
414 - 'token_uri': client_info['token_uri'],
415 - 'message': message,
416 - }
417 + "OAuth2Decorator doesn't support this OAuth 2.0 flow.")
418 + constructor_kwargs = dict(kwargs)
419 + constructor_kwargs.update({
420 + 'auth_uri': client_info['auth_uri'],
421 + 'token_uri': client_info['token_uri'],
422 + 'message': message,
423 + })
424 revoke_uri = client_info.get('revoke_uri')
425 if revoke_uri is not None:
426 constructor_kwargs['revoke_uri'] = revoke_uri
427 @@ -960,4 +984,4 @@ def oauth2decorator_from_clientsecrets(filename, scope,
428
429 """
430 return OAuth2DecoratorFromClientSecrets(filename, scope,
431 - message=message, cache=cache)
432 + message=message, cache=cache)
433 \ No newline at end of file
434 diff --git a/third_party/oauth2client/client.py b/third_party/oauth2client/clien t.py
435 index 6901f3f..8fe28a3 100644
436 --- a/third_party/oauth2client/client.py
437 +++ b/third_party/oauth2client/client.py
438 @@ -1,4 +1,4 @@
439 -# Copyright (C) 2010 Google Inc.
440 +# Copyright 2014 Google Inc. All rights reserved.
441 #
442 # Licensed under the Apache License, Version 2.0 (the "License");
443 # you may not use this file except in compliance with the License.
444 @@ -20,44 +20,48 @@ Tools for interacting with OAuth 2.0 protected resources.
445 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
446
447 import base64
448 -import clientsecrets
449 +import collections
450 import copy
451 import datetime
452 -from .. import httplib2
453 +import json
454 import logging
455 +import os
456 +import socket
457 import sys
458 +import tempfile
459 import time
460 -import urllib
461 -import urlparse
462 +import shutil
463
464 +from .. import httplib2
465 +from . import clientsecrets
466 from . import GOOGLE_AUTH_URI
467 +from . import GOOGLE_DEVICE_URI
468 from . import GOOGLE_REVOKE_URI
469 from . import GOOGLE_TOKEN_URI
470 from . import util
471 -from .anyjson import simplejson
472 +from third_party import six
473 +from third_party.six.moves import urllib
474
475 HAS_OPENSSL = False
476 HAS_CRYPTO = False
477 try:
478 - from . import crypt
479 + from oauth2client import crypt
480 HAS_CRYPTO = True
481 if crypt.OpenSSLVerifier is not None:
482 HAS_OPENSSL = True
483 except ImportError:
484 pass
485
486 -try:
487 - from urlparse import parse_qsl
488 -except ImportError:
489 - from cgi import parse_qsl
490 -
491 logger = logging.getLogger(__name__)
492
493 # Expiry is stored in RFC3339 UTC format
494 EXPIRY_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
495
496 # Which certs to use to validate id_tokens received.
497 -ID_TOKEN_VERIFICATON_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
498 +ID_TOKEN_VERIFICATION_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
499 +# This symbol previously had a typo in the name; we keep the old name
500 +# around for now, but will remove it in the future.
501 +ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS
502
503 # Constant to use for the out of band OAuth 2.0 flow.
504 OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
505 @@ -65,6 +69,42 @@ OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
506 # Google Data client libraries may need to set this to [401, 403].
507 REFRESH_STATUS_CODES = [401]
508
509 +# The value representing user credentials.
510 +AUTHORIZED_USER = 'authorized_user'
511 +
512 +# The value representing service account credentials.
513 +SERVICE_ACCOUNT = 'service_account'
514 +
515 +# The environment variable pointing the file with local
516 +# Application Default Credentials.
517 +GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS'
518 +# The ~/.config subdirectory containing gcloud credentials. Intended
519 +# to be swapped out in tests.
520 +_CLOUDSDK_CONFIG_DIRECTORY = 'gcloud'
521 +
522 +# The error message we show users when we can't find the Application
523 +# Default Credentials.
524 +ADC_HELP_MSG = (
525 + 'The Application Default Credentials are not available. They are available '
526 + 'if running in Google Compute Engine. Otherwise, the environment variable '
527 + + GOOGLE_APPLICATION_CREDENTIALS +
528 + ' must be defined pointing to a file defining the credentials. See '
529 + 'https://developers.google.com/accounts/docs/application-default-credential s' # pylint:disable=line-too-long
530 + ' for more information.')
531 +
532 +# The access token along with the seconds in which it expires.
533 +AccessTokenInfo = collections.namedtuple(
534 + 'AccessTokenInfo', ['access_token', 'expires_in'])
535 +
536 +DEFAULT_ENV_NAME = 'UNKNOWN'
537 +
538 +# If set to True _get_environment avoid GCE check (_detect_gce_environment)
539 +NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False')
540 +
541 +class SETTINGS(object):
542 + """Settings namespace for globally defined values."""
543 + env_name = None
544 +
545
546 class Error(Exception):
547 """Base error for this module."""
548 @@ -91,13 +131,25 @@ class AccessTokenCredentialsError(Error):
549
550
551 class VerifyJwtTokenError(Error):
552 - """Could on retrieve certificates for validation."""
553 + """Could not retrieve certificates for validation."""
554
555
556 class NonAsciiHeaderError(Error):
557 """Header names and values must be ASCII strings."""
558
559
560 +class ApplicationDefaultCredentialsError(Error):
561 + """Error retrieving the Application Default Credentials."""
562 +
563 +
564 +class OAuth2DeviceCodeError(Error):
565 + """Error trying to retrieve a device code."""
566 +
567 +
568 +class CryptoUnavailableError(Error, NotImplementedError):
569 + """Raised when a crypto library is required, but none is available."""
570 +
571 +
572 def _abstract():
573 raise NotImplementedError('You need to override this function')
574
575 @@ -125,11 +177,12 @@ class Credentials(object):
576 an HTTP transport.
577
578 Subclasses must also specify a classmethod named 'from_json' that takes a JSO N
579 - string as input and returns an instaniated Credentials object.
580 + string as input and returns an instantiated Credentials object.
581 """
582
583 NON_SERIALIZED_MEMBERS = ['store']
584
585 +
586 def authorize(self, http):
587 """Take an httplib2.Http instance (or equivalent) and authorizes it.
588
589 @@ -143,6 +196,7 @@ class Credentials(object):
590 """
591 _abstract()
592
593 +
594 def refresh(self, http):
595 """Forces a refresh of the access_token.
596
597 @@ -152,6 +206,7 @@ class Credentials(object):
598 """
599 _abstract()
600
601 +
602 def revoke(self, http):
603 """Revokes a refresh_token and makes the credentials void.
604
605 @@ -161,6 +216,7 @@ class Credentials(object):
606 """
607 _abstract()
608
609 +
610 def apply(self, headers):
611 """Add the authorization to the headers.
612
613 @@ -184,12 +240,16 @@ class Credentials(object):
614 for member in strip:
615 if member in d:
616 del d[member]
617 - if 'token_expiry' in d and isinstance(d['token_expiry'], datetime.datetime) :
618 + if (d.get('token_expiry') and
619 + isinstance(d['token_expiry'], datetime.datetime)):
620 d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
621 # Add in information we will need later to reconsistitue this instance.
622 d['_class'] = t.__name__
623 d['_module'] = t.__module__
624 - return simplejson.dumps(d)
625 + for key, val in d.items():
626 + if isinstance(val, bytes):
627 + d[key] = val.decode('utf-8')
628 + return json.dumps(d)
629
630 def to_json(self):
631 """Creating a JSON representation of an instance of Credentials.
632 @@ -212,14 +272,16 @@ class Credentials(object):
633 An instance of the subclass of Credentials that was serialized with
634 to_json().
635 """
636 - data = simplejson.loads(s)
637 + if six.PY3 and isinstance(s, bytes):
638 + s = s.decode('utf-8')
639 + data = json.loads(s)
640 # Find and call the right classmethod from_json() to restore the object.
641 module = data['_module']
642 try:
643 m = __import__(module)
644 except ImportError:
645 # In case there's an object from the old package structure, update it
646 - module = module.replace('.apiclient', '')
647 + module = module.replace('.googleapiclient', '')
648 m = __import__(module)
649
650 m = __import__(module, fromlist=module.split('.')[:-1])
651 @@ -228,13 +290,13 @@ class Credentials(object):
652 return from_json(s)
653
654 @classmethod
655 - def from_json(cls, s):
656 + def from_json(cls, unused_data):
657 """Instantiate a Credentials object from a JSON description of it.
658
659 The JSON should have been produced by calling .to_json() on the object.
660
661 Args:
662 - data: dict, A deserialized JSON object.
663 + unused_data: dict, A deserialized JSON object.
664
665 Returns:
666 An instance of a Credentials subclass.
667 @@ -356,8 +418,10 @@ def clean_headers(headers):
668 """
669 clean = {}
670 try:
671 - for k, v in headers.iteritems():
672 - clean[str(k)] = str(v)
673 + for k, v in six.iteritems(headers):
674 + clean_k = k if isinstance(k, bytes) else str(k).encode('ascii')
675 + clean_v = v if isinstance(v, bytes) else str(v).encode('ascii')
676 + clean[clean_k] = clean_v
677 except UnicodeEncodeError:
678 raise NonAsciiHeaderError(k + ': ' + v)
679 return clean
680 @@ -373,11 +437,11 @@ def _update_query_params(uri, params):
681 Returns:
682 The same URI but with the new query parameters added.
683 """
684 - parts = list(urlparse.urlparse(uri))
685 - query_params = dict(parse_qsl(parts[4])) # 4 is the index of the query part
686 + parts = urllib.parse.urlparse(uri)
687 + query_params = dict(urllib.parse.parse_qsl(parts.query))
688 query_params.update(params)
689 - parts[4] = urllib.urlencode(query_params)
690 - return urlparse.urlunparse(parts)
691 + new_parts = parts._replace(query=urllib.parse.urlencode(query_params))
692 + return urllib.parse.urlunparse(new_parts)
693
694
695 class OAuth2Credentials(Credentials):
696 @@ -445,22 +509,23 @@ class OAuth2Credentials(Credentials):
697 it.
698
699 Args:
700 - http: An instance of httplib2.Http
701 - or something that acts like it.
702 + http: An instance of ``httplib2.Http`` or something that acts
703 + like it.
704
705 Returns:
706 A modified instance of http that was passed in.
707
708 - Example:
709 + Example::
710
711 h = httplib2.Http()
712 h = credentials.authorize(h)
713
714 - You can't create a new OAuth subclass of httplib2.Authenication
715 + You can't create a new OAuth subclass of httplib2.Authentication
716 because it never gets passed the absolute URI, which is needed for
717 signing. So instead we have to overload 'request' with a closure
718 that adds in the Authorization header and then calls the original
719 version of 'request()'.
720 +
721 """
722 request_orig = http.request
723
724 @@ -473,10 +538,12 @@ class OAuth2Credentials(Credentials):
725 logger.info('Attempting refresh to obtain initial access_token')
726 self._refresh(request_orig)
727
728 - # Modify the request headers to add the appropriate
729 + # Clone and modify the request headers to add the appropriate
730 # Authorization header.
731 if headers is None:
732 headers = {}
733 + else:
734 + headers = dict(headers)
735 self.apply(headers)
736
737 if self.user_agent is not None:
738 @@ -489,7 +556,7 @@ class OAuth2Credentials(Credentials):
739 redirections, connection_type)
740
741 if resp.status in REFRESH_STATUS_CODES:
742 - logger.info('Refreshing due to a %s' % str(resp.status))
743 + logger.info('Refreshing due to a %s', resp.status)
744 self._refresh(request_orig)
745 self.apply(headers)
746 return request_orig(uri, method, body, clean_headers(headers),
747 @@ -545,13 +612,15 @@ class OAuth2Credentials(Credentials):
748 Returns:
749 An instance of a Credentials subclass.
750 """
751 - data = simplejson.loads(s)
752 - if 'token_expiry' in data and not isinstance(data['token_expiry'],
753 - datetime.datetime):
754 + if six.PY3 and isinstance(s, bytes):
755 + s = s.decode('utf-8')
756 + data = json.loads(s)
757 + if (data.get('token_expiry') and
758 + not isinstance(data['token_expiry'], datetime.datetime)):
759 try:
760 data['token_expiry'] = datetime.datetime.strptime(
761 data['token_expiry'], EXPIRY_FORMAT)
762 - except:
763 + except ValueError:
764 data['token_expiry'] = None
765 retval = cls(
766 data['access_token'],
767 @@ -586,11 +655,24 @@ class OAuth2Credentials(Credentials):
768 return True
769 return False
770
771 + def get_access_token(self, http=None):
772 + """Return the access token and its expiration information.
773 +
774 + If the token does not exist, get one.
775 + If the token expired, refresh it.
776 + """
777 + if not self.access_token or self.access_token_expired:
778 + if not http:
779 + http = httplib2.Http()
780 + self.refresh(http)
781 + return AccessTokenInfo(access_token=self.access_token,
782 + expires_in=self._expires_in())
783 +
784 def set_store(self, store):
785 """Set the Storage for the credential.
786
787 Args:
788 - store: Storage, an implementation of Stroage object.
789 + store: Storage, an implementation of Storage object.
790 This is needed to store the latest access_token if it
791 has expired and been refreshed. This implementation uses
792 locking to check for updates before updating the
793 @@ -598,6 +680,25 @@ class OAuth2Credentials(Credentials):
794 """
795 self.store = store
796
797 + def _expires_in(self):
798 + """Return the number of seconds until this token expires.
799 +
800 + If token_expiry is in the past, this method will return 0, meaning the
801 + token has already expired.
802 + If token_expiry is None, this method will return None. Note that returning
803 + 0 in such a case would not be fair: the token may still be valid;
804 + we just don't know anything about it.
805 + """
806 + if self.token_expiry:
807 + now = datetime.datetime.utcnow()
808 + if self.token_expiry > now:
809 + time_delta = self.token_expiry - now
810 + # TODO(orestica): return time_delta.total_seconds()
811 + # once dropping support for Python 2.6
812 + return time_delta.days * 86400 + time_delta.seconds
813 + else:
814 + return 0
815 +
816 def _updateFromCredential(self, other):
817 """Update this Credential from another instance."""
818 self.__dict__.update(other.__getstate__())
819 @@ -615,7 +716,7 @@ class OAuth2Credentials(Credentials):
820
821 def _generate_refresh_request_body(self):
822 """Generate the body that will be used in the refresh request."""
823 - body = urllib.urlencode({
824 + body = urllib.parse.urlencode({
825 'grant_type': 'refresh_token',
826 'client_id': self.client_id,
827 'client_secret': self.client_secret,
828 @@ -679,9 +780,10 @@ class OAuth2Credentials(Credentials):
829 logger.info('Refreshing access_token')
830 resp, content = http_request(
831 self.token_uri, method='POST', body=body, headers=headers)
832 + if six.PY3 and isinstance(content, bytes):
833 + content = content.decode('utf-8')
834 if resp.status == 200:
835 - # TODO(jcgregorio) Raise an error if loads fails?
836 - d = simplejson.loads(content)
837 + d = json.loads(content)
838 self.token_response = d
839 self.access_token = d['access_token']
840 self.refresh_token = d.get('refresh_token', self.refresh_token)
841 @@ -690,35 +792,40 @@ class OAuth2Credentials(Credentials):
842 seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
843 else:
844 self.token_expiry = None
845 + # On temporary refresh errors, the user does not actually have to
846 + # re-authorize, so we unflag here.
847 + self.invalid = False
848 if self.store:
849 self.store.locked_put(self)
850 else:
851 # An {'error':...} response body means the token is expired or revoked,
852 # so we flag the credentials as such.
853 - logger.info('Failed to retrieve access token: %s' % content)
854 + logger.info('Failed to retrieve access token: %s', content)
855 error_msg = 'Invalid response %s.' % resp['status']
856 try:
857 - d = simplejson.loads(content)
858 + d = json.loads(content)
859 if 'error' in d:
860 error_msg = d['error']
861 + if 'error_description' in d:
862 + error_msg += ': ' + d['error_description']
863 self.invalid = True
864 if self.store:
865 self.store.locked_put(self)
866 - except StandardError:
867 + except (TypeError, ValueError):
868 pass
869 raise AccessTokenRefreshError(error_msg)
870
871 def _revoke(self, http_request):
872 - """Revokes the refresh_token and deletes the store if available.
873 + """Revokes this credential and deletes the stored copy (if it exists).
874
875 Args:
876 http_request: callable, a callable that matches the method signature of
877 httplib2.Http.request, used to make the revoke request.
878 """
879 - self._do_revoke(http_request, self.refresh_token)
880 + self._do_revoke(http_request, self.refresh_token or self.access_token)
881
882 def _do_revoke(self, http_request, token):
883 - """Revokes the credentials and deletes the store if available.
884 + """Revokes this credential and deletes the stored copy (if it exists).
885
886 Args:
887 http_request: callable, a callable that matches the method signature of
888 @@ -738,10 +845,10 @@ class OAuth2Credentials(Credentials):
889 else:
890 error_msg = 'Invalid response %s.' % resp.status
891 try:
892 - d = simplejson.loads(content)
893 + d = json.loads(content)
894 if 'error' in d:
895 error_msg = d['error']
896 - except StandardError:
897 + except (TypeError, ValueError):
898 pass
899 raise TokenRevokeError(error_msg)
900
901 @@ -763,7 +870,8 @@ class AccessTokenCredentials(OAuth2Credentials):
902
903 AccessTokenCredentials objects may be safely pickled and unpickled.
904
905 - Usage:
906 + Usage::
907 +
908 credentials = AccessTokenCredentials('<an access token>',
909 'my-user-agent/1.0')
910 http = httplib2.Http()
911 @@ -799,10 +907,12 @@ class AccessTokenCredentials(OAuth2Credentials):
912
913 @classmethod
914 def from_json(cls, s):
915 - data = simplejson.loads(s)
916 + if six.PY3 and isinstance(s, bytes):
917 + s = s.decode('utf-8')
918 + data = json.loads(s)
919 retval = AccessTokenCredentials(
920 - data['access_token'],
921 - data['user_agent'])
922 + data['access_token'],
923 + data['user_agent'])
924 return retval
925
926 def _refresh(self, http_request):
927 @@ -819,7 +929,434 @@ class AccessTokenCredentials(OAuth2Credentials):
928 self._do_revoke(http_request, self.access_token)
929
930
931 -class AssertionCredentials(OAuth2Credentials):
932 +def _detect_gce_environment(urlopen=None):
933 + """Determine if the current environment is Compute Engine.
934 +
935 + Args:
936 + urlopen: Optional argument. Function used to open a connection to a URL.
937 +
938 + Returns:
939 + Boolean indicating whether or not the current environment is Google
940 + Compute Engine.
941 + """
942 + urlopen = urlopen or urllib.request.urlopen
943 + # Note: the explicit `timeout` below is a workaround. The underlying
944 + # issue is that resolving an unknown host on some networks will take
945 + # 20-30 seconds; making this timeout short fixes the issue, but
946 + # could lead to false negatives in the event that we are on GCE, but
947 + # the metadata resolution was particularly slow. The latter case is
948 + # "unlikely".
949 + try:
950 + response = urlopen('http://169.254.169.254/', timeout=1)
951 + return response.info().get('Metadata-Flavor', '') == 'Google'
952 + except socket.timeout:
953 + logger.info('Timeout attempting to reach GCE metadata service.')
954 + return False
955 + except urllib.error.URLError as e:
956 + if isinstance(getattr(e, 'reason', None), socket.timeout):
957 + logger.info('Timeout attempting to reach GCE metadata service.')
958 + return False
959 +
960 +
961 +def _get_environment(urlopen=None):
962 + """Detect the environment the code is being run on.
963 +
964 + Args:
965 + urlopen: Optional argument. Function used to open a connection to a URL.
966 +
967 + Returns:
968 + The value of SETTINGS.env_name after being set. If already
969 + set, simply returns the value.
970 + """
971 + if SETTINGS.env_name is not None:
972 + return SETTINGS.env_name
973 +
974 + # None is an unset value, not the default.
975 + SETTINGS.env_name = DEFAULT_ENV_NAME
976 +
977 + server_software = os.environ.get('SERVER_SOFTWARE', '')
978 + if server_software.startswith('Google App Engine/'):
979 + SETTINGS.env_name = 'GAE_PRODUCTION'
980 + elif server_software.startswith('Development/'):
981 + SETTINGS.env_name = 'GAE_LOCAL'
982 + elif NO_GCE_CHECK != 'True' and _detect_gce_environment(urlopen=urlopen):
983 + SETTINGS.env_name = 'GCE_PRODUCTION'
984 +
985 + return SETTINGS.env_name
986 +
987 +
988 +class GoogleCredentials(OAuth2Credentials):
989 + """Application Default Credentials for use in calling Google APIs.
990 +
991 + The Application Default Credentials are being constructed as a function of
992 + the environment where the code is being run.
993 + More details can be found on this page:
994 + https://developers.google.com/accounts/docs/application-default-credentials
995 +
996 + Here is an example of how to use the Application Default Credentials for a
997 + service that requires authentication:
998 +
999 + from googleapiclient.discovery import build
1000 + from oauth2client.client import GoogleCredentials
1001 +
1002 + credentials = GoogleCredentials.get_application_default()
1003 + service = build('compute', 'v1', credentials=credentials)
1004 +
1005 + PROJECT = 'bamboo-machine-422'
1006 + ZONE = 'us-central1-a'
1007 + request = service.instances().list(project=PROJECT, zone=ZONE)
1008 + response = request.execute()
1009 +
1010 + print(response)
1011 + """
1012 +
1013 + def __init__(self, access_token, client_id, client_secret, refresh_token,
1014 + token_expiry, token_uri, user_agent,
1015 + revoke_uri=GOOGLE_REVOKE_URI):
1016 + """Create an instance of GoogleCredentials.
1017 +
1018 + This constructor is not usually called by the user, instead
1019 + GoogleCredentials objects are instantiated by
1020 + GoogleCredentials.from_stream() or
1021 + GoogleCredentials.get_application_default().
1022 +
1023 + Args:
1024 + access_token: string, access token.
1025 + client_id: string, client identifier.
1026 + client_secret: string, client secret.
1027 + refresh_token: string, refresh token.
1028 + token_expiry: datetime, when the access_token expires.
1029 + token_uri: string, URI of token endpoint.
1030 + user_agent: string, The HTTP User-Agent to provide for this application.
1031 + revoke_uri: string, URI for revoke endpoint.
1032 + Defaults to GOOGLE_REVOKE_URI; a token can't be revoked if this is None .
1033 + """
1034 + super(GoogleCredentials, self).__init__(
1035 + access_token, client_id, client_secret, refresh_token, token_expiry,
1036 + token_uri, user_agent, revoke_uri=revoke_uri)
1037 +
1038 + def create_scoped_required(self):
1039 + """Whether this Credentials object is scopeless.
1040 +
1041 + create_scoped(scopes) method needs to be called in order to create
1042 + a Credentials object for API calls.
1043 + """
1044 + return False
1045 +
1046 + def create_scoped(self, scopes):
1047 + """Create a Credentials object for the given scopes.
1048 +
1049 + The Credentials type is preserved.
1050 + """
1051 + return self
1052 +
1053 + @property
1054 + def serialization_data(self):
1055 + """Get the fields and their values identifying the current credentials."""
1056 + return {
1057 + 'type': 'authorized_user',
1058 + 'client_id': self.client_id,
1059 + 'client_secret': self.client_secret,
1060 + 'refresh_token': self.refresh_token
1061 + }
1062 +
1063 + @staticmethod
1064 + def _implicit_credentials_from_gae(env_name=None):
1065 + """Attempts to get implicit credentials in Google App Engine env.
1066 +
1067 + If the current environment is not detected as App Engine, returns None,
1068 + indicating no Google App Engine credentials can be detected from the
1069 + current environment.
1070 +
1071 + Args:
1072 + env_name: String, indicating current environment.
1073 +
1074 + Returns:
1075 + None, if not in GAE, else an appengine.AppAssertionCredentials object.
1076 + """
1077 + env_name = env_name or _get_environment()
1078 + if env_name not in ('GAE_PRODUCTION', 'GAE_LOCAL'):
1079 + return None
1080 +
1081 + return _get_application_default_credential_GAE()
1082 +
1083 + @staticmethod
1084 + def _implicit_credentials_from_gce(env_name=None):
1085 + """Attempts to get implicit credentials in Google Compute Engine env.
1086 +
1087 + If the current environment is not detected as Compute Engine, returns None,
1088 + indicating no Google Compute Engine credentials can be detected from the
1089 + current environment.
1090 +
1091 + Args:
1092 + env_name: String, indicating current environment.
1093 +
1094 + Returns:
1095 + None, if not in GCE, else a gce.AppAssertionCredentials object.
1096 + """
1097 + env_name = env_name or _get_environment()
1098 + if env_name != 'GCE_PRODUCTION':
1099 + return None
1100 +
1101 + return _get_application_default_credential_GCE()
1102 +
1103 + @staticmethod
1104 + def _implicit_credentials_from_files(env_name=None):
1105 + """Attempts to get implicit credentials from local credential files.
1106 +
1107 + First checks if the environment variable GOOGLE_APPLICATION_CREDENTIALS
1108 + is set with a filename and then falls back to a configuration file (the
1109 + "well known" file) associated with the 'gcloud' command line tool.
1110 +
1111 + Args:
1112 + env_name: Unused argument.
1113 +
1114 + Returns:
1115 + Credentials object associated with the GOOGLE_APPLICATION_CREDENTIALS
1116 + file or the "well known" file if either exist. If neither file is
1117 + define, returns None, indicating no credentials from a file can
1118 + detected from the current environment.
1119 + """
1120 + credentials_filename = _get_environment_variable_file()
1121 + if not credentials_filename:
1122 + credentials_filename = _get_well_known_file()
1123 + if os.path.isfile(credentials_filename):
1124 + extra_help = (' (produced automatically when running'
1125 + ' "gcloud auth login" command)')
1126 + else:
1127 + credentials_filename = None
1128 + else:
1129 + extra_help = (' (pointed to by ' + GOOGLE_APPLICATION_CREDENTIALS +
1130 + ' environment variable)')
1131 +
1132 + if not credentials_filename:
1133 + return
1134 +
1135 + try:
1136 + return _get_application_default_credential_from_file(credentials_filename )
1137 + except (ApplicationDefaultCredentialsError, ValueError) as error:
1138 + _raise_exception_for_reading_json(credentials_filename, extra_help, error )
1139 +
1140 + @classmethod
1141 + def _get_implicit_credentials(cls):
1142 + """Gets credentials implicitly from the environment.
1143 +
1144 + Checks environment in order of precedence:
1145 + - Google App Engine (production and testing)
1146 + - Environment variable GOOGLE_APPLICATION_CREDENTIALS pointing to
1147 + a file with stored credentials information.
1148 + - Stored "well known" file associated with `gcloud` command line tool.
1149 + - Google Compute Engine production environment.
1150 +
1151 + Exceptions:
1152 + ApplicationDefaultCredentialsError: raised when the credentials fail
1153 + to be retrieved.
1154 + """
1155 + env_name = _get_environment()
1156 +
1157 + # Environ checks (in order). Assumes each checker takes `env_name`
1158 + # as a kwarg.
1159 + environ_checkers = [
1160 + cls._implicit_credentials_from_gae,
1161 + cls._implicit_credentials_from_files,
1162 + cls._implicit_credentials_from_gce,
1163 + ]
1164 +
1165 + for checker in environ_checkers:
1166 + credentials = checker(env_name=env_name)
1167 + if credentials is not None:
1168 + return credentials
1169 +
1170 + # If no credentials, fail.
1171 + raise ApplicationDefaultCredentialsError(ADC_HELP_MSG)
1172 +
1173 + @staticmethod
1174 + def get_application_default():
1175 + """Get the Application Default Credentials for the current environment.
1176 +
1177 + Exceptions:
1178 + ApplicationDefaultCredentialsError: raised when the credentials fail
1179 + to be retrieved.
1180 + """
1181 + return GoogleCredentials._get_implicit_credentials()
1182 +
1183 + @staticmethod
1184 + def from_stream(credential_filename):
1185 + """Create a Credentials object by reading the information from a given file .
1186 +
1187 + It returns an object of type GoogleCredentials.
1188 +
1189 + Args:
1190 + credential_filename: the path to the file from where the credentials
1191 + are to be read
1192 +
1193 + Exceptions:
1194 + ApplicationDefaultCredentialsError: raised when the credentials fail
1195 + to be retrieved.
1196 + """
1197 +
1198 + if credential_filename and os.path.isfile(credential_filename):
1199 + try:
1200 + return _get_application_default_credential_from_file(
1201 + credential_filename)
1202 + except (ApplicationDefaultCredentialsError, ValueError) as error:
1203 + extra_help = ' (provided as parameter to the from_stream() method)'
1204 + _raise_exception_for_reading_json(credential_filename,
1205 + extra_help,
1206 + error)
1207 + else:
1208 + raise ApplicationDefaultCredentialsError(
1209 + 'The parameter passed to the from_stream() '
1210 + 'method should point to a file.')
1211 +
1212 +
1213 +def _save_private_file(filename, json_contents):
1214 + """Saves a file with read-write permissions on for the owner.
1215 +
1216 + Args:
1217 + filename: String. Absolute path to file.
1218 + json_contents: JSON serializable object to be saved.
1219 + """
1220 + temp_filename = tempfile.mktemp()
1221 + file_desc = os.open(temp_filename, os.O_WRONLY | os.O_CREAT, 0o600)
1222 + with os.fdopen(file_desc, 'w') as file_handle:
1223 + json.dump(json_contents, file_handle, sort_keys=True,
1224 + indent=2, separators=(',', ': '))
1225 + shutil.move(temp_filename, filename)
1226 +
1227 +
1228 +def save_to_well_known_file(credentials, well_known_file=None):
1229 + """Save the provided GoogleCredentials to the well known file.
1230 +
1231 + Args:
1232 + credentials:
1233 + the credentials to be saved to the well known file;
1234 + it should be an instance of GoogleCredentials
1235 + well_known_file:
1236 + the name of the file where the credentials are to be saved;
1237 + this parameter is supposed to be used for testing only
1238 + """
1239 + # TODO(orestica): move this method to tools.py
1240 + # once the argparse import gets fixed (it is not present in Python 2.6)
1241 +
1242 + if well_known_file is None:
1243 + well_known_file = _get_well_known_file()
1244 +
1245 + credentials_data = credentials.serialization_data
1246 + _save_private_file(well_known_file, credentials_data)
1247 +
1248 +
1249 +def _get_environment_variable_file():
1250 + application_default_credential_filename = (
1251 + os.environ.get(GOOGLE_APPLICATION_CREDENTIALS,
1252 + None))
1253 +
1254 + if application_default_credential_filename:
1255 + if os.path.isfile(application_default_credential_filename):
1256 + return application_default_credential_filename
1257 + else:
1258 + raise ApplicationDefaultCredentialsError(
1259 + 'File ' + application_default_credential_filename + ' (pointed by ' +
1260 + GOOGLE_APPLICATION_CREDENTIALS +
1261 + ' environment variable) does not exist!')
1262 +
1263 +
1264 +def _get_well_known_file():
1265 + """Get the well known file produced by command 'gcloud auth login'."""
1266 + # TODO(orestica): Revisit this method once gcloud provides a better way
1267 + # of pinpointing the exact location of the file.
1268 +
1269 + WELL_KNOWN_CREDENTIALS_FILE = 'application_default_credentials.json'
1270 +
1271 + if os.name == 'nt':
1272 + try:
1273 + default_config_path = os.path.join(os.environ['APPDATA'],
1274 + _CLOUDSDK_CONFIG_DIRECTORY)
1275 + except KeyError:
1276 + # This should never happen unless someone is really messing with things.
1277 + drive = os.environ.get('SystemDrive', 'C:')
1278 + default_config_path = os.path.join(drive, '\\',
1279 + _CLOUDSDK_CONFIG_DIRECTORY)
1280 + else:
1281 + default_config_path = os.path.join(os.path.expanduser('~'),
1282 + '.config',
1283 + _CLOUDSDK_CONFIG_DIRECTORY)
1284 +
1285 + default_config_path = os.path.join(default_config_path,
1286 + WELL_KNOWN_CREDENTIALS_FILE)
1287 +
1288 + return default_config_path
1289 +
1290 +
1291 +def _get_application_default_credential_from_file(filename):
1292 + """Build the Application Default Credentials from file."""
1293 +
1294 + from oauth2client import service_account
1295 +
1296 + # read the credentials from the file
1297 + with open(filename) as file_obj:
1298 + client_credentials = json.load(file_obj)
1299 +
1300 + credentials_type = client_credentials.get('type')
1301 + if credentials_type == AUTHORIZED_USER:
1302 + required_fields = set(['client_id', 'client_secret', 'refresh_token'])
1303 + elif credentials_type == SERVICE_ACCOUNT:
1304 + required_fields = set(['client_id', 'client_email', 'private_key_id',
1305 + 'private_key'])
1306 + else:
1307 + raise ApplicationDefaultCredentialsError(
1308 + "'type' field should be defined (and have one of the '" +
1309 + AUTHORIZED_USER + "' or '" + SERVICE_ACCOUNT + "' values)")
1310 +
1311 + missing_fields = required_fields.difference(client_credentials.keys())
1312 +
1313 + if missing_fields:
1314 + _raise_exception_for_missing_fields(missing_fields)
1315 +
1316 + if client_credentials['type'] == AUTHORIZED_USER:
1317 + return GoogleCredentials(
1318 + access_token=None,
1319 + client_id=client_credentials['client_id'],
1320 + client_secret=client_credentials['client_secret'],
1321 + refresh_token=client_credentials['refresh_token'],
1322 + token_expiry=None,
1323 + token_uri=GOOGLE_TOKEN_URI,
1324 + user_agent='Python client library')
1325 + else: # client_credentials['type'] == SERVICE_ACCOUNT
1326 + return service_account._ServiceAccountCredentials(
1327 + service_account_id=client_credentials['client_id'],
1328 + service_account_email=client_credentials['client_email'],
1329 + private_key_id=client_credentials['private_key_id'],
1330 + private_key_pkcs8_text=client_credentials['private_key'],
1331 + scopes=[])
1332 +
1333 +
1334 +def _raise_exception_for_missing_fields(missing_fields):
1335 + raise ApplicationDefaultCredentialsError(
1336 + 'The following field(s) must be defined: ' + ', '.join(missing_fields))
1337 +
1338 +
1339 +def _raise_exception_for_reading_json(credential_file,
1340 + extra_help,
1341 + error):
1342 + raise ApplicationDefaultCredentialsError(
1343 + 'An error was encountered while reading json file: '+
1344 + credential_file + extra_help + ': ' + str(error))
1345 +
1346 +
1347 +def _get_application_default_credential_GAE():
1348 + from oauth2client.appengine import AppAssertionCredentials
1349 +
1350 + return AppAssertionCredentials([])
1351 +
1352 +
1353 +def _get_application_default_credential_GCE():
1354 + from oauth2client.gce import AppAssertionCredentials
1355 +
1356 + return AppAssertionCredentials([])
1357 +
1358 +
1359 +class AssertionCredentials(GoogleCredentials):
1360 """Abstract Credentials object used for OAuth 2.0 assertion grants.
1361
1362 This credential does not require a flow to instantiate because it
1363 @@ -859,7 +1396,7 @@ class AssertionCredentials(OAuth2Credentials):
1364 def _generate_refresh_request_body(self):
1365 assertion = self._generate_assertion()
1366
1367 - body = urllib.urlencode({
1368 + body = urllib.parse.urlencode({
1369 'assertion': assertion,
1370 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
1371 })
1372 @@ -882,141 +1419,157 @@ class AssertionCredentials(OAuth2Credentials):
1373 self._do_revoke(http_request, self.access_token)
1374
1375
1376 -if HAS_CRYPTO:
1377 - # PyOpenSSL and PyCrypto are not prerequisites for oauth2client, so if it is
1378 - # missing then don't create the SignedJwtAssertionCredentials or the
1379 - # verify_id_token() method.
1380 +def _RequireCryptoOrDie():
1381 + """Ensure we have a crypto library, or throw CryptoUnavailableError.
1382 +
1383 + The oauth2client.crypt module requires either PyCrypto or PyOpenSSL
1384 + to be available in order to function, but these are optional
1385 + dependencies.
1386 + """
1387 + if not HAS_CRYPTO:
1388 + raise CryptoUnavailableError('No crypto library available')
1389
1390 - class SignedJwtAssertionCredentials(AssertionCredentials):
1391 - """Credentials object used for OAuth 2.0 Signed JWT assertion grants.
1392
1393 - This credential does not require a flow to instantiate because it represent s
1394 - a two legged flow, and therefore has all of the required information to
1395 - generate and refresh its own access tokens.
1396 +class SignedJwtAssertionCredentials(AssertionCredentials):
1397 + """Credentials object used for OAuth 2.0 Signed JWT assertion grants.
1398 +
1399 + This credential does not require a flow to instantiate because it
1400 + represents a two legged flow, and therefore has all of the required
1401 + information to generate and refresh its own access tokens.
1402 +
1403 + SignedJwtAssertionCredentials requires either PyOpenSSL, or PyCrypto
1404 + 2.6 or later. For App Engine you may also consider using
1405 + AppAssertionCredentials.
1406 + """
1407 +
1408 + MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
1409 +
1410 + @util.positional(4)
1411 + def __init__(self,
1412 + service_account_name,
1413 + private_key,
1414 + scope,
1415 + private_key_password='notasecret',
1416 + user_agent=None,
1417 + token_uri=GOOGLE_TOKEN_URI,
1418 + revoke_uri=GOOGLE_REVOKE_URI,
1419 + **kwargs):
1420 + """Constructor for SignedJwtAssertionCredentials.
1421 +
1422 + Args:
1423 + service_account_name: string, id for account, usually an email address.
1424 + private_key: string, private key in PKCS12 or PEM format.
1425 + scope: string or iterable of strings, scope(s) of the credentials being
1426 + requested.
1427 + private_key_password: string, password for private_key, unused if
1428 + private_key is in PEM format.
1429 + user_agent: string, HTTP User-Agent to provide for this application.
1430 + token_uri: string, URI for token endpoint. For convenience
1431 + defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1432 + revoke_uri: string, URI for revoke endpoint.
1433 + kwargs: kwargs, Additional parameters to add to the JWT token, for
1434 + example sub=joe@xample.org.
1435
1436 - SignedJwtAssertionCredentials requires either PyOpenSSL, or PyCrypto 2.6 or
1437 - later. For App Engine you may also consider using AppAssertionCredentials.
1438 + Raises:
1439 + CryptoUnavailableError if no crypto library is available.
1440 """
1441 + _RequireCryptoOrDie()
1442 + super(SignedJwtAssertionCredentials, self).__init__(
1443 + None,
1444 + user_agent=user_agent,
1445 + token_uri=token_uri,
1446 + revoke_uri=revoke_uri,
1447 + )
1448
1449 - MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
1450 + self.scope = util.scopes_to_string(scope)
1451
1452 - @util.positional(4)
1453 - def __init__(self,
1454 - service_account_name,
1455 - private_key,
1456 - scope,
1457 - private_key_password='notasecret',
1458 - user_agent=None,
1459 - token_uri=GOOGLE_TOKEN_URI,
1460 - revoke_uri=GOOGLE_REVOKE_URI,
1461 - **kwargs):
1462 - """Constructor for SignedJwtAssertionCredentials.
1463 -
1464 - Args:
1465 - service_account_name: string, id for account, usually an email address.
1466 - private_key: string, private key in PKCS12 or PEM format.
1467 - scope: string or iterable of strings, scope(s) of the credentials being
1468 - requested.
1469 - private_key_password: string, password for private_key, unused if
1470 - private_key is in PEM format.
1471 - user_agent: string, HTTP User-Agent to provide for this application.
1472 - token_uri: string, URI for token endpoint. For convenience
1473 - defaults to Google's endpoints but any OAuth 2.0 provider can be used .
1474 - revoke_uri: string, URI for revoke endpoint.
1475 - kwargs: kwargs, Additional parameters to add to the JWT token, for
1476 - example sub=joe@xample.org."""
1477 -
1478 - super(SignedJwtAssertionCredentials, self).__init__(
1479 - None,
1480 - user_agent=user_agent,
1481 - token_uri=token_uri,
1482 - revoke_uri=revoke_uri,
1483 - )
1484 -
1485 - self.scope = util.scopes_to_string(scope)
1486 -
1487 - # Keep base64 encoded so it can be stored in JSON.
1488 - self.private_key = base64.b64encode(private_key)
1489 -
1490 - self.private_key_password = private_key_password
1491 - self.service_account_name = service_account_name
1492 - self.kwargs = kwargs
1493 -
1494 - @classmethod
1495 - def from_json(cls, s):
1496 - data = simplejson.loads(s)
1497 - retval = SignedJwtAssertionCredentials(
1498 - data['service_account_name'],
1499 - base64.b64decode(data['private_key']),
1500 - data['scope'],
1501 - private_key_password=data['private_key_password'],
1502 - user_agent=data['user_agent'],
1503 - token_uri=data['token_uri'],
1504 - **data['kwargs']
1505 - )
1506 - retval.invalid = data['invalid']
1507 - retval.access_token = data['access_token']
1508 - return retval
1509 -
1510 - def _generate_assertion(self):
1511 - """Generate the assertion that will be used in the request."""
1512 - now = long(time.time())
1513 - payload = {
1514 - 'aud': self.token_uri,
1515 - 'scope': self.scope,
1516 - 'iat': now,
1517 - 'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
1518 - 'iss': self.service_account_name
1519 - }
1520 - payload.update(self.kwargs)
1521 - logger.debug(str(payload))
1522 + # Keep base64 encoded so it can be stored in JSON.
1523 + self.private_key = base64.b64encode(private_key)
1524 + if isinstance(self.private_key, six.text_type):
1525 + self.private_key = self.private_key.encode('utf-8')
1526
1527 - private_key = base64.b64decode(self.private_key)
1528 - return crypt.make_signed_jwt(crypt.Signer.from_string(
1529 - private_key, self.private_key_password), payload)
1530 + self.private_key_password = private_key_password
1531 + self.service_account_name = service_account_name
1532 + self.kwargs = kwargs
1533
1534 - # Only used in verify_id_token(), which is always calling to the same URI
1535 - # for the certs.
1536 - _cached_http = httplib2.Http(MemoryCache())
1537 + @classmethod
1538 + def from_json(cls, s):
1539 + data = json.loads(s)
1540 + retval = SignedJwtAssertionCredentials(
1541 + data['service_account_name'],
1542 + base64.b64decode(data['private_key']),
1543 + data['scope'],
1544 + private_key_password=data['private_key_password'],
1545 + user_agent=data['user_agent'],
1546 + token_uri=data['token_uri'],
1547 + **data['kwargs']
1548 + )
1549 + retval.invalid = data['invalid']
1550 + retval.access_token = data['access_token']
1551 + return retval
1552
1553 - @util.positional(2)
1554 - def verify_id_token(id_token, audience, http=None,
1555 - cert_uri=ID_TOKEN_VERIFICATON_CERTS):
1556 - """Verifies a signed JWT id_token.
1557 + def _generate_assertion(self):
1558 + """Generate the assertion that will be used in the request."""
1559 + now = int(time.time())
1560 + payload = {
1561 + 'aud': self.token_uri,
1562 + 'scope': self.scope,
1563 + 'iat': now,
1564 + 'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
1565 + 'iss': self.service_account_name
1566 + }
1567 + payload.update(self.kwargs)
1568 + logger.debug(str(payload))
1569
1570 - This function requires PyOpenSSL and because of that it does not work on
1571 - App Engine.
1572 + private_key = base64.b64decode(self.private_key)
1573 + return crypt.make_signed_jwt(crypt.Signer.from_string(
1574 + private_key, self.private_key_password), payload)
1575
1576 - Args:
1577 - id_token: string, A Signed JWT.
1578 - audience: string, The audience 'aud' that the token should be for.
1579 - http: httplib2.Http, instance to use to make the HTTP request. Callers
1580 - should supply an instance that has caching enabled.
1581 - cert_uri: string, URI of the certificates in JSON format to
1582 - verify the JWT against.
1583 +# Only used in verify_id_token(), which is always calling to the same URI
1584 +# for the certs.
1585 +_cached_http = httplib2.Http(MemoryCache())
1586
1587 - Returns:
1588 - The deserialized JSON in the JWT.
1589 +@util.positional(2)
1590 +def verify_id_token(id_token, audience, http=None,
1591 + cert_uri=ID_TOKEN_VERIFICATION_CERTS):
1592 + """Verifies a signed JWT id_token.
1593
1594 - Raises:
1595 - oauth2client.crypt.AppIdentityError if the JWT fails to verify.
1596 - """
1597 - if http is None:
1598 - http = _cached_http
1599 + This function requires PyOpenSSL and because of that it does not work on
1600 + App Engine.
1601
1602 - resp, content = http.request(cert_uri)
1603 + Args:
1604 + id_token: string, A Signed JWT.
1605 + audience: string, The audience 'aud' that the token should be for.
1606 + http: httplib2.Http, instance to use to make the HTTP request. Callers
1607 + should supply an instance that has caching enabled.
1608 + cert_uri: string, URI of the certificates in JSON format to
1609 + verify the JWT against.
1610
1611 - if resp.status == 200:
1612 - certs = simplejson.loads(content)
1613 - return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
1614 - else:
1615 - raise VerifyJwtTokenError('Status code: %d' % resp.status)
1616 + Returns:
1617 + The deserialized JSON in the JWT.
1618 +
1619 + Raises:
1620 + oauth2client.crypt.AppIdentityError: if the JWT fails to verify.
1621 + CryptoUnavailableError: if no crypto library is available.
1622 + """
1623 + _RequireCryptoOrDie()
1624 + if http is None:
1625 + http = _cached_http
1626 +
1627 + resp, content = http.request(cert_uri)
1628 +
1629 + if resp.status == 200:
1630 + certs = json.loads(content.decode('utf-8'))
1631 + return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
1632 + else:
1633 + raise VerifyJwtTokenError('Status code: %d' % resp.status)
1634
1635
1636 def _urlsafe_b64decode(b64string):
1637 # Guard against unicode strings, which base64 can't handle.
1638 - b64string = b64string.encode('ascii')
1639 - padded = b64string + '=' * (4 - len(b64string) % 4)
1640 + if isinstance(b64string, six.text_type):
1641 + b64string = b64string.encode('ascii')
1642 + padded = b64string + b'=' * (4 - len(b64string) % 4)
1643 return base64.urlsafe_b64decode(padded)
1644
1645
1646 @@ -1026,18 +1579,21 @@ def _extract_id_token(id_token):
1647 Does the extraction w/o checking the signature.
1648
1649 Args:
1650 - id_token: string, OAuth 2.0 id_token.
1651 + id_token: string or bytestring, OAuth 2.0 id_token.
1652
1653 Returns:
1654 object, The deserialized JSON payload.
1655 """
1656 - segments = id_token.split('.')
1657 + if type(id_token) == bytes:
1658 + segments = id_token.split(b'.')
1659 + else:
1660 + segments = id_token.split(u'.')
1661
1662 - if (len(segments) != 3):
1663 + if len(segments) != 3:
1664 raise VerifyJwtTokenError(
1665 - 'Wrong number of segments in token: %s' % id_token)
1666 + 'Wrong number of segments in token: %s' % id_token)
1667
1668 - return simplejson.loads(_urlsafe_b64decode(segments[1]))
1669 + return json.loads(_urlsafe_b64decode(segments[1]).decode('utf-8'))
1670
1671
1672 def _parse_exchange_token_response(content):
1673 @@ -1055,11 +1611,12 @@ def _parse_exchange_token_response(content):
1674 """
1675 resp = {}
1676 try:
1677 - resp = simplejson.loads(content)
1678 - except StandardError:
1679 + resp = json.loads(content.decode('utf-8'))
1680 + except Exception:
1681 # different JSON libs raise different exceptions,
1682 # so we just do a catch-all here
1683 - resp = dict(parse_qsl(content))
1684 + content = content.decode('utf-8')
1685 + resp = dict(urllib.parse.parse_qsl(content))
1686
1687 # some providers respond with 'expires', others with 'expires_in'
1688 if resp and 'expires' in resp:
1689 @@ -1073,14 +1630,15 @@ def credentials_from_code(client_id, client_secret, scop e, code,
1690 redirect_uri='postmessage', http=None,
1691 user_agent=None, token_uri=GOOGLE_TOKEN_URI,
1692 auth_uri=GOOGLE_AUTH_URI,
1693 - revoke_uri=GOOGLE_REVOKE_URI):
1694 + revoke_uri=GOOGLE_REVOKE_URI,
1695 + device_uri=GOOGLE_DEVICE_URI):
1696 """Exchanges an authorization code for an OAuth2Credentials object.
1697
1698 Args:
1699 client_id: string, client identifier.
1700 client_secret: string, client secret.
1701 scope: string or iterable of strings, scope(s) to request.
1702 - code: string, An authroization code, most likely passed down from
1703 + code: string, An authorization code, most likely passed down from
1704 the client
1705 redirect_uri: string, this is generally set to 'postmessage' to match the
1706 redirect_uri that the client specified
1707 @@ -1091,6 +1649,8 @@ def credentials_from_code(client_id, client_secret, scope, code,
1708 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1709 revoke_uri: string, URI for revoke endpoint. For convenience
1710 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1711 + device_uri: string, URI for device authorization endpoint. For convenience
1712 + defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1713
1714 Returns:
1715 An OAuth2Credentials object.
1716 @@ -1102,7 +1662,7 @@ def credentials_from_code(client_id, client_secret, scope, code,
1717 flow = OAuth2WebServerFlow(client_id, client_secret, scope,
1718 redirect_uri=redirect_uri, user_agent=user_agent,
1719 auth_uri=auth_uri, token_uri=token_uri,
1720 - revoke_uri=revoke_uri)
1721 + revoke_uri=revoke_uri, device_uri=device_uri)
1722
1723 credentials = flow.step2_exchange(code, http=http)
1724 return credentials
1725 @@ -1113,7 +1673,8 @@ def credentials_from_clientsecrets_and_code(filename, scop e, code,
1726 message = None,
1727 redirect_uri='postmessage',
1728 http=None,
1729 - cache=None):
1730 + cache=None,
1731 + device_uri=None):
1732 """Returns OAuth2Credentials from a clientsecrets file and an auth code.
1733
1734 Will create the right kind of Flow based on the contents of the clientsecrets
1735 @@ -1133,6 +1694,7 @@ def credentials_from_clientsecrets_and_code(filename, scop e, code,
1736 http: httplib2.Http, optional http instance to use to do the fetch
1737 cache: An optional cache service client that implements get() and set()
1738 methods. See clientsecrets.loadfile() for details.
1739 + device_uri: string, OAuth 2.0 device authorization endpoint
1740
1741 Returns:
1742 An OAuth2Credentials object.
1743 @@ -1145,11 +1707,49 @@ def credentials_from_clientsecrets_and_code(filename, sc ope, code,
1744 invalid.
1745 """
1746 flow = flow_from_clientsecrets(filename, scope, message=message, cache=cache,
1747 - redirect_uri=redirect_uri)
1748 + redirect_uri=redirect_uri,
1749 + device_uri=device_uri)
1750 credentials = flow.step2_exchange(code, http=http)
1751 return credentials
1752
1753
1754 +class DeviceFlowInfo(collections.namedtuple('DeviceFlowInfo', (
1755 + 'device_code', 'user_code', 'interval', 'verification_url',
1756 + 'user_code_expiry'))):
1757 + """Intermediate information the OAuth2 for devices flow."""
1758 +
1759 + @classmethod
1760 + def FromResponse(cls, response):
1761 + """Create a DeviceFlowInfo from a server response.
1762 +
1763 + The response should be a dict containing entries as described here:
1764 +
1765 + http://tools.ietf.org/html/draft-ietf-oauth-v2-05#section-3.7.1
1766 + """
1767 + # device_code, user_code, and verification_url are required.
1768 + kwargs = {
1769 + 'device_code': response['device_code'],
1770 + 'user_code': response['user_code'],
1771 + }
1772 + # The response may list the verification address as either
1773 + # verification_url or verification_uri, so we check for both.
1774 + verification_url = response.get(
1775 + 'verification_url', response.get('verification_uri'))
1776 + if verification_url is None:
1777 + raise OAuth2DeviceCodeError(
1778 + 'No verification_url provided in server response')
1779 + kwargs['verification_url'] = verification_url
1780 + # expires_in and interval are optional.
1781 + kwargs.update({
1782 + 'interval': response.get('interval'),
1783 + 'user_code_expiry': None,
1784 + })
1785 + if 'expires_in' in response:
1786 + kwargs['user_code_expiry'] = datetime.datetime.now() + datetime.timedelta (
1787 + seconds=int(response['expires_in']))
1788 +
1789 + return cls(**kwargs)
1790 +
1791 class OAuth2WebServerFlow(Flow):
1792 """Does the Web Server Flow for OAuth 2.0.
1793
1794 @@ -1163,6 +1763,8 @@ class OAuth2WebServerFlow(Flow):
1795 auth_uri=GOOGLE_AUTH_URI,
1796 token_uri=GOOGLE_TOKEN_URI,
1797 revoke_uri=GOOGLE_REVOKE_URI,
1798 + login_hint=None,
1799 + device_uri=GOOGLE_DEVICE_URI,
1800 **kwargs):
1801 """Constructor for OAuth2WebServerFlow.
1802
1803 @@ -1185,6 +1787,11 @@ class OAuth2WebServerFlow(Flow):
1804 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1805 revoke_uri: string, URI for revoke endpoint. For convenience
1806 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1807 + login_hint: string, Either an email address or domain. Passing this hint
1808 + will either pre-fill the email box on the sign-in form or select the
1809 + proper multi-login session, thereby simplifying the login flow.
1810 + device_uri: string, URI for device authorization endpoint. For convenienc e
1811 + defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1812 **kwargs: dict, The keyword arguments are all optional and required
1813 parameters for the OAuth calls.
1814 """
1815 @@ -1192,10 +1799,12 @@ class OAuth2WebServerFlow(Flow):
1816 self.client_secret = client_secret
1817 self.scope = util.scopes_to_string(scope)
1818 self.redirect_uri = redirect_uri
1819 + self.login_hint = login_hint
1820 self.user_agent = user_agent
1821 self.auth_uri = auth_uri
1822 self.token_uri = token_uri
1823 self.revoke_uri = revoke_uri
1824 + self.device_uri = device_uri
1825 self.params = {
1826 'access_type': 'offline',
1827 'response_type': 'code',
1828 @@ -1216,8 +1825,9 @@ class OAuth2WebServerFlow(Flow):
1829 A URI as a string to redirect the user to begin the authorization flow.
1830 """
1831 if redirect_uri is not None:
1832 - logger.warning(('The redirect_uri parameter for'
1833 - 'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. Please'
1834 + logger.warning((
1835 + 'The redirect_uri parameter for '
1836 + 'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. Please '
1837 'move to passing the redirect_uri in via the constructor.'))
1838 self.redirect_uri = redirect_uri
1839
1840 @@ -1229,45 +1839,107 @@ class OAuth2WebServerFlow(Flow):
1841 'redirect_uri': self.redirect_uri,
1842 'scope': self.scope,
1843 }
1844 + if self.login_hint is not None:
1845 + query_params['login_hint'] = self.login_hint
1846 query_params.update(self.params)
1847 return _update_query_params(self.auth_uri, query_params)
1848
1849 + @util.positional(1)
1850 + def step1_get_device_and_user_codes(self, http=None):
1851 + """Returns a user code and the verification URL where to enter it
1852 +
1853 + Returns:
1854 + A user code as a string for the user to authorize the application
1855 + An URL as a string where the user has to enter the code
1856 + """
1857 + if self.device_uri is None:
1858 + raise ValueError('The value of device_uri must not be None.')
1859 +
1860 + body = urllib.parse.urlencode({
1861 + 'client_id': self.client_id,
1862 + 'scope': self.scope,
1863 + })
1864 + headers = {
1865 + 'content-type': 'application/x-www-form-urlencoded',
1866 + }
1867 +
1868 + if self.user_agent is not None:
1869 + headers['user-agent'] = self.user_agent
1870 +
1871 + if http is None:
1872 + http = httplib2.Http()
1873 +
1874 + resp, content = http.request(self.device_uri, method='POST', body=body,
1875 + headers=headers)
1876 + if resp.status == 200:
1877 + try:
1878 + flow_info = json.loads(content)
1879 + except ValueError as e:
1880 + raise OAuth2DeviceCodeError(
1881 + 'Could not parse server response as JSON: "%s", error: "%s"' % (
1882 + content, e))
1883 + return DeviceFlowInfo.FromResponse(flow_info)
1884 + else:
1885 + error_msg = 'Invalid response %s.' % resp.status
1886 + try:
1887 + d = json.loads(content)
1888 + if 'error' in d:
1889 + error_msg += ' Error: %s' % d['error']
1890 + except ValueError:
1891 + # Couldn't decode a JSON response, stick with the default message.
1892 + pass
1893 + raise OAuth2DeviceCodeError(error_msg)
1894 +
1895 @util.positional(2)
1896 - def step2_exchange(self, code, http=None):
1897 - """Exhanges a code for OAuth2Credentials.
1898 + def step2_exchange(self, code=None, http=None, device_flow_info=None):
1899 + """Exchanges a code for OAuth2Credentials.
1900
1901 Args:
1902 - code: string or dict, either the code as a string, or a dictionary
1903 - of the query parameters to the redirect_uri, which contains
1904 - the code.
1905 - http: httplib2.Http, optional http instance to use to do the fetch
1906 +
1907 + code: string, a dict-like object, or None. For a non-device
1908 + flow, this is either the response code as a string, or a
1909 + dictionary of query parameters to the redirect_uri. For a
1910 + device flow, this should be None.
1911 + http: httplib2.Http, optional http instance to use when fetching
1912 + credentials.
1913 + device_flow_info: DeviceFlowInfo, return value from step1 in the
1914 + case of a device flow.
1915
1916 Returns:
1917 An OAuth2Credentials object that can be used to authorize requests.
1918
1919 Raises:
1920 - FlowExchangeError if a problem occured exchanging the code for a
1921 - refresh_token.
1922 - """
1923 + FlowExchangeError: if a problem occurred exchanging the code for a
1924 + refresh_token.
1925 + ValueError: if code and device_flow_info are both provided or both
1926 + missing.
1927
1928 - if not (isinstance(code, str) or isinstance(code, unicode)):
1929 + """
1930 + if code is None and device_flow_info is None:
1931 + raise ValueError('No code or device_flow_info provided.')
1932 + if code is not None and device_flow_info is not None:
1933 + raise ValueError('Cannot provide both code and device_flow_info.')
1934 +
1935 + if code is None:
1936 + code = device_flow_info.device_code
1937 + elif not isinstance(code, six.string_types):
1938 if 'code' not in code:
1939 - if 'error' in code:
1940 - error_msg = code['error']
1941 - else:
1942 - error_msg = 'No code was supplied in the query parameters.'
1943 - raise FlowExchangeError(error_msg)
1944 - else:
1945 - code = code['code']
1946 + raise FlowExchangeError(code.get(
1947 + 'error', 'No code was supplied in the query parameters.'))
1948 + code = code['code']
1949
1950 - body = urllib.urlencode({
1951 - 'grant_type': 'authorization_code',
1952 + post_data = {
1953 'client_id': self.client_id,
1954 'client_secret': self.client_secret,
1955 'code': code,
1956 - 'redirect_uri': self.redirect_uri,
1957 'scope': self.scope,
1958 - })
1959 + }
1960 + if device_flow_info is not None:
1961 + post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
1962 + else:
1963 + post_data['grant_type'] = 'authorization_code'
1964 + post_data['redirect_uri'] = self.redirect_uri
1965 + body = urllib.parse.urlencode(post_data)
1966 headers = {
1967 'content-type': 'application/x-www-form-urlencoded',
1968 }
1969 @@ -1284,26 +1956,31 @@ class OAuth2WebServerFlow(Flow):
1970 if resp.status == 200 and 'access_token' in d:
1971 access_token = d['access_token']
1972 refresh_token = d.get('refresh_token', None)
1973 + if not refresh_token:
1974 + logger.info(
1975 + 'Received token response with no refresh_token. Consider '
1976 + "reauthenticating with approval_prompt='force'.")
1977 token_expiry = None
1978 if 'expires_in' in d:
1979 token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
1980 seconds=int(d['expires_in']))
1981
1982 + extracted_id_token = None
1983 if 'id_token' in d:
1984 - d['id_token'] = _extract_id_token(d['id_token'])
1985 + extracted_id_token = _extract_id_token(d['id_token'])
1986
1987 logger.info('Successfully retrieved access token')
1988 return OAuth2Credentials(access_token, self.client_id,
1989 self.client_secret, refresh_token, token_expiry,
1990 self.token_uri, self.user_agent,
1991 revoke_uri=self.revoke_uri,
1992 - id_token=d.get('id_token', None),
1993 + id_token=extracted_id_token,
1994 token_response=d)
1995 else:
1996 - logger.info('Failed to retrieve access token: %s' % content)
1997 + logger.info('Failed to retrieve access token: %s', content)
1998 if 'error' in d:
1999 # you never know what those providers got to say
2000 - error_msg = unicode(d['error'])
2001 + error_msg = str(d['error']) + str(d.get('error_description', ''))
2002 else:
2003 error_msg = 'Invalid response: %s.' % str(resp.status)
2004 raise FlowExchangeError(error_msg)
2005 @@ -1311,7 +1988,8 @@ class OAuth2WebServerFlow(Flow):
2006
2007 @util.positional(2)
2008 def flow_from_clientsecrets(filename, scope, redirect_uri=None,
2009 - message=None, cache=None):
2010 + message=None, cache=None, login_hint=None,
2011 + device_uri=None):
2012 """Create a Flow from a clientsecrets file.
2013
2014 Will create the right kind of Flow based on the contents of the clientsecrets
2015 @@ -1329,6 +2007,11 @@ def flow_from_clientsecrets(filename, scope, redirect_uri =None,
2016 provided then clientsecrets.InvalidClientSecretsError will be raised.
2017 cache: An optional cache service client that implements get() and set()
2018 methods. See clientsecrets.loadfile() for details.
2019 + login_hint: string, Either an email address or domain. Passing this hint
2020 + will either pre-fill the email box on the sign-in form or select the
2021 + proper multi-login session, thereby simplifying the login flow.
2022 + device_uri: string, URI for device authorization endpoint. For convenience
2023 + defaults to Google's endpoints but any OAuth 2.0 provider can be used.
2024
2025 Returns:
2026 A Flow object.
2027 @@ -1345,10 +2028,13 @@ def flow_from_clientsecrets(filename, scope, redirect_ur i=None,
2028 'redirect_uri': redirect_uri,
2029 'auth_uri': client_info['auth_uri'],
2030 'token_uri': client_info['token_uri'],
2031 + 'login_hint': login_hint,
2032 }
2033 revoke_uri = client_info.get('revoke_uri')
2034 if revoke_uri is not None:
2035 constructor_kwargs['revoke_uri'] = revoke_uri
2036 + if device_uri is not None:
2037 + constructor_kwargs['device_uri'] = device_uri
2038 return OAuth2WebServerFlow(
2039 client_info['client_id'], client_info['client_secret'],
2040 scope, **constructor_kwargs)
2041 @@ -1360,4 +2046,4 @@ def flow_from_clientsecrets(filename, scope, redirect_uri= None,
2042 raise
2043 else:
2044 raise UnknownClientSecretsFlowError(
2045 - 'This OAuth 2.0 flow is unsupported: %r' % client_type)
2046 + 'This OAuth 2.0 flow is unsupported: %r' % client_type)
2047 \ No newline at end of file
2048 diff --git a/third_party/oauth2client/clientsecrets.py b/third_party/oauth2clien t/clientsecrets.py
2049 index ac99aae..af079ec 100644
2050 --- a/third_party/oauth2client/clientsecrets.py
2051 +++ b/third_party/oauth2client/clientsecrets.py
2052 @@ -1,4 +1,4 @@
2053 -# Copyright (C) 2011 Google Inc.
2054 +# Copyright 2014 Google Inc. All rights reserved.
2055 #
2056 # Licensed under the Apache License, Version 2.0 (the "License");
2057 # you may not use this file except in compliance with the License.
2058 @@ -20,8 +20,10 @@ an OAuth 2.0 protected service.
2059
2060 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
2061
2062 +import json
2063 +
2064 +from third_party import six
2065
2066 -from anyjson import simplejson
2067
2068 # Properties that make a client_secrets.json file valid.
2069 TYPE_WEB = 'web'
2070 @@ -68,11 +70,21 @@ class InvalidClientSecretsError(Error):
2071
2072
2073 def _validate_clientsecrets(obj):
2074 - if obj is None or len(obj) != 1:
2075 - raise InvalidClientSecretsError('Invalid file format.')
2076 - client_type = obj.keys()[0]
2077 - if client_type not in VALID_CLIENT.keys():
2078 - raise InvalidClientSecretsError('Unknown client type: %s.' % client_type)
2079 + _INVALID_FILE_FORMAT_MSG = (
2080 + 'Invalid file format. See '
2081 + 'https://developers.google.com/api-client-library/'
2082 + 'python/guide/aaa_client_secrets')
2083 +
2084 + if obj is None:
2085 + raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG)
2086 + if len(obj) != 1:
2087 + raise InvalidClientSecretsError(
2088 + _INVALID_FILE_FORMAT_MSG + ' '
2089 + 'Expected a JSON object with a single property for a "web" or '
2090 + '"installed" application')
2091 + client_type = tuple(obj)[0]
2092 + if client_type not in VALID_CLIENT:
2093 + raise InvalidClientSecretsError('Unknown client type: %s.' % (client_type,) )
2094 client_info = obj[client_type]
2095 for prop_name in VALID_CLIENT[client_type]['required']:
2096 if prop_name not in client_info:
2097 @@ -87,22 +99,19 @@ def _validate_clientsecrets(obj):
2098
2099
2100 def load(fp):
2101 - obj = simplejson.load(fp)
2102 + obj = json.load(fp)
2103 return _validate_clientsecrets(obj)
2104
2105
2106 def loads(s):
2107 - obj = simplejson.loads(s)
2108 + obj = json.loads(s)
2109 return _validate_clientsecrets(obj)
2110
2111
2112 def _loadfile(filename):
2113 try:
2114 - fp = file(filename, 'r')
2115 - try:
2116 - obj = simplejson.load(fp)
2117 - finally:
2118 - fp.close()
2119 + with open(filename, 'r') as fp:
2120 + obj = json.load(fp)
2121 except IOError:
2122 raise InvalidClientSecretsError('File not found: "%s"' % filename)
2123 return _validate_clientsecrets(obj)
2124 @@ -114,10 +123,12 @@ def loadfile(filename, cache=None):
2125 Typical cache storage would be App Engine memcache service,
2126 but you can pass in any other cache client that implements
2127 these methods:
2128 - - get(key, namespace=ns)
2129 - - set(key, value, namespace=ns)
2130
2131 - Usage:
2132 + * ``get(key, namespace=ns)``
2133 + * ``set(key, value, namespace=ns)``
2134 +
2135 + Usage::
2136 +
2137 # without caching
2138 client_type, client_info = loadfile('secrets.json')
2139 # using App Engine memcache service
2140 @@ -150,4 +161,4 @@ def loadfile(filename, cache=None):
2141 obj = {client_type: client_info}
2142 cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
2143
2144 - return obj.iteritems().next()
2145 + return next(six.iteritems(obj))
2146 \ No newline at end of file
2147 diff --git a/third_party/oauth2client/crypt.py b/third_party/oauth2client/crypt. py
2148 index 2d31815..b5b8cab 100644
2149 --- a/third_party/oauth2client/crypt.py
2150 +++ b/third_party/oauth2client/crypt.py
2151 @@ -1,7 +1,6 @@
2152 -#!/usr/bin/python2.4
2153 # -*- coding: utf-8 -*-
2154 #
2155 -# Copyright (C) 2011 Google Inc.
2156 +# Copyright 2014 Google Inc. All rights reserved.
2157 #
2158 # Licensed under the Apache License, Version 2.0 (the "License");
2159 # you may not use this file except in compliance with the License.
2160 @@ -14,13 +13,15 @@
2161 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2162 # See the License for the specific language governing permissions and
2163 # limitations under the License.
2164 +"""Crypto-related routines for oauth2client."""
2165
2166 import base64
2167 -import hashlib
2168 +import json
2169 import logging
2170 +import sys
2171 import time
2172
2173 -from anyjson import simplejson
2174 +from third_party import six
2175
2176
2177 CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
2178 @@ -38,7 +39,6 @@ class AppIdentityError(Exception):
2179 try:
2180 from OpenSSL import crypto
2181
2182 -
2183 class OpenSSLVerifier(object):
2184 """Verifies the signature on a message."""
2185
2186 @@ -62,6 +62,8 @@ try:
2187 key that this object was constructed with.
2188 """
2189 try:
2190 + if isinstance(message, six.text_type):
2191 + message = message.encode('utf-8')
2192 crypto.verify(self._pubkey, signature, message, 'sha256')
2193 return True
2194 except:
2195 @@ -104,15 +106,17 @@ try:
2196 """Signs a message.
2197
2198 Args:
2199 - message: string, Message to be signed.
2200 + message: bytes, Message to be signed.
2201
2202 Returns:
2203 string, The signature of the message for the given key.
2204 """
2205 + if isinstance(message, six.text_type):
2206 + message = message.encode('utf-8')
2207 return crypto.sign(self._key, message, 'sha256')
2208
2209 @staticmethod
2210 - def from_string(key, password='notasecret'):
2211 + def from_string(key, password=b'notasecret'):
2212 """Construct a Signer instance from a string.
2213
2214 Args:
2215 @@ -125,21 +129,45 @@ try:
2216 Raises:
2217 OpenSSL.crypto.Error if the key can't be parsed.
2218 """
2219 - if key.startswith('-----BEGIN '):
2220 - pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
2221 + parsed_pem_key = _parse_pem_key(key)
2222 + if parsed_pem_key:
2223 + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
2224 else:
2225 + if isinstance(password, six.text_type):
2226 + password = password.encode('utf-8')
2227 pkey = crypto.load_pkcs12(key, password).get_privatekey()
2228 return OpenSSLSigner(pkey)
2229
2230 +
2231 + def pkcs12_key_as_pem(private_key_text, private_key_password):
2232 + """Convert the contents of a PKCS12 key to PEM using OpenSSL.
2233 +
2234 + Args:
2235 + private_key_text: String. Private key.
2236 + private_key_password: String. Password for PKCS12.
2237 +
2238 + Returns:
2239 + String. PEM contents of ``private_key_text``.
2240 + """
2241 + decoded_body = base64.b64decode(private_key_text)
2242 + if isinstance(private_key_password, six.string_types):
2243 + private_key_password = private_key_password.encode('ascii')
2244 +
2245 + pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
2246 + return crypto.dump_privatekey(crypto.FILETYPE_PEM,
2247 + pkcs12.get_privatekey())
2248 except ImportError:
2249 OpenSSLVerifier = None
2250 OpenSSLSigner = None
2251 + def pkcs12_key_as_pem(*args, **kwargs):
2252 + raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.')
2253
2254
2255 try:
2256 from Crypto.PublicKey import RSA
2257 from Crypto.Hash import SHA256
2258 from Crypto.Signature import PKCS1_v1_5
2259 + from Crypto.Util.asn1 import DerSequence
2260
2261
2262 class PyCryptoVerifier(object):
2263 @@ -181,14 +209,17 @@ try:
2264
2265 Returns:
2266 Verifier instance.
2267 -
2268 - Raises:
2269 - NotImplementedError if is_x509_cert is true.
2270 """
2271 if is_x509_cert:
2272 - raise NotImplementedError(
2273 - 'X509 certs are not supported by the PyCrypto library. '
2274 - 'Try using PyOpenSSL if native code is an option.')
2275 + if isinstance(key_pem, six.text_type):
2276 + key_pem = key_pem.encode('ascii')
2277 + pemLines = key_pem.replace(b' ', b'').split()
2278 + certDer = _urlsafe_b64decode(b''.join(pemLines[1:-1]))
2279 + certSeq = DerSequence()
2280 + certSeq.decode(certDer)
2281 + tbsSeq = DerSequence()
2282 + tbsSeq.decode(certSeq[0])
2283 + pubkey = RSA.importKey(tbsSeq[6])
2284 else:
2285 pubkey = RSA.importKey(key_pem)
2286 return PyCryptoVerifier(pubkey)
2287 @@ -214,6 +245,8 @@ try:
2288 Returns:
2289 string, The signature of the message for the given key.
2290 """
2291 + if isinstance(message, six.text_type):
2292 + message = message.encode('utf-8')
2293 return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
2294
2295 @staticmethod
2296 @@ -230,11 +263,12 @@ try:
2297 Raises:
2298 NotImplementedError if they key isn't in PEM format.
2299 """
2300 - if key.startswith('-----BEGIN '):
2301 - pkey = RSA.importKey(key)
2302 + parsed_pem_key = _parse_pem_key(key)
2303 + if parsed_pem_key:
2304 + pkey = RSA.importKey(parsed_pem_key)
2305 else:
2306 raise NotImplementedError(
2307 - 'PKCS12 format is not supported by the PyCrpto library. '
2308 + 'PKCS12 format is not supported by the PyCrypto library. '
2309 'Try converting to a "PEM" '
2310 '(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
2311 'or using PyOpenSSL if native code is an option.')
2312 @@ -256,19 +290,39 @@ else:
2313 'PyOpenSSL, or PyCrypto 2.6 or later')
2314
2315
2316 +def _parse_pem_key(raw_key_input):
2317 + """Identify and extract PEM keys.
2318 +
2319 + Determines whether the given key is in the format of PEM key, and extracts
2320 + the relevant part of the key if it is.
2321 +
2322 + Args:
2323 + raw_key_input: The contents of a private key file (either PEM or PKCS12).
2324 +
2325 + Returns:
2326 + string, The actual key if the contents are from a PEM file, or else None.
2327 + """
2328 + offset = raw_key_input.find(b'-----BEGIN ')
2329 + if offset != -1:
2330 + return raw_key_input[offset:]
2331 +
2332 +
2333 def _urlsafe_b64encode(raw_bytes):
2334 - return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
2335 + if isinstance(raw_bytes, six.text_type):
2336 + raw_bytes = raw_bytes.encode('utf-8')
2337 + return base64.urlsafe_b64encode(raw_bytes).decode('ascii').rstrip('=')
2338
2339
2340 def _urlsafe_b64decode(b64string):
2341 # Guard against unicode strings, which base64 can't handle.
2342 - b64string = b64string.encode('ascii')
2343 - padded = b64string + '=' * (4 - len(b64string) % 4)
2344 + if isinstance(b64string, six.text_type):
2345 + b64string = b64string.encode('ascii')
2346 + padded = b64string + b'=' * (4 - len(b64string) % 4)
2347 return base64.urlsafe_b64decode(padded)
2348
2349
2350 def _json_encode(data):
2351 - return simplejson.dumps(data, separators = (',', ':'))
2352 + return json.dumps(data, separators=(',', ':'))
2353
2354
2355 def make_signed_jwt(signer, payload):
2356 @@ -286,8 +340,8 @@ def make_signed_jwt(signer, payload):
2357 header = {'typ': 'JWT', 'alg': 'RS256'}
2358
2359 segments = [
2360 - _urlsafe_b64encode(_json_encode(header)),
2361 - _urlsafe_b64encode(_json_encode(payload)),
2362 + _urlsafe_b64encode(_json_encode(header)),
2363 + _urlsafe_b64encode(_json_encode(payload)),
2364 ]
2365 signing_input = '.'.join(segments)
2366
2367 @@ -318,9 +372,8 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
2368 """
2369 segments = jwt.split('.')
2370
2371 - if (len(segments) != 3):
2372 - raise AppIdentityError(
2373 - 'Wrong number of segments in token: %s' % jwt)
2374 + if len(segments) != 3:
2375 + raise AppIdentityError('Wrong number of segments in token: %s' % jwt)
2376 signed = '%s.%s' % (segments[0], segments[1])
2377
2378 signature = _urlsafe_b64decode(segments[2])
2379 @@ -328,15 +381,15 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
2380 # Parse token.
2381 json_body = _urlsafe_b64decode(segments[1])
2382 try:
2383 - parsed = simplejson.loads(json_body)
2384 + parsed = json.loads(json_body.decode('utf-8'))
2385 except:
2386 raise AppIdentityError('Can\'t parse token: %s' % json_body)
2387
2388 # Check signature.
2389 verified = False
2390 - for (keyname, pem) in certs.items():
2391 + for pem in certs.values():
2392 verifier = Verifier.from_string(pem, True)
2393 - if (verifier.verify(signed, signature)):
2394 + if verifier.verify(signed, signature):
2395 verified = True
2396 break
2397 if not verified:
2398 @@ -349,21 +402,20 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
2399 earliest = iat - CLOCK_SKEW_SECS
2400
2401 # Check expiration timestamp.
2402 - now = long(time.time())
2403 + now = int(time.time())
2404 exp = parsed.get('exp')
2405 if exp is None:
2406 raise AppIdentityError('No exp field in token: %s' % json_body)
2407 if exp >= now + MAX_TOKEN_LIFETIME_SECS:
2408 - raise AppIdentityError(
2409 - 'exp field too far in future: %s' % json_body)
2410 + raise AppIdentityError('exp field too far in future: %s' % json_body)
2411 latest = exp + CLOCK_SKEW_SECS
2412
2413 if now < earliest:
2414 raise AppIdentityError('Token used too early, %d < %d: %s' %
2415 - (now, earliest, json_body))
2416 + (now, earliest, json_body))
2417 if now > latest:
2418 raise AppIdentityError('Token used too late, %d > %d: %s' %
2419 - (now, latest, json_body))
2420 + (now, latest, json_body))
2421
2422 # Check audience.
2423 if audience is not None:
2424 @@ -372,6 +424,6 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
2425 raise AppIdentityError('No aud field in token: %s' % json_body)
2426 if aud != audience:
2427 raise AppIdentityError('Wrong recipient, %s != %s: %s' %
2428 - (aud, audience, json_body))
2429 + (aud, audience, json_body))
2430
2431 - return parsed
2432 + return parsed
2433 \ No newline at end of file
2434 diff --git a/third_party/oauth2client/devshell.py b/third_party/oauth2client/dev shell.py
2435 new file mode 100644
2436 index 0000000..69d3808
2437 --- /dev/null
2438 +++ b/third_party/oauth2client/devshell.py
2439 @@ -0,0 +1,135 @@
2440 +# Copyright 2015 Google Inc. All Rights Reserved.
2441 +#
2442 +# Licensed under the Apache License, Version 2.0 (the "License");
2443 +# you may not use this file except in compliance with the License.
2444 +# You may obtain a copy of the License at
2445 +#
2446 +# http://www.apache.org/licenses/LICENSE-2.0
2447 +#
2448 +# Unless required by applicable law or agreed to in writing, software
2449 +# distributed under the License is distributed on an "AS IS" BASIS,
2450 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2451 +# See the License for the specific language governing permissions and
2452 +# limitations under the License.
2453 +
2454 +"""OAuth 2.0 utitilies for Google Developer Shell environment."""
2455 +
2456 +import json
2457 +import os
2458 +
2459 +from . import client
2460 +
2461 +
2462 +DEVSHELL_ENV = 'DEVSHELL_CLIENT_PORT'
2463 +
2464 +
2465 +class Error(Exception):
2466 + """Errors for this module."""
2467 + pass
2468 +
2469 +
2470 +class CommunicationError(Error):
2471 + """Errors for communication with the Developer Shell server."""
2472 +
2473 +
2474 +class NoDevshellServer(Error):
2475 + """Error when no Developer Shell server can be contacted."""
2476 +
2477 +
2478 +# The request for credential information to the Developer Shell client socket i s
2479 +# always an empty PBLite-formatted JSON object, so just define it as a constant .
2480 +CREDENTIAL_INFO_REQUEST_JSON = '[]'
2481 +
2482 +
2483 +class CredentialInfoResponse(object):
2484 + """Credential information response from Developer Shell server.
2485 +
2486 + The credential information response from Developer Shell socket is a
2487 + PBLite-formatted JSON array with fields encoded by their index in the array:
2488 + * Index 0 - user email
2489 + * Index 1 - default project ID. None if the project context is not known.
2490 + * Index 2 - OAuth2 access token. None if there is no valid auth context.
2491 + """
2492 +
2493 + def __init__(self, json_string):
2494 + """Initialize the response data from JSON PBLite array."""
2495 + pbl = json.loads(json_string)
2496 + if not isinstance(pbl, list):
2497 + raise ValueError('Not a list: ' + str(pbl))
2498 + pbl_len = len(pbl)
2499 + self.user_email = pbl[0] if pbl_len > 0 else None
2500 + self.project_id = pbl[1] if pbl_len > 1 else None
2501 + self.access_token = pbl[2] if pbl_len > 2 else None
2502 +
2503 +
2504 +def _SendRecv():
2505 + """Communicate with the Developer Shell server socket."""
2506 +
2507 + port = int(os.getenv(DEVSHELL_ENV, 0))
2508 + if port == 0:
2509 + raise NoDevshellServer()
2510 +
2511 + import socket
2512 +
2513 + sock = socket.socket()
2514 + sock.connect(('localhost', port))
2515 +
2516 + data = CREDENTIAL_INFO_REQUEST_JSON
2517 + msg = '%s\n%s' % (len(data), data)
2518 + sock.sendall(msg.encode())
2519 +
2520 + header = sock.recv(6).decode()
2521 + if '\n' not in header:
2522 + raise CommunicationError('saw no newline in the first 6 bytes')
2523 + len_str, json_str = header.split('\n', 1)
2524 + to_read = int(len_str) - len(json_str)
2525 + if to_read > 0:
2526 + json_str += sock.recv(to_read, socket.MSG_WAITALL).decode()
2527 +
2528 + return CredentialInfoResponse(json_str)
2529 +
2530 +
2531 +class DevshellCredentials(client.GoogleCredentials):
2532 + """Credentials object for Google Developer Shell environment.
2533 +
2534 + This object will allow a Google Developer Shell session to identify its user
2535 + to Google and other OAuth 2.0 servers that can verify assertions. It can be
2536 + used for the purpose of accessing data stored under the user account.
2537 +
2538 + This credential does not require a flow to instantiate because it represents
2539 + a two legged flow, and therefore has all of the required information to
2540 + generate and refresh its own access tokens.
2541 + """
2542 +
2543 + def __init__(self, user_agent=None):
2544 + super(DevshellCredentials, self).__init__(
2545 + None, # access_token, initialized below
2546 + None, # client_id
2547 + None, # client_secret
2548 + None, # refresh_token
2549 + None, # token_expiry
2550 + None, # token_uri
2551 + user_agent)
2552 + self._refresh(None)
2553 +
2554 + def _refresh(self, http_request):
2555 + self.devshell_response = _SendRecv()
2556 + self.access_token = self.devshell_response.access_token
2557 +
2558 + @property
2559 + def user_email(self):
2560 + return self.devshell_response.user_email
2561 +
2562 + @property
2563 + def project_id(self):
2564 + return self.devshell_response.project_id
2565 +
2566 + @classmethod
2567 + def from_json(cls, json_data):
2568 + raise NotImplementedError(
2569 + 'Cannot load Developer Shell credentials from JSON.')
2570 +
2571 + @property
2572 + def serialization_data(self):
2573 + raise NotImplementedError(
2574 + 'Cannot serialize Developer Shell credentials.')
2575 diff --git a/third_party/oauth2client/django_orm.py b/third_party/oauth2client/d jango_orm.py
2576 index d54d20c..ffbcf92 100644
2577 --- a/third_party/oauth2client/django_orm.py
2578 +++ b/third_party/oauth2client/django_orm.py
2579 @@ -1,4 +1,4 @@
2580 -# Copyright (C) 2010 Google Inc.
2581 +# Copyright 2014 Google Inc. All rights reserved.
2582 #
2583 # Licensed under the Apache License, Version 2.0 (the "License");
2584 # you may not use this file except in compliance with the License.
2585 @@ -116,14 +116,21 @@ class Storage(BaseStorage):
2586 credential.set_store(self)
2587 return credential
2588
2589 - def locked_put(self, credentials):
2590 + def locked_put(self, credentials, overwrite=False):
2591 """Write a Credentials to the datastore.
2592
2593 Args:
2594 credentials: Credentials, the credentials to store.
2595 + overwrite: Boolean, indicates whether you would like these credentials to
2596 + overwrite any existing stored credentials.
2597 """
2598 args = {self.key_name: self.key_value}
2599 - entity = self.model_class(**args)
2600 +
2601 + if overwrite:
2602 + entity, unused_is_new = self.model_class.objects.get_or_create(**args)
2603 + else:
2604 + entity = self.model_class(**args)
2605 +
2606 setattr(entity, self.property_name, credentials)
2607 entity.save()
2608
2609 @@ -131,4 +138,4 @@ class Storage(BaseStorage):
2610 """Delete Credentials from the datastore."""
2611
2612 query = {self.key_name: self.key_value}
2613 - entities = self.model_class.objects.filter(**query).delete()
2614 + entities = self.model_class.objects.filter(**query).delete()
2615 \ No newline at end of file
2616 diff --git a/third_party/oauth2client/file.py b/third_party/oauth2client/file.py
2617 index 1895f94..4337fcf 100644
2618 --- a/third_party/oauth2client/file.py
2619 +++ b/third_party/oauth2client/file.py
2620 @@ -1,4 +1,4 @@
2621 -# Copyright (C) 2010 Google Inc.
2622 +# Copyright 2014 Google Inc. All rights reserved.
2623 #
2624 # Licensed under the Apache License, Version 2.0 (the "License");
2625 # you may not use this file except in compliance with the License.
2626 @@ -21,12 +21,10 @@ credentials.
2627 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
2628
2629 import os
2630 -import stat
2631 import threading
2632
2633 -from anyjson import simplejson
2634 -from client import Storage as BaseStorage
2635 from client import Credentials
2636 +from client import Storage as BaseStorage
2637
2638
2639 class CredentialsFileSymbolicLinkError(Exception):
2640 @@ -92,7 +90,7 @@ class Storage(BaseStorage):
2641 simple version of "touch" to ensure the file has been created.
2642 """
2643 if not os.path.exists(self._filename):
2644 - old_umask = os.umask(0177)
2645 + old_umask = os.umask(0o177)
2646 try:
2647 open(self._filename, 'a+b').close()
2648 finally:
2649 @@ -110,7 +108,7 @@ class Storage(BaseStorage):
2650
2651 self._create_file_if_needed()
2652 self._validate_file()
2653 - f = open(self._filename, 'wb')
2654 + f = open(self._filename, 'w')
2655 f.write(credentials.to_json())
2656 f.close()
2657
2658 @@ -121,4 +119,4 @@ class Storage(BaseStorage):
2659 credentials: Credentials, the credentials to store.
2660 """
2661
2662 - os.unlink(self._filename)
2663 + os.unlink(self._filename)
2664 \ No newline at end of file
2665 diff --git a/third_party/oauth2client/gce.py b/third_party/oauth2client/gce.py
2666 index c7fd7c1..ebd14e3 100644
2667 --- a/third_party/oauth2client/gce.py
2668 +++ b/third_party/oauth2client/gce.py
2669 @@ -1,4 +1,4 @@
2670 -# Copyright (C) 2012 Google Inc.
2671 +# Copyright 2014 Google Inc. All rights reserved.
2672 #
2673 # Licensed under the Apache License, Version 2.0 (the "License");
2674 # you may not use this file except in compliance with the License.
2675 @@ -19,14 +19,13 @@ Utilities for making it easier to use OAuth 2.0 on Google Co mpute Engine.
2676
2677 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
2678
2679 -import httplib2
2680 +import json
2681 import logging
2682 -import uritemplate
2683 +from third_party.six.moves import urllib
2684
2685 -from oauth2client import util
2686 -from oauth2client.anyjson import simplejson
2687 -from oauth2client.client import AccessTokenRefreshError
2688 -from oauth2client.client import AssertionCredentials
2689 +from third_party.oauth2client import util
2690 +from third_party.oauth2client.client import AccessTokenRefreshError
2691 +from third_party.oauth2client.client import AssertionCredentials
2692
2693 logger = logging.getLogger(__name__)
2694
2695 @@ -57,13 +56,14 @@ class AppAssertionCredentials(AssertionCredentials):
2696 requested.
2697 """
2698 self.scope = util.scopes_to_string(scope)
2699 + self.kwargs = kwargs
2700
2701 # Assertion type is no longer used, but still in the parent class signature .
2702 super(AppAssertionCredentials, self).__init__(None)
2703
2704 @classmethod
2705 - def from_json(cls, json):
2706 - data = simplejson.loads(json)
2707 + def from_json(cls, json_data):
2708 + data = json.loads(json_data)
2709 return AppAssertionCredentials(data['scope'])
2710
2711 def _refresh(self, http_request):
2712 @@ -78,13 +78,28 @@ class AppAssertionCredentials(AssertionCredentials):
2713 Raises:
2714 AccessTokenRefreshError: When the refresh fails.
2715 """
2716 - uri = uritemplate.expand(META, {'scope': self.scope})
2717 + query = '?scope=%s' % urllib.parse.quote(self.scope, '')
2718 + uri = META.replace('{?scope}', query)
2719 response, content = http_request(uri)
2720 if response.status == 200:
2721 try:
2722 - d = simplejson.loads(content)
2723 - except StandardError, e:
2724 + d = json.loads(content)
2725 + except Exception as e:
2726 raise AccessTokenRefreshError(str(e))
2727 self.access_token = d['accessToken']
2728 else:
2729 + if response.status == 404:
2730 + content += (' This can occur if a VM was created'
2731 + ' with no service account or scopes.')
2732 raise AccessTokenRefreshError(content)
2733 +
2734 + @property
2735 + def serialization_data(self):
2736 + raise NotImplementedError(
2737 + 'Cannot serialize credentials for GCE service accounts.')
2738 +
2739 + def create_scoped_required(self):
2740 + return not self.scope
2741 +
2742 + def create_scoped(self, scopes):
2743 + return AppAssertionCredentials(scopes, **self.kwargs)
2744 \ No newline at end of file
2745 diff --git a/third_party/oauth2client/keyring_storage.py b/third_party/oauth2cli ent/keyring_storage.py
2746 index efe2949..512ac77 100644
2747 --- a/third_party/oauth2client/keyring_storage.py
2748 +++ b/third_party/oauth2client/keyring_storage.py
2749 @@ -1,4 +1,4 @@
2750 -# Copyright (C) 2012 Google Inc.
2751 +# Copyright 2014 Google Inc. All rights reserved.
2752 #
2753 # Licensed under the Apache License, Version 2.0 (the "License");
2754 # you may not use this file except in compliance with the License.
2755 @@ -19,11 +19,12 @@ A Storage for Credentials that uses the keyring module.
2756
2757 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
2758
2759 -import keyring
2760 import threading
2761
2762 -from client import Storage as BaseStorage
2763 +import keyring
2764 +
2765 from client import Credentials
2766 +from client import Storage as BaseStorage
2767
2768
2769 class Storage(BaseStorage):
2770 @@ -106,4 +107,4 @@ class Storage(BaseStorage):
2771 Args:
2772 credentials: Credentials, the credentials to store.
2773 """
2774 - keyring.set_password(self._service_name, self._user_name, '')
2775 + keyring.set_password(self._service_name, self._user_name, '')
2776 \ No newline at end of file
2777 diff --git a/third_party/oauth2client/locked_file.py b/third_party/oauth2client/ locked_file.py
2778 index 858b702..fa2c6c1 100644
2779 --- a/third_party/oauth2client/locked_file.py
2780 +++ b/third_party/oauth2client/locked_file.py
2781 @@ -1,4 +1,4 @@
2782 -# Copyright 2011 Google Inc.
2783 +# Copyright 2014 Google Inc. All rights reserved.
2784 #
2785 # Licensed under the Apache License, Version 2.0 (the "License");
2786 # you may not use this file except in compliance with the License.
2787 @@ -17,17 +17,21 @@
2788 This module first tries to use fcntl locking to ensure serialized access
2789 to a file, then falls back on a lock file if that is unavialable.
2790
2791 -Usage:
2792 +Usage::
2793 +
2794 f = LockedFile('filename', 'r+b', 'rb')
2795 f.open_and_lock()
2796 if f.is_locked():
2797 - print 'Acquired filename with r+b mode'
2798 + print('Acquired filename with r+b mode')
2799 f.file_handle().write('locked data')
2800 else:
2801 - print 'Aquired filename with rb mode'
2802 + print('Acquired filename with rb mode')
2803 f.unlock_and_close()
2804 +
2805 """
2806
2807 +from __future__ import print_function
2808 +
2809 __author__ = 'cache@google.com (David T McWherter)'
2810
2811 import errno
2812 @@ -70,6 +74,7 @@ class _Opener(object):
2813 self._mode = mode
2814 self._fallback_mode = fallback_mode
2815 self._fh = None
2816 + self._lock_fd = None
2817
2818 def is_locked(self):
2819 """Was the file locked."""
2820 @@ -122,7 +127,7 @@ class _PosixOpener(_Opener):
2821 validate_file(self._filename)
2822 try:
2823 self._fh = open(self._filename, self._mode)
2824 - except IOError, e:
2825 + except IOError as e:
2826 # If we can't access with _mode, try _fallback_mode and don't lock.
2827 if e.errno == errno.EACCES:
2828 self._fh = open(self._filename, self._fallback_mode)
2829 @@ -137,12 +142,12 @@ class _PosixOpener(_Opener):
2830 self._locked = True
2831 break
2832
2833 - except OSError, e:
2834 + except OSError as e:
2835 if e.errno != errno.EEXIST:
2836 raise
2837 if (time.time() - start_time) >= timeout:
2838 - logger.warn('Could not acquire lock %s in %s seconds' % (
2839 - lock_filename, timeout))
2840 + logger.warn('Could not acquire lock %s in %s seconds',
2841 + lock_filename, timeout)
2842 # Close the file and open in fallback_mode.
2843 if self._fh:
2844 self._fh.close()
2845 @@ -192,9 +197,9 @@ try:
2846 validate_file(self._filename)
2847 try:
2848 self._fh = open(self._filename, self._mode)
2849 - except IOError, e:
2850 + except IOError as e:
2851 # If we can't access with _mode, try _fallback_mode and don't lock.
2852 - if e.errno == errno.EACCES:
2853 + if e.errno in (errno.EPERM, errno.EACCES):
2854 self._fh = open(self._filename, self._fallback_mode)
2855 return
2856
2857 @@ -204,16 +209,16 @@ try:
2858 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
2859 self._locked = True
2860 return
2861 - except IOError, e:
2862 + except IOError as e:
2863 # If not retrying, then just pass on the error.
2864 if timeout == 0:
2865 - raise e
2866 + raise
2867 if e.errno != errno.EACCES:
2868 - raise e
2869 + raise
2870 # We could not acquire the lock. Try again.
2871 if (time.time() - start_time) >= timeout:
2872 - logger.warn('Could not lock %s in %s seconds' % (
2873 - self._filename, timeout))
2874 + logger.warn('Could not lock %s in %s seconds',
2875 + self._filename, timeout)
2876 if self._fh:
2877 self._fh.close()
2878 self._fh = open(self._filename, self._fallback_mode)
2879 @@ -267,7 +272,7 @@ try:
2880 validate_file(self._filename)
2881 try:
2882 self._fh = open(self._filename, self._mode)
2883 - except IOError, e:
2884 + except IOError as e:
2885 # If we can't access with _mode, try _fallback_mode and don't lock.
2886 if e.errno == errno.EACCES:
2887 self._fh = open(self._filename, self._fallback_mode)
2888 @@ -284,9 +289,9 @@ try:
2889 pywintypes.OVERLAPPED())
2890 self._locked = True
2891 return
2892 - except pywintypes.error, e:
2893 + except pywintypes.error as e:
2894 if timeout == 0:
2895 - raise e
2896 + raise
2897
2898 # If the error is not that the file is already in use, raise.
2899 if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
2900 @@ -308,7 +313,7 @@ try:
2901 try:
2902 hfile = win32file._get_osfhandle(self._fh.fileno())
2903 win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
2904 - except pywintypes.error, e:
2905 + except pywintypes.error as e:
2906 if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
2907 raise
2908 self._locked = False
2909 @@ -370,4 +375,4 @@ class LockedFile(object):
2910
2911 def unlock_and_close(self):
2912 """Unlock and close a file."""
2913 - self._opener.unlock_and_close()
2914 + self._opener.unlock_and_close()
2915 \ No newline at end of file
2916 diff --git a/third_party/oauth2client/multistore_file.py b/third_party/oauth2cli ent/multistore_file.py
2917 index ea89027..5008c00 100644
2918 --- a/third_party/oauth2client/multistore_file.py
2919 +++ b/third_party/oauth2client/multistore_file.py
2920 @@ -1,4 +1,4 @@
2921 -# Copyright 2011 Google Inc.
2922 +# Copyright 2014 Google Inc. All rights reserved.
2923 #
2924 # Licensed under the Apache License, Version 2.0 (the "License");
2925 # you may not use this file except in compliance with the License.
2926 @@ -19,39 +19,41 @@ credentials can be stored in one file. That file supports lo cking
2927 both in a single process and across processes.
2928
2929 The credential themselves are keyed off of:
2930 +
2931 * client_id
2932 * user_agent
2933 * scope
2934
2935 -The format of the stored data is like so:
2936 -{
2937 - 'file_version': 1,
2938 - 'data': [
2939 - {
2940 - 'key': {
2941 - 'clientId': '<client id>',
2942 - 'userAgent': '<user agent>',
2943 - 'scope': '<scope>'
2944 - },
2945 - 'credential': {
2946 - # JSON serialized Credentials.
2947 +The format of the stored data is like so::
2948 +
2949 + {
2950 + 'file_version': 1,
2951 + 'data': [
2952 + {
2953 + 'key': {
2954 + 'clientId': '<client id>',
2955 + 'userAgent': '<user agent>',
2956 + 'scope': '<scope>'
2957 + },
2958 + 'credential': {
2959 + # JSON serialized Credentials.
2960 + }
2961 }
2962 - }
2963 - ]
2964 -}
2965 + ]
2966 + }
2967 +
2968 """
2969
2970 __author__ = 'jbeda@google.com (Joe Beda)'
2971
2972 -import base64
2973 import errno
2974 +import json
2975 import logging
2976 import os
2977 import threading
2978
2979 -from anyjson import simplejson
2980 -from .client import Storage as BaseStorage
2981 from .client import Credentials
2982 +from .client import Storage as BaseStorage
2983 from . import util
2984 from locked_file import LockedFile
2985
2986 @@ -64,12 +66,10 @@ _multistores_lock = threading.Lock()
2987
2988 class Error(Exception):
2989 """Base error for this module."""
2990 - pass
2991
2992
2993 class NewerCredentialStoreError(Error):
2994 - """The credential store is a newer version that supported."""
2995 - pass
2996 + """The credential store is a newer version than supported."""
2997
2998
2999 @util.positional(4)
3000 @@ -193,7 +193,7 @@ class _MultiStore(object):
3001
3002 This will create the file if necessary.
3003 """
3004 - self._file = LockedFile(filename, 'r+b', 'rb')
3005 + self._file = LockedFile(filename, 'r+', 'r')
3006 self._thread_lock = threading.Lock()
3007 self._read_only = False
3008 self._warn_on_readonly = warn_on_readonly
3009 @@ -271,7 +271,7 @@ class _MultiStore(object):
3010 simple version of "touch" to ensure the file has been created.
3011 """
3012 if not os.path.exists(self._file.filename()):
3013 - old_umask = os.umask(0177)
3014 + old_umask = os.umask(0o177)
3015 try:
3016 open(self._file.filename(), 'a+b').close()
3017 finally:
3018 @@ -280,13 +280,23 @@ class _MultiStore(object):
3019 def _lock(self):
3020 """Lock the entire multistore."""
3021 self._thread_lock.acquire()
3022 - self._file.open_and_lock()
3023 + try:
3024 + self._file.open_and_lock()
3025 + except IOError as e:
3026 + if e.errno == errno.ENOSYS:
3027 + logger.warn('File system does not support locking the credentials '
3028 + 'file.')
3029 + elif e.errno == errno.ENOLCK:
3030 + logger.warn('File system is out of resources for writing the '
3031 + 'credentials file (is your disk full?).')
3032 + else:
3033 + raise
3034 if not self._file.is_locked():
3035 self._read_only = True
3036 if self._warn_on_readonly:
3037 logger.warn('The credentials file (%s) is not writable. Opening in '
3038 'read-only mode. Any refreshed credentials will only be '
3039 - 'valid for this run.' % self._file.filename())
3040 + 'valid for this run.', self._file.filename())
3041 if os.path.getsize(self._file.filename()) == 0:
3042 logger.debug('Initializing empty multistore file')
3043 # The multistore is empty so write out an empty file.
3044 @@ -315,7 +325,7 @@ class _MultiStore(object):
3045 """
3046 assert self._thread_lock.locked()
3047 self._file.file_handle().seek(0)
3048 - return simplejson.load(self._file.file_handle())
3049 + return json.load(self._file.file_handle())
3050
3051 def _locked_json_write(self, data):
3052 """Write a JSON serializable data structure to the multistore.
3053 @@ -329,7 +339,7 @@ class _MultiStore(object):
3054 if self._read_only:
3055 return
3056 self._file.file_handle().seek(0)
3057 - simplejson.dump(data, self._file.file_handle(), sort_keys=True, indent=2)
3058 + json.dump(data, self._file.file_handle(), sort_keys=True, indent=2, separat ors=(',', ': '))
3059 self._file.file_handle().truncate()
3060
3061 def _refresh_data_cache(self):
3062 @@ -387,7 +397,7 @@ class _MultiStore(object):
3063 raw_key = cred_entry['key']
3064 key = util.dict_to_tuple_key(raw_key)
3065 credential = None
3066 - credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credent ial']))
3067 + credential = Credentials.new_from_json(json.dumps(cred_entry['credential']) )
3068 return (key, credential)
3069
3070 def _write(self):
3071 @@ -400,7 +410,7 @@ class _MultiStore(object):
3072 raw_data['data'] = raw_creds
3073 for (cred_key, cred) in self._data.items():
3074 raw_key = dict(cred_key)
3075 - raw_cred = simplejson.loads(cred.to_json())
3076 + raw_cred = json.loads(cred.to_json())
3077 raw_creds.append({'key': raw_key, 'credential': raw_cred})
3078 self._locked_json_write(raw_data)
3079
3080 @@ -462,4 +472,4 @@ class _MultiStore(object):
3081 Returns:
3082 A Storage object that can be used to get/set this cred
3083 """
3084 - return self._Storage(self, key)
3085 + return self._Storage(self, key)
3086 \ No newline at end of file
3087 diff --git a/third_party/oauth2client/old_run.py b/third_party/oauth2client/old_ run.py
3088 index da23358..221fff8 100644
3089 --- a/third_party/oauth2client/old_run.py
3090 +++ b/third_party/oauth2client/old_run.py
3091 @@ -1,4 +1,4 @@
3092 -# Copyright (C) 2013 Google Inc.
3093 +# Copyright 2014 Google Inc. All rights reserved.
3094 #
3095 # Licensed under the Apache License, Version 2.0 (the "License");
3096 # you may not use this file except in compliance with the License.
3097 @@ -15,6 +15,7 @@
3098 """This module holds the old run() function which is deprecated, the
3099 tools.run_flow() function should be used in its place."""
3100
3101 +from __future__ import print_function
3102
3103 import logging
3104 import socket
3105 @@ -22,9 +23,9 @@ import sys
3106 import webbrowser
3107
3108 import gflags
3109 -
3110 -from oauth2client import client
3111 -from oauth2client import util
3112 +from third_party.six.moves import input
3113 +from third_party.oauth2client import client
3114 +from third_party.oauth2client import util
3115 from tools import ClientRedirectHandler
3116 from tools import ClientRedirectServer
3117
3118 @@ -48,39 +49,38 @@ gflags.DEFINE_multi_int('auth_host_port', [8080, 8090],
3119 def run(flow, storage, http=None):
3120 """Core code for a command-line application.
3121
3122 - The run() function is called from your application and runs through all
3123 - the steps to obtain credentials. It takes a Flow argument and attempts to
3124 - open an authorization server page in the user's default web browser. The
3125 - server asks the user to grant your application access to the user's data.
3126 - If the user grants access, the run() function returns new credentials. The
3127 - new credentials are also stored in the Storage argument, which updates the
3128 - file associated with the Storage object.
3129 + The ``run()`` function is called from your application and runs
3130 + through all the steps to obtain credentials. It takes a ``Flow``
3131 + argument and attempts to open an authorization server page in the
3132 + user's default web browser. The server asks the user to grant your
3133 + application access to the user's data. If the user grants access,
3134 + the ``run()`` function returns new credentials. The new credentials
3135 + are also stored in the ``storage`` argument, which updates the file
3136 + associated with the ``Storage`` object.
3137
3138 It presumes it is run from a command-line application and supports the
3139 following flags:
3140
3141 - --auth_host_name: Host name to use when running a local web server
3142 - to handle redirects during OAuth authorization.
3143 - (default: 'localhost')
3144 + ``--auth_host_name`` (string, default: ``localhost``)
3145 + Host name to use when running a local web server to handle
3146 + redirects during OAuth authorization.
3147
3148 - --auth_host_port: Port to use when running a local web server to handle
3149 - redirects during OAuth authorization.;
3150 - repeat this option to specify a list of values
3151 - (default: '[8080, 8090]')
3152 - (an integer)
3153 + ``--auth_host_port`` (integer, default: ``[8080, 8090]``)
3154 + Port to use when running a local web server to handle redirects
3155 + during OAuth authorization. Repeat this option to specify a list
3156 + of values.
3157
3158 - --[no]auth_local_webserver: Run a local web server to handle redirects
3159 - during OAuth authorization.
3160 - (default: 'true')
3161 + ``--[no]auth_local_webserver`` (boolean, default: ``True``)
3162 + Run a local web server to handle redirects during OAuth authorization.
3163
3164 - Since it uses flags make sure to initialize the gflags module before
3165 - calling run().
3166 + Since it uses flags make sure to initialize the ``gflags`` module before
3167 + calling ``run()``.
3168
3169 Args:
3170 flow: Flow, an OAuth 2.0 Flow to step through.
3171 - storage: Storage, a Storage to store the credential in.
3172 - http: An instance of httplib2.Http.request
3173 - or something that acts like it.
3174 + storage: Storage, a ``Storage`` to store the credential in.
3175 + http: An instance of ``httplib2.Http.request`` or something that acts
3176 + like it.
3177
3178 Returns:
3179 Credentials, the obtained credential.
3180 @@ -96,20 +96,20 @@ def run(flow, storage, http=None):
3181 try:
3182 httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
3183 ClientRedirectHandler)
3184 - except socket.error, e:
3185 + except socket.error as e:
3186 pass
3187 else:
3188 success = True
3189 break
3190 FLAGS.auth_local_webserver = success
3191 if not success:
3192 - print 'Failed to start a local webserver listening on either port 8080'
3193 - print 'or port 9090. Please check your firewall settings and locally'
3194 - print 'running programs that may be blocking or using those ports.'
3195 - print
3196 - print 'Falling back to --noauth_local_webserver and continuing with',
3197 - print 'authorization.'
3198 - print
3199 + print('Failed to start a local webserver listening on either port 8080')
3200 + print('or port 9090. Please check your firewall settings and locally')
3201 + print('running programs that may be blocking or using those ports.')
3202 + print()
3203 + print('Falling back to --noauth_local_webserver and continuing with')
3204 + print('authorization.')
3205 + print()
3206
3207 if FLAGS.auth_local_webserver:
3208 oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
3209 @@ -120,20 +120,20 @@ def run(flow, storage, http=None):
3210
3211 if FLAGS.auth_local_webserver:
3212 webbrowser.open(authorize_url, new=1, autoraise=True)
3213 - print 'Your browser has been opened to visit:'
3214 - print
3215 - print ' ' + authorize_url
3216 - print
3217 - print 'If your browser is on a different machine then exit and re-run'
3218 - print 'this application with the command-line parameter '
3219 - print
3220 - print ' --noauth_local_webserver'
3221 - print
3222 + print('Your browser has been opened to visit:')
3223 + print()
3224 + print(' ' + authorize_url)
3225 + print()
3226 + print('If your browser is on a different machine then exit and re-run')
3227 + print('this application with the command-line parameter ')
3228 + print()
3229 + print(' --noauth_local_webserver')
3230 + print()
3231 else:
3232 - print 'Go to the following link in your browser:'
3233 - print
3234 - print ' ' + authorize_url
3235 - print
3236 + print('Go to the following link in your browser:')
3237 + print()
3238 + print(' ' + authorize_url)
3239 + print()
3240
3241 code = None
3242 if FLAGS.auth_local_webserver:
3243 @@ -143,18 +143,18 @@ def run(flow, storage, http=None):
3244 if 'code' in httpd.query_params:
3245 code = httpd.query_params['code']
3246 else:
3247 - print 'Failed to find "code" in the query parameters of the redirect.'
3248 + print('Failed to find "code" in the query parameters of the redirect.')
3249 sys.exit('Try running with --noauth_local_webserver.')
3250 else:
3251 - code = raw_input('Enter verification code: ').strip()
3252 + code = input('Enter verification code: ').strip()
3253
3254 try:
3255 credential = flow.step2_exchange(code, http=http)
3256 - except client.FlowExchangeError, e:
3257 + except client.FlowExchangeError as e:
3258 sys.exit('Authentication has failed: %s' % e)
3259
3260 storage.put(credential)
3261 credential.set_store(storage)
3262 - print 'Authentication successful.'
3263 + print('Authentication successful.')
3264
3265 - return credential
3266 + return credential
3267 \ No newline at end of file
3268 diff --git a/third_party/oauth2client/service_account.py b/third_party/oauth2cli ent/service_account.py
3269 new file mode 100644
3270 index 0000000..b6b694c
3271 --- /dev/null
3272 +++ b/third_party/oauth2client/service_account.py
3273 @@ -0,0 +1,139 @@
3274 +# Copyright 2014 Google Inc. All rights reserved.
3275 +#
3276 +# Licensed under the Apache License, Version 2.0 (the "License");
3277 +# you may not use this file except in compliance with the License.
3278 +# You may obtain a copy of the License at
3279 +#
3280 +# http://www.apache.org/licenses/LICENSE-2.0
3281 +#
3282 +# Unless required by applicable law or agreed to in writing, software
3283 +# distributed under the License is distributed on an "AS IS" BASIS,
3284 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3285 +# See the License for the specific language governing permissions and
3286 +# limitations under the License.
3287 +
3288 +"""A service account credentials class.
3289 +
3290 +This credentials class is implemented on top of rsa library.
3291 +"""
3292 +
3293 +import base64
3294 +import json
3295 +import time
3296 +
3297 +from pyasn1.codec.ber import decoder
3298 +from pyasn1_modules.rfc5208 import PrivateKeyInfo
3299 +import rsa
3300 +
3301 +from . import GOOGLE_REVOKE_URI
3302 +from . import GOOGLE_TOKEN_URI
3303 +from . import util
3304 +from client import AssertionCredentials
3305 +from third_party import six
3306 +
3307 +
3308 +class _ServiceAccountCredentials(AssertionCredentials):
3309 + """Class representing a service account (signed JWT) credential."""
3310 +
3311 + MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
3312 +
3313 + def __init__(self, service_account_id, service_account_email, private_key_id,
3314 + private_key_pkcs8_text, scopes, user_agent=None,
3315 + token_uri=GOOGLE_TOKEN_URI, revoke_uri=GOOGLE_REVOKE_URI,
3316 + **kwargs):
3317 +
3318 + super(_ServiceAccountCredentials, self).__init__(
3319 + None, user_agent=user_agent, token_uri=token_uri, revoke_uri=revoke_uri )
3320 +
3321 + self._service_account_id = service_account_id
3322 + self._service_account_email = service_account_email
3323 + self._private_key_id = private_key_id
3324 + self._private_key = _get_private_key(private_key_pkcs8_text)
3325 + self._private_key_pkcs8_text = private_key_pkcs8_text
3326 + self._scopes = util.scopes_to_string(scopes)
3327 + self._user_agent = user_agent
3328 + self._token_uri = token_uri
3329 + self._revoke_uri = revoke_uri
3330 + self._kwargs = kwargs
3331 +
3332 + def _generate_assertion(self):
3333 + """Generate the assertion that will be used in the request."""
3334 +
3335 + header = {
3336 + 'alg': 'RS256',
3337 + 'typ': 'JWT',
3338 + 'kid': self._private_key_id
3339 + }
3340 +
3341 + now = int(time.time())
3342 + payload = {
3343 + 'aud': self._token_uri,
3344 + 'scope': self._scopes,
3345 + 'iat': now,
3346 + 'exp': now + _ServiceAccountCredentials.MAX_TOKEN_LIFETIME_SECS,
3347 + 'iss': self._service_account_email
3348 + }
3349 + payload.update(self._kwargs)
3350 +
3351 + assertion_input = (_urlsafe_b64encode(header) + b'.' +
3352 + _urlsafe_b64encode(payload))
3353 +
3354 + # Sign the assertion.
3355 + rsa_bytes = rsa.pkcs1.sign(assertion_input, self._private_key, 'SHA-256')
3356 + signature = base64.urlsafe_b64encode(rsa_bytes).rstrip(b'=')
3357 +
3358 + return assertion_input + b'.' + signature
3359 +
3360 + def sign_blob(self, blob):
3361 + # Ensure that it is bytes
3362 + try:
3363 + blob = blob.encode('utf-8')
3364 + except AttributeError:
3365 + pass
3366 + return (self._private_key_id,
3367 + rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
3368 +
3369 + @property
3370 + def service_account_email(self):
3371 + return self._service_account_email
3372 +
3373 + @property
3374 + def serialization_data(self):
3375 + return {
3376 + 'type': 'service_account',
3377 + 'client_id': self._service_account_id,
3378 + 'client_email': self._service_account_email,
3379 + 'private_key_id': self._private_key_id,
3380 + 'private_key': self._private_key_pkcs8_text
3381 + }
3382 +
3383 + def create_scoped_required(self):
3384 + return not self._scopes
3385 +
3386 + def create_scoped(self, scopes):
3387 + return _ServiceAccountCredentials(self._service_account_id,
3388 + self._service_account_email,
3389 + self._private_key_id,
3390 + self._private_key_pkcs8_text,
3391 + scopes,
3392 + user_agent=self._user_agent,
3393 + token_uri=self._token_uri,
3394 + revoke_uri=self._revoke_uri,
3395 + **self._kwargs)
3396 +
3397 +
3398 +def _urlsafe_b64encode(data):
3399 + return base64.urlsafe_b64encode(
3400 + json.dumps(data, separators=(',', ':')).encode('UTF-8')).rstrip(b'=')
3401 +
3402 +
3403 +def _get_private_key(private_key_pkcs8_text):
3404 + """Get an RSA private key object from a pkcs8 representation."""
3405 +
3406 + if not isinstance(private_key_pkcs8_text, six.binary_type):
3407 + private_key_pkcs8_text = private_key_pkcs8_text.encode('ascii')
3408 + der = rsa.pem.load_pem(private_key_pkcs8_text, 'PRIVATE KEY')
3409 + asn1_private_key, _ = decoder.decode(der, asn1Spec=PrivateKeyInfo())
3410 + return rsa.PrivateKey.load_pkcs1(
3411 + asn1_private_key.getComponentByName('privateKey').asOctets(),
3412 + format='DER')
3413 \ No newline at end of file
3414 diff --git a/third_party/oauth2client/tools.py b/third_party/oauth2client/tools. py
3415 index 12c68bf..3df1bbe 100644
3416 --- a/third_party/oauth2client/tools.py
3417 +++ b/third_party/oauth2client/tools.py
3418 @@ -1,4 +1,4 @@
3419 -# Copyright (C) 2013 Google Inc.
3420 +# Copyright 2014 Google Inc. All rights reserved.
3421 #
3422 # Licensed under the Apache License, Version 2.0 (the "License");
3423 # you may not use this file except in compliance with the License.
3424 @@ -19,27 +19,22 @@ generated credentials in a common file that is used by other example apps in
3425 the same directory.
3426 """
3427
3428 +from __future__ import print_function
3429 +
3430 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
3431 __all__ = ['argparser', 'run_flow', 'run', 'message_if_missing']
3432
3433 -
3434 -import BaseHTTPServer
3435 -import argparse
3436 -import httplib2
3437 import logging
3438 -import os
3439 import socket
3440 import sys
3441 -import webbrowser
3442
3443 -from oauth2client import client
3444 -from oauth2client import file
3445 -from oauth2client import util
3446 +from third_party.six.moves import BaseHTTPServer
3447 +from third_party.six.moves import urllib
3448 +from third_party.six.moves import input
3449 +
3450 +from . import client
3451 +from . import util
3452
3453 -try:
3454 - from urlparse import parse_qsl
3455 -except ImportError:
3456 - from cgi import parse_qsl
3457
3458 _CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
3459
3460 @@ -52,20 +47,27 @@ with information from the APIs Console <https://code.google. com/apis/console>.
3461
3462 """
3463
3464 -# run_parser is an ArgumentParser that contains command-line options expected
3465 +def _CreateArgumentParser():
3466 + try:
3467 + import argparse
3468 + except ImportError:
3469 + return None
3470 + parser = argparse.ArgumentParser(add_help=False)
3471 + parser.add_argument('--auth_host_name', default='localhost',
3472 + help='Hostname when running a local web server.')
3473 + parser.add_argument('--noauth_local_webserver', action='store_true',
3474 + default=False, help='Do not run a local web server.')
3475 + parser.add_argument('--auth_host_port', default=[8080, 8090], type=int,
3476 + nargs='*', help='Port web server should listen on.')
3477 + parser.add_argument('--logging_level', default='ERROR',
3478 + choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] ,
3479 + help='Set the logging level of detail.')
3480 + return parser
3481 +
3482 +# argparser is an ArgumentParser that contains command-line options expected
3483 # by tools.run(). Pass it in as part of the 'parents' argument to your own
3484 # ArgumentParser.
3485 -argparser = argparse.ArgumentParser(add_help=False)
3486 -argparser.add_argument('--auth_host_name', default='localhost',
3487 - help='Hostname when running a local web server.')
3488 -argparser.add_argument('--noauth_local_webserver', action='store_true',
3489 - default=False, help='Do not run a local web server.')
3490 -argparser.add_argument('--auth_host_port', default=[8080, 8090], type=int,
3491 - nargs='*', help='Port web server should listen on.')
3492 -argparser.add_argument('--logging_level', default='ERROR',
3493 - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR',
3494 - 'CRITICAL'],
3495 - help='Set the logging level of detail.')
3496 +argparser = _CreateArgumentParser()
3497
3498
3499 class ClientRedirectServer(BaseHTTPServer.HTTPServer):
3500 @@ -84,72 +86,75 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHa ndler):
3501 into the servers query_params and then stops serving.
3502 """
3503
3504 - def do_GET(s):
3505 + def do_GET(self):
3506 """Handle a GET request.
3507
3508 Parses the query parameters and prints a message
3509 if the flow has completed. Note that we can't detect
3510 if an error occurred.
3511 """
3512 - s.send_response(200)
3513 - s.send_header("Content-type", "text/html")
3514 - s.end_headers()
3515 - query = s.path.split('?', 1)[-1]
3516 - query = dict(parse_qsl(query))
3517 - s.server.query_params = query
3518 - s.wfile.write("<html><head><title>Authentication Status</title></head>")
3519 - s.wfile.write("<body><p>The authentication flow has completed.</p>")
3520 - s.wfile.write("</body></html>")
3521 + self.send_response(200)
3522 + self.send_header("Content-type", "text/html")
3523 + self.end_headers()
3524 + query = self.path.split('?', 1)[-1]
3525 + query = dict(urllib.parse.parse_qsl(query))
3526 + self.server.query_params = query
3527 + self.wfile.write(b"<html><head><title>Authentication Status</title></head>" )
3528 + self.wfile.write(b"<body><p>The authentication flow has completed.</p>")
3529 + self.wfile.write(b"</body></html>")
3530
3531 def log_message(self, format, *args):
3532 """Do not log messages to stdout while running as command line program."""
3533 - pass
3534
3535
3536 @util.positional(3)
3537 def run_flow(flow, storage, flags, http=None):
3538 """Core code for a command-line application.
3539
3540 - The run() function is called from your application and runs through all the
3541 - steps to obtain credentials. It takes a Flow argument and attempts to open an
3542 - authorization server page in the user's default web browser. The server asks
3543 - the user to grant your application access to the user's data. If the user
3544 - grants access, the run() function returns new credentials. The new credential s
3545 - are also stored in the Storage argument, which updates the file associated
3546 - with the Storage object.
3547 + The ``run()`` function is called from your application and runs
3548 + through all the steps to obtain credentials. It takes a ``Flow``
3549 + argument and attempts to open an authorization server page in the
3550 + user's default web browser. The server asks the user to grant your
3551 + application access to the user's data. If the user grants access,
3552 + the ``run()`` function returns new credentials. The new credentials
3553 + are also stored in the ``storage`` argument, which updates the file
3554 + associated with the ``Storage`` object.
3555
3556 It presumes it is run from a command-line application and supports the
3557 following flags:
3558
3559 - --auth_host_name: Host name to use when running a local web server
3560 - to handle redirects during OAuth authorization.
3561 - (default: 'localhost')
3562 + ``--auth_host_name`` (string, default: ``localhost``)
3563 + Host name to use when running a local web server to handle
3564 + redirects during OAuth authorization.
3565 +
3566 + ``--auth_host_port`` (integer, default: ``[8080, 8090]``)
3567 + Port to use when running a local web server to handle redirects
3568 + during OAuth authorization. Repeat this option to specify a list
3569 + of values.
3570 +
3571 + ``--[no]auth_local_webserver`` (boolean, default: ``True``)
3572 + Run a local web server to handle redirects during OAuth authorization.
3573 +
3574
3575 - --auth_host_port: Port to use when running a local web server to handle
3576 - redirects during OAuth authorization.;
3577 - repeat this option to specify a list of values
3578 - (default: '[8080, 8090]')
3579 - (an integer)
3580
3581 - --[no]auth_local_webserver: Run a local web server to handle redirects
3582 - during OAuth authorization.
3583 - (default: 'true')
3584
3585 - The tools module defines an ArgumentParser the already contains the flag
3586 - definitions that run() requires. You can pass that ArgumentParser to your
3587 - ArgumentParser constructor:
3588 + The tools module defines an ``ArgumentParser`` the already contains the flag
3589 + definitions that ``run()`` requires. You can pass that ``ArgumentParser`` to your
3590 + ``ArgumentParser`` constructor::
3591
3592 - parser = argparse.ArgumentParser(description=__doc__,
3593 - formatter_class=argparse.RawDescriptionHelpFormatter,
3594 - parents=[tools.run_parser])
3595 - flags = parser.parse_args(argv)
3596 + parser = argparse.ArgumentParser(description=__doc__,
3597 + formatter_class=argparse.RawDescriptionHelpFormatter,
3598 + parents=[tools.argparser])
3599 + flags = parser.parse_args(argv)
3600
3601 Args:
3602 flow: Flow, an OAuth 2.0 Flow to step through.
3603 - storage: Storage, a Storage to store the credential in.
3604 - flags: argparse.ArgumentParser, the command-line flags.
3605 - http: An instance of httplib2.Http.request
3606 - or something that acts like it.
3607 + storage: Storage, a ``Storage`` to store the credential in.
3608 + flags: ``argparse.Namespace``, The command-line flags. This is the
3609 + object returned from calling ``parse_args()`` on
3610 + ``argparse.ArgumentParser`` as described above.
3611 + http: An instance of ``httplib2.Http.request`` or something that
3612 + acts like it.
3613
3614 Returns:
3615 Credentials, the obtained credential.
3616 @@ -163,20 +168,20 @@ def run_flow(flow, storage, flags, http=None):
3617 try:
3618 httpd = ClientRedirectServer((flags.auth_host_name, port),
3619 ClientRedirectHandler)
3620 - except socket.error, e:
3621 + except socket.error:
3622 pass
3623 else:
3624 success = True
3625 break
3626 flags.noauth_local_webserver = not success
3627 if not success:
3628 - print 'Failed to start a local webserver listening on either port 8080'
3629 - print 'or port 9090. Please check your firewall settings and locally'
3630 - print 'running programs that may be blocking or using those ports.'
3631 - print
3632 - print 'Falling back to --noauth_local_webserver and continuing with',
3633 - print 'authorization.'
3634 - print
3635 + print('Failed to start a local webserver listening on either port 8080')
3636 + print('or port 9090. Please check your firewall settings and locally')
3637 + print('running programs that may be blocking or using those ports.')
3638 + print()
3639 + print('Falling back to --noauth_local_webserver and continuing with')
3640 + print('authorization.')
3641 + print()
3642
3643 if not flags.noauth_local_webserver:
3644 oauth_callback = 'http://%s:%s/' % (flags.auth_host_name, port_number)
3645 @@ -186,21 +191,22 @@ def run_flow(flow, storage, flags, http=None):
3646 authorize_url = flow.step1_get_authorize_url()
3647
3648 if not flags.noauth_local_webserver:
3649 + import webbrowser
3650 webbrowser.open(authorize_url, new=1, autoraise=True)
3651 - print 'Your browser has been opened to visit:'
3652 - print
3653 - print ' ' + authorize_url
3654 - print
3655 - print 'If your browser is on a different machine then exit and re-run this'
3656 - print 'application with the command-line parameter '
3657 - print
3658 - print ' --noauth_local_webserver'
3659 - print
3660 + print('Your browser has been opened to visit:')
3661 + print()
3662 + print(' ' + authorize_url)
3663 + print()
3664 + print('If your browser is on a different machine then exit and re-run this' )
3665 + print('application with the command-line parameter ')
3666 + print()
3667 + print(' --noauth_local_webserver')
3668 + print()
3669 else:
3670 - print 'Go to the following link in your browser:'
3671 - print
3672 - print ' ' + authorize_url
3673 - print
3674 + print('Go to the following link in your browser:')
3675 + print()
3676 + print(' ' + authorize_url)
3677 + print()
3678
3679 code = None
3680 if not flags.noauth_local_webserver:
3681 @@ -210,19 +216,19 @@ def run_flow(flow, storage, flags, http=None):
3682 if 'code' in httpd.query_params:
3683 code = httpd.query_params['code']
3684 else:
3685 - print 'Failed to find "code" in the query parameters of the redirect.'
3686 + print('Failed to find "code" in the query parameters of the redirect.')
3687 sys.exit('Try running with --noauth_local_webserver.')
3688 else:
3689 - code = raw_input('Enter verification code: ').strip()
3690 + code = input('Enter verification code: ').strip()
3691
3692 try:
3693 credential = flow.step2_exchange(code, http=http)
3694 - except client.FlowExchangeError, e:
3695 + except client.FlowExchangeError as e:
3696 sys.exit('Authentication has failed: %s' % e)
3697
3698 storage.put(credential)
3699 credential.set_store(storage)
3700 - print 'Authentication successful.'
3701 + print('Authentication successful.')
3702
3703 return credential
3704
3705 @@ -233,11 +239,11 @@ def message_if_missing(filename):
3706 return _CLIENT_SECRETS_MESSAGE % filename
3707
3708 try:
3709 - from old_run import run
3710 - from old_run import FLAGS
3711 + from oauth2client.old_run import run
3712 + from oauth2client.old_run import FLAGS
3713 except ImportError:
3714 def run(*args, **kwargs):
3715 raise NotImplementedError(
3716 'The gflags library must be installed to use tools.run(). '
3717 'Please install gflags or preferrably switch to using '
3718 - 'tools.run_flow().')
3719 + 'tools.run_flow().')
3720 \ No newline at end of file
3721 diff --git a/third_party/oauth2client/util.py b/third_party/oauth2client/util.py
3722 index 90dff15..b0963ef 100644
3723 --- a/third_party/oauth2client/util.py
3724 +++ b/third_party/oauth2client/util.py
3725 @@ -1,6 +1,6 @@
3726 #!/usr/bin/env python
3727 #
3728 -# Copyright 2010 Google Inc.
3729 +# Copyright 2014 Google Inc. All rights reserved.
3730 #
3731 # Licensed under the Apache License, Version 2.0 (the "License");
3732 # you may not use this file except in compliance with the License.
3733 @@ -17,26 +17,27 @@
3734
3735 """Common utility library."""
3736
3737 -__author__ = ['rafek@google.com (Rafe Kaplan)',
3738 - 'guido@google.com (Guido van Rossum)',
3739 +__author__ = [
3740 + 'rafek@google.com (Rafe Kaplan)',
3741 + 'guido@google.com (Guido van Rossum)',
3742 ]
3743 +
3744 __all__ = [
3745 - 'positional',
3746 - 'POSITIONAL_WARNING',
3747 - 'POSITIONAL_EXCEPTION',
3748 - 'POSITIONAL_IGNORE',
3749 + 'positional',
3750 + 'POSITIONAL_WARNING',
3751 + 'POSITIONAL_EXCEPTION',
3752 + 'POSITIONAL_IGNORE',
3753 ]
3754
3755 +import functools
3756 import inspect
3757 import logging
3758 +import sys
3759 import types
3760 -import urllib
3761 -import urlparse
3762
3763 -try:
3764 - from urlparse import parse_qsl
3765 -except ImportError:
3766 - from cgi import parse_qsl
3767 +from third_party import six
3768 +from third_party.six.moves import urllib
3769 +
3770
3771 logger = logging.getLogger(__name__)
3772
3773 @@ -51,56 +52,58 @@ positional_parameters_enforcement = POSITIONAL_WARNING
3774 def positional(max_positional_args):
3775 """A decorator to declare that only the first N arguments my be positional.
3776
3777 - This decorator makes it easy to support Python 3 style key-word only
3778 - parameters. For example, in Python 3 it is possible to write:
3779 + This decorator makes it easy to support Python 3 style keyword-only
3780 + parameters. For example, in Python 3 it is possible to write::
3781
3782 def fn(pos1, *, kwonly1=None, kwonly1=None):
3783 ...
3784
3785 - All named parameters after * must be a keyword:
3786 + All named parameters after ``*`` must be a keyword::
3787
3788 fn(10, 'kw1', 'kw2') # Raises exception.
3789 fn(10, kwonly1='kw1') # Ok.
3790
3791 - Example:
3792 - To define a function like above, do:
3793 + Example
3794 + ^^^^^^^
3795
3796 - @positional(1)
3797 - def fn(pos1, kwonly1=None, kwonly2=None):
3798 - ...
3799 + To define a function like above, do::
3800
3801 - If no default value is provided to a keyword argument, it becomes a require d
3802 - keyword argument:
3803 + @positional(1)
3804 + def fn(pos1, kwonly1=None, kwonly2=None):
3805 + ...
3806
3807 - @positional(0)
3808 - def fn(required_kw):
3809 - ...
3810 + If no default value is provided to a keyword argument, it becomes a required
3811 + keyword argument::
3812
3813 - This must be called with the keyword parameter:
3814 + @positional(0)
3815 + def fn(required_kw):
3816 + ...
3817
3818 - fn() # Raises exception.
3819 - fn(10) # Raises exception.
3820 - fn(required_kw=10) # Ok.
3821 + This must be called with the keyword parameter::
3822
3823 - When defining instance or class methods always remember to account for
3824 - 'self' and 'cls':
3825 + fn() # Raises exception.
3826 + fn(10) # Raises exception.
3827 + fn(required_kw=10) # Ok.
3828
3829 - class MyClass(object):
3830 + When defining instance or class methods always remember to account for
3831 + ``self`` and ``cls``::
3832
3833 - @positional(2)
3834 - def my_method(self, pos1, kwonly1=None):
3835 - ...
3836 + class MyClass(object):
3837
3838 - @classmethod
3839 - @positional(2)
3840 - def my_method(cls, pos1, kwonly1=None):
3841 - ...
3842 + @positional(2)
3843 + def my_method(self, pos1, kwonly1=None):
3844 + ...
3845 +
3846 + @classmethod
3847 + @positional(2)
3848 + def my_method(cls, pos1, kwonly1=None):
3849 + ...
3850
3851 The positional decorator behavior is controlled by
3852 - util.positional_parameters_enforcement, which may be set to
3853 - POSITIONAL_EXCEPTION, POSITIONAL_WARNING or POSITIONAL_IGNORE to raise an
3854 - exception, log a warning, or do nothing, respectively, if a declaration is
3855 - violated.
3856 + ``util.positional_parameters_enforcement``, which may be set to
3857 + ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
3858 + ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
3859 + nothing, respectively, if a declaration is violated.
3860
3861 Args:
3862 max_positional_arguments: Maximum number of positional arguments. All
3863 @@ -114,8 +117,10 @@ def positional(max_positional_args):
3864 TypeError if a key-word only argument is provided as a positional
3865 parameter, but only if util.positional_parameters_enforcement is set to
3866 POSITIONAL_EXCEPTION.
3867 +
3868 """
3869 def positional_decorator(wrapped):
3870 + @functools.wraps(wrapped)
3871 def positional_wrapper(*args, **kwargs):
3872 if len(args) > max_positional_args:
3873 plural_s = ''
3874 @@ -132,7 +137,7 @@ def positional(max_positional_args):
3875 return wrapped(*args, **kwargs)
3876 return positional_wrapper
3877
3878 - if isinstance(max_positional_args, (int, long)):
3879 + if isinstance(max_positional_args, six.integer_types):
3880 return positional_decorator
3881 else:
3882 args, _, _, defaults = inspect.getargspec(max_positional_args)
3883 @@ -152,7 +157,7 @@ def scopes_to_string(scopes):
3884 Returns:
3885 The scopes formatted as a single string.
3886 """
3887 - if isinstance(scopes, types.StringTypes):
3888 + if isinstance(scopes, six.string_types):
3889 return scopes
3890 else:
3891 return ' '.join(scopes)
3892 @@ -189,8 +194,8 @@ def _add_query_parameter(url, name, value):
3893 if value is None:
3894 return url
3895 else:
3896 - parsed = list(urlparse.urlparse(url))
3897 - q = dict(parse_qsl(parsed[4]))
3898 + parsed = list(urllib.parse.urlparse(url))
3899 + q = dict(urllib.parse.parse_qsl(parsed[4]))
3900 q[name] = value
3901 - parsed[4] = urllib.urlencode(q)
3902 - return urlparse.urlunparse(parsed)
3903 + parsed[4] = urllib.parse.urlencode(q)
3904 + return urllib.parse.urlunparse(parsed)
3905 \ No newline at end of file
3906 diff --git a/third_party/oauth2client/xsrfutil.py b/third_party/oauth2client/xsr futil.py
3907 index 7e1fe5c..bea7c87 100644
3908 --- a/third_party/oauth2client/xsrfutil.py
3909 +++ b/third_party/oauth2client/xsrfutil.py
3910 @@ -1,6 +1,5 @@
3911 -#!/usr/bin/python2.5
3912 #
3913 -# Copyright 2010 the Melange authors.
3914 +# Copyright 2014 the Melange authors.
3915 #
3916 # Licensed under the Apache License, Version 2.0 (the "License");
3917 # you may not use this file except in compliance with the License.
3918 @@ -17,25 +16,36 @@
3919 """Helper methods for creating & verifying XSRF tokens."""
3920
3921 __authors__ = [
3922 - '"Doug Coker" <dcoker@google.com>',
3923 - '"Joe Gregorio" <jcgregorio@google.com>',
3924 + '"Doug Coker" <dcoker@google.com>',
3925 + '"Joe Gregorio" <jcgregorio@google.com>',
3926 ]
3927
3928
3929 import base64
3930 import hmac
3931 -import os # for urandom
3932 import time
3933
3934 +import six
3935 from oauth2client import util
3936
3937
3938 # Delimiter character
3939 -DELIMITER = ':'
3940 +DELIMITER = b':'
3941 +
3942
3943 # 1 hour in seconds
3944 DEFAULT_TIMEOUT_SECS = 1*60*60
3945
3946 +
3947 +def _force_bytes(s):
3948 + if isinstance(s, bytes):
3949 + return s
3950 + s = str(s)
3951 + if isinstance(s, six.text_type):
3952 + return s.encode('utf-8')
3953 + return s
3954 +
3955 +
3956 @util.positional(2)
3957 def generate_token(key, user_id, action_id="", when=None):
3958 """Generates a URL-safe token for the given user, action, time tuple.
3959 @@ -51,18 +61,16 @@ def generate_token(key, user_id, action_id="", when=None):
3960 Returns:
3961 A string XSRF protection token.
3962 """
3963 - when = when or int(time.time())
3964 - digester = hmac.new(key)
3965 - digester.update(str(user_id))
3966 + when = _force_bytes(when or int(time.time()))
3967 + digester = hmac.new(_force_bytes(key))
3968 + digester.update(_force_bytes(user_id))
3969 digester.update(DELIMITER)
3970 - digester.update(action_id)
3971 + digester.update(_force_bytes(action_id))
3972 digester.update(DELIMITER)
3973 - digester.update(str(when))
3974 + digester.update(when)
3975 digest = digester.digest()
3976
3977 - token = base64.urlsafe_b64encode('%s%s%d' % (digest,
3978 - DELIMITER,
3979 - when))
3980 + token = base64.urlsafe_b64encode(digest + DELIMITER + when)
3981 return token
3982
3983
3984 @@ -87,8 +95,8 @@ def validate_token(key, token, user_id, action_id="", current_ time=None):
3985 if not token:
3986 return False
3987 try:
3988 - decoded = base64.urlsafe_b64decode(str(token))
3989 - token_time = long(decoded.split(DELIMITER)[-1])
3990 + decoded = base64.urlsafe_b64decode(token)
3991 + token_time = int(decoded.split(DELIMITER)[-1])
3992 except (TypeError, ValueError):
3993 return False
3994 if current_time is None:
3995 @@ -105,9 +113,6 @@ def validate_token(key, token, user_id, action_id="", curren t_time=None):
3996
3997 # Perform constant time comparison to avoid timing attacks
3998 different = 0
3999 - for x, y in zip(token, expected_token):
4000 - different |= ord(x) ^ ord(y)
4001 - if different:
4002 - return False
4003 -
4004 - return True
4005 + for x, y in zip(bytearray(token), bytearray(expected_token)):
4006 + different |= x ^ y
4007 + return not different
4008 \ No newline at end of file
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698