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

Unified Diff: tools/telemetry/third_party/gsutilz/third_party/apitools/apitools/base/py/credentials_lib.py

Issue 1376593003: Roll gsutil version to 4.15. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 3 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 side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/third_party/gsutilz/third_party/apitools/apitools/base/py/credentials_lib.py
diff --git a/tools/telemetry/third_party/gsutilz/third_party/apitools/apitools/base/py/credentials_lib.py b/tools/telemetry/third_party/gsutilz/third_party/apitools/apitools/base/py/credentials_lib.py
old mode 100755
new mode 100644
index 30789433e5fe86da0c13e91b40f1d2bbd1328ba0..9a8f36ec8d48f0dbbaf61e405879a627cf4198a3
--- a/tools/telemetry/third_party/gsutilz/third_party/apitools/apitools/base/py/credentials_lib.py
+++ b/tools/telemetry/third_party/gsutilz/third_party/apitools/apitools/base/py/credentials_lib.py
@@ -5,6 +5,7 @@ from __future__ import print_function
import datetime
import json
import os
+import threading
import httplib2
import oauth2client
@@ -38,63 +39,70 @@ __all__ = [
]
-# TODO(craigcitro): Expose the extra args here somewhere higher up,
-# possibly as flags in the generated CLI.
+# Lock when accessing the cache file to avoid resource contention.
+cache_file_lock = threading.Lock()
+
+
+def SetCredentialsCacheFileLock(lock):
+ global cache_file_lock # pylint: disable=global-statement
+ cache_file_lock = lock
+
+
+# List of additional methods we use when attempting to construct
+# credentials. Users can register their own methods here, which we try
+# before the defaults.
+_CREDENTIALS_METHODS = []
+
+
+def _RegisterCredentialsMethod(method, position=None):
+ """Register a new method for fetching credentials.
+
+ This new method should be a function with signature:
+ client_info, **kwds -> Credentials or None
+ This method can be used as a decorator, unless position needs to
+ be supplied.
+
+ Note that method must *always* accept arbitrary keyword arguments.
+
+ Args:
+ method: New credential-fetching method.
+ position: (default: None) Where in the list of methods to
+ add this; if None, we append. In all but rare cases,
+ this should be either 0 or None.
+ Returns:
+ method, for use as a decorator.
+
+ """
+ if position is None:
+ position = len(_CREDENTIALS_METHODS)
+ else:
+ position = min(position, len(_CREDENTIALS_METHODS))
+ _CREDENTIALS_METHODS.insert(position, method)
+ return method
+
+
def GetCredentials(package_name, scopes, client_id, client_secret, user_agent,
credentials_filename=None,
- service_account_name=None, service_account_keyfile=None,
- service_account_json_keyfile=None,
api_key=None, # pylint: disable=unused-argument
- client=None): # pylint: disable=unused-argument
+ client=None, # pylint: disable=unused-argument
+ oauth2client_args=None,
+ **kwds):
"""Attempt to get credentials, using an oauth dance as the last resort."""
scopes = util.NormalizeScopes(scopes)
- if ((service_account_name and not service_account_keyfile) or
- (service_account_keyfile and not service_account_name)):
- raise exceptions.CredentialsError(
- 'Service account name or keyfile provided without the other')
- # TODO(craigcitro): Error checking.
client_info = {
'client_id': client_id,
'client_secret': client_secret,
- 'scope': ' '.join(sorted(util.NormalizeScopes(scopes))),
+ 'scope': ' '.join(sorted(scopes)),
'user_agent': user_agent or '%s-generated/0.1' % package_name,
}
- service_account_kwargs = {
- 'user_agent': client_info['user_agent'],
- }
- if service_account_json_keyfile:
- with open(service_account_json_keyfile) as keyfile:
- service_account_info = json.load(keyfile)
- account_type = service_account_info.get('type')
- if account_type != oauth2client.client.SERVICE_ACCOUNT:
- raise exceptions.CredentialsError(
- 'Invalid service account credentials: %s' % (
- service_account_json_keyfile,))
- # pylint: disable=protected-access
- credentials = oauth2client.service_account._ServiceAccountCredentials(
- service_account_id=service_account_info['client_id'],
- service_account_email=service_account_info['client_email'],
- private_key_id=service_account_info['private_key_id'],
- private_key_pkcs8_text=service_account_info['private_key'],
- scopes=scopes,
- **service_account_kwargs)
- # pylint: enable=protected-access
- return credentials
- if service_account_name is not None:
- credentials = ServiceAccountCredentialsFromFile(
- service_account_name, service_account_keyfile, scopes,
- service_account_kwargs=service_account_kwargs)
+ for method in _CREDENTIALS_METHODS:
+ credentials = method(client_info, **kwds)
if credentials is not None:
return credentials
- credentials = GaeAssertionCredentials.Get(scopes)
- if credentials is not None:
- return credentials
- credentials = GceAssertionCredentials.Get(scopes)
- if credentials is not None:
- return credentials
credentials_filename = credentials_filename or os.path.expanduser(
'~/.apitools.token')
- credentials = CredentialsFromFile(credentials_filename, client_info)
+ credentials = CredentialsFromFile(credentials_filename, client_info,
+ oauth2client_args=oauth2client_args)
if credentials is not None:
return credentials
raise exceptions.CredentialsError('Could not create valid credentials')
@@ -130,15 +138,26 @@ def _EnsureFileExists(filename):
return True
-def _OpenNoProxy(request):
- """Wrapper around urllib2.open that ignores proxies."""
+def _GceMetadataRequest(relative_url, use_metadata_ip=False):
+ """Request the given url from the GCE metadata service."""
+ if use_metadata_ip:
+ base_url = 'http://169.254.169.254/'
+ else:
+ base_url = 'http://metadata.google.internal/'
+ url = base_url + 'computeMetadata/v1/' + relative_url
+ # Extra header requirement can be found here:
+ # https://developers.google.com/compute/docs/metadata
+ headers = {'Metadata-Flavor': 'Google'}
+ request = urllib.request.Request(url, headers=headers)
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
- return opener.open(request)
+ try:
+ response = opener.open(request)
+ except urllib.error.URLError as e:
+ raise exceptions.CommunicationError(
+ 'Could not reach metadata service: %s' % e.reason)
+ return response
-# TODO(craigcitro): We override to add some utility code, and to
-# update the old refresh implementation. Push this code into
-# oauth2client.
class GceAssertionCredentials(oauth2client.gce.AppAssertionCredentials):
"""Assertion credentials for GCE instances."""
@@ -159,13 +178,10 @@ class GceAssertionCredentials(oauth2client.gce.AppAssertionCredentials):
# identified these scopes in the same execution. However, the
# available scopes don't change once an instance is created,
# so there is no reason to perform more than one query.
- #
- # TODO(craigcitro): Move this into oauth2client.
self.__service_account_name = service_account_name
- cache_filename = None
cached_scopes = None
- if 'cache_filename' in kwds:
- cache_filename = kwds['cache_filename']
+ cache_filename = kwds.get('cache_filename')
+ if cache_filename:
cached_scopes = self._CheckCacheFileForMatch(
cache_filename, scopes)
@@ -197,20 +213,23 @@ class GceAssertionCredentials(oauth2client.gce.AppAssertionCredentials):
'scopes': sorted(list(scopes)) if scopes else None,
'svc_acct_name': self.__service_account_name,
}
- if _EnsureFileExists(cache_filename):
- locked_file = oauth2client.locked_file.LockedFile(
- cache_filename, 'r+b', 'rb')
- try:
- locked_file.open_and_lock()
- cached_creds_str = locked_file.file_handle().read()
- if cached_creds_str:
- # Cached credentials metadata dict.
- cached_creds = json.loads(cached_creds_str)
- if creds['svc_acct_name'] == cached_creds['svc_acct_name']:
- if creds['scopes'] in (None, cached_creds['scopes']):
- scopes = cached_creds['scopes']
- finally:
- locked_file.unlock_and_close()
+ with cache_file_lock:
+ if _EnsureFileExists(cache_filename):
+ locked_file = oauth2client.locked_file.LockedFile(
+ cache_filename, 'r+b', 'rb')
+ try:
+ locked_file.open_and_lock()
+ cached_creds_str = locked_file.file_handle().read()
+ if cached_creds_str:
+ # Cached credentials metadata dict.
+ cached_creds = json.loads(cached_creds_str)
+ if (creds['svc_acct_name'] ==
+ cached_creds['svc_acct_name']):
+ if (creds['scopes'] in
+ (None, cached_creds['scopes'])):
+ scopes = cached_creds['scopes']
+ finally:
+ locked_file.unlock_and_close()
return scopes
def _WriteCacheFile(self, cache_filename, scopes):
@@ -223,22 +242,23 @@ class GceAssertionCredentials(oauth2client.gce.AppAssertionCredentials):
cache_filename: Cache filename to check.
scopes: Scopes for the desired credentials.
"""
- if _EnsureFileExists(cache_filename):
- locked_file = oauth2client.locked_file.LockedFile(
- cache_filename, 'r+b', 'rb')
- try:
- locked_file.open_and_lock()
- if locked_file.is_locked():
- creds = { # Credentials metadata dict.
- 'scopes': sorted(list(scopes)),
- 'svc_acct_name': self.__service_account_name}
- locked_file.file_handle().write(
- json.dumps(creds, encoding='ascii'))
- # If it's not locked, the locking process will
- # write the same data to the file, so just
- # continue.
- finally:
- locked_file.unlock_and_close()
+ with cache_file_lock:
+ if _EnsureFileExists(cache_filename):
+ locked_file = oauth2client.locked_file.LockedFile(
+ cache_filename, 'r+b', 'rb')
+ try:
+ locked_file.open_and_lock()
+ if locked_file.is_locked():
+ creds = { # Credentials metadata dict.
+ 'scopes': sorted(list(scopes)),
+ 'svc_acct_name': self.__service_account_name}
+ locked_file.file_handle().write(
+ json.dumps(creds, encoding='ascii'))
+ # If it's not locked, the locking process will
+ # write the same data to the file, so just
+ # continue.
+ finally:
+ locked_file.unlock_and_close()
def _ScopesFromMetadataServer(self, scopes):
if not util.DetectGce():
@@ -260,35 +280,16 @@ class GceAssertionCredentials(oauth2client.gce.AppAssertionCredentials):
return scopes
def GetServiceAccount(self, account):
- account_uri = (
- 'http://metadata.google.internal/computeMetadata/'
- 'v1/instance/service-accounts')
- additional_headers = {'X-Google-Metadata-Request': 'True'}
- request = urllib.request.Request(
- account_uri, headers=additional_headers)
- try:
- response = _OpenNoProxy(request)
- except urllib.error.URLError as e:
- raise exceptions.CommunicationError(
- 'Could not reach metadata service: %s' % e.reason)
+ relative_url = 'instance/service-accounts'
+ response = _GceMetadataRequest(relative_url)
response_lines = [line.rstrip('/\n\r')
for line in response.readlines()]
return account in response_lines
def GetInstanceScopes(self):
- # Extra header requirement can be found here:
- # https://developers.google.com/compute/docs/metadata
- scopes_uri = (
- 'http://metadata.google.internal/computeMetadata/v1/instance/'
- 'service-accounts/%s/scopes') % self.__service_account_name
- additional_headers = {'X-Google-Metadata-Request': 'True'}
- request = urllib.request.Request(
- scopes_uri, headers=additional_headers)
- try:
- response = _OpenNoProxy(request)
- except urllib.error.URLError as e:
- raise exceptions.CommunicationError(
- 'Could not reach metadata service: %s' % e.reason)
+ relative_url = 'instance/service-accounts/{0}/scopes'.format(
+ self.__service_account_name)
+ response = _GceMetadataRequest(relative_url)
return util.NormalizeScopes(scope.strip()
for scope in response.readlines())
@@ -312,24 +313,21 @@ class GceAssertionCredentials(oauth2client.gce.AppAssertionCredentials):
If self.store is initialized, store acquired credentials there.
"""
- token_uri = (
- 'http://metadata.google.internal/computeMetadata/v1/instance/'
- 'service-accounts/%s/token') % self.__service_account_name
- extra_headers = {'X-Google-Metadata-Request': 'True'}
- request = urllib.request.Request(token_uri, headers=extra_headers)
+ relative_url = 'instance/service-accounts/{0}/token'.format(
+ self.__service_account_name)
try:
- content = _OpenNoProxy(request).read()
- except urllib.error.URLError as e:
+ response = _GceMetadataRequest(relative_url)
+ except exceptions.CommunicationError:
self.invalid = True
if self.store:
self.store.locked_put(self)
- raise exceptions.CommunicationError(
- 'Could not reach metadata service: %s' % e.reason)
+ raise
+ content = response.read()
try:
credential_info = json.loads(content)
except ValueError:
raise exceptions.CredentialsError(
- 'Invalid credentials response: uri %s' % token_uri)
+ 'Could not parse response as JSON: %s' % content)
self.access_token = credential_info['access_token']
if 'expires_in' in credential_info:
@@ -346,7 +344,11 @@ class GceAssertionCredentials(oauth2client.gce.AppAssertionCredentials):
@classmethod
def from_json(cls, json_data):
data = json.loads(json_data)
- credentials = GceAssertionCredentials(scopes=[data['scope']])
+ kwargs = {}
+ if 'cache_filename' in data.get('kwargs', []):
+ kwargs['cache_filename'] = data['kwargs']['cache_filename']
+ credentials = GceAssertionCredentials(scopes=[data['scope']],
+ **kwargs)
if 'access_token' in data:
credentials.access_token = data['access_token']
if 'token_expiry' in data:
@@ -415,7 +417,7 @@ def _GetRunFlowFlags(args=None):
parser = argparse.ArgumentParser(parents=[tools.argparser])
# Get command line argparse flags.
- flags = parser.parse_args(args=args)
+ flags, _ = parser.parse_known_args(args=args)
# Allow `gflags` and `argparse` to be used side-by-side.
if hasattr(FLAGS, 'auth_host_name'):
@@ -428,7 +430,7 @@ def _GetRunFlowFlags(args=None):
# TODO(craigcitro): Switch this from taking a path to taking a stream.
-def CredentialsFromFile(path, client_info):
+def CredentialsFromFile(path, client_info, oauth2client_args=None):
"""Read credentials from a file."""
credential_store = oauth2client.multistore_file.get_credential_storage(
path,
@@ -440,19 +442,19 @@ def CredentialsFromFile(path, client_info):
credentials = credential_store.get()
if credentials is None or credentials.invalid:
print('Generating new OAuth credentials ...')
- while True:
+ for _ in range(20):
# If authorization fails, we want to retry, rather than let this
# cascade up and get caught elsewhere. If users want out of the
# retry loop, they can ^C.
try:
flow = oauth2client.client.OAuth2WebServerFlow(**client_info)
- flags = _GetRunFlowFlags()
+ flags = _GetRunFlowFlags(args=oauth2client_args)
credentials = tools.run_flow(flow, credential_store, flags)
break
except (oauth2client.client.FlowExchangeError, SystemExit) as e:
# Here SystemExit is "no credential at all", and the
- # FlowExchangeError is "invalid" -- usually because you reused
- # a token.
+ # FlowExchangeError is "invalid" -- usually because
+ # you reused a token.
print('Invalid authorization: %s' % (e,))
except httplib2.HttpLib2Error as e:
print('Communication error: %s' % (e,))
@@ -487,3 +489,74 @@ def GetUserinfo(credentials, http=None): # pylint: disable=invalid-name
credentials.refresh(http)
response, content = http.request(url)
return json.loads(content or '{}') # Save ourselves from an empty reply.
+
+
+@_RegisterCredentialsMethod
+def _GetServiceAccountCredentials(
+ client_info, service_account_name=None, service_account_keyfile=None,
+ service_account_json_keyfile=None, **unused_kwds):
+ if ((service_account_name and not service_account_keyfile) or
+ (service_account_keyfile and not service_account_name)):
+ raise exceptions.CredentialsError(
+ 'Service account name or keyfile provided without the other')
+ scopes = client_info['scope'].split()
+ user_agent = client_info['user_agent']
+ if service_account_json_keyfile:
+ with open(service_account_json_keyfile) as keyfile:
+ service_account_info = json.load(keyfile)
+ account_type = service_account_info.get('type')
+ if account_type != oauth2client.client.SERVICE_ACCOUNT:
+ raise exceptions.CredentialsError(
+ 'Invalid service account credentials: %s' % (
+ service_account_json_keyfile,))
+ # pylint: disable=protected-access
+ credentials = oauth2client.service_account._ServiceAccountCredentials(
+ service_account_id=service_account_info['client_id'],
+ service_account_email=service_account_info['client_email'],
+ private_key_id=service_account_info['private_key_id'],
+ private_key_pkcs8_text=service_account_info['private_key'],
+ scopes=scopes, user_agent=user_agent)
+ # pylint: enable=protected-access
+ return credentials
+ if service_account_name is not None:
+ credentials = ServiceAccountCredentialsFromFile(
+ service_account_name, service_account_keyfile, scopes,
+ service_account_kwargs={'user_agent': user_agent})
+ if credentials is not None:
+ return credentials
+
+
+@_RegisterCredentialsMethod
+def _GetGaeServiceAccount(unused_client_info, scopes, **unused_kwds):
+ return GaeAssertionCredentials.Get(scopes=scopes)
+
+
+@_RegisterCredentialsMethod
+def _GetGceServiceAccount(unused_client_info, scopes, **unused_kwds):
+ return GceAssertionCredentials.Get(scopes=scopes)
+
+
+@_RegisterCredentialsMethod
+def _GetApplicationDefaultCredentials(
+ unused_client_info, scopes, skip_application_default_credentials=False,
+ **unused_kwds):
+ if skip_application_default_credentials:
+ return None
+ gc = oauth2client.client.GoogleCredentials
+ with cache_file_lock:
+ try:
+ # pylint: disable=protected-access
+ # We've already done our own check for GAE/GCE
+ # credentials, we don't want to pay for checking again.
+ credentials = gc._implicit_credentials_from_files()
+ except oauth2client.client.ApplicationDefaultCredentialsError:
+ return None
+ # If we got back a non-service account credential, we need to use
+ # a heuristic to decide whether or not the application default
+ # credential will work for us. We assume that if we're requesting
+ # cloud-platform, our scopes are a subset of cloud scopes, and the
+ # ADC will work.
+ cp = 'https://www.googleapis.com/auth/cloud-platform'
+ if not isinstance(credentials, gc) or cp in scopes:
+ return credentials
+ return None

Powered by Google App Engine
This is Rietveld 408576698