• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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