1#!/usr/bin/env python 2# 3# Copyright 2015 Google Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Common credentials classes and constructors.""" 18from __future__ import print_function 19 20import argparse 21import contextlib 22import datetime 23import json 24import os 25import threading 26import warnings 27 28import httplib2 29import oauth2client 30import oauth2client.client 31from oauth2client import service_account 32from oauth2client import tools # for gflags declarations 33import six 34from six.moves import http_client 35from six.moves import urllib 36 37from apitools.base.py import exceptions 38from apitools.base.py import util 39 40# App Engine does not support ctypes which are required for the 41# monotonic time used in fasteners. Conversely, App Engine does 42# not support colocated concurrent processes, so process locks 43# are not needed. 44try: 45 import fasteners 46 _FASTENERS_AVAILABLE = True 47except ImportError as import_error: 48 server_env = os.environ.get('SERVER_SOFTWARE', '') 49 if not (server_env.startswith('Development') or 50 server_env.startswith('Google App Engine')): 51 raise import_error 52 _FASTENERS_AVAILABLE = False 53 54# Note: we try the oauth2client imports two ways, to accomodate layout 55# changes in oauth2client 2.0+. We can remove these once we no longer 56# support oauth2client < 2.0. 57# 58# pylint: disable=wrong-import-order,ungrouped-imports 59try: 60 from oauth2client.contrib import gce 61except ImportError: 62 from oauth2client import gce 63 64try: 65 from oauth2client.contrib import multiprocess_file_storage 66 _NEW_FILESTORE = True 67except ImportError: 68 _NEW_FILESTORE = False 69 try: 70 from oauth2client.contrib import multistore_file 71 except ImportError: 72 from oauth2client import multistore_file 73 74try: 75 import gflags 76 FLAGS = gflags.FLAGS 77except ImportError: 78 FLAGS = None 79 80 81__all__ = [ 82 'CredentialsFromFile', 83 'GaeAssertionCredentials', 84 'GceAssertionCredentials', 85 'GetCredentials', 86 'GetUserinfo', 87 'ServiceAccountCredentialsFromFile', 88] 89 90 91# Lock when accessing the cache file to avoid resource contention. 92cache_file_lock = threading.Lock() 93 94 95def SetCredentialsCacheFileLock(lock): 96 global cache_file_lock # pylint: disable=global-statement 97 cache_file_lock = lock 98 99 100# List of additional methods we use when attempting to construct 101# credentials. Users can register their own methods here, which we try 102# before the defaults. 103_CREDENTIALS_METHODS = [] 104 105 106def _RegisterCredentialsMethod(method, position=None): 107 """Register a new method for fetching credentials. 108 109 This new method should be a function with signature: 110 client_info, **kwds -> Credentials or None 111 This method can be used as a decorator, unless position needs to 112 be supplied. 113 114 Note that method must *always* accept arbitrary keyword arguments. 115 116 Args: 117 method: New credential-fetching method. 118 position: (default: None) Where in the list of methods to 119 add this; if None, we append. In all but rare cases, 120 this should be either 0 or None. 121 Returns: 122 method, for use as a decorator. 123 124 """ 125 if position is None: 126 position = len(_CREDENTIALS_METHODS) 127 else: 128 position = min(position, len(_CREDENTIALS_METHODS)) 129 _CREDENTIALS_METHODS.insert(position, method) 130 return method 131 132 133def GetCredentials(package_name, scopes, client_id, client_secret, user_agent, 134 credentials_filename=None, 135 api_key=None, # pylint: disable=unused-argument 136 client=None, # pylint: disable=unused-argument 137 oauth2client_args=None, 138 **kwds): 139 """Attempt to get credentials, using an oauth dance as the last resort.""" 140 scopes = util.NormalizeScopes(scopes) 141 client_info = { 142 'client_id': client_id, 143 'client_secret': client_secret, 144 'scope': ' '.join(sorted(scopes)), 145 'user_agent': user_agent or '%s-generated/0.1' % package_name, 146 } 147 for method in _CREDENTIALS_METHODS: 148 credentials = method(client_info, **kwds) 149 if credentials is not None: 150 return credentials 151 credentials_filename = credentials_filename or os.path.expanduser( 152 '~/.apitools.token') 153 credentials = CredentialsFromFile(credentials_filename, client_info, 154 oauth2client_args=oauth2client_args) 155 if credentials is not None: 156 return credentials 157 raise exceptions.CredentialsError('Could not create valid credentials') 158 159 160def ServiceAccountCredentialsFromFile(filename, scopes, user_agent=None): 161 """Use the credentials in filename to create a token for scopes.""" 162 filename = os.path.expanduser(filename) 163 # We have two options, based on our version of oauth2client. 164 if oauth2client.__version__ > '1.5.2': 165 # oauth2client >= 2.0.0 166 credentials = ( 167 service_account.ServiceAccountCredentials.from_json_keyfile_name( 168 filename, scopes=scopes)) 169 if credentials is not None: 170 if user_agent is not None: 171 credentials.user_agent = user_agent 172 return credentials 173 else: 174 # oauth2client < 2.0.0 175 with open(filename) as keyfile: 176 service_account_info = json.load(keyfile) 177 account_type = service_account_info.get('type') 178 if account_type != oauth2client.client.SERVICE_ACCOUNT: 179 raise exceptions.CredentialsError( 180 'Invalid service account credentials: %s' % (filename,)) 181 # pylint: disable=protected-access 182 credentials = service_account._ServiceAccountCredentials( 183 service_account_id=service_account_info['client_id'], 184 service_account_email=service_account_info['client_email'], 185 private_key_id=service_account_info['private_key_id'], 186 private_key_pkcs8_text=service_account_info['private_key'], 187 scopes=scopes, user_agent=user_agent) 188 # pylint: enable=protected-access 189 return credentials 190 191 192def ServiceAccountCredentialsFromP12File( 193 service_account_name, private_key_filename, scopes, user_agent): 194 """Create a new credential from the named .p12 keyfile.""" 195 private_key_filename = os.path.expanduser(private_key_filename) 196 scopes = util.NormalizeScopes(scopes) 197 if oauth2client.__version__ > '1.5.2': 198 # oauth2client >= 2.0.0 199 credentials = ( 200 service_account.ServiceAccountCredentials.from_p12_keyfile( 201 service_account_name, private_key_filename, scopes=scopes)) 202 if credentials is not None: 203 credentials.user_agent = user_agent 204 return credentials 205 else: 206 # oauth2client < 2.0.0 207 with open(private_key_filename, 'rb') as key_file: 208 return oauth2client.client.SignedJwtAssertionCredentials( 209 service_account_name, key_file.read(), scopes, 210 user_agent=user_agent) 211 212 213def _GceMetadataRequest(relative_url, use_metadata_ip=False): 214 """Request the given url from the GCE metadata service.""" 215 if use_metadata_ip: 216 base_url = os.environ.get('GCE_METADATA_IP', '169.254.169.254') 217 else: 218 base_url = os.environ.get( 219 'GCE_METADATA_ROOT', 'metadata.google.internal') 220 url = 'http://' + base_url + '/computeMetadata/v1/' + relative_url 221 # Extra header requirement can be found here: 222 # https://developers.google.com/compute/docs/metadata 223 headers = {'Metadata-Flavor': 'Google'} 224 request = urllib.request.Request(url, headers=headers) 225 opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) 226 try: 227 response = opener.open(request) 228 except urllib.error.URLError as e: 229 raise exceptions.CommunicationError( 230 'Could not reach metadata service: %s' % e.reason) 231 return response 232 233 234class GceAssertionCredentials(gce.AppAssertionCredentials): 235 236 """Assertion credentials for GCE instances.""" 237 238 def __init__(self, scopes=None, service_account_name='default', **kwds): 239 """Initializes the credentials instance. 240 241 Args: 242 scopes: The scopes to get. If None, whatever scopes that are 243 available to the instance are used. 244 service_account_name: The service account to retrieve the scopes 245 from. 246 **kwds: Additional keyword args. 247 248 """ 249 # If there is a connectivity issue with the metadata server, 250 # detection calls may fail even if we've already successfully 251 # identified these scopes in the same execution. However, the 252 # available scopes don't change once an instance is created, 253 # so there is no reason to perform more than one query. 254 self.__service_account_name = six.ensure_text( 255 service_account_name, 256 encoding='utf-8',) 257 cached_scopes = None 258 cache_filename = kwds.get('cache_filename') 259 if cache_filename: 260 cached_scopes = self._CheckCacheFileForMatch( 261 cache_filename, scopes) 262 263 scopes = cached_scopes or self._ScopesFromMetadataServer(scopes) 264 265 if cache_filename and not cached_scopes: 266 self._WriteCacheFile(cache_filename, scopes) 267 268 # We check the scopes above, but don't need them again after 269 # this point. Newer versions of oauth2client let us drop them 270 # here, but since we support older versions as well, we just 271 # catch and squelch the warning. 272 with warnings.catch_warnings(): 273 warnings.simplefilter('ignore') 274 super(GceAssertionCredentials, self).__init__(scope=scopes, **kwds) 275 276 @classmethod 277 def Get(cls, *args, **kwds): 278 try: 279 return cls(*args, **kwds) 280 except exceptions.Error: 281 return None 282 283 def _CheckCacheFileForMatch(self, cache_filename, scopes): 284 """Checks the cache file to see if it matches the given credentials. 285 286 Args: 287 cache_filename: Cache filename to check. 288 scopes: Scopes for the desired credentials. 289 290 Returns: 291 List of scopes (if cache matches) or None. 292 """ 293 creds = { # Credentials metadata dict. 294 'scopes': sorted(list(scopes)) if scopes else None, 295 'svc_acct_name': self.__service_account_name, 296 } 297 cache_file = _MultiProcessCacheFile(cache_filename) 298 try: 299 cached_creds_str = cache_file.LockedRead() 300 if not cached_creds_str: 301 return None 302 cached_creds = json.loads(cached_creds_str) 303 if creds['svc_acct_name'] == cached_creds['svc_acct_name']: 304 if creds['scopes'] in (None, cached_creds['scopes']): 305 return cached_creds['scopes'] 306 except KeyboardInterrupt: 307 raise 308 except: # pylint: disable=bare-except 309 # Treat exceptions as a cache miss. 310 pass 311 312 def _WriteCacheFile(self, cache_filename, scopes): 313 """Writes the credential metadata to the cache file. 314 315 This does not save the credentials themselves (CredentialStore class 316 optionally handles that after this class is initialized). 317 318 Args: 319 cache_filename: Cache filename to check. 320 scopes: Scopes for the desired credentials. 321 """ 322 # Credentials metadata dict. 323 scopes = sorted([six.ensure_text(scope) for scope in scopes]) 324 creds = {'scopes': scopes, 325 'svc_acct_name': self.__service_account_name} 326 creds_str = json.dumps(creds) 327 cache_file = _MultiProcessCacheFile(cache_filename) 328 try: 329 cache_file.LockedWrite(creds_str) 330 except KeyboardInterrupt: 331 raise 332 except: # pylint: disable=bare-except 333 # Treat exceptions as a cache miss. 334 pass 335 336 def _ScopesFromMetadataServer(self, scopes): 337 """Returns instance scopes based on GCE metadata server.""" 338 if not util.DetectGce(): 339 raise exceptions.ResourceUnavailableError( 340 'GCE credentials requested outside a GCE instance') 341 if not self.GetServiceAccount(self.__service_account_name): 342 raise exceptions.ResourceUnavailableError( 343 'GCE credentials requested but service account ' 344 '%s does not exist.' % self.__service_account_name) 345 if scopes: 346 scope_ls = util.NormalizeScopes(scopes) 347 instance_scopes = self.GetInstanceScopes() 348 if scope_ls > instance_scopes: 349 raise exceptions.CredentialsError( 350 'Instance did not have access to scopes %s' % ( 351 sorted(list(scope_ls - instance_scopes)),)) 352 else: 353 scopes = self.GetInstanceScopes() 354 return scopes 355 356 def GetServiceAccount(self, account): 357 relative_url = 'instance/service-accounts' 358 response = _GceMetadataRequest(relative_url) 359 response_lines = [six.ensure_str(line).rstrip(u'/\n\r') 360 for line in response.readlines()] 361 return account in response_lines 362 363 def GetInstanceScopes(self): 364 relative_url = 'instance/service-accounts/{0}/scopes'.format( 365 self.__service_account_name) 366 response = _GceMetadataRequest(relative_url) 367 return util.NormalizeScopes(six.ensure_str(scope).strip() 368 for scope in response.readlines()) 369 370 # pylint: disable=arguments-differ 371 def _refresh(self, do_request): 372 """Refresh self.access_token. 373 374 This function replaces AppAssertionCredentials._refresh, which 375 does not use the credential store and is therefore poorly 376 suited for multi-threaded scenarios. 377 378 Args: 379 do_request: A function matching httplib2.Http.request's signature. 380 381 """ 382 # pylint: disable=protected-access 383 oauth2client.client.OAuth2Credentials._refresh(self, do_request) 384 # pylint: enable=protected-access 385 386 def _do_refresh_request(self, unused_http_request): 387 """Refresh self.access_token by querying the metadata server. 388 389 If self.store is initialized, store acquired credentials there. 390 """ 391 relative_url = 'instance/service-accounts/{0}/token'.format( 392 self.__service_account_name) 393 try: 394 response = _GceMetadataRequest(relative_url) 395 except exceptions.CommunicationError: 396 self.invalid = True 397 if self.store: 398 self.store.locked_put(self) 399 raise 400 content = six.ensure_str(response.read()) 401 try: 402 credential_info = json.loads(content) 403 except ValueError: 404 raise exceptions.CredentialsError( 405 'Could not parse response as JSON: %s' % content) 406 407 self.access_token = credential_info['access_token'] 408 if 'expires_in' in credential_info: 409 expires_in = int(credential_info['expires_in']) 410 self.token_expiry = ( 411 datetime.timedelta(seconds=expires_in) + 412 datetime.datetime.utcnow()) 413 else: 414 self.token_expiry = None 415 self.invalid = False 416 if self.store: 417 self.store.locked_put(self) 418 419 def to_json(self): 420 # OAuth2Client made gce.AppAssertionCredentials unserializable as of 421 # v3.0, but we need those credentials to be serializable for use with 422 # this library, so we use AppAssertionCredentials' parent's to_json 423 # method. 424 # pylint: disable=bad-super-call 425 return super(gce.AppAssertionCredentials, self).to_json() 426 427 @classmethod 428 def from_json(cls, json_data): 429 data = json.loads(json_data) 430 kwargs = {} 431 if 'cache_filename' in data.get('kwargs', []): 432 kwargs['cache_filename'] = data['kwargs']['cache_filename'] 433 # Newer versions of GceAssertionCredentials don't have a "scope" 434 # attribute. 435 scope_list = None 436 if 'scope' in data: 437 scope_list = [data['scope']] 438 credentials = GceAssertionCredentials(scopes=scope_list, **kwargs) 439 if 'access_token' in data: 440 credentials.access_token = data['access_token'] 441 if 'token_expiry' in data: 442 credentials.token_expiry = datetime.datetime.strptime( 443 data['token_expiry'], oauth2client.client.EXPIRY_FORMAT) 444 if 'invalid' in data: 445 credentials.invalid = data['invalid'] 446 return credentials 447 448 @property 449 def serialization_data(self): 450 raise NotImplementedError( 451 'Cannot serialize credentials for GCE service accounts.') 452 453 454# TODO(craigcitro): Currently, we can't even *load* 455# `oauth2client.appengine` without being on appengine, because of how 456# it handles imports. Fix that by splitting that module into 457# GAE-specific and GAE-independent bits, and guarding imports. 458class GaeAssertionCredentials(oauth2client.client.AssertionCredentials): 459 460 """Assertion credentials for Google App Engine apps.""" 461 462 def __init__(self, scopes, **kwds): 463 if not util.DetectGae(): 464 raise exceptions.ResourceUnavailableError( 465 'GCE credentials requested outside a GCE instance') 466 self._scopes = list(util.NormalizeScopes(scopes)) 467 super(GaeAssertionCredentials, self).__init__(None, **kwds) 468 469 @classmethod 470 def Get(cls, *args, **kwds): 471 try: 472 return cls(*args, **kwds) 473 except exceptions.Error: 474 return None 475 476 @classmethod 477 def from_json(cls, json_data): 478 data = json.loads(json_data) 479 return GaeAssertionCredentials(data['_scopes']) 480 481 def _refresh(self, _): 482 """Refresh self.access_token. 483 484 Args: 485 _: (ignored) A function matching httplib2.Http.request's signature. 486 """ 487 # pylint: disable=import-error 488 from google.appengine.api import app_identity 489 try: 490 token, _ = app_identity.get_access_token(self._scopes) 491 except app_identity.Error as e: 492 raise exceptions.CredentialsError(str(e)) 493 self.access_token = token 494 495 def sign_blob(self, blob): 496 """Cryptographically sign a blob (of bytes). 497 498 This method is provided to support a common interface, but 499 the actual key used for a Google Compute Engine service account 500 is not available, so it can't be used to sign content. 501 502 Args: 503 blob: bytes, Message to be signed. 504 505 Raises: 506 NotImplementedError, always. 507 """ 508 raise NotImplementedError( 509 'Compute Engine service accounts cannot sign blobs') 510 511 512def _GetRunFlowFlags(args=None): 513 """Retrieves command line flags based on gflags module.""" 514 # There's one rare situation where gsutil will not have argparse 515 # available, but doesn't need anything depending on argparse anyway, 516 # since they're bringing their own credentials. So we just allow this 517 # to fail with an ImportError in those cases. 518 # 519 parser = argparse.ArgumentParser(parents=[tools.argparser]) 520 # Get command line argparse flags. 521 flags, _ = parser.parse_known_args(args=args) 522 523 # Allow `gflags` and `argparse` to be used side-by-side. 524 if hasattr(FLAGS, 'auth_host_name'): 525 flags.auth_host_name = FLAGS.auth_host_name 526 if hasattr(FLAGS, 'auth_host_port'): 527 flags.auth_host_port = FLAGS.auth_host_port 528 if hasattr(FLAGS, 'auth_local_webserver'): 529 flags.noauth_local_webserver = (not FLAGS.auth_local_webserver) 530 return flags 531 532 533# TODO(craigcitro): Switch this from taking a path to taking a stream. 534def CredentialsFromFile(path, client_info, oauth2client_args=None): 535 """Read credentials from a file.""" 536 user_agent = client_info['user_agent'] 537 scope_key = client_info['scope'] 538 if not isinstance(scope_key, six.string_types): 539 scope_key = ':'.join(scope_key) 540 storage_key = client_info['client_id'] + user_agent + scope_key 541 542 if _NEW_FILESTORE: 543 credential_store = multiprocess_file_storage.MultiprocessFileStorage( 544 path, storage_key) 545 else: 546 credential_store = multistore_file.get_credential_storage_custom_string_key( # noqa 547 path, storage_key) 548 if hasattr(FLAGS, 'auth_local_webserver'): 549 FLAGS.auth_local_webserver = False 550 credentials = credential_store.get() 551 if credentials is None or credentials.invalid: 552 print('Generating new OAuth credentials ...') 553 for _ in range(20): 554 # If authorization fails, we want to retry, rather than let this 555 # cascade up and get caught elsewhere. If users want out of the 556 # retry loop, they can ^C. 557 try: 558 flow = oauth2client.client.OAuth2WebServerFlow(**client_info) 559 flags = _GetRunFlowFlags(args=oauth2client_args) 560 credentials = tools.run_flow(flow, credential_store, flags) 561 break 562 except (oauth2client.client.FlowExchangeError, SystemExit) as e: 563 # Here SystemExit is "no credential at all", and the 564 # FlowExchangeError is "invalid" -- usually because 565 # you reused a token. 566 print('Invalid authorization: %s' % (e,)) 567 except httplib2.HttpLib2Error as e: 568 print('Communication error: %s' % (e,)) 569 raise exceptions.CredentialsError( 570 'Communication error creating credentials: %s' % e) 571 return credentials 572 573 574class _MultiProcessCacheFile(object): 575 """Simple multithreading and multiprocessing safe cache file. 576 577 Notes on behavior: 578 * the fasteners.InterProcessLock object cannot reliably prevent threads 579 from double-acquiring a lock. A threading lock is used in addition to 580 the InterProcessLock. The threading lock is always acquired first and 581 released last. 582 * The interprocess lock will not deadlock. If a process can not acquire 583 the interprocess lock within `_lock_timeout` the call will return as 584 a cache miss or unsuccessful cache write. 585 * App Engine environments cannot be process locked because (1) the runtime 586 does not provide monotonic time and (2) different processes may or may 587 not share the same machine. Because of this, process locks are disabled 588 and locking is only guaranteed to protect against multithreaded access. 589 """ 590 591 _lock_timeout = 1 592 _encoding = 'utf-8' 593 _thread_lock = threading.Lock() 594 595 def __init__(self, filename): 596 self._file = None 597 self._filename = filename 598 if _FASTENERS_AVAILABLE: 599 self._process_lock_getter = self._ProcessLockAcquired 600 self._process_lock = fasteners.InterProcessLock( 601 '{0}.lock'.format(filename)) 602 else: 603 self._process_lock_getter = self._DummyLockAcquired 604 self._process_lock = None 605 606 @contextlib.contextmanager 607 def _ProcessLockAcquired(self): 608 """Context manager for process locks with timeout.""" 609 try: 610 is_locked = self._process_lock.acquire(timeout=self._lock_timeout) 611 yield is_locked 612 finally: 613 if is_locked: 614 self._process_lock.release() 615 616 @contextlib.contextmanager 617 def _DummyLockAcquired(self): 618 """Lock context manager for environments without process locks.""" 619 yield True 620 621 def LockedRead(self): 622 """Acquire an interprocess lock and dump cache contents. 623 624 This method safely acquires the locks then reads a string 625 from the cache file. If the file does not exist and cannot 626 be created, it will return None. If the locks cannot be 627 acquired, this will also return None. 628 629 Returns: 630 cache data - string if present, None on failure. 631 """ 632 file_contents = None 633 with self._thread_lock: 634 if not self._EnsureFileExists(): 635 return None 636 with self._process_lock_getter() as acquired_plock: 637 if not acquired_plock: 638 return None 639 with open(self._filename, 'rb') as f: 640 file_contents = f.read().decode(encoding=self._encoding) 641 return file_contents 642 643 def LockedWrite(self, cache_data): 644 """Acquire an interprocess lock and write a string. 645 646 This method safely acquires the locks then writes a string 647 to the cache file. If the string is written successfully 648 the function will return True, if the write fails for any 649 reason it will return False. 650 651 Args: 652 cache_data: string or bytes to write. 653 654 Returns: 655 bool: success 656 """ 657 if isinstance(cache_data, six.text_type): 658 cache_data = cache_data.encode(encoding=self._encoding) 659 660 with self._thread_lock: 661 if not self._EnsureFileExists(): 662 return False 663 with self._process_lock_getter() as acquired_plock: 664 if not acquired_plock: 665 return False 666 with open(self._filename, 'wb') as f: 667 f.write(cache_data) 668 return True 669 670 def _EnsureFileExists(self): 671 """Touches a file; returns False on error, True on success.""" 672 if not os.path.exists(self._filename): 673 old_umask = os.umask(0o177) 674 try: 675 open(self._filename, 'a+b').close() 676 except OSError: 677 return False 678 finally: 679 os.umask(old_umask) 680 return True 681 682 683# TODO(craigcitro): Push this into oauth2client. 684def GetUserinfo(credentials, http=None): # pylint: disable=invalid-name 685 """Get the userinfo associated with the given credentials. 686 687 This is dependent on the token having either the userinfo.email or 688 userinfo.profile scope for the given token. 689 690 Args: 691 credentials: (oauth2client.client.Credentials) incoming credentials 692 http: (httplib2.Http, optional) http instance to use 693 694 Returns: 695 The email address for this token, or None if the required scopes 696 aren't available. 697 """ 698 http = http or httplib2.Http() 699 url = _GetUserinfoUrl(credentials) 700 # We ignore communication woes here (i.e. SSL errors, socket 701 # timeout), as handling these should be done in a common location. 702 response, content = http.request(url) 703 if response.status == http_client.BAD_REQUEST: 704 credentials.refresh(http) 705 url = _GetUserinfoUrl(credentials) 706 response, content = http.request(url) 707 return json.loads(content or '{}') # Save ourselves from an empty reply. 708 709 710def _GetUserinfoUrl(credentials): 711 url_root = 'https://oauth2.googleapis.com/tokeninfo' 712 query_args = {'access_token': credentials.access_token} 713 return '?'.join((url_root, urllib.parse.urlencode(query_args))) 714 715 716@_RegisterCredentialsMethod 717def _GetServiceAccountCredentials( 718 client_info, service_account_name=None, service_account_keyfile=None, 719 service_account_json_keyfile=None, **unused_kwds): 720 """Returns ServiceAccountCredentials from give file.""" 721 scopes = client_info['scope'].split() 722 user_agent = client_info['user_agent'] 723 # Use the .json credentials, if provided. 724 if service_account_json_keyfile: 725 return ServiceAccountCredentialsFromFile( 726 service_account_json_keyfile, scopes, user_agent=user_agent) 727 # Fall back to .p12 if there's no .json credentials. 728 if ((service_account_name and not service_account_keyfile) or 729 (service_account_keyfile and not service_account_name)): 730 raise exceptions.CredentialsError( 731 'Service account name or keyfile provided without the other') 732 if service_account_name is not None: 733 return ServiceAccountCredentialsFromP12File( 734 service_account_name, service_account_keyfile, scopes, user_agent) 735 736 737@_RegisterCredentialsMethod 738def _GetGaeServiceAccount(client_info, **unused_kwds): 739 scopes = client_info['scope'].split(' ') 740 return GaeAssertionCredentials.Get(scopes=scopes) 741 742 743@_RegisterCredentialsMethod 744def _GetGceServiceAccount(client_info, **unused_kwds): 745 scopes = client_info['scope'].split(' ') 746 return GceAssertionCredentials.Get(scopes=scopes) 747 748 749@_RegisterCredentialsMethod 750def _GetApplicationDefaultCredentials( 751 client_info, skip_application_default_credentials=False, 752 **unused_kwds): 753 """Returns ADC with right scopes.""" 754 scopes = client_info['scope'].split() 755 if skip_application_default_credentials: 756 return None 757 gc = oauth2client.client.GoogleCredentials 758 with cache_file_lock: 759 try: 760 # pylint: disable=protected-access 761 # We've already done our own check for GAE/GCE 762 # credentials, we don't want to pay for checking again. 763 credentials = gc._implicit_credentials_from_files() 764 except oauth2client.client.ApplicationDefaultCredentialsError: 765 return None 766 # If we got back a non-service account credential, we need to use 767 # a heuristic to decide whether or not the application default 768 # credential will work for us. We assume that if we're requesting 769 # cloud-platform, our scopes are a subset of cloud scopes, and the 770 # ADC will work. 771 cp = 'https://www.googleapis.com/auth/cloud-platform' 772 if credentials is None: 773 return None 774 if not isinstance(credentials, gc) or cp in scopes: 775 return credentials.create_scoped(scopes) 776 return None 777