Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(230)

Side by Side Diff: third_party/google-endpoints/oauth2client/client.py

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

Powered by Google App Engine
This is Rietveld 408576698