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