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 clientsecrets
24 import copy
25 import datetime
26 import httplib2
27 import logging
28 import os
29 import sys
30 import time
31 import urllib
32 import urlparse
33
34 from anyjson import simplejson
35
36 HAS_OPENSSL = False
37 try:
38 from oauth2client.crypt import Signer
39 from oauth2client.crypt import make_signed_jwt
40 from oauth2client.crypt import verify_signed_jwt_with_certs
41 HAS_OPENSSL = True
42 except ImportError:
43 pass
44
45 try:
46 from urlparse import parse_qsl
47 except ImportError:
48 from cgi import parse_qsl
49
50 logger = logging.getLogger(__name__)
51
52
53 EXPIRY_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
54
55
56 ID_TOKEN_VERIFICATON_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
57
58
59 OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
60
61
62 -class Error(Exception):
63 """Base error for this module."""
64 pass
65
68 """Error trying to exchange an authorization grant for an access token."""
69 pass
70
73 """Error trying to refresh an expired access token."""
74 pass
75
77 """The client secrets file called for an unknown type of OAuth 2.0 flow. """
78 pass
79
82 """Having only the access_token means no refresh is possible."""
83 pass
84
87 """Could on retrieve certificates for validation."""
88 pass
89
92 raise NotImplementedError('You need to override this function')
93
96 """httplib2 Cache implementation which only caches locally."""
97
100
101 - def get(self, key):
102 return self.cache.get(key)
103
104 - def set(self, key, value):
105 self.cache[key] = value
106
108 self.cache.pop(key, None)
109
112 """Base class for all Credentials objects.
113
114 Subclasses must define an authorize() method that applies the credentials to
115 an HTTP transport.
116
117 Subclasses must also specify a classmethod named 'from_json' that takes a JSON
118 string as input and returns an instaniated Credentials object.
119 """
120
121 NON_SERIALIZED_MEMBERS = ['store']
122
124 """Take an httplib2.Http instance (or equivalent) and
125 authorizes it for the set of credentials, usually by
126 replacing http.request() with a method that adds in
127 the appropriate headers and then delegates to the original
128 Http.request() method.
129 """
130 _abstract()
131
133 """Forces a refresh of the access_token.
134
135 Args:
136 http: httplib2.Http, an http object to be used to make the refresh
137 request.
138 """
139 _abstract()
140
141 - def apply(self, headers):
142 """Add the authorization to the headers.
143
144 Args:
145 headers: dict, the headers to add the Authorization header to.
146 """
147 _abstract()
148
150 """Utility function for creating a JSON representation of an instance of Credentials.
151
152 Args:
153 strip: array, An array of names of members to not include in the JSON.
154
155 Returns:
156 string, a JSON representation of this instance, suitable to pass to
157 from_json().
158 """
159 t = type(self)
160 d = copy.copy(self.__dict__)
161 for member in strip:
162 if member in d:
163 del d[member]
164 if 'token_expiry' in d and isinstance(d['token_expiry'], datetime.datetime):
165 d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
166
167 d['_class'] = t.__name__
168 d['_module'] = t.__module__
169 return simplejson.dumps(d)
170
172 """Creating a JSON representation of an instance of Credentials.
173
174 Returns:
175 string, a JSON representation of this instance, suitable to pass to
176 from_json().
177 """
178 return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
179
180 @classmethod
182 """Utility class method to instantiate a Credentials subclass from a JSON
183 representation produced by to_json().
184
185 Args:
186 s: string, JSON from to_json().
187
188 Returns:
189 An instance of the subclass of Credentials that was serialized with
190 to_json().
191 """
192 data = simplejson.loads(s)
193
194 module = data['_module']
195 try:
196 m = __import__(module)
197 except ImportError:
198
199 module = module.replace('.apiclient', '')
200 m = __import__(module)
201
202 m = __import__(module, fromlist=module.split('.')[:-1])
203 kls = getattr(m, data['_class'])
204 from_json = getattr(kls, 'from_json')
205 return from_json(s)
206
207 @classmethod
209 """Instantiate a Credentials object from a JSON description of it.
210
211 The JSON should have been produced by calling .to_json() on the object.
212
213 Args:
214 data: dict, A deserialized JSON object.
215
216 Returns:
217 An instance of a Credentials subclass.
218 """
219 return Credentials()
220
221
222 -class Flow(object):
223 """Base class for all Flow objects."""
224 pass
225
228 """Base class for all Storage objects.
229
230 Store and retrieve a single credential. This class supports locking
231 such that multiple processes and threads can operate on a single
232 store.
233 """
234
236 """Acquires any lock necessary to access this Storage.
237
238 This lock is not reentrant.
239 """
240 pass
241
243 """Release the Storage lock.
244
245 Trying to release a lock that isn't held will result in a
246 RuntimeError.
247 """
248 pass
249
251 """Retrieve credential.
252
253 The Storage lock must be held when this is called.
254
255 Returns:
256 oauth2client.client.Credentials
257 """
258 _abstract()
259
261 """Write a credential.
262
263 The Storage lock must be held when this is called.
264
265 Args:
266 credentials: Credentials, the credentials to store.
267 """
268 _abstract()
269
271 """Delete a credential.
272
273 The Storage lock must be held when this is called.
274 """
275 _abstract()
276
278 """Retrieve credential.
279
280 The Storage lock must *not* be held when this is called.
281
282 Returns:
283 oauth2client.client.Credentials
284 """
285 self.acquire_lock()
286 try:
287 return self.locked_get()
288 finally:
289 self.release_lock()
290
291 - def put(self, credentials):
292 """Write a credential.
293
294 The Storage lock must be held when this is called.
295
296 Args:
297 credentials: Credentials, the credentials to store.
298 """
299 self.acquire_lock()
300 try:
301 self.locked_put(credentials)
302 finally:
303 self.release_lock()
304
306 """Delete credential.
307
308 Frees any resources associated with storing the credential.
309 The Storage lock must *not* be held when this is called.
310
311 Returns:
312 None
313 """
314 self.acquire_lock()
315 try:
316 return self.locked_delete()
317 finally:
318 self.release_lock()
319
322 """Credentials object for OAuth 2.0.
323
324 Credentials can be applied to an httplib2.Http object using the authorize()
325 method, which then adds the OAuth 2.0 access token to each request.
326
327 OAuth2Credentials objects may be safely pickled and unpickled.
328 """
329
330 - def __init__(self, access_token, client_id, client_secret, refresh_token,
331 token_expiry, token_uri, user_agent, id_token=None):
332 """Create an instance of OAuth2Credentials.
333
334 This constructor is not usually called by the user, instead
335 OAuth2Credentials objects are instantiated by the OAuth2WebServerFlow.
336
337 Args:
338 access_token: string, access token.
339 client_id: string, client identifier.
340 client_secret: string, client secret.
341 refresh_token: string, refresh token.
342 token_expiry: datetime, when the access_token expires.
343 token_uri: string, URI of token endpoint.
344 user_agent: string, The HTTP User-Agent to provide for this application.
345 id_token: object, The identity of the resource owner.
346
347 Notes:
348 store: callable, A callable that when passed a Credential
349 will store the credential back to where it came from.
350 This is needed to store the latest access_token if it
351 has expired and been refreshed.
352 """
353 self.access_token = access_token
354 self.client_id = client_id
355 self.client_secret = client_secret
356 self.refresh_token = refresh_token
357 self.store = None
358 self.token_expiry = token_expiry
359 self.token_uri = token_uri
360 self.user_agent = user_agent
361 self.id_token = id_token
362
363
364
365 self.invalid = False
366
368 """Authorize an httplib2.Http instance with these credentials.
369
370 The modified http.request method will add authentication headers to each
371 request and will refresh access_tokens when a 401 is received on a
372 request. In addition the http.request method has a credentials property,
373 http.request.credentials, which is the Credentials object that authorized
374 it.
375
376 Args:
377 http: An instance of httplib2.Http
378 or something that acts like it.
379
380 Returns:
381 A modified instance of http that was passed in.
382
383 Example:
384
385 h = httplib2.Http()
386 h = credentials.authorize(h)
387
388 You can't create a new OAuth subclass of httplib2.Authenication
389 because it never gets passed the absolute URI, which is needed for
390 signing. So instead we have to overload 'request' with a closure
391 that adds in the Authorization header and then calls the original
392 version of 'request()'.
393 """
394 request_orig = http.request
395
396
397 def new_request(uri, method='GET', body=None, headers=None,
398 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
399 connection_type=None):
400 if not self.access_token:
401 logger.info('Attempting refresh to obtain initial access_token')
402 self._refresh(request_orig)
403
404
405
406 if headers is None:
407 headers = {}
408 self.apply(headers)
409
410 if self.user_agent is not None:
411 if 'user-agent' in headers:
412 headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
413 else:
414 headers['user-agent'] = self.user_agent
415
416 resp, content = request_orig(uri, method, body, headers,
417 redirections, connection_type)
418
419
420 if resp.status in [401, 403]:
421 logger.info('Refreshing due to a %s' % str(resp.status))
422 self._refresh(request_orig)
423 self.apply(headers)
424 return request_orig(uri, method, body, headers,
425 redirections, connection_type)
426 else:
427 return (resp, content)
428
429
430 http.request = new_request
431
432
433 setattr(http.request, 'credentials', self)
434
435 return http
436
438 """Forces a refresh of the access_token.
439
440 Args:
441 http: httplib2.Http, an http object to be used to make the refresh
442 request.
443 """
444 self._refresh(http.request)
445
446 - def apply(self, headers):
447 """Add the authorization to the headers.
448
449 Args:
450 headers: dict, the headers to add the Authorization header to.
451 """
452 headers['Authorization'] = 'Bearer ' + self.access_token
453
456
457 @classmethod
459 """Instantiate a Credentials object from a JSON description of it. The JSON
460 should have been produced by calling .to_json() on the object.
461
462 Args:
463 data: dict, A deserialized JSON object.
464
465 Returns:
466 An instance of a Credentials subclass.
467 """
468 data = simplejson.loads(s)
469 if 'token_expiry' in data and not isinstance(data['token_expiry'],
470 datetime.datetime):
471 try:
472 data['token_expiry'] = datetime.datetime.strptime(
473 data['token_expiry'], EXPIRY_FORMAT)
474 except:
475 data['token_expiry'] = None
476 retval = OAuth2Credentials(
477 data['access_token'],
478 data['client_id'],
479 data['client_secret'],
480 data['refresh_token'],
481 data['token_expiry'],
482 data['token_uri'],
483 data['user_agent'],
484 data.get('id_token', None))
485 retval.invalid = data['invalid']
486 return retval
487
488 @property
490 """True if the credential is expired or invalid.
491
492 If the token_expiry isn't set, we assume the token doesn't expire.
493 """
494 if self.invalid:
495 return True
496
497 if not self.token_expiry:
498 return False
499
500 now = datetime.datetime.utcnow()
501 if now >= self.token_expiry:
502 logger.info('access_token is expired. Now: %s, token_expiry: %s',
503 now, self.token_expiry)
504 return True
505 return False
506
508 """Set the Storage for the credential.
509
510 Args:
511 store: Storage, an implementation of Stroage object.
512 This is needed to store the latest access_token if it
513 has expired and been refreshed. This implementation uses
514 locking to check for updates before updating the
515 access_token.
516 """
517 self.store = store
518
520 """Update this Credential from another instance."""
521 self.__dict__.update(other.__getstate__())
522
524 """Trim the state down to something that can be pickled."""
525 d = copy.copy(self.__dict__)
526 del d['store']
527 return d
528
530 """Reconstitute the state of the object from being pickled."""
531 self.__dict__.update(state)
532 self.store = None
533
535 """Generate the body that will be used in the refresh request."""
536 body = urllib.urlencode({
537 'grant_type': 'refresh_token',
538 'client_id': self.client_id,
539 'client_secret': self.client_secret,
540 'refresh_token': self.refresh_token,
541 })
542 return body
543
545 """Generate the headers that will be used in the refresh request."""
546 headers = {
547 'content-type': 'application/x-www-form-urlencoded',
548 }
549
550 if self.user_agent is not None:
551 headers['user-agent'] = self.user_agent
552
553 return headers
554
556 """Refreshes the access_token.
557
558 This method first checks by reading the Storage object if available.
559 If a refresh is still needed, it holds the Storage lock until the
560 refresh is completed.
561
562 Args:
563 http_request: callable, a callable that matches the method signature of
564 httplib2.Http.request, used to make the refresh request.
565
566 Raises:
567 AccessTokenRefreshError: When the refresh fails.
568 """
569 if not self.store:
570 self._do_refresh_request(http_request)
571 else:
572 self.store.acquire_lock()
573 try:
574 new_cred = self.store.locked_get()
575 if (new_cred and not new_cred.invalid and
576 new_cred.access_token != self.access_token):
577 logger.info('Updated access_token read from Storage')
578 self._updateFromCredential(new_cred)
579 else:
580 self._do_refresh_request(http_request)
581 finally:
582 self.store.release_lock()
583
585 """Refresh the access_token using the refresh_token.
586
587 Args:
588 http_request: callable, a callable that matches the method signature of
589 httplib2.Http.request, used to make the refresh request.
590
591 Raises:
592 AccessTokenRefreshError: When the refresh fails.
593 """
594 body = self._generate_refresh_request_body()
595 headers = self._generate_refresh_request_headers()
596
597 logger.info('Refreshing access_token')
598 resp, content = http_request(
599 self.token_uri, method='POST', body=body, headers=headers)
600 if resp.status == 200:
601
602 d = simplejson.loads(content)
603 self.access_token = d['access_token']
604 self.refresh_token = d.get('refresh_token', self.refresh_token)
605 if 'expires_in' in d:
606 self.token_expiry = datetime.timedelta(
607 seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
608 else:
609 self.token_expiry = None
610 if self.store:
611 self.store.locked_put(self)
612 else:
613
614
615 logger.info('Failed to retrieve access token: %s' % content)
616 error_msg = 'Invalid response %s.' % resp['status']
617 try:
618 d = simplejson.loads(content)
619 if 'error' in d:
620 error_msg = d['error']
621 self.invalid = True
622 if self.store:
623 self.store.locked_put(self)
624 except:
625 pass
626 raise AccessTokenRefreshError(error_msg)
627
630 """Credentials object for OAuth 2.0.
631
632 Credentials can be applied to an httplib2.Http object using the
633 authorize() method, which then signs each request from that object
634 with the OAuth 2.0 access token. This set of credentials is for the
635 use case where you have acquired an OAuth 2.0 access_token from
636 another place such as a JavaScript client or another web
637 application, and wish to use it from Python. Because only the
638 access_token is present it can not be refreshed and will in time
639 expire.
640
641 AccessTokenCredentials objects may be safely pickled and unpickled.
642
643 Usage:
644 credentials = AccessTokenCredentials('<an access token>',
645 'my-user-agent/1.0')
646 http = httplib2.Http()
647 http = credentials.authorize(http)
648
649 Exceptions:
650 AccessTokenCredentialsExpired: raised when the access_token expires or is
651 revoked.
652 """
653
654 - def __init__(self, access_token, user_agent):
655 """Create an instance of OAuth2Credentials
656
657 This is one of the few types if Credentials that you should contrust,
658 Credentials objects are usually instantiated by a Flow.
659
660 Args:
661 access_token: string, access token.
662 user_agent: string, The HTTP User-Agent to provide for this application.
663
664 Notes:
665 store: callable, a callable that when passed a Credential
666 will store the credential back to where it came from.
667 """
668 super(AccessTokenCredentials, self).__init__(
669 access_token,
670 None,
671 None,
672 None,
673 None,
674 None,
675 user_agent)
676
677
678 @classmethod
680 data = simplejson.loads(s)
681 retval = AccessTokenCredentials(
682 data['access_token'],
683 data['user_agent'])
684 return retval
685
689
692 """Abstract Credentials object used for OAuth 2.0 assertion grants.
693
694 This credential does not require a flow to instantiate because it
695 represents a two legged flow, and therefore has all of the required
696 information to generate and refresh its own access tokens. It must
697 be subclassed to generate the appropriate assertion string.
698
699 AssertionCredentials objects may be safely pickled and unpickled.
700 """
701
702 - def __init__(self, assertion_type, user_agent,
703 token_uri='https://accounts.google.com/o/oauth2/token',
704 **unused_kwargs):
705 """Constructor for AssertionFlowCredentials.
706
707 Args:
708 assertion_type: string, assertion type that will be declared to the auth
709 server
710 user_agent: string, The HTTP User-Agent to provide for this application.
711 token_uri: string, URI for token endpoint. For convenience
712 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
713 """
714 super(AssertionCredentials, self).__init__(
715 None,
716 None,
717 None,
718 None,
719 None,
720 token_uri,
721 user_agent)
722 self.assertion_type = assertion_type
723
725 assertion = self._generate_assertion()
726
727 body = urllib.urlencode({
728 'assertion_type': self.assertion_type,
729 'assertion': assertion,
730 'grant_type': 'assertion',
731 })
732
733 return body
734
736 """Generate the assertion string that will be used in the access token
737 request.
738 """
739 _abstract()
740
741 if HAS_OPENSSL:
747 """Credentials object used for OAuth 2.0 Signed JWT assertion grants.
748
749 This credential does not require a flow to instantiate because it represents
750 a two legged flow, and therefore has all of the required information to
751 generate and refresh its own access tokens.
752
753 SignedJwtAssertionCredentials requires PyOpenSSL and because of that it does
754 not work on App Engine. For App Engine you may consider using
755 AppAssertionCredentials.
756 """
757
758 MAX_TOKEN_LIFETIME_SECS = 3600
759
760 - def __init__(self,
761 service_account_name,
762 private_key,
763 scope,
764 private_key_password='notasecret',
765 user_agent=None,
766 token_uri='https://accounts.google.com/o/oauth2/token',
767 **kwargs):
768 """Constructor for SignedJwtAssertionCredentials.
769
770 Args:
771 service_account_name: string, id for account, usually an email address.
772 private_key: string, private key in P12 format.
773 scope: string or list of strings, scope(s) of the credentials being
774 requested.
775 private_key_password: string, password for private_key.
776 user_agent: string, HTTP User-Agent to provide for this application.
777 token_uri: string, URI for token endpoint. For convenience
778 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
779 kwargs: kwargs, Additional parameters to add to the JWT token, for
780 example prn=joe@xample.org."""
781
782 super(SignedJwtAssertionCredentials, self).__init__(
783 'http://oauth.net/grant_type/jwt/1.0/bearer',
784 user_agent,
785 token_uri=token_uri,
786 )
787
788 if type(scope) is list:
789 scope = ' '.join(scope)
790 self.scope = scope
791
792
793 self.private_key = base64.b64encode(private_key)
794
795 self.private_key_password = private_key_password
796 self.service_account_name = service_account_name
797 self.kwargs = kwargs
798
799 @classmethod
801 data = simplejson.loads(s)
802 retval = SignedJwtAssertionCredentials(
803 data['service_account_name'],
804 base64.b64decode(data['private_key']),
805 data['scope'],
806 private_key_password=data['private_key_password'],
807 user_agent=data['user_agent'],
808 token_uri=data['token_uri'],
809 **data['kwargs']
810 )
811 retval.invalid = data['invalid']
812 retval.access_token = data['access_token']
813 return retval
814
816 """Generate the assertion that will be used in the request."""
817 now = long(time.time())
818 payload = {
819 'aud': self.token_uri,
820 'scope': self.scope,
821 'iat': now,
822 'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
823 'iss': self.service_account_name
824 }
825 payload.update(self.kwargs)
826 logger.debug(str(payload))
827
828 private_key = base64.b64decode(self.private_key)
829 return make_signed_jwt(
830 Signer.from_string(private_key, self.private_key_password), payload)
831
832
833
834 _cached_http = httplib2.Http(MemoryCache())
838 """Verifies a signed JWT id_token.
839
840 This function requires PyOpenSSL and because of that it does not work on
841 App Engine. For App Engine you may consider using AppAssertionCredentials.
842
843 Args:
844 id_token: string, A Signed JWT.
845 audience: string, The audience 'aud' that the token should be for.
846 http: httplib2.Http, instance to use to make the HTTP request. Callers
847 should supply an instance that has caching enabled.
848 cert_uri: string, URI of the certificates in JSON format to
849 verify the JWT against.
850
851 Returns:
852 The deserialized JSON in the JWT.
853
854 Raises:
855 oauth2client.crypt.AppIdentityError if the JWT fails to verify.
856 """
857 if http is None:
858 http = _cached_http
859
860 resp, content = http.request(cert_uri)
861
862 if resp.status == 200:
863 certs = simplejson.loads(content)
864 return verify_signed_jwt_with_certs(id_token, certs, audience)
865 else:
866 raise VerifyJwtTokenError('Status code: %d' % resp.status)
867
870
871 b64string = b64string.encode('ascii')
872 padded = b64string + '=' * (4 - len(b64string) % 4)
873 return base64.urlsafe_b64decode(padded)
874
877 """Extract the JSON payload from a JWT.
878
879 Does the extraction w/o checking the signature.
880
881 Args:
882 id_token: string, OAuth 2.0 id_token.
883
884 Returns:
885 object, The deserialized JSON payload.
886 """
887 segments = id_token.split('.')
888
889 if (len(segments) != 3):
890 raise VerifyJwtTokenError(
891 'Wrong number of segments in token: %s' % id_token)
892
893 return simplejson.loads(_urlsafe_b64decode(segments[1]))
894
896 """Parses response of an exchange token request.
897
898 Most providers return JSON but some (e.g. Facebook) return a
899 url-encoded string.
900
901 Args:
902 content: The body of a response
903
904 Returns:
905 Content as a dictionary object. Note that the dict could be empty,
906 i.e. {}. That basically indicates a failure.
907 """
908 resp = {}
909 try:
910 resp = simplejson.loads(content)
911 except StandardError:
912
913
914 resp = dict(parse_qsl(content))
915
916
917 if resp and 'expires' in resp:
918 resp['expires_in'] = resp.pop('expires')
919
920 return resp
921
922 -def credentials_from_code(client_id, client_secret, scope, code,
923 redirect_uri = 'postmessage',
924 http=None, user_agent=None,
925 token_uri='https://accounts.google.com/o/oauth2/token'):
926 """Exchanges an authorization code for an OAuth2Credentials object.
927
928 Args:
929 client_id: string, client identifier.
930 client_secret: string, client secret.
931 scope: string or list of strings, scope(s) to request.
932 code: string, An authroization code, most likely passed down from
933 the client
934 redirect_uri: string, this is generally set to 'postmessage' to match the
935 redirect_uri that the client specified
936 http: httplib2.Http, optional http instance to use to do the fetch
937 token_uri: string, URI for token endpoint. For convenience
938 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
939 Returns:
940 An OAuth2Credentials object.
941
942 Raises:
943 FlowExchangeError if the authorization code cannot be exchanged for an
944 access token
945 """
946 flow = OAuth2WebServerFlow(client_id, client_secret, scope, user_agent,
947 'https://accounts.google.com/o/oauth2/auth',
948 token_uri)
949
950
951 uriThatWeDontReallyUse = flow.step1_get_authorize_url(redirect_uri)
952 credentials = flow.step2_exchange(code, http)
953 return credentials
954
961 """Returns OAuth2Credentials from a clientsecrets file and an auth code.
962
963 Will create the right kind of Flow based on the contents of the clientsecrets
964 file or will raise InvalidClientSecretsError for unknown types of Flows.
965
966 Args:
967 filename: string, File name of clientsecrets.
968 scope: string or list of strings, scope(s) to request.
969 code: string, An authroization code, most likely passed down from
970 the client
971 message: string, A friendly string to display to the user if the
972 clientsecrets file is missing or invalid. If message is provided then
973 sys.exit will be called in the case of an error. If message in not
974 provided then clientsecrets.InvalidClientSecretsError will be raised.
975 redirect_uri: string, this is generally set to 'postmessage' to match the
976 redirect_uri that the client specified
977 http: httplib2.Http, optional http instance to use to do the fetch
978 cache: An optional cache service client that implements get() and set()
979 methods. See clientsecrets.loadfile() for details.
980
981 Returns:
982 An OAuth2Credentials object.
983
984 Raises:
985 FlowExchangeError if the authorization code cannot be exchanged for an
986 access token
987 UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
988 clientsecrets.InvalidClientSecretsError if the clientsecrets file is
989 invalid.
990 """
991 flow = flow_from_clientsecrets(filename, scope, message=message, cache=cache)
992
993 uriThatWeDontReallyUse = flow.step1_get_authorize_url(redirect_uri)
994 credentials = flow.step2_exchange(code, http)
995 return credentials
996
999 """Does the Web Server Flow for OAuth 2.0.
1000
1001 OAuth2Credentials objects may be safely pickled and unpickled.
1002 """
1003
1004 - def __init__(self, client_id, client_secret, scope, user_agent=None,
1005 auth_uri='https://accounts.google.com/o/oauth2/auth',
1006 token_uri='https://accounts.google.com/o/oauth2/token',
1007 **kwargs):
1008 """Constructor for OAuth2WebServerFlow.
1009
1010 Args:
1011 client_id: string, client identifier.
1012 client_secret: string client secret.
1013 scope: string or list of strings, scope(s) of the credentials being
1014 requested.
1015 user_agent: string, HTTP User-Agent to provide for this application.
1016 auth_uri: string, URI for authorization endpoint. For convenience
1017 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1018 token_uri: string, URI for token endpoint. For convenience
1019 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
1020 **kwargs: dict, The keyword arguments are all optional and required
1021 parameters for the OAuth calls.
1022 """
1023 self.client_id = client_id
1024 self.client_secret = client_secret
1025 if type(scope) is list:
1026 scope = ' '.join(scope)
1027 self.scope = scope
1028 self.user_agent = user_agent
1029 self.auth_uri = auth_uri
1030 self.token_uri = token_uri
1031 self.params = {
1032 'access_type': 'offline',
1033 }
1034 self.params.update(kwargs)
1035 self.redirect_uri = None
1036
1038 """Returns a URI to redirect to the provider.
1039
1040 Args:
1041 redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
1042 a non-web-based application, or a URI that handles the callback from
1043 the authorization server.
1044
1045 If redirect_uri is 'urn:ietf:wg:oauth:2.0:oob' then pass in the
1046 generated verification code to step2_exchange,
1047 otherwise pass in the query parameters received
1048 at the callback uri to step2_exchange.
1049 """
1050
1051 self.redirect_uri = redirect_uri
1052 query = {
1053 'response_type': 'code',
1054 'client_id': self.client_id,
1055 'redirect_uri': redirect_uri,
1056 'scope': self.scope,
1057 }
1058 query.update(self.params)
1059 parts = list(urlparse.urlparse(self.auth_uri))
1060 query.update(dict(parse_qsl(parts[4])))
1061 parts[4] = urllib.urlencode(query)
1062 return urlparse.urlunparse(parts)
1063
1065 """Exhanges a code for OAuth2Credentials.
1066
1067 Args:
1068 code: string or dict, either the code as a string, or a dictionary
1069 of the query parameters to the redirect_uri, which contains
1070 the code.
1071 http: httplib2.Http, optional http instance to use to do the fetch
1072
1073 Returns:
1074 An OAuth2Credentials object that can be used to authorize requests.
1075
1076 Raises:
1077 FlowExchangeError if a problem occured exchanging the code for a
1078 refresh_token.
1079 """
1080
1081 if not (isinstance(code, str) or isinstance(code, unicode)):
1082 if 'code' not in code:
1083 if 'error' in code:
1084 error_msg = code['error']
1085 else:
1086 error_msg = 'No code was supplied in the query parameters.'
1087 raise FlowExchangeError(error_msg)
1088 else:
1089 code = code['code']
1090
1091 body = urllib.urlencode({
1092 'grant_type': 'authorization_code',
1093 'client_id': self.client_id,
1094 'client_secret': self.client_secret,
1095 'code': code,
1096 'redirect_uri': self.redirect_uri,
1097 'scope': self.scope,
1098 })
1099 headers = {
1100 'content-type': 'application/x-www-form-urlencoded',
1101 }
1102
1103 if self.user_agent is not None:
1104 headers['user-agent'] = self.user_agent
1105
1106 if http is None:
1107 http = httplib2.Http()
1108
1109 resp, content = http.request(self.token_uri, method='POST', body=body,
1110 headers=headers)
1111 d = _parse_exchange_token_response(content)
1112 if resp.status == 200 and 'access_token' in d:
1113 access_token = d['access_token']
1114 refresh_token = d.get('refresh_token', None)
1115 token_expiry = None
1116 if 'expires_in' in d:
1117 token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
1118 seconds=int(d['expires_in']))
1119
1120 if 'id_token' in d:
1121 d['id_token'] = _extract_id_token(d['id_token'])
1122
1123 logger.info('Successfully retrieved access token: %s' % content)
1124 return OAuth2Credentials(access_token, self.client_id,
1125 self.client_secret, refresh_token, token_expiry,
1126 self.token_uri, self.user_agent,
1127 id_token=d.get('id_token', None))
1128 else:
1129 logger.info('Failed to retrieve access token: %s' % content)
1130 if 'error' in d:
1131
1132 error_msg = unicode(d['error'])
1133 else:
1134 error_msg = 'Invalid response: %s.' % str(resp.status)
1135 raise FlowExchangeError(error_msg)
1136
1138 """Create a Flow from a clientsecrets file.
1139
1140 Will create the right kind of Flow based on the contents of the clientsecrets
1141 file or will raise InvalidClientSecretsError for unknown types of Flows.
1142
1143 Args:
1144 filename: string, File name of client secrets.
1145 scope: string or list of strings, scope(s) to request.
1146 message: string, A friendly string to display to the user if the
1147 clientsecrets file is missing or invalid. If message is provided then
1148 sys.exit will be called in the case of an error. If message in not
1149 provided then clientsecrets.InvalidClientSecretsError will be raised.
1150 cache: An optional cache service client that implements get() and set()
1151 methods. See clientsecrets.loadfile() for details.
1152
1153 Returns:
1154 A Flow object.
1155
1156 Raises:
1157 UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
1158 clientsecrets.InvalidClientSecretsError if the clientsecrets file is
1159 invalid.
1160 """
1161 try:
1162 client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
1163 if client_type in [clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
1164 return OAuth2WebServerFlow(
1165 client_info['client_id'],
1166 client_info['client_secret'],
1167 scope,
1168 None,
1169 client_info['auth_uri'],
1170 client_info['token_uri'])
1171 except clientsecrets.InvalidClientSecretsError:
1172 if message:
1173 sys.exit(message)
1174 else:
1175 raise
1176 else:
1177 raise UnknownClientSecretsFlowError(
1178 'This OAuth 2.0 flow is unsupported: "%s"' * client_type)
1179