Index: tools/telemetry/third_party/gsutil/third_party/apitools/apitools/base/py/credentials_lib.py |
diff --git a/tools/telemetry/third_party/gsutil/third_party/apitools/apitools/base/py/credentials_lib.py b/tools/telemetry/third_party/gsutil/third_party/apitools/apitools/base/py/credentials_lib.py |
deleted file mode 100644 |
index 30789433e5fe86da0c13e91b40f1d2bbd1328ba0..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/third_party/gsutil/third_party/apitools/apitools/base/py/credentials_lib.py |
+++ /dev/null |
@@ -1,489 +0,0 @@ |
-#!/usr/bin/env python |
-"""Common credentials classes and constructors.""" |
-from __future__ import print_function |
- |
-import datetime |
-import json |
-import os |
- |
-import httplib2 |
-import oauth2client |
-import oauth2client.client |
-import oauth2client.gce |
-import oauth2client.locked_file |
-import oauth2client.multistore_file |
-import oauth2client.service_account |
-from oauth2client import tools # for gflags declarations |
-from six.moves import http_client |
-from six.moves import urllib |
- |
-from apitools.base.py import exceptions |
-from apitools.base.py import util |
- |
-try: |
- import gflags |
- FLAGS = gflags.FLAGS |
-except ImportError: |
- FLAGS = None |
- |
- |
-__all__ = [ |
- 'CredentialsFromFile', |
- 'GaeAssertionCredentials', |
- 'GceAssertionCredentials', |
- 'GetCredentials', |
- 'GetUserinfo', |
- 'ServiceAccountCredentials', |
- 'ServiceAccountCredentialsFromFile', |
-] |
- |
- |
-# TODO(craigcitro): Expose the extra args here somewhere higher up, |
-# possibly as flags in the generated CLI. |
-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 |
- """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))), |
- '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) |
- 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) |
- if credentials is not None: |
- return credentials |
- raise exceptions.CredentialsError('Could not create valid credentials') |
- |
- |
-def ServiceAccountCredentialsFromFile( |
- service_account_name, private_key_filename, scopes, |
- service_account_kwargs=None): |
- with open(private_key_filename) as key_file: |
- return ServiceAccountCredentials( |
- service_account_name, key_file.read(), scopes, |
- service_account_kwargs=service_account_kwargs) |
- |
- |
-def ServiceAccountCredentials(service_account_name, private_key, scopes, |
- service_account_kwargs=None): |
- service_account_kwargs = service_account_kwargs or {} |
- scopes = util.NormalizeScopes(scopes) |
- return oauth2client.client.SignedJwtAssertionCredentials( |
- service_account_name, private_key, scopes, **service_account_kwargs) |
- |
- |
-def _EnsureFileExists(filename): |
- """Touches a file; returns False on error, True on success.""" |
- if not os.path.exists(filename): |
- old_umask = os.umask(0o177) |
- try: |
- open(filename, 'a+b').close() |
- except OSError: |
- return False |
- finally: |
- os.umask(old_umask) |
- return True |
- |
- |
-def _OpenNoProxy(request): |
- """Wrapper around urllib2.open that ignores proxies.""" |
- opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) |
- return opener.open(request) |
- |
- |
-# 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.""" |
- |
- def __init__(self, scopes=None, service_account_name='default', **kwds): |
- """Initializes the credentials instance. |
- |
- Args: |
- scopes: The scopes to get. If None, whatever scopes that are |
- available to the instance are used. |
- service_account_name: The service account to retrieve the scopes |
- from. |
- **kwds: Additional keyword args. |
- |
- """ |
- # If there is a connectivity issue with the metadata server, |
- # detection calls may fail even if we've already successfully |
- # 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'] |
- cached_scopes = self._CheckCacheFileForMatch( |
- cache_filename, scopes) |
- |
- scopes = cached_scopes or self._ScopesFromMetadataServer(scopes) |
- |
- if cache_filename and not cached_scopes: |
- self._WriteCacheFile(cache_filename, scopes) |
- |
- super(GceAssertionCredentials, self).__init__(scopes, **kwds) |
- |
- @classmethod |
- def Get(cls, *args, **kwds): |
- try: |
- return cls(*args, **kwds) |
- except exceptions.Error: |
- return None |
- |
- def _CheckCacheFileForMatch(self, cache_filename, scopes): |
- """Checks the cache file to see if it matches the given credentials. |
- |
- Args: |
- cache_filename: Cache filename to check. |
- scopes: Scopes for the desired credentials. |
- |
- Returns: |
- List of scopes (if cache matches) or None. |
- """ |
- creds = { # Credentials metadata dict. |
- '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() |
- return scopes |
- |
- def _WriteCacheFile(self, cache_filename, scopes): |
- """Writes the credential metadata to the cache file. |
- |
- This does not save the credentials themselves (CredentialStore class |
- optionally handles that after this class is initialized). |
- |
- Args: |
- 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() |
- |
- def _ScopesFromMetadataServer(self, scopes): |
- if not util.DetectGce(): |
- raise exceptions.ResourceUnavailableError( |
- 'GCE credentials requested outside a GCE instance') |
- if not self.GetServiceAccount(self.__service_account_name): |
- raise exceptions.ResourceUnavailableError( |
- 'GCE credentials requested but service account ' |
- '%s does not exist.' % self.__service_account_name) |
- if scopes: |
- scope_ls = util.NormalizeScopes(scopes) |
- instance_scopes = self.GetInstanceScopes() |
- if scope_ls > instance_scopes: |
- raise exceptions.CredentialsError( |
- 'Instance did not have access to scopes %s' % ( |
- sorted(list(scope_ls - instance_scopes)),)) |
- else: |
- scopes = self.GetInstanceScopes() |
- 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) |
- 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) |
- return util.NormalizeScopes(scope.strip() |
- for scope in response.readlines()) |
- |
- def _refresh(self, do_request): |
- """Refresh self.access_token. |
- |
- This function replaces AppAssertionCredentials._refresh, which |
- does not use the credential store and is therefore poorly |
- suited for multi-threaded scenarios. |
- |
- Args: |
- do_request: A function matching httplib2.Http.request's signature. |
- |
- """ |
- # pylint: disable=protected-access |
- oauth2client.client.OAuth2Credentials._refresh(self, do_request) |
- # pylint: enable=protected-access |
- |
- def _do_refresh_request(self, unused_http_request): |
- """Refresh self.access_token by querying the metadata server. |
- |
- 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) |
- try: |
- content = _OpenNoProxy(request).read() |
- except urllib.error.URLError as e: |
- self.invalid = True |
- if self.store: |
- self.store.locked_put(self) |
- raise exceptions.CommunicationError( |
- 'Could not reach metadata service: %s' % e.reason) |
- try: |
- credential_info = json.loads(content) |
- except ValueError: |
- raise exceptions.CredentialsError( |
- 'Invalid credentials response: uri %s' % token_uri) |
- |
- self.access_token = credential_info['access_token'] |
- if 'expires_in' in credential_info: |
- expires_in = int(credential_info['expires_in']) |
- self.token_expiry = ( |
- datetime.timedelta(seconds=expires_in) + |
- datetime.datetime.utcnow()) |
- else: |
- self.token_expiry = None |
- self.invalid = False |
- if self.store: |
- self.store.locked_put(self) |
- |
- @classmethod |
- def from_json(cls, json_data): |
- data = json.loads(json_data) |
- credentials = GceAssertionCredentials(scopes=[data['scope']]) |
- if 'access_token' in data: |
- credentials.access_token = data['access_token'] |
- if 'token_expiry' in data: |
- credentials.token_expiry = datetime.datetime.strptime( |
- data['token_expiry'], oauth2client.client.EXPIRY_FORMAT) |
- if 'invalid' in data: |
- credentials.invalid = data['invalid'] |
- return credentials |
- |
- @property |
- def serialization_data(self): |
- raise NotImplementedError( |
- 'Cannot serialize credentials for GCE service accounts.') |
- |
- |
-# TODO(craigcitro): Currently, we can't even *load* |
-# `oauth2client.appengine` without being on appengine, because of how |
-# it handles imports. Fix that by splitting that module into |
-# GAE-specific and GAE-independent bits, and guarding imports. |
-class GaeAssertionCredentials(oauth2client.client.AssertionCredentials): |
- |
- """Assertion credentials for Google App Engine apps.""" |
- |
- def __init__(self, scopes, **kwds): |
- if not util.DetectGae(): |
- raise exceptions.ResourceUnavailableError( |
- 'GCE credentials requested outside a GCE instance') |
- self._scopes = list(util.NormalizeScopes(scopes)) |
- super(GaeAssertionCredentials, self).__init__(None, **kwds) |
- |
- @classmethod |
- def Get(cls, *args, **kwds): |
- try: |
- return cls(*args, **kwds) |
- except exceptions.Error: |
- return None |
- |
- @classmethod |
- def from_json(cls, json_data): |
- data = json.loads(json_data) |
- return GaeAssertionCredentials(data['_scopes']) |
- |
- def _refresh(self, _): |
- """Refresh self.access_token. |
- |
- Args: |
- _: (ignored) A function matching httplib2.Http.request's signature. |
- """ |
- from google.appengine.api import app_identity |
- try: |
- token, _ = app_identity.get_access_token(self._scopes) |
- except app_identity.Error as e: |
- raise exceptions.CredentialsError(str(e)) |
- self.access_token = token |
- |
- |
-def _GetRunFlowFlags(args=None): |
- # There's one rare situation where gsutil will not have argparse |
- # available, but doesn't need anything depending on argparse anyway, |
- # since they're bringing their own credentials. So we just allow this |
- # to fail with an ImportError in those cases. |
- # |
- # TODO(craigcitro): Move this import back to the top when we drop |
- # python 2.6 support (eg when gsutil does). |
- import argparse |
- |
- parser = argparse.ArgumentParser(parents=[tools.argparser]) |
- # Get command line argparse flags. |
- flags = parser.parse_args(args=args) |
- |
- # Allow `gflags` and `argparse` to be used side-by-side. |
- if hasattr(FLAGS, 'auth_host_name'): |
- flags.auth_host_name = FLAGS.auth_host_name |
- if hasattr(FLAGS, 'auth_host_port'): |
- flags.auth_host_port = FLAGS.auth_host_port |
- if hasattr(FLAGS, 'auth_local_webserver'): |
- flags.noauth_local_webserver = (not FLAGS.auth_local_webserver) |
- return flags |
- |
- |
-# TODO(craigcitro): Switch this from taking a path to taking a stream. |
-def CredentialsFromFile(path, client_info): |
- """Read credentials from a file.""" |
- credential_store = oauth2client.multistore_file.get_credential_storage( |
- path, |
- client_info['client_id'], |
- client_info['user_agent'], |
- client_info['scope']) |
- if hasattr(FLAGS, 'auth_local_webserver'): |
- FLAGS.auth_local_webserver = False |
- credentials = credential_store.get() |
- if credentials is None or credentials.invalid: |
- print('Generating new OAuth credentials ...') |
- while True: |
- # 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() |
- 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. |
- print('Invalid authorization: %s' % (e,)) |
- except httplib2.HttpLib2Error as e: |
- print('Communication error: %s' % (e,)) |
- raise exceptions.CredentialsError( |
- 'Communication error creating credentials: %s' % e) |
- return credentials |
- |
- |
-# TODO(craigcitro): Push this into oauth2client. |
-def GetUserinfo(credentials, http=None): # pylint: disable=invalid-name |
- """Get the userinfo associated with the given credentials. |
- |
- This is dependent on the token having either the userinfo.email or |
- userinfo.profile scope for the given token. |
- |
- Args: |
- credentials: (oauth2client.client.Credentials) incoming credentials |
- http: (httplib2.Http, optional) http instance to use |
- |
- Returns: |
- The email address for this token, or None if the required scopes |
- aren't available. |
- """ |
- http = http or httplib2.Http() |
- url_root = 'https://www.googleapis.com/oauth2/v2/tokeninfo' |
- query_args = {'access_token': credentials.access_token} |
- url = '?'.join((url_root, urllib.parse.urlencode(query_args))) |
- # We ignore communication woes here (i.e. SSL errors, socket |
- # timeout), as handling these should be done in a common location. |
- response, content = http.request(url) |
- if response.status == http_client.BAD_REQUEST: |
- credentials.refresh(http) |
- response, content = http.request(url) |
- return json.loads(content or '{}') # Save ourselves from an empty reply. |