Home | History | Annotate | Download | only in oauth2client
      1 # Copyright 2014 Google Inc. All rights reserved.
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #      http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 
     15 """An OAuth 2.0 client.
     16 
     17 Tools for interacting with OAuth 2.0 protected resources.
     18 """
     19 
     20 __author__ = 'jcgregorio (at] google.com (Joe Gregorio)'
     21 
     22 import base64
     23 import collections
     24 import copy
     25 import datetime
     26 import json
     27 import logging
     28 import os
     29 import socket
     30 import sys
     31 import tempfile
     32 import time
     33 import shutil
     34 import six
     35 from six.moves import urllib
     36 
     37 import httplib2
     38 from oauth2client import clientsecrets
     39 from oauth2client import GOOGLE_AUTH_URI
     40 from oauth2client import GOOGLE_DEVICE_URI
     41 from oauth2client import GOOGLE_REVOKE_URI
     42 from oauth2client import GOOGLE_TOKEN_URI
     43 from oauth2client import util
     44 
     45 HAS_OPENSSL = False
     46 HAS_CRYPTO = False
     47 try:
     48   from oauth2client import crypt
     49   HAS_CRYPTO = True
     50   if crypt.OpenSSLVerifier is not None:
     51     HAS_OPENSSL = True
     52 except ImportError:
     53   pass
     54 
     55 logger = logging.getLogger(__name__)
     56 
     57 # Expiry is stored in RFC3339 UTC format
     58 EXPIRY_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
     59 
     60 # Which certs to use to validate id_tokens received.
     61 ID_TOKEN_VERIFICATION_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
     62 # This symbol previously had a typo in the name; we keep the old name
     63 # around for now, but will remove it in the future.
     64 ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS
     65 
     66 # Constant to use for the out of band OAuth 2.0 flow.
     67 OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
     68 
     69 # Google Data client libraries may need to set this to [401, 403].
     70 REFRESH_STATUS_CODES = [401]
     71 
     72 # The value representing user credentials.
     73 AUTHORIZED_USER = 'authorized_user'
     74 
     75 # The value representing service account credentials.
     76 SERVICE_ACCOUNT = 'service_account'
     77 
     78 # The environment variable pointing the file with local
     79 # Application Default Credentials.
     80 GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS'
     81 # The ~/.config subdirectory containing gcloud credentials. Intended
     82 # to be swapped out in tests.
     83 _CLOUDSDK_CONFIG_DIRECTORY = 'gcloud'
     84 # The environment variable name which can replace ~/.config if set.
     85 _CLOUDSDK_CONFIG_ENV_VAR = 'CLOUDSDK_CONFIG'
     86 
     87 # The error message we show users when we can't find the Application
     88 # Default Credentials.
     89 ADC_HELP_MSG = (
     90     'The Application Default Credentials are not available. They are available '
     91     'if running in Google Compute Engine. Otherwise, the environment variable '
     92     + GOOGLE_APPLICATION_CREDENTIALS +
     93     ' must be defined pointing to a file defining the credentials. See '
     94     'https://developers.google.com/accounts/docs/application-default-credentials'  # pylint:disable=line-too-long
     95     ' for more information.')
     96 
     97 # The access token along with the seconds in which it expires.
     98 AccessTokenInfo = collections.namedtuple(
     99     'AccessTokenInfo', ['access_token', 'expires_in'])
    100 
    101 DEFAULT_ENV_NAME = 'UNKNOWN'
    102 
    103 # If set to True _get_environment avoid GCE check (_detect_gce_environment)
    104 NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False')
    105 
    106 class SETTINGS(object):
    107   """Settings namespace for globally defined values."""
    108   env_name = None
    109 
    110 
    111 class Error(Exception):
    112   """Base error for this module."""
    113 
    114 
    115 class FlowExchangeError(Error):
    116   """Error trying to exchange an authorization grant for an access token."""
    117 
    118 
    119 class AccessTokenRefreshError(Error):
    120   """Error trying to refresh an expired access token."""
    121 
    122 
    123 class TokenRevokeError(Error):
    124   """Error trying to revoke a token."""
    125 
    126 
    127 class UnknownClientSecretsFlowError(Error):
    128   """The client secrets file called for an unknown type of OAuth 2.0 flow. """
    129 
    130 
    131 class AccessTokenCredentialsError(Error):
    132   """Having only the access_token means no refresh is possible."""
    133 
    134 
    135 class VerifyJwtTokenError(Error):
    136   """Could not retrieve certificates for validation."""
    137 
    138 
    139 class NonAsciiHeaderError(Error):
    140   """Header names and values must be ASCII strings."""
    141 
    142 
    143 class ApplicationDefaultCredentialsError(Error):
    144   """Error retrieving the Application Default Credentials."""
    145 
    146 
    147 class OAuth2DeviceCodeError(Error):
    148   """Error trying to retrieve a device code."""
    149 
    150 
    151 class CryptoUnavailableError(Error, NotImplementedError):
    152   """Raised when a crypto library is required, but none is available."""
    153 
    154 
    155 def _abstract():
    156   raise NotImplementedError('You need to override this function')
    157 
    158 
    159 class MemoryCache(object):
    160   """httplib2 Cache implementation which only caches locally."""
    161 
    162   def __init__(self):
    163     self.cache = {}
    164 
    165   def get(self, key):
    166     return self.cache.get(key)
    167 
    168   def set(self, key, value):
    169     self.cache[key] = value
    170 
    171   def delete(self, key):
    172     self.cache.pop(key, None)
    173 
    174 
    175 class Credentials(object):
    176   """Base class for all Credentials objects.
    177 
    178   Subclasses must define an authorize() method that applies the credentials to
    179   an HTTP transport.
    180 
    181   Subclasses must also specify a classmethod named 'from_json' that takes a JSON
    182   string as input and returns an instantiated Credentials object.
    183   """
    184 
    185   NON_SERIALIZED_MEMBERS = ['store']
    186 
    187 
    188   def authorize(self, http):
    189     """Take an httplib2.Http instance (or equivalent) and authorizes it.
    190 
    191     Authorizes it for the set of credentials, usually by replacing
    192     http.request() with a method that adds in the appropriate headers and then
    193     delegates to the original Http.request() method.
    194 
    195     Args:
    196       http: httplib2.Http, an http object to be used to make the refresh
    197         request.
    198     """
    199     _abstract()
    200 
    201 
    202   def refresh(self, http):
    203     """Forces a refresh of the access_token.
    204 
    205     Args:
    206       http: httplib2.Http, an http object to be used to make the refresh
    207         request.
    208     """
    209     _abstract()
    210 
    211 
    212   def revoke(self, http):
    213     """Revokes a refresh_token and makes the credentials void.
    214 
    215     Args:
    216       http: httplib2.Http, an http object to be used to make the revoke
    217         request.
    218     """
    219     _abstract()
    220 
    221 
    222   def apply(self, headers):
    223     """Add the authorization to the headers.
    224 
    225     Args:
    226       headers: dict, the headers to add the Authorization header to.
    227     """
    228     _abstract()
    229 
    230   def _to_json(self, strip):
    231     """Utility function that creates JSON repr. of a Credentials object.
    232 
    233     Args:
    234       strip: array, An array of names of members to not include in the JSON.
    235 
    236     Returns:
    237        string, a JSON representation of this instance, suitable to pass to
    238        from_json().
    239     """
    240     t = type(self)
    241     d = copy.copy(self.__dict__)
    242     for member in strip:
    243       if member in d:
    244         del d[member]
    245     if (d.get('token_expiry') and
    246         isinstance(d['token_expiry'], datetime.datetime)):
    247       d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
    248     # Add in information we will need later to reconsistitue this instance.
    249     d['_class'] = t.__name__
    250     d['_module'] = t.__module__
    251     for key, val in d.items():
    252       if isinstance(val, bytes):
    253         d[key] = val.decode('utf-8')
    254     return json.dumps(d)
    255 
    256   def to_json(self):
    257     """Creating a JSON representation of an instance of Credentials.
    258 
    259     Returns:
    260        string, a JSON representation of this instance, suitable to pass to
    261        from_json().
    262     """
    263     return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
    264 
    265   @classmethod
    266   def new_from_json(cls, s):
    267     """Utility class method to instantiate a Credentials subclass from a JSON
    268     representation produced by to_json().
    269 
    270     Args:
    271       s: string, JSON from to_json().
    272 
    273     Returns:
    274       An instance of the subclass of Credentials that was serialized with
    275       to_json().
    276     """
    277     if six.PY3 and isinstance(s, bytes):
    278       s = s.decode('utf-8')
    279     data = json.loads(s)
    280     # Find and call the right classmethod from_json() to restore the object.
    281     module = data['_module']
    282     try:
    283       m = __import__(module)
    284     except ImportError:
    285       # In case there's an object from the old package structure, update it
    286       module = module.replace('.googleapiclient', '')
    287       m = __import__(module)
    288 
    289     m = __import__(module, fromlist=module.split('.')[:-1])
    290     kls = getattr(m, data['_class'])
    291     from_json = getattr(kls, 'from_json')
    292     return from_json(s)
    293 
    294   @classmethod
    295   def from_json(cls, unused_data):
    296     """Instantiate a Credentials object from a JSON description of it.
    297 
    298     The JSON should have been produced by calling .to_json() on the object.
    299 
    300     Args:
    301       unused_data: dict, A deserialized JSON object.
    302 
    303     Returns:
    304       An instance of a Credentials subclass.
    305     """
    306     return Credentials()
    307 
    308 
    309 class Flow(object):
    310   """Base class for all Flow objects."""
    311   pass
    312 
    313 
    314 class Storage(object):
    315   """Base class for all Storage objects.
    316 
    317   Store and retrieve a single credential. This class supports locking
    318   such that multiple processes and threads can operate on a single
    319   store.
    320   """
    321 
    322   def acquire_lock(self):
    323     """Acquires any lock necessary to access this Storage.
    324 
    325     This lock is not reentrant.
    326     """
    327     pass
    328 
    329   def release_lock(self):
    330     """Release the Storage lock.
    331 
    332     Trying to release a lock that isn't held will result in a
    333     RuntimeError.
    334     """
    335     pass
    336 
    337   def locked_get(self):
    338     """Retrieve credential.
    339 
    340     The Storage lock must be held when this is called.
    341 
    342     Returns:
    343       oauth2client.client.Credentials
    344     """
    345     _abstract()
    346 
    347   def locked_put(self, credentials):
    348     """Write a credential.
    349 
    350     The Storage lock must be held when this is called.
    351 
    352     Args:
    353       credentials: Credentials, the credentials to store.
    354     """
    355     _abstract()
    356 
    357   def locked_delete(self):
    358     """Delete a credential.
    359 
    360     The Storage lock must be held when this is called.
    361     """
    362     _abstract()
    363 
    364   def get(self):
    365     """Retrieve credential.
    366 
    367     The Storage lock must *not* be held when this is called.
    368 
    369     Returns:
    370       oauth2client.client.Credentials
    371     """
    372     self.acquire_lock()
    373     try:
    374       return self.locked_get()
    375     finally:
    376       self.release_lock()
    377 
    378   def put(self, credentials):
    379     """Write a credential.
    380 
    381     The Storage lock must be held when this is called.
    382 
    383     Args:
    384       credentials: Credentials, the credentials to store.
    385     """
    386     self.acquire_lock()
    387     try:
    388       self.locked_put(credentials)
    389     finally:
    390       self.release_lock()
    391 
    392   def delete(self):
    393     """Delete credential.
    394 
    395     Frees any resources associated with storing the credential.
    396     The Storage lock must *not* be held when this is called.
    397 
    398     Returns:
    399       None
    400     """
    401     self.acquire_lock()
    402     try:
    403       return self.locked_delete()
    404     finally:
    405       self.release_lock()
    406 
    407 
    408 def clean_headers(headers):
    409   """Forces header keys and values to be strings, i.e not unicode.
    410 
    411   The httplib module just concats the header keys and values in a way that may
    412   make the message header a unicode string, which, if it then tries to
    413   contatenate to a binary request body may result in a unicode decode error.
    414 
    415   Args:
    416     headers: dict, A dictionary of headers.
    417 
    418   Returns:
    419     The same dictionary but with all the keys converted to strings.
    420   """
    421   clean = {}
    422   try:
    423     for k, v in six.iteritems(headers):
    424       clean_k = k if isinstance(k, bytes) else str(k).encode('ascii')
    425       clean_v = v if isinstance(v, bytes) else str(v).encode('ascii')
    426       clean[clean_k] = clean_v
    427   except UnicodeEncodeError:
    428     raise NonAsciiHeaderError(k + ': ' + v)
    429   return clean
    430 
    431 
    432 def _update_query_params(uri, params):
    433   """Updates a URI with new query parameters.
    434 
    435   Args:
    436     uri: string, A valid URI, with potential existing query parameters.
    437     params: dict, A dictionary of query parameters.
    438 
    439   Returns:
    440     The same URI but with the new query parameters added.
    441   """
    442   parts = urllib.parse.urlparse(uri)
    443   query_params = dict(urllib.parse.parse_qsl(parts.query))
    444   query_params.update(params)
    445   new_parts = parts._replace(query=urllib.parse.urlencode(query_params))
    446   return urllib.parse.urlunparse(new_parts)
    447 
    448 
    449 class OAuth2Credentials(Credentials):
    450   """Credentials object for OAuth 2.0.
    451 
    452   Credentials can be applied to an httplib2.Http object using the authorize()
    453   method, which then adds the OAuth 2.0 access token to each request.
    454 
    455   OAuth2Credentials objects may be safely pickled and unpickled.
    456   """
    457 
    458   @util.positional(8)
    459   def __init__(self, access_token, client_id, client_secret, refresh_token,
    460                token_expiry, token_uri, user_agent, revoke_uri=None,
    461                id_token=None, token_response=None):
    462     """Create an instance of OAuth2Credentials.
    463 
    464     This constructor is not usually called by the user, instead
    465     OAuth2Credentials objects are instantiated by the OAuth2WebServerFlow.
    466 
    467     Args:
    468       access_token: string, access token.
    469       client_id: string, client identifier.
    470       client_secret: string, client secret.
    471       refresh_token: string, refresh token.
    472       token_expiry: datetime, when the access_token expires.
    473       token_uri: string, URI of token endpoint.
    474       user_agent: string, The HTTP User-Agent to provide for this application.
    475       revoke_uri: string, URI for revoke endpoint. Defaults to None; a token
    476         can't be revoked if this is None.
    477       id_token: object, The identity of the resource owner.
    478       token_response: dict, the decoded response to the token request. None
    479         if a token hasn't been requested yet. Stored because some providers
    480         (e.g. wordpress.com) include extra fields that clients may want.
    481 
    482     Notes:
    483       store: callable, A callable that when passed a Credential
    484         will store the credential back to where it came from.
    485         This is needed to store the latest access_token if it
    486         has expired and been refreshed.
    487     """
    488     self.access_token = access_token
    489     self.client_id = client_id
    490     self.client_secret = client_secret
    491     self.refresh_token = refresh_token
    492     self.store = None
    493     self.token_expiry = token_expiry
    494     self.token_uri = token_uri
    495     self.user_agent = user_agent
    496     self.revoke_uri = revoke_uri
    497     self.id_token = id_token
    498     self.token_response = token_response
    499 
    500     # True if the credentials have been revoked or expired and can't be
    501     # refreshed.
    502     self.invalid = False
    503 
    504   def authorize(self, http):
    505     """Authorize an httplib2.Http instance with these credentials.
    506 
    507     The modified http.request method will add authentication headers to each
    508     request and will refresh access_tokens when a 401 is received on a
    509     request. In addition the http.request method has a credentials property,
    510     http.request.credentials, which is the Credentials object that authorized
    511     it.
    512 
    513     Args:
    514        http: An instance of ``httplib2.Http`` or something that acts
    515          like it.
    516 
    517     Returns:
    518        A modified instance of http that was passed in.
    519 
    520     Example::
    521 
    522       h = httplib2.Http()
    523       h = credentials.authorize(h)
    524 
    525     You can't create a new OAuth subclass of httplib2.Authentication
    526     because it never gets passed the absolute URI, which is needed for
    527     signing. So instead we have to overload 'request' with a closure
    528     that adds in the Authorization header and then calls the original
    529     version of 'request()'.
    530 
    531     """
    532     request_orig = http.request
    533 
    534     # The closure that will replace 'httplib2.Http.request'.
    535     @util.positional(1)
    536     def new_request(uri, method='GET', body=None, headers=None,
    537                     redirections=httplib2.DEFAULT_MAX_REDIRECTS,
    538                     connection_type=None):
    539       if not self.access_token:
    540         logger.info('Attempting refresh to obtain initial access_token')
    541         self._refresh(request_orig)
    542 
    543       # Clone and modify the request headers to add the appropriate
    544       # Authorization header.
    545       if headers is None:
    546         headers = {}
    547       else:
    548         headers = dict(headers)
    549       self.apply(headers)
    550 
    551       if self.user_agent is not None:
    552         if 'user-agent' in headers:
    553           headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
    554         else:
    555           headers['user-agent'] = self.user_agent
    556 
    557       body_stream_position = None
    558       if all(getattr(body, stream_prop, None) for stream_prop in
    559              ('read', 'seek', 'tell')):
    560         body_stream_position = body.tell()
    561 
    562       resp, content = request_orig(uri, method, body, clean_headers(headers),
    563                                    redirections, connection_type)
    564 
    565       # A stored token may expire between the time it is retrieved and the time
    566       # the request is made, so we may need to try twice.
    567       max_refresh_attempts = 2
    568       for refresh_attempt in range(max_refresh_attempts):
    569         if resp.status not in REFRESH_STATUS_CODES:
    570           break
    571         logger.info('Refreshing due to a %s (attempt %s/%s)', resp.status,
    572                     refresh_attempt + 1, max_refresh_attempts)
    573         self._refresh(request_orig)
    574         self.apply(headers)
    575         if body_stream_position is not None:
    576           body.seek(body_stream_position)
    577 
    578         resp, content = request_orig(uri, method, body, clean_headers(headers),
    579                                      redirections, connection_type)
    580 
    581       return (resp, content)
    582 
    583     # Replace the request method with our own closure.
    584     http.request = new_request
    585 
    586     # Set credentials as a property of the request method.
    587     setattr(http.request, 'credentials', self)
    588 
    589     return http
    590 
    591   def refresh(self, http):
    592     """Forces a refresh of the access_token.
    593 
    594     Args:
    595       http: httplib2.Http, an http object to be used to make the refresh
    596         request.
    597     """
    598     self._refresh(http.request)
    599 
    600   def revoke(self, http):
    601     """Revokes a refresh_token and makes the credentials void.
    602 
    603     Args:
    604       http: httplib2.Http, an http object to be used to make the revoke
    605         request.
    606     """
    607     self._revoke(http.request)
    608 
    609   def apply(self, headers):
    610     """Add the authorization to the headers.
    611 
    612     Args:
    613       headers: dict, the headers to add the Authorization header to.
    614     """
    615     headers['Authorization'] = 'Bearer ' + self.access_token
    616 
    617   def to_json(self):
    618     return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
    619 
    620   @classmethod
    621   def from_json(cls, s):
    622     """Instantiate a Credentials object from a JSON description of it. The JSON
    623     should have been produced by calling .to_json() on the object.
    624 
    625     Args:
    626       data: dict, A deserialized JSON object.
    627 
    628     Returns:
    629       An instance of a Credentials subclass.
    630     """
    631     if six.PY3 and isinstance(s, bytes):
    632       s = s.decode('utf-8')
    633     data = json.loads(s)
    634     if (data.get('token_expiry') and
    635         not isinstance(data['token_expiry'], datetime.datetime)):
    636       try:
    637         data['token_expiry'] = datetime.datetime.strptime(
    638             data['token_expiry'], EXPIRY_FORMAT)
    639       except ValueError:
    640         data['token_expiry'] = None
    641     retval = cls(
    642         data['access_token'],
    643         data['client_id'],
    644         data['client_secret'],
    645         data['refresh_token'],
    646         data['token_expiry'],
    647         data['token_uri'],
    648         data['user_agent'],
    649         revoke_uri=data.get('revoke_uri', None),
    650         id_token=data.get('id_token', None),
    651         token_response=data.get('token_response', None))
    652     retval.invalid = data['invalid']
    653     return retval
    654 
    655   @property
    656   def access_token_expired(self):
    657     """True if the credential is expired or invalid.
    658 
    659     If the token_expiry isn't set, we assume the token doesn't expire.
    660     """
    661     if self.invalid:
    662       return True
    663 
    664     if not self.token_expiry:
    665       return False
    666 
    667     now = datetime.datetime.utcnow()
    668     if now >= self.token_expiry:
    669       logger.info('access_token is expired. Now: %s, token_expiry: %s',
    670                   now, self.token_expiry)
    671       return True
    672     return False
    673 
    674   def get_access_token(self, http=None):
    675     """Return the access token and its expiration information.
    676 
    677     If the token does not exist, get one.
    678     If the token expired, refresh it.
    679     """
    680     if not self.access_token or self.access_token_expired:
    681       if not http:
    682         http = httplib2.Http()
    683       self.refresh(http)
    684     return AccessTokenInfo(access_token=self.access_token,
    685                            expires_in=self._expires_in())
    686 
    687   def set_store(self, store):
    688     """Set the Storage for the credential.
    689 
    690     Args:
    691       store: Storage, an implementation of Storage object.
    692         This is needed to store the latest access_token if it
    693         has expired and been refreshed. This implementation uses
    694         locking to check for updates before updating the
    695         access_token.
    696     """
    697     self.store = store
    698 
    699   def _expires_in(self):
    700     """Return the number of seconds until this token expires.
    701 
    702     If token_expiry is in the past, this method will return 0, meaning the
    703     token has already expired.
    704     If token_expiry is None, this method will return None. Note that returning
    705     0 in such a case would not be fair: the token may still be valid;
    706     we just don't know anything about it.
    707     """
    708     if self.token_expiry:
    709       now = datetime.datetime.utcnow()
    710       if self.token_expiry > now:
    711         time_delta = self.token_expiry - now
    712         # TODO(orestica): return time_delta.total_seconds()
    713         # once dropping support for Python 2.6
    714         return time_delta.days * 86400 + time_delta.seconds
    715       else:
    716         return 0
    717 
    718   def _updateFromCredential(self, other):
    719     """Update this Credential from another instance."""
    720     self.__dict__.update(other.__getstate__())
    721 
    722   def __getstate__(self):
    723     """Trim the state down to something that can be pickled."""
    724     d = copy.copy(self.__dict__)
    725     del d['store']
    726     return d
    727 
    728   def __setstate__(self, state):
    729     """Reconstitute the state of the object from being pickled."""
    730     self.__dict__.update(state)
    731     self.store = None
    732 
    733   def _generate_refresh_request_body(self):
    734     """Generate the body that will be used in the refresh request."""
    735     body = urllib.parse.urlencode({
    736         'grant_type': 'refresh_token',
    737         'client_id': self.client_id,
    738         'client_secret': self.client_secret,
    739         'refresh_token': self.refresh_token,
    740         })
    741     return body
    742 
    743   def _generate_refresh_request_headers(self):
    744     """Generate the headers that will be used in the refresh request."""
    745     headers = {
    746         'content-type': 'application/x-www-form-urlencoded',
    747     }
    748 
    749     if self.user_agent is not None:
    750       headers['user-agent'] = self.user_agent
    751 
    752     return headers
    753 
    754   def _refresh(self, http_request):
    755     """Refreshes the access_token.
    756 
    757     This method first checks by reading the Storage object if available.
    758     If a refresh is still needed, it holds the Storage lock until the
    759     refresh is completed.
    760 
    761     Args:
    762       http_request: callable, a callable that matches the method signature of
    763         httplib2.Http.request, used to make the refresh request.
    764 
    765     Raises:
    766       AccessTokenRefreshError: When the refresh fails.
    767     """
    768     if not self.store:
    769       self._do_refresh_request(http_request)
    770     else:
    771       self.store.acquire_lock()
    772       try:
    773         new_cred = self.store.locked_get()
    774 
    775         if (new_cred and not new_cred.invalid and
    776             new_cred.access_token != self.access_token and
    777             not new_cred.access_token_expired):
    778           logger.info('Updated access_token read from Storage')
    779           self._updateFromCredential(new_cred)
    780         else:
    781           self._do_refresh_request(http_request)
    782       finally:
    783         self.store.release_lock()
    784 
    785   def _do_refresh_request(self, http_request):
    786     """Refresh the access_token using the refresh_token.
    787 
    788     Args:
    789       http_request: callable, a callable that matches the method signature of
    790         httplib2.Http.request, used to make the refresh request.
    791 
    792     Raises:
    793       AccessTokenRefreshError: When the refresh fails.
    794     """
    795     body = self._generate_refresh_request_body()
    796     headers = self._generate_refresh_request_headers()
    797 
    798     logger.info('Refreshing access_token')
    799     resp, content = http_request(
    800         self.token_uri, method='POST', body=body, headers=headers)
    801     if six.PY3 and isinstance(content, bytes):
    802       content = content.decode('utf-8')
    803     if resp.status == 200:
    804       d = json.loads(content)
    805       self.token_response = d
    806       self.access_token = d['access_token']
    807       self.refresh_token = d.get('refresh_token', self.refresh_token)
    808       if 'expires_in' in d:
    809         self.token_expiry = datetime.timedelta(
    810             seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
    811       else:
    812         self.token_expiry = None
    813       # On temporary refresh errors, the user does not actually have to
    814       # re-authorize, so we unflag here.
    815       self.invalid = False
    816       if self.store:
    817         self.store.locked_put(self)
    818     else:
    819       # An {'error':...} response body means the token is expired or revoked,
    820       # so we flag the credentials as such.
    821       logger.info('Failed to retrieve access token: %s', content)
    822       error_msg = 'Invalid response %s.' % resp['status']
    823       try:
    824         d = json.loads(content)
    825         if 'error' in d:
    826           error_msg = d['error']
    827           if 'error_description' in d:
    828             error_msg += ': ' + d['error_description']
    829           self.invalid = True
    830           if self.store:
    831             self.store.locked_put(self)
    832       except (TypeError, ValueError):
    833         pass
    834       raise AccessTokenRefreshError(error_msg)
    835 
    836   def _revoke(self, http_request):
    837     """Revokes this credential and deletes the stored copy (if it exists).
    838 
    839     Args:
    840       http_request: callable, a callable that matches the method signature of
    841         httplib2.Http.request, used to make the revoke request.
    842     """
    843     self._do_revoke(http_request, self.refresh_token or self.access_token)
    844 
    845   def _do_revoke(self, http_request, token):
    846     """Revokes this credential and deletes the stored copy (if it exists).
    847 
    848     Args:
    849       http_request: callable, a callable that matches the method signature of
    850         httplib2.Http.request, used to make the refresh request.
    851       token: A string used as the token to be revoked. Can be either an
    852         access_token or refresh_token.
    853 
    854     Raises:
    855       TokenRevokeError: If the revoke request does not return with a 200 OK.
    856     """
    857     logger.info('Revoking token')
    858     query_params = {'token': token}
    859     token_revoke_uri = _update_query_params(self.revoke_uri, query_params)
    860     resp, content = http_request(token_revoke_uri)
    861     if resp.status == 200:
    862       self.invalid = True
    863     else:
    864       error_msg = 'Invalid response %s.' % resp.status
    865       try:
    866         d = json.loads(content)
    867         if 'error' in d:
    868           error_msg = d['error']
    869       except (TypeError, ValueError):
    870         pass
    871       raise TokenRevokeError(error_msg)
    872 
    873     if self.store:
    874       self.store.delete()
    875 
    876 
    877 class AccessTokenCredentials(OAuth2Credentials):
    878   """Credentials object for OAuth 2.0.
    879 
    880   Credentials can be applied to an httplib2.Http object using the
    881   authorize() method, which then signs each request from that object
    882   with the OAuth 2.0 access token. This set of credentials is for the
    883   use case where you have acquired an OAuth 2.0 access_token from
    884   another place such as a JavaScript client or another web
    885   application, and wish to use it from Python. Because only the
    886   access_token is present it can not be refreshed and will in time
    887   expire.
    888 
    889   AccessTokenCredentials objects may be safely pickled and unpickled.
    890 
    891   Usage::
    892 
    893     credentials = AccessTokenCredentials('<an access token>',
    894       'my-user-agent/1.0')
    895     http = httplib2.Http()
    896     http = credentials.authorize(http)
    897 
    898   Exceptions:
    899     AccessTokenCredentialsExpired: raised when the access_token expires or is
    900       revoked.
    901   """
    902 
    903   def __init__(self, access_token, user_agent, revoke_uri=None):
    904     """Create an instance of OAuth2Credentials
    905 
    906     This is one of the few types if Credentials that you should contrust,
    907     Credentials objects are usually instantiated by a Flow.
    908 
    909     Args:
    910       access_token: string, access token.
    911       user_agent: string, The HTTP User-Agent to provide for this application.
    912       revoke_uri: string, URI for revoke endpoint. Defaults to None; a token
    913         can't be revoked if this is None.
    914     """
    915     super(AccessTokenCredentials, self).__init__(
    916         access_token,
    917         None,
    918         None,
    919         None,
    920         None,
    921         None,
    922         user_agent,
    923         revoke_uri=revoke_uri)
    924 
    925 
    926   @classmethod
    927   def from_json(cls, s):
    928     if six.PY3 and isinstance(s, bytes):
    929       s = s.decode('utf-8')
    930     data = json.loads(s)
    931     retval = AccessTokenCredentials(
    932       data['access_token'],
    933       data['user_agent'])
    934     return retval
    935 
    936   def _refresh(self, http_request):
    937     raise AccessTokenCredentialsError(
    938         'The access_token is expired or invalid and can\'t be refreshed.')
    939 
    940   def _revoke(self, http_request):
    941     """Revokes the access_token and deletes the store if available.
    942 
    943     Args:
    944       http_request: callable, a callable that matches the method signature of
    945         httplib2.Http.request, used to make the revoke request.
    946     """
    947     self._do_revoke(http_request, self.access_token)
    948 
    949 
    950 def _detect_gce_environment(urlopen=None):
    951   """Determine if the current environment is Compute Engine.
    952 
    953   Args:
    954       urlopen: Optional argument. Function used to open a connection to a URL.
    955 
    956   Returns:
    957       Boolean indicating whether or not the current environment is Google
    958           Compute Engine.
    959   """
    960   urlopen = urlopen or urllib.request.urlopen
    961   # Note: the explicit `timeout` below is a workaround. The underlying
    962   # issue is that resolving an unknown host on some networks will take
    963   # 20-30 seconds; making this timeout short fixes the issue, but
    964   # could lead to false negatives in the event that we are on GCE, but
    965   # the metadata resolution was particularly slow. The latter case is
    966   # "unlikely".
    967   try:
    968     response = urlopen('http://169.254.169.254/', timeout=1)
    969     return response.info().get('Metadata-Flavor', '') == 'Google'
    970   except socket.timeout:
    971     logger.info('Timeout attempting to reach GCE metadata service.')
    972     return False
    973   except urllib.error.URLError as e:
    974     if isinstance(getattr(e, 'reason', None), socket.timeout):
    975       logger.info('Timeout attempting to reach GCE metadata service.')
    976     return False
    977 
    978 
    979 def _get_environment(urlopen=None):
    980   """Detect the environment the code is being run on.
    981 
    982   Args:
    983       urlopen: Optional argument. Function used to open a connection to a URL.
    984 
    985   Returns:
    986       The value of SETTINGS.env_name after being set. If already
    987           set, simply returns the value.
    988   """
    989   if SETTINGS.env_name is not None:
    990     return SETTINGS.env_name
    991 
    992   # None is an unset value, not the default.
    993   SETTINGS.env_name = DEFAULT_ENV_NAME
    994 
    995   try:
    996     import google.appengine
    997     has_gae_sdk = True
    998   except ImportError:
    999     has_gae_sdk = False
   1000 
   1001   if has_gae_sdk:
   1002     server_software = os.environ.get('SERVER_SOFTWARE', '')
   1003     if server_software.startswith('Google App Engine/'):
   1004       SETTINGS.env_name = 'GAE_PRODUCTION'
   1005     elif server_software.startswith('Development/'):
   1006       SETTINGS.env_name = 'GAE_LOCAL'
   1007   elif NO_GCE_CHECK != 'True' and _detect_gce_environment(urlopen=urlopen):
   1008     SETTINGS.env_name = 'GCE_PRODUCTION'
   1009 
   1010   return SETTINGS.env_name
   1011 
   1012 
   1013 class GoogleCredentials(OAuth2Credentials):
   1014   """Application Default Credentials for use in calling Google APIs.
   1015 
   1016   The Application Default Credentials are being constructed as a function of
   1017   the environment where the code is being run.
   1018   More details can be found on this page:
   1019   https://developers.google.com/accounts/docs/application-default-credentials
   1020 
   1021   Here is an example of how to use the Application Default Credentials for a
   1022   service that requires authentication:
   1023 
   1024       from googleapiclient.discovery import build
   1025       from oauth2client.client import GoogleCredentials
   1026 
   1027       credentials = GoogleCredentials.get_application_default()
   1028       service = build('compute', 'v1', credentials=credentials)
   1029 
   1030       PROJECT = 'bamboo-machine-422'
   1031       ZONE = 'us-central1-a'
   1032       request = service.instances().list(project=PROJECT, zone=ZONE)
   1033       response = request.execute()
   1034 
   1035       print(response)
   1036  """
   1037 
   1038   def __init__(self, access_token, client_id, client_secret, refresh_token,
   1039                token_expiry, token_uri, user_agent,
   1040                revoke_uri=GOOGLE_REVOKE_URI):
   1041     """Create an instance of GoogleCredentials.
   1042 
   1043     This constructor is not usually called by the user, instead
   1044     GoogleCredentials objects are instantiated by
   1045     GoogleCredentials.from_stream() or
   1046     GoogleCredentials.get_application_default().
   1047 
   1048     Args:
   1049       access_token: string, access token.
   1050       client_id: string, client identifier.
   1051       client_secret: string, client secret.
   1052       refresh_token: string, refresh token.
   1053       token_expiry: datetime, when the access_token expires.
   1054       token_uri: string, URI of token endpoint.
   1055       user_agent: string, The HTTP User-Agent to provide for this application.
   1056       revoke_uri: string, URI for revoke endpoint.
   1057         Defaults to GOOGLE_REVOKE_URI; a token can't be revoked if this is None.
   1058     """
   1059     super(GoogleCredentials, self).__init__(
   1060         access_token, client_id, client_secret, refresh_token, token_expiry,
   1061         token_uri, user_agent, revoke_uri=revoke_uri)
   1062 
   1063   def create_scoped_required(self):
   1064     """Whether this Credentials object is scopeless.
   1065 
   1066     create_scoped(scopes) method needs to be called in order to create
   1067     a Credentials object for API calls.
   1068     """
   1069     return False
   1070 
   1071   def create_scoped(self, scopes):
   1072     """Create a Credentials object for the given scopes.
   1073 
   1074     The Credentials type is preserved.
   1075     """
   1076     return self
   1077 
   1078   @property
   1079   def serialization_data(self):
   1080     """Get the fields and their values identifying the current credentials."""
   1081     return {
   1082         'type': 'authorized_user',
   1083         'client_id': self.client_id,
   1084         'client_secret': self.client_secret,
   1085         'refresh_token': self.refresh_token
   1086     }
   1087 
   1088   @staticmethod
   1089   def _implicit_credentials_from_gae(env_name=None):
   1090     """Attempts to get implicit credentials in Google App Engine env.
   1091 
   1092     If the current environment is not detected as App Engine, returns None,
   1093     indicating no Google App Engine credentials can be detected from the
   1094     current environment.
   1095 
   1096     Args:
   1097         env_name: String, indicating current environment.
   1098 
   1099     Returns:
   1100         None, if not in GAE, else an appengine.AppAssertionCredentials object.
   1101     """
   1102     env_name = env_name or _get_environment()
   1103     if env_name not in ('GAE_PRODUCTION', 'GAE_LOCAL'):
   1104       return None
   1105 
   1106     return _get_application_default_credential_GAE()
   1107 
   1108   @staticmethod
   1109   def _implicit_credentials_from_gce(env_name=None):
   1110     """Attempts to get implicit credentials in Google Compute Engine env.
   1111 
   1112     If the current environment is not detected as Compute Engine, returns None,
   1113     indicating no Google Compute Engine credentials can be detected from the
   1114     current environment.
   1115 
   1116     Args:
   1117         env_name: String, indicating current environment.
   1118 
   1119     Returns:
   1120         None, if not in GCE, else a gce.AppAssertionCredentials object.
   1121     """
   1122     env_name = env_name or _get_environment()
   1123     if env_name != 'GCE_PRODUCTION':
   1124       return None
   1125 
   1126     return _get_application_default_credential_GCE()
   1127 
   1128   @staticmethod
   1129   def _implicit_credentials_from_files(env_name=None):
   1130     """Attempts to get implicit credentials from local credential files.
   1131 
   1132     First checks if the environment variable GOOGLE_APPLICATION_CREDENTIALS
   1133     is set with a filename and then falls back to a configuration file (the
   1134     "well known" file) associated with the 'gcloud' command line tool.
   1135 
   1136     Args:
   1137         env_name: Unused argument.
   1138 
   1139     Returns:
   1140         Credentials object associated with the GOOGLE_APPLICATION_CREDENTIALS
   1141             file or the "well known" file if either exist. If neither file is
   1142             define, returns None, indicating no credentials from a file can
   1143             detected from the current environment.
   1144     """
   1145     credentials_filename = _get_environment_variable_file()
   1146     if not credentials_filename:
   1147       credentials_filename = _get_well_known_file()
   1148       if os.path.isfile(credentials_filename):
   1149         extra_help = (' (produced automatically when running'
   1150                       ' "gcloud auth login" command)')
   1151       else:
   1152         credentials_filename = None
   1153     else:
   1154       extra_help = (' (pointed to by ' + GOOGLE_APPLICATION_CREDENTIALS +
   1155                     ' environment variable)')
   1156 
   1157     if not credentials_filename:
   1158       return
   1159 
   1160     try:
   1161       return _get_application_default_credential_from_file(credentials_filename)
   1162     except (ApplicationDefaultCredentialsError, ValueError) as error:
   1163       _raise_exception_for_reading_json(credentials_filename, extra_help, error)
   1164 
   1165   @classmethod
   1166   def _get_implicit_credentials(cls):
   1167     """Gets credentials implicitly from the environment.
   1168 
   1169     Checks environment in order of precedence:
   1170     - Google App Engine (production and testing)
   1171     - Environment variable GOOGLE_APPLICATION_CREDENTIALS pointing to
   1172       a file with stored credentials information.
   1173     - Stored "well known" file associated with `gcloud` command line tool.
   1174     - Google Compute Engine production environment.
   1175 
   1176     Exceptions:
   1177       ApplicationDefaultCredentialsError: raised when the credentials fail
   1178           to be retrieved.
   1179     """
   1180     env_name = _get_environment()
   1181 
   1182     # Environ checks (in order). Assumes each checker takes `env_name`
   1183     # as a kwarg.
   1184     environ_checkers = [
   1185       cls._implicit_credentials_from_gae,
   1186       cls._implicit_credentials_from_files,
   1187       cls._implicit_credentials_from_gce,
   1188     ]
   1189 
   1190     for checker in environ_checkers:
   1191       credentials = checker(env_name=env_name)
   1192       if credentials is not None:
   1193         return credentials
   1194 
   1195     # If no credentials, fail.
   1196     raise ApplicationDefaultCredentialsError(ADC_HELP_MSG)
   1197 
   1198   @staticmethod
   1199   def get_application_default():
   1200     """Get the Application Default Credentials for the current environment.
   1201 
   1202     Exceptions:
   1203       ApplicationDefaultCredentialsError: raised when the credentials fail
   1204                                           to be retrieved.
   1205     """
   1206     return GoogleCredentials._get_implicit_credentials()
   1207 
   1208   @staticmethod
   1209   def from_stream(credential_filename):
   1210     """Create a Credentials object by reading the information from a given file.
   1211 
   1212     It returns an object of type GoogleCredentials.
   1213 
   1214     Args:
   1215       credential_filename: the path to the file from where the credentials
   1216         are to be read
   1217 
   1218     Exceptions:
   1219       ApplicationDefaultCredentialsError: raised when the credentials fail
   1220                                           to be retrieved.
   1221     """
   1222 
   1223     if credential_filename and os.path.isfile(credential_filename):
   1224       try:
   1225         return _get_application_default_credential_from_file(
   1226             credential_filename)
   1227       except (ApplicationDefaultCredentialsError, ValueError) as error:
   1228         extra_help = ' (provided as parameter to the from_stream() method)'
   1229         _raise_exception_for_reading_json(credential_filename,
   1230                                           extra_help,
   1231                                           error)
   1232     else:
   1233       raise ApplicationDefaultCredentialsError(
   1234           'The parameter passed to the from_stream() '
   1235           'method should point to a file.')
   1236 
   1237 
   1238 def _save_private_file(filename, json_contents):
   1239   """Saves a file with read-write permissions on for the owner.
   1240 
   1241   Args:
   1242     filename: String. Absolute path to file.
   1243     json_contents: JSON serializable object to be saved.
   1244   """
   1245   temp_filename = tempfile.mktemp()
   1246   file_desc = os.open(temp_filename, os.O_WRONLY | os.O_CREAT, 0o600)
   1247   with os.fdopen(file_desc, 'w') as file_handle:
   1248     json.dump(json_contents, file_handle, sort_keys=True,
   1249               indent=2, separators=(',', ': '))
   1250   shutil.move(temp_filename, filename)
   1251 
   1252 
   1253 def save_to_well_known_file(credentials, well_known_file=None):
   1254   """Save the provided GoogleCredentials to the well known file.
   1255 
   1256   Args:
   1257     credentials:
   1258       the credentials to be saved to the well known file;
   1259       it should be an instance of GoogleCredentials
   1260     well_known_file:
   1261       the name of the file where the credentials are to be saved;
   1262       this parameter is supposed to be used for testing only
   1263   """
   1264   # TODO(orestica): move this method to tools.py
   1265   # once the argparse import gets fixed (it is not present in Python 2.6)
   1266 
   1267   if well_known_file is None:
   1268     well_known_file = _get_well_known_file()
   1269 
   1270   config_dir = os.path.dirname(well_known_file)
   1271   if not os.path.isdir(config_dir):
   1272     raise OSError('Config directory does not exist: %s' % config_dir)
   1273 
   1274   credentials_data = credentials.serialization_data
   1275   _save_private_file(well_known_file, credentials_data)
   1276 
   1277 
   1278 def _get_environment_variable_file():
   1279   application_default_credential_filename = (
   1280       os.environ.get(GOOGLE_APPLICATION_CREDENTIALS,
   1281                      None))
   1282 
   1283   if application_default_credential_filename:
   1284     if os.path.isfile(application_default_credential_filename):
   1285       return application_default_credential_filename
   1286     else:
   1287       raise ApplicationDefaultCredentialsError(
   1288           'File ' + application_default_credential_filename + ' (pointed by ' +
   1289           GOOGLE_APPLICATION_CREDENTIALS +
   1290           ' environment variable) does not exist!')
   1291 
   1292 
   1293 def _get_well_known_file():
   1294   """Get the well known file produced by command 'gcloud auth login'."""
   1295   # TODO(orestica): Revisit this method once gcloud provides a better way
   1296   # of pinpointing the exact location of the file.
   1297 
   1298   WELL_KNOWN_CREDENTIALS_FILE = 'application_default_credentials.json'
   1299 
   1300   default_config_dir = os.getenv(_CLOUDSDK_CONFIG_ENV_VAR)
   1301   if default_config_dir is None:
   1302     if os.name == 'nt':
   1303       try:
   1304         default_config_dir = os.path.join(os.environ['APPDATA'],
   1305                                           _CLOUDSDK_CONFIG_DIRECTORY)
   1306       except KeyError:
   1307         # This should never happen unless someone is really messing with things.
   1308         drive = os.environ.get('SystemDrive', 'C:')
   1309         default_config_dir = os.path.join(drive, '\\',
   1310                                           _CLOUDSDK_CONFIG_DIRECTORY)
   1311     else:
   1312       default_config_dir = os.path.join(os.path.expanduser('~'),
   1313                                         '.config',
   1314                                         _CLOUDSDK_CONFIG_DIRECTORY)
   1315 
   1316   return os.path.join(default_config_dir, WELL_KNOWN_CREDENTIALS_FILE)
   1317 
   1318 
   1319 def _get_application_default_credential_from_file(filename):
   1320   """Build the Application Default Credentials from file."""
   1321 
   1322   from oauth2client import service_account
   1323 
   1324   # read the credentials from the file
   1325   with open(filename) as file_obj:
   1326     client_credentials = json.load(file_obj)
   1327 
   1328   credentials_type = client_credentials.get('type')
   1329   if credentials_type == AUTHORIZED_USER:
   1330     required_fields = set(['client_id', 'client_secret', 'refresh_token'])
   1331   elif credentials_type == SERVICE_ACCOUNT:
   1332     required_fields = set(['client_id', 'client_email', 'private_key_id',
   1333                            'private_key'])
   1334   else:
   1335     raise ApplicationDefaultCredentialsError(
   1336         "'type' field should be defined (and have one of the '" +
   1337         AUTHORIZED_USER + "' or '" + SERVICE_ACCOUNT + "' values)")
   1338 
   1339   missing_fields = required_fields.difference(client_credentials.keys())
   1340 
   1341   if missing_fields:
   1342     _raise_exception_for_missing_fields(missing_fields)
   1343 
   1344   if client_credentials['type'] == AUTHORIZED_USER:
   1345     return GoogleCredentials(
   1346         access_token=None,
   1347         client_id=client_credentials['client_id'],
   1348         client_secret=client_credentials['client_secret'],
   1349         refresh_token=client_credentials['refresh_token'],
   1350         token_expiry=None,
   1351         token_uri=GOOGLE_TOKEN_URI,
   1352         user_agent='Python client library')
   1353   else:  # client_credentials['type'] == SERVICE_ACCOUNT
   1354     return service_account._ServiceAccountCredentials(
   1355         service_account_id=client_credentials['client_id'],
   1356         service_account_email=client_credentials['client_email'],
   1357         private_key_id=client_credentials['private_key_id'],
   1358         private_key_pkcs8_text=client_credentials['private_key'],
   1359         scopes=[])
   1360 
   1361 
   1362 def _raise_exception_for_missing_fields(missing_fields):
   1363   raise ApplicationDefaultCredentialsError(
   1364       'The following field(s) must be defined: ' + ', '.join(missing_fields))
   1365 
   1366 
   1367 def _raise_exception_for_reading_json(credential_file,
   1368                                       extra_help,
   1369                                       error):
   1370   raise ApplicationDefaultCredentialsError(
   1371       'An error was encountered while reading json file: '+
   1372       credential_file + extra_help + ': ' + str(error))
   1373 
   1374 
   1375 def _get_application_default_credential_GAE():
   1376   from oauth2client.appengine import AppAssertionCredentials
   1377 
   1378   return AppAssertionCredentials([])
   1379 
   1380 
   1381 def _get_application_default_credential_GCE():
   1382   from oauth2client.gce import AppAssertionCredentials
   1383 
   1384   return AppAssertionCredentials([])
   1385 
   1386 
   1387 class AssertionCredentials(GoogleCredentials):
   1388   """Abstract Credentials object used for OAuth 2.0 assertion grants.
   1389 
   1390   This credential does not require a flow to instantiate because it
   1391   represents a two legged flow, and therefore has all of the required
   1392   information to generate and refresh its own access tokens. It must
   1393   be subclassed to generate the appropriate assertion string.
   1394 
   1395   AssertionCredentials objects may be safely pickled and unpickled.
   1396   """
   1397 
   1398   @util.positional(2)
   1399   def __init__(self, assertion_type, user_agent=None,
   1400                token_uri=GOOGLE_TOKEN_URI,
   1401                revoke_uri=GOOGLE_REVOKE_URI,
   1402                **unused_kwargs):
   1403     """Constructor for AssertionFlowCredentials.
   1404 
   1405     Args:
   1406       assertion_type: string, assertion type that will be declared to the auth
   1407         server
   1408       user_agent: string, The HTTP User-Agent to provide for this application.
   1409       token_uri: string, URI for token endpoint. For convenience
   1410         defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1411       revoke_uri: string, URI for revoke endpoint.
   1412     """
   1413     super(AssertionCredentials, self).__init__(
   1414         None,
   1415         None,
   1416         None,
   1417         None,
   1418         None,
   1419         token_uri,
   1420         user_agent,
   1421         revoke_uri=revoke_uri)
   1422     self.assertion_type = assertion_type
   1423 
   1424   def _generate_refresh_request_body(self):
   1425     assertion = self._generate_assertion()
   1426 
   1427     body = urllib.parse.urlencode({
   1428         'assertion': assertion,
   1429         'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
   1430         })
   1431 
   1432     return body
   1433 
   1434   def _generate_assertion(self):
   1435     """Generate the assertion string that will be used in the access token
   1436     request.
   1437     """
   1438     _abstract()
   1439 
   1440   def _revoke(self, http_request):
   1441     """Revokes the access_token and deletes the store if available.
   1442 
   1443     Args:
   1444       http_request: callable, a callable that matches the method signature of
   1445         httplib2.Http.request, used to make the revoke request.
   1446     """
   1447     self._do_revoke(http_request, self.access_token)
   1448 
   1449 
   1450 def _RequireCryptoOrDie():
   1451   """Ensure we have a crypto library, or throw CryptoUnavailableError.
   1452 
   1453   The oauth2client.crypt module requires either PyCrypto or PyOpenSSL
   1454   to be available in order to function, but these are optional
   1455   dependencies.
   1456   """
   1457   if not HAS_CRYPTO:
   1458     raise CryptoUnavailableError('No crypto library available')
   1459 
   1460 
   1461 class SignedJwtAssertionCredentials(AssertionCredentials):
   1462   """Credentials object used for OAuth 2.0 Signed JWT assertion grants.
   1463 
   1464   This credential does not require a flow to instantiate because it
   1465   represents a two legged flow, and therefore has all of the required
   1466   information to generate and refresh its own access tokens.
   1467 
   1468   SignedJwtAssertionCredentials requires either PyOpenSSL, or PyCrypto
   1469   2.6 or later. For App Engine you may also consider using
   1470   AppAssertionCredentials.
   1471   """
   1472 
   1473   MAX_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds
   1474 
   1475   @util.positional(4)
   1476   def __init__(self,
   1477                service_account_name,
   1478                private_key,
   1479                scope,
   1480                private_key_password='notasecret',
   1481                user_agent=None,
   1482                token_uri=GOOGLE_TOKEN_URI,
   1483                revoke_uri=GOOGLE_REVOKE_URI,
   1484                **kwargs):
   1485     """Constructor for SignedJwtAssertionCredentials.
   1486 
   1487     Args:
   1488       service_account_name: string, id for account, usually an email address.
   1489       private_key: string, private key in PKCS12 or PEM format.
   1490       scope: string or iterable of strings, scope(s) of the credentials being
   1491         requested.
   1492       private_key_password: string, password for private_key, unused if
   1493         private_key is in PEM format.
   1494       user_agent: string, HTTP User-Agent to provide for this application.
   1495       token_uri: string, URI for token endpoint. For convenience
   1496         defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1497       revoke_uri: string, URI for revoke endpoint.
   1498       kwargs: kwargs, Additional parameters to add to the JWT token, for
   1499         example sub=joe (at] xample.org.
   1500 
   1501     Raises:
   1502       CryptoUnavailableError if no crypto library is available.
   1503     """
   1504     _RequireCryptoOrDie()
   1505     super(SignedJwtAssertionCredentials, self).__init__(
   1506         None,
   1507         user_agent=user_agent,
   1508         token_uri=token_uri,
   1509         revoke_uri=revoke_uri,
   1510         )
   1511 
   1512     self.scope = util.scopes_to_string(scope)
   1513 
   1514     # Keep base64 encoded so it can be stored in JSON.
   1515     self.private_key = base64.b64encode(private_key)
   1516     if isinstance(self.private_key, six.text_type):
   1517       self.private_key = self.private_key.encode('utf-8')
   1518 
   1519     self.private_key_password = private_key_password
   1520     self.service_account_name = service_account_name
   1521     self.kwargs = kwargs
   1522 
   1523   @classmethod
   1524   def from_json(cls, s):
   1525     data = json.loads(s)
   1526     retval = SignedJwtAssertionCredentials(
   1527         data['service_account_name'],
   1528         base64.b64decode(data['private_key']),
   1529         data['scope'],
   1530         private_key_password=data['private_key_password'],
   1531         user_agent=data['user_agent'],
   1532         token_uri=data['token_uri'],
   1533         **data['kwargs']
   1534         )
   1535     retval.invalid = data['invalid']
   1536     retval.access_token = data['access_token']
   1537     return retval
   1538 
   1539   def _generate_assertion(self):
   1540     """Generate the assertion that will be used in the request."""
   1541     now = int(time.time())
   1542     payload = {
   1543         'aud': self.token_uri,
   1544         'scope': self.scope,
   1545         'iat': now,
   1546         'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
   1547         'iss': self.service_account_name
   1548     }
   1549     payload.update(self.kwargs)
   1550     logger.debug(str(payload))
   1551 
   1552     private_key = base64.b64decode(self.private_key)
   1553     return crypt.make_signed_jwt(crypt.Signer.from_string(
   1554         private_key, self.private_key_password), payload)
   1555 
   1556 # Only used in verify_id_token(), which is always calling to the same URI
   1557 # for the certs.
   1558 _cached_http = httplib2.Http(MemoryCache())
   1559 
   1560 @util.positional(2)
   1561 def verify_id_token(id_token, audience, http=None,
   1562                     cert_uri=ID_TOKEN_VERIFICATION_CERTS):
   1563   """Verifies a signed JWT id_token.
   1564 
   1565   This function requires PyOpenSSL and because of that it does not work on
   1566   App Engine.
   1567 
   1568   Args:
   1569     id_token: string, A Signed JWT.
   1570     audience: string, The audience 'aud' that the token should be for.
   1571     http: httplib2.Http, instance to use to make the HTTP request. Callers
   1572       should supply an instance that has caching enabled.
   1573     cert_uri: string, URI of the certificates in JSON format to
   1574       verify the JWT against.
   1575 
   1576   Returns:
   1577     The deserialized JSON in the JWT.
   1578 
   1579   Raises:
   1580     oauth2client.crypt.AppIdentityError: if the JWT fails to verify.
   1581     CryptoUnavailableError: if no crypto library is available.
   1582   """
   1583   _RequireCryptoOrDie()
   1584   if http is None:
   1585     http = _cached_http
   1586 
   1587   resp, content = http.request(cert_uri)
   1588 
   1589   if resp.status == 200:
   1590     certs = json.loads(content.decode('utf-8'))
   1591     return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
   1592   else:
   1593     raise VerifyJwtTokenError('Status code: %d' % resp.status)
   1594 
   1595 
   1596 def _urlsafe_b64decode(b64string):
   1597   # Guard against unicode strings, which base64 can't handle.
   1598   if isinstance(b64string, six.text_type):
   1599     b64string = b64string.encode('ascii')
   1600   padded = b64string + b'=' * (4 - len(b64string) % 4)
   1601   return base64.urlsafe_b64decode(padded)
   1602 
   1603 
   1604 def _extract_id_token(id_token):
   1605   """Extract the JSON payload from a JWT.
   1606 
   1607   Does the extraction w/o checking the signature.
   1608 
   1609   Args:
   1610     id_token: string or bytestring, OAuth 2.0 id_token.
   1611 
   1612   Returns:
   1613     object, The deserialized JSON payload.
   1614   """
   1615   if type(id_token) == bytes:
   1616     segments = id_token.split(b'.')
   1617   else:
   1618     segments = id_token.split(u'.')
   1619 
   1620   if len(segments) != 3:
   1621     raise VerifyJwtTokenError(
   1622         'Wrong number of segments in token: %s' % id_token)
   1623 
   1624   return json.loads(_urlsafe_b64decode(segments[1]).decode('utf-8'))
   1625 
   1626 
   1627 def _parse_exchange_token_response(content):
   1628   """Parses response of an exchange token request.
   1629 
   1630   Most providers return JSON but some (e.g. Facebook) return a
   1631   url-encoded string.
   1632 
   1633   Args:
   1634     content: The body of a response
   1635 
   1636   Returns:
   1637     Content as a dictionary object. Note that the dict could be empty,
   1638     i.e. {}. That basically indicates a failure.
   1639   """
   1640   resp = {}
   1641   try:
   1642     resp = json.loads(content.decode('utf-8'))
   1643   except Exception:
   1644     # different JSON libs raise different exceptions,
   1645     # so we just do a catch-all here
   1646     content = content.decode('utf-8')
   1647     resp = dict(urllib.parse.parse_qsl(content))
   1648 
   1649   # some providers respond with 'expires', others with 'expires_in'
   1650   if resp and 'expires' in resp:
   1651     resp['expires_in'] = resp.pop('expires')
   1652 
   1653   return resp
   1654 
   1655 
   1656 @util.positional(4)
   1657 def credentials_from_code(client_id, client_secret, scope, code,
   1658                           redirect_uri='postmessage', http=None,
   1659                           user_agent=None, token_uri=GOOGLE_TOKEN_URI,
   1660                           auth_uri=GOOGLE_AUTH_URI,
   1661                           revoke_uri=GOOGLE_REVOKE_URI,
   1662                           device_uri=GOOGLE_DEVICE_URI):
   1663   """Exchanges an authorization code for an OAuth2Credentials object.
   1664 
   1665   Args:
   1666     client_id: string, client identifier.
   1667     client_secret: string, client secret.
   1668     scope: string or iterable of strings, scope(s) to request.
   1669     code: string, An authorization code, most likely passed down from
   1670       the client
   1671     redirect_uri: string, this is generally set to 'postmessage' to match the
   1672       redirect_uri that the client specified
   1673     http: httplib2.Http, optional http instance to use to do the fetch
   1674     token_uri: string, URI for token endpoint. For convenience
   1675       defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1676     auth_uri: string, URI for authorization endpoint. For convenience
   1677       defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1678     revoke_uri: string, URI for revoke endpoint. For convenience
   1679       defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1680     device_uri: string, URI for device authorization endpoint. For convenience
   1681       defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1682 
   1683   Returns:
   1684     An OAuth2Credentials object.
   1685 
   1686   Raises:
   1687     FlowExchangeError if the authorization code cannot be exchanged for an
   1688      access token
   1689   """
   1690   flow = OAuth2WebServerFlow(client_id, client_secret, scope,
   1691                              redirect_uri=redirect_uri, user_agent=user_agent,
   1692                              auth_uri=auth_uri, token_uri=token_uri,
   1693                              revoke_uri=revoke_uri, device_uri=device_uri)
   1694 
   1695   credentials = flow.step2_exchange(code, http=http)
   1696   return credentials
   1697 
   1698 
   1699 @util.positional(3)
   1700 def credentials_from_clientsecrets_and_code(filename, scope, code,
   1701                                             message = None,
   1702                                             redirect_uri='postmessage',
   1703                                             http=None,
   1704                                             cache=None,
   1705                                             device_uri=None):
   1706   """Returns OAuth2Credentials from a clientsecrets file and an auth code.
   1707 
   1708   Will create the right kind of Flow based on the contents of the clientsecrets
   1709   file or will raise InvalidClientSecretsError for unknown types of Flows.
   1710 
   1711   Args:
   1712     filename: string, File name of clientsecrets.
   1713     scope: string or iterable of strings, scope(s) to request.
   1714     code: string, An authorization code, most likely passed down from
   1715       the client
   1716     message: string, A friendly string to display to the user if the
   1717       clientsecrets file is missing or invalid. If message is provided then
   1718       sys.exit will be called in the case of an error. If message in not
   1719       provided then clientsecrets.InvalidClientSecretsError will be raised.
   1720     redirect_uri: string, this is generally set to 'postmessage' to match the
   1721       redirect_uri that the client specified
   1722     http: httplib2.Http, optional http instance to use to do the fetch
   1723     cache: An optional cache service client that implements get() and set()
   1724       methods. See clientsecrets.loadfile() for details.
   1725     device_uri: string, OAuth 2.0 device authorization endpoint
   1726 
   1727   Returns:
   1728     An OAuth2Credentials object.
   1729 
   1730   Raises:
   1731     FlowExchangeError if the authorization code cannot be exchanged for an
   1732      access token
   1733     UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
   1734     clientsecrets.InvalidClientSecretsError if the clientsecrets file is
   1735       invalid.
   1736   """
   1737   flow = flow_from_clientsecrets(filename, scope, message=message, cache=cache,
   1738                                  redirect_uri=redirect_uri,
   1739                                  device_uri=device_uri)
   1740   credentials = flow.step2_exchange(code, http=http)
   1741   return credentials
   1742 
   1743 
   1744 class DeviceFlowInfo(collections.namedtuple('DeviceFlowInfo', (
   1745     'device_code', 'user_code', 'interval', 'verification_url',
   1746     'user_code_expiry'))):
   1747   """Intermediate information the OAuth2 for devices flow."""
   1748 
   1749   @classmethod
   1750   def FromResponse(cls, response):
   1751     """Create a DeviceFlowInfo from a server response.
   1752 
   1753     The response should be a dict containing entries as described here:
   1754 
   1755       http://tools.ietf.org/html/draft-ietf-oauth-v2-05#section-3.7.1
   1756     """
   1757     # device_code, user_code, and verification_url are required.
   1758     kwargs = {
   1759         'device_code': response['device_code'],
   1760         'user_code': response['user_code'],
   1761     }
   1762     # The response may list the verification address as either
   1763     # verification_url or verification_uri, so we check for both.
   1764     verification_url = response.get(
   1765         'verification_url', response.get('verification_uri'))
   1766     if verification_url is None:
   1767       raise OAuth2DeviceCodeError(
   1768           'No verification_url provided in server response')
   1769     kwargs['verification_url'] = verification_url
   1770     # expires_in and interval are optional.
   1771     kwargs.update({
   1772         'interval': response.get('interval'),
   1773         'user_code_expiry': None,
   1774     })
   1775     if 'expires_in' in response:
   1776       kwargs['user_code_expiry'] = datetime.datetime.now() + datetime.timedelta(
   1777           seconds=int(response['expires_in']))
   1778 
   1779     return cls(**kwargs)
   1780 
   1781 class OAuth2WebServerFlow(Flow):
   1782   """Does the Web Server Flow for OAuth 2.0.
   1783 
   1784   OAuth2WebServerFlow objects may be safely pickled and unpickled.
   1785   """
   1786 
   1787   @util.positional(4)
   1788   def __init__(self, client_id, client_secret, scope,
   1789                redirect_uri=None,
   1790                user_agent=None,
   1791                auth_uri=GOOGLE_AUTH_URI,
   1792                token_uri=GOOGLE_TOKEN_URI,
   1793                revoke_uri=GOOGLE_REVOKE_URI,
   1794                login_hint=None,
   1795                device_uri=GOOGLE_DEVICE_URI,
   1796                **kwargs):
   1797     """Constructor for OAuth2WebServerFlow.
   1798 
   1799     The kwargs argument is used to set extra query parameters on the
   1800     auth_uri. For example, the access_type and approval_prompt
   1801     query parameters can be set via kwargs.
   1802 
   1803     Args:
   1804       client_id: string, client identifier.
   1805       client_secret: string client secret.
   1806       scope: string or iterable of strings, scope(s) of the credentials being
   1807         requested.
   1808       redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
   1809         a non-web-based application, or a URI that handles the callback from
   1810         the authorization server.
   1811       user_agent: string, HTTP User-Agent to provide for this application.
   1812       auth_uri: string, URI for authorization endpoint. For convenience
   1813         defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1814       token_uri: string, URI for token endpoint. For convenience
   1815         defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1816       revoke_uri: string, URI for revoke endpoint. For convenience
   1817         defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1818       login_hint: string, Either an email address or domain. Passing this hint
   1819         will either pre-fill the email box on the sign-in form or select the
   1820         proper multi-login session, thereby simplifying the login flow.
   1821       device_uri: string, URI for device authorization endpoint. For convenience
   1822         defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   1823       **kwargs: dict, The keyword arguments are all optional and required
   1824                         parameters for the OAuth calls.
   1825     """
   1826     self.client_id = client_id
   1827     self.client_secret = client_secret
   1828     self.scope = util.scopes_to_string(scope)
   1829     self.redirect_uri = redirect_uri
   1830     self.login_hint = login_hint
   1831     self.user_agent = user_agent
   1832     self.auth_uri = auth_uri
   1833     self.token_uri = token_uri
   1834     self.revoke_uri = revoke_uri
   1835     self.device_uri = device_uri
   1836     self.params = {
   1837         'access_type': 'offline',
   1838         'response_type': 'code',
   1839     }
   1840     self.params.update(kwargs)
   1841 
   1842   @util.positional(1)
   1843   def step1_get_authorize_url(self, redirect_uri=None):
   1844     """Returns a URI to redirect to the provider.
   1845 
   1846     Args:
   1847       redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
   1848         a non-web-based application, or a URI that handles the callback from
   1849         the authorization server. This parameter is deprecated, please move to
   1850         passing the redirect_uri in via the constructor.
   1851 
   1852     Returns:
   1853       A URI as a string to redirect the user to begin the authorization flow.
   1854     """
   1855     if redirect_uri is not None:
   1856       logger.warning((
   1857           'The redirect_uri parameter for '
   1858           'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. Please '
   1859           'move to passing the redirect_uri in via the constructor.'))
   1860       self.redirect_uri = redirect_uri
   1861 
   1862     if self.redirect_uri is None:
   1863       raise ValueError('The value of redirect_uri must not be None.')
   1864 
   1865     query_params = {
   1866         'client_id': self.client_id,
   1867         'redirect_uri': self.redirect_uri,
   1868         'scope': self.scope,
   1869     }
   1870     if self.login_hint is not None:
   1871       query_params['login_hint'] = self.login_hint
   1872     query_params.update(self.params)
   1873     return _update_query_params(self.auth_uri, query_params)
   1874 
   1875   @util.positional(1)
   1876   def step1_get_device_and_user_codes(self, http=None):
   1877     """Returns a user code and the verification URL where to enter it
   1878 
   1879     Returns:
   1880       A user code as a string for the user to authorize the application
   1881       An URL as a string where the user has to enter the code
   1882     """
   1883     if self.device_uri is None:
   1884       raise ValueError('The value of device_uri must not be None.')
   1885 
   1886     body = urllib.parse.urlencode({
   1887         'client_id': self.client_id,
   1888         'scope': self.scope,
   1889     })
   1890     headers = {
   1891         'content-type': 'application/x-www-form-urlencoded',
   1892     }
   1893 
   1894     if self.user_agent is not None:
   1895       headers['user-agent'] = self.user_agent
   1896 
   1897     if http is None:
   1898       http = httplib2.Http()
   1899 
   1900     resp, content = http.request(self.device_uri, method='POST', body=body,
   1901                                  headers=headers)
   1902     if resp.status == 200:
   1903       try:
   1904         flow_info = json.loads(content)
   1905       except ValueError as e:
   1906         raise OAuth2DeviceCodeError(
   1907             'Could not parse server response as JSON: "%s", error: "%s"' % (
   1908                 content, e))
   1909       return DeviceFlowInfo.FromResponse(flow_info)
   1910     else:
   1911       error_msg = 'Invalid response %s.' % resp.status
   1912       try:
   1913         d = json.loads(content)
   1914         if 'error' in d:
   1915           error_msg += ' Error: %s' % d['error']
   1916       except ValueError:
   1917         # Couldn't decode a JSON response, stick with the default message.
   1918         pass
   1919       raise OAuth2DeviceCodeError(error_msg)
   1920 
   1921   @util.positional(2)
   1922   def step2_exchange(self, code=None, http=None, device_flow_info=None):
   1923     """Exchanges a code for OAuth2Credentials.
   1924 
   1925     Args:
   1926 
   1927       code: string, a dict-like object, or None. For a non-device
   1928           flow, this is either the response code as a string, or a
   1929           dictionary of query parameters to the redirect_uri. For a
   1930           device flow, this should be None.
   1931       http: httplib2.Http, optional http instance to use when fetching
   1932           credentials.
   1933       device_flow_info: DeviceFlowInfo, return value from step1 in the
   1934           case of a device flow.
   1935 
   1936     Returns:
   1937       An OAuth2Credentials object that can be used to authorize requests.
   1938 
   1939     Raises:
   1940       FlowExchangeError: if a problem occurred exchanging the code for a
   1941           refresh_token.
   1942       ValueError: if code and device_flow_info are both provided or both
   1943           missing.
   1944 
   1945     """
   1946     if code is None and device_flow_info is None:
   1947       raise ValueError('No code or device_flow_info provided.')
   1948     if code is not None and device_flow_info is not None:
   1949       raise ValueError('Cannot provide both code and device_flow_info.')
   1950 
   1951     if code is None:
   1952       code = device_flow_info.device_code
   1953     elif not isinstance(code, six.string_types):
   1954       if 'code' not in code:
   1955         raise FlowExchangeError(code.get(
   1956             'error', 'No code was supplied in the query parameters.'))
   1957       code = code['code']
   1958 
   1959     post_data = {
   1960         'client_id': self.client_id,
   1961         'client_secret': self.client_secret,
   1962         'code': code,
   1963         'scope': self.scope,
   1964     }
   1965     if device_flow_info is not None:
   1966       post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
   1967     else:
   1968       post_data['grant_type'] = 'authorization_code'
   1969       post_data['redirect_uri'] = self.redirect_uri
   1970     body = urllib.parse.urlencode(post_data)
   1971     headers = {
   1972         'content-type': 'application/x-www-form-urlencoded',
   1973     }
   1974 
   1975     if self.user_agent is not None:
   1976       headers['user-agent'] = self.user_agent
   1977 
   1978     if http is None:
   1979       http = httplib2.Http()
   1980 
   1981     resp, content = http.request(self.token_uri, method='POST', body=body,
   1982                                  headers=headers)
   1983     d = _parse_exchange_token_response(content)
   1984     if resp.status == 200 and 'access_token' in d:
   1985       access_token = d['access_token']
   1986       refresh_token = d.get('refresh_token', None)
   1987       if not refresh_token:
   1988         logger.info(
   1989             'Received token response with no refresh_token. Consider '
   1990             "reauthenticating with approval_prompt='force'.")
   1991       token_expiry = None
   1992       if 'expires_in' in d:
   1993         token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
   1994             seconds=int(d['expires_in']))
   1995 
   1996       extracted_id_token = None
   1997       if 'id_token' in d:
   1998         extracted_id_token = _extract_id_token(d['id_token'])
   1999 
   2000       logger.info('Successfully retrieved access token')
   2001       return OAuth2Credentials(access_token, self.client_id,
   2002                                self.client_secret, refresh_token, token_expiry,
   2003                                self.token_uri, self.user_agent,
   2004                                revoke_uri=self.revoke_uri,
   2005                                id_token=extracted_id_token,
   2006                                token_response=d)
   2007     else:
   2008       logger.info('Failed to retrieve access token: %s', content)
   2009       if 'error' in d:
   2010         # you never know what those providers got to say
   2011         error_msg = str(d['error']) + str(d.get('error_description', ''))
   2012       else:
   2013         error_msg = 'Invalid response: %s.' % str(resp.status)
   2014       raise FlowExchangeError(error_msg)
   2015 
   2016 
   2017 @util.positional(2)
   2018 def flow_from_clientsecrets(filename, scope, redirect_uri=None,
   2019                             message=None, cache=None, login_hint=None,
   2020                             device_uri=None):
   2021   """Create a Flow from a clientsecrets file.
   2022 
   2023   Will create the right kind of Flow based on the contents of the clientsecrets
   2024   file or will raise InvalidClientSecretsError for unknown types of Flows.
   2025 
   2026   Args:
   2027     filename: string, File name of client secrets.
   2028     scope: string or iterable of strings, scope(s) to request.
   2029     redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
   2030       a non-web-based application, or a URI that handles the callback from
   2031       the authorization server.
   2032     message: string, A friendly string to display to the user if the
   2033       clientsecrets file is missing or invalid. If message is provided then
   2034       sys.exit will be called in the case of an error. If message in not
   2035       provided then clientsecrets.InvalidClientSecretsError will be raised.
   2036     cache: An optional cache service client that implements get() and set()
   2037       methods. See clientsecrets.loadfile() for details.
   2038     login_hint: string, Either an email address or domain. Passing this hint
   2039       will either pre-fill the email box on the sign-in form or select the
   2040       proper multi-login session, thereby simplifying the login flow.
   2041     device_uri: string, URI for device authorization endpoint. For convenience
   2042       defaults to Google's endpoints but any OAuth 2.0 provider can be used.
   2043 
   2044   Returns:
   2045     A Flow object.
   2046 
   2047   Raises:
   2048     UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
   2049     clientsecrets.InvalidClientSecretsError if the clientsecrets file is
   2050       invalid.
   2051   """
   2052   try:
   2053     client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
   2054     if client_type in (clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED):
   2055       constructor_kwargs = {
   2056           'redirect_uri': redirect_uri,
   2057           'auth_uri': client_info['auth_uri'],
   2058           'token_uri': client_info['token_uri'],
   2059           'login_hint': login_hint,
   2060       }
   2061       revoke_uri = client_info.get('revoke_uri')
   2062       if revoke_uri is not None:
   2063         constructor_kwargs['revoke_uri'] = revoke_uri
   2064       if device_uri is not None:
   2065         constructor_kwargs['device_uri'] = device_uri
   2066       return OAuth2WebServerFlow(
   2067           client_info['client_id'], client_info['client_secret'],
   2068           scope, **constructor_kwargs)
   2069 
   2070   except clientsecrets.InvalidClientSecretsError:
   2071     if message:
   2072       sys.exit(message)
   2073     else:
   2074       raise
   2075   else:
   2076     raise UnknownClientSecretsFlowError(
   2077         'This OAuth 2.0 flow is unsupported: %r' % client_type)
   2078