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