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 if resp.status == 401:
420 logger.info('Refreshing due to a 401')
421 self._refresh(request_orig)
422 self.apply(headers)
423 return request_orig(uri, method, body, headers,
424 redirections, connection_type)
425 else:
426 return (resp, content)
427
428
429 http.request = new_request
430
431
432 setattr(http.request, 'credentials', self)
433
434 return http
435
437 """Forces a refresh of the access_token.
438
439 Args:
440 http: httplib2.Http, an http object to be used to make the refresh
441 request.
442 """
443 self._refresh(http.request)
444
445 - def apply(self, headers):
446 """Add the authorization to the headers.
447
448 Args:
449 headers: dict, the headers to add the Authorization header to.
450 """
451 headers['Authorization'] = 'Bearer ' + self.access_token
452
455
456 @classmethod
458 """Instantiate a Credentials object from a JSON description of it. The JSON
459 should have been produced by calling .to_json() on the object.
460
461 Args:
462 data: dict, A deserialized JSON object.
463
464 Returns:
465 An instance of a Credentials subclass.
466 """
467 data = simplejson.loads(s)
468 if 'token_expiry' in data and not isinstance(data['token_expiry'],
469 datetime.datetime):
470 try:
471 data['token_expiry'] = datetime.datetime.strptime(
472 data['token_expiry'], EXPIRY_FORMAT)
473 except:
474 data['token_expiry'] = None
475 retval = OAuth2Credentials(
476 data['access_token'],
477 data['client_id'],
478 data['client_secret'],
479 data['refresh_token'],
480 data['token_expiry'],
481 data['token_uri'],
482 data['user_agent'],
483 data.get('id_token', None))
484 retval.invalid = data['invalid']
485 return retval
486
487 @property
489 """True if the credential is expired or invalid.
490
491 If the token_expiry isn't set, we assume the token doesn't expire.
492 """
493 if self.invalid:
494 return True
495
496 if not self.token_expiry:
497 return False
498
499 now = datetime.datetime.utcnow()
500 if now >= self.token_expiry:
501 logger.info('access_token is expired. Now: %s, token_expiry: %s',
502 now, self.token_expiry)
503 return True
504 return False
505
507 """Set the Storage for the credential.
508
509 Args:
510 store: Storage, an implementation of Stroage object.
511 This is needed to store the latest access_token if it
512 has expired and been refreshed. This implementation uses
513 locking to check for updates before updating the
514 access_token.
515 """
516 self.store = store
517
519 """Update this Credential from another instance."""
520 self.__dict__.update(other.__getstate__())
521
523 """Trim the state down to something that can be pickled."""
524 d = copy.copy(self.__dict__)
525 del d['store']
526 return d
527
529 """Reconstitute the state of the object from being pickled."""
530 self.__dict__.update(state)
531 self.store = None
532
534 """Generate the body that will be used in the refresh request."""
535 body = urllib.urlencode({
536 'grant_type': 'refresh_token',
537 'client_id': self.client_id,
538 'client_secret': self.client_secret,
539 'refresh_token': self.refresh_token,
540 })
541 return body
542
544 """Generate the headers that will be used in the refresh request."""
545 headers = {
546 'content-type': 'application/x-www-form-urlencoded',
547 }
548
549 if self.user_agent is not None:
550 headers['user-agent'] = self.user_agent
551
552 return headers
553
555 """Refreshes the access_token.
556
557 This method first checks by reading the Storage object if available.
558 If a refresh is still needed, it holds the Storage lock until the
559 refresh is completed.
560
561 Args:
562 http_request: callable, a callable that matches the method signature of
563 httplib2.Http.request, used to make the refresh request.
564
565 Raises:
566 AccessTokenRefreshError: When the refresh fails.
567 """
568 if not self.store:
569 self._do_refresh_request(http_request)
570 else:
571 self.store.acquire_lock()
572 try:
573 new_cred = self.store.locked_get()
574 if (new_cred and not new_cred.invalid and
575 new_cred.access_token != self.access_token):
576 logger.info('Updated access_token read from Storage')
577 self._updateFromCredential(new_cred)
578 else:
579 self._do_refresh_request(http_request)
580 finally:
581 self.store.release_lock()
582
584 """Refresh the access_token using the refresh_token.
585
586 Args:
587 http_request: callable, a callable that matches the method signature of
588 httplib2.Http.request, used to make the refresh request.
589
590 Raises:
591 AccessTokenRefreshError: When the refresh fails.
592 """
593 body = self._generate_refresh_request_body()
594 headers = self._generate_refresh_request_headers()
595
596 logger.info('Refreshing access_token')
597 resp, content = http_request(
598 self.token_uri, method='POST', body=body, headers=headers)
599 if resp.status == 200:
600
601 d = simplejson.loads(content)
602 self.access_token = d['access_token']
603 self.refresh_token = d.get('refresh_token', self.refresh_token)
604 if 'expires_in' in d:
605 self.token_expiry = datetime.timedelta(
606 seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
607 else:
608 self.token_expiry = None
609 if self.store:
610 self.store.locked_put(self)
611 else:
612
613
614 logger.info('Failed to retrieve access token: %s' % content)
615 error_msg = 'Invalid response %s.' % resp['status']
616 try:
617 d = simplejson.loads(content)
618 if 'error' in d:
619 error_msg = d['error']
620 self.invalid = True
621 if self.store:
622 self.store.locked_put(self)
623 except:
624 pass
625 raise AccessTokenRefreshError(error_msg)
626
629 """Credentials object for OAuth 2.0.
630
631 Credentials can be applied to an httplib2.Http object using the
632 authorize() method, which then signs each request from that object
633 with the OAuth 2.0 access token. This set of credentials is for the
634 use case where you have acquired an OAuth 2.0 access_token from
635 another place such as a JavaScript client or another web
636 application, and wish to use it from Python. Because only the
637 access_token is present it can not be refreshed and will in time
638 expire.
639
640 AccessTokenCredentials objects may be safely pickled and unpickled.
641
642 Usage:
643 credentials = AccessTokenCredentials('<an access token>',
644 'my-user-agent/1.0')
645 http = httplib2.Http()
646 http = credentials.authorize(http)
647
648 Exceptions:
649 AccessTokenCredentialsExpired: raised when the access_token expires or is
650 revoked.
651 """
652
653 - def __init__(self, access_token, user_agent):
654 """Create an instance of OAuth2Credentials
655
656 This is one of the few types if Credentials that you should contrust,
657 Credentials objects are usually instantiated by a Flow.
658
659 Args:
660 access_token: string, access token.
661 user_agent: string, The HTTP User-Agent to provide for this application.
662
663 Notes:
664 store: callable, a callable that when passed a Credential
665 will store the credential back to where it came from.
666 """
667 super(AccessTokenCredentials, self).__init__(
668 access_token,
669 None,
670 None,
671 None,
672 None,
673 None,
674 user_agent)
675
676
677 @classmethod
679 data = simplejson.loads(s)
680 retval = AccessTokenCredentials(
681 data['access_token'],
682 data['user_agent'])
683 return retval
684
688
691 """Abstract Credentials object used for OAuth 2.0 assertion grants.
692
693 This credential does not require a flow to instantiate because it
694 represents a two legged flow, and therefore has all of the required
695 information to generate and refresh its own access tokens. It must
696 be subclassed to generate the appropriate assertion string.
697
698 AssertionCredentials objects may be safely pickled and unpickled.
699 """
700
701 - def __init__(self, assertion_type, user_agent,
702 token_uri='https://accounts.google.com/o/oauth2/token',
703 **unused_kwargs):
704 """Constructor for AssertionFlowCredentials.
705
706 Args:
707 assertion_type: string, assertion type that will be declared to the auth
708 server
709 user_agent: string, The HTTP User-Agent to provide for this application.
710 token_uri: string, URI for token endpoint. For convenience
711 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
712 """
713 super(AssertionCredentials, self).__init__(
714 None,
715 None,
716 None,
717 None,
718 None,
719 token_uri,
720 user_agent)
721 self.assertion_type = assertion_type
722
724 assertion = self._generate_assertion()
725
726 body = urllib.urlencode({
727 'assertion_type': self.assertion_type,
728 'assertion': assertion,
729 'grant_type': 'assertion',
730 })
731
732 return body
733
735 """Generate the assertion string that will be used in the access token
736 request.
737 """
738 _abstract()
739
740 if HAS_OPENSSL:
746 """Credentials object used for OAuth 2.0 Signed JWT assertion grants.
747
748 This credential does not require a flow to instantiate because it
749 represents a two legged flow, and therefore has all of the required
750 information to generate and refresh its own access tokens.
751 """
752
753 MAX_TOKEN_LIFETIME_SECS = 3600
754
755 - def __init__(self,
756 service_account_name,
757 private_key,
758 scope,
759 private_key_password='notasecret',
760 user_agent=None,
761 token_uri='https://accounts.google.com/o/oauth2/token',
762 **kwargs):
763 """Constructor for SignedJwtAssertionCredentials.
764
765 Args:
766 service_account_name: string, id for account, usually an email address.
767 private_key: string, private key in P12 format.
768 scope: string or list of strings, scope(s) of the credentials being
769 requested.
770 private_key_password: string, password for private_key.
771 user_agent: string, HTTP User-Agent to provide for this application.
772 token_uri: string, URI for token endpoint. For convenience
773 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
774 kwargs: kwargs, Additional parameters to add to the JWT token, for
775 example prn=joe@xample.org."""
776
777 super(SignedJwtAssertionCredentials, self).__init__(
778 'http://oauth.net/grant_type/jwt/1.0/bearer',
779 user_agent,
780 token_uri=token_uri,
781 )
782
783 if type(scope) is list:
784 scope = ' '.join(scope)
785 self.scope = scope
786
787 self.private_key = private_key
788 self.private_key_password = private_key_password
789 self.service_account_name = service_account_name
790 self.kwargs = kwargs
791
792 @classmethod
794 data = simplejson.loads(s)
795 retval = SignedJwtAssertionCredentials(
796 data['service_account_name'],
797 data['private_key'],
798 data['private_key_password'],
799 data['scope'],
800 data['user_agent'],
801 data['token_uri'],
802 data['kwargs']
803 )
804 retval.invalid = data['invalid']
805 return retval
806
808 """Generate the assertion that will be used in the request."""
809 now = long(time.time())
810 payload = {
811 'aud': self.token_uri,
812 'scope': self.scope,
813 'iat': now,
814 'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
815 'iss': self.service_account_name
816 }
817 payload.update(self.kwargs)
818 logger.debug(str(payload))
819
820 return make_signed_jwt(
821 Signer.from_string(self.private_key, self.private_key_password),
822 payload)
823
824
825
826 _cached_http = httplib2.Http(MemoryCache())
830 """Verifies a signed JWT id_token.
831
832 Args:
833 id_token: string, A Signed JWT.
834 audience: string, The audience 'aud' that the token should be for.
835 http: httplib2.Http, instance to use to make the HTTP request. Callers
836 should supply an instance that has caching enabled.
837 cert_uri: string, URI of the certificates in JSON format to
838 verify the JWT against.
839
840 Returns:
841 The deserialized JSON in the JWT.
842
843 Raises:
844 oauth2client.crypt.AppIdentityError if the JWT fails to verify.
845 """
846 if http is None:
847 http = _cached_http
848
849 resp, content = http.request(cert_uri)
850
851 if resp.status == 200:
852 certs = simplejson.loads(content)
853 return verify_signed_jwt_with_certs(id_token, certs, audience)
854 else:
855 raise VerifyJwtTokenError('Status code: %d' % resp.status)
856
859
860 b64string = b64string.encode('ascii')
861 padded = b64string + '=' * (4 - len(b64string) % 4)
862 return base64.urlsafe_b64decode(padded)
863
866 """Extract the JSON payload from a JWT.
867
868 Does the extraction w/o checking the signature.
869
870 Args:
871 id_token: string, OAuth 2.0 id_token.
872
873 Returns:
874 object, The deserialized JSON payload.
875 """
876 segments = id_token.split('.')
877
878 if (len(segments) != 3):
879 raise VerifyJwtTokenError(
880 'Wrong number of segments in token: %s' % id_token)
881
882 return simplejson.loads(_urlsafe_b64decode(segments[1]))
883
884 -def credentials_from_code(client_id, client_secret, scope, code,
885 redirect_uri = 'postmessage',
886 http=None, user_agent=None,
887 token_uri='https://accounts.google.com/o/oauth2/token'):
888 """Exchanges an authorization code for an OAuth2Credentials object.
889
890 Args:
891 client_id: string, client identifier.
892 client_secret: string, client secret.
893 scope: string or list of strings, scope(s) to request.
894 code: string, An authroization code, most likely passed down from
895 the client
896 redirect_uri: string, this is generally set to 'postmessage' to match the
897 redirect_uri that the client specified
898 http: httplib2.Http, optional http instance to use to do the fetch
899 token_uri: string, URI for token endpoint. For convenience
900 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
901 Returns:
902 An OAuth2Credentials object.
903
904 Raises:
905 FlowExchangeError if the authorization code cannot be exchanged for an
906 access token
907 """
908 flow = OAuth2WebServerFlow(client_id, client_secret, scope, user_agent,
909 'https://accounts.google.com/o/oauth2/auth',
910 token_uri)
911
912
913 uriThatWeDontReallyUse = flow.step1_get_authorize_url(redirect_uri)
914 credentials = flow.step2_exchange(code, http)
915 return credentials
916
922 """Returns OAuth2Credentials from a clientsecrets file and an auth code.
923
924 Will create the right kind of Flow based on the contents of the clientsecrets
925 file or will raise InvalidClientSecretsError for unknown types of Flows.
926
927 Args:
928 filename: string, File name of clientsecrets.
929 scope: string or list of strings, scope(s) to request.
930 code: string, An authroization code, most likely passed down from
931 the client
932 message: string, A friendly string to display to the user if the
933 clientsecrets file is missing or invalid. If message is provided then
934 sys.exit will be called in the case of an error. If message in not
935 provided then clientsecrets.InvalidClientSecretsError will be raised.
936 redirect_uri: string, this is generally set to 'postmessage' to match the
937 redirect_uri that the client specified
938 http: httplib2.Http, optional http instance to use to do the fetch
939
940 Returns:
941 An OAuth2Credentials object.
942
943 Raises:
944 FlowExchangeError if the authorization code cannot be exchanged for an
945 access token
946 UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
947 clientsecrets.InvalidClientSecretsError if the clientsecrets file is
948 invalid.
949 """
950 flow = flow_from_clientsecrets(filename, scope, message)
951
952 uriThatWeDontReallyUse = flow.step1_get_authorize_url(redirect_uri)
953 credentials = flow.step2_exchange(code, http)
954 return credentials
955
958 """Does the Web Server Flow for OAuth 2.0.
959
960 OAuth2Credentials objects may be safely pickled and unpickled.
961 """
962
963 - def __init__(self, client_id, client_secret, scope, user_agent=None,
964 auth_uri='https://accounts.google.com/o/oauth2/auth',
965 token_uri='https://accounts.google.com/o/oauth2/token',
966 **kwargs):
967 """Constructor for OAuth2WebServerFlow.
968
969 Args:
970 client_id: string, client identifier.
971 client_secret: string client secret.
972 scope: string or list of strings, scope(s) of the credentials being
973 requested.
974 user_agent: string, HTTP User-Agent to provide for this application.
975 auth_uri: string, URI for authorization endpoint. For convenience
976 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
977 token_uri: string, URI for token endpoint. For convenience
978 defaults to Google's endpoints but any OAuth 2.0 provider can be used.
979 **kwargs: dict, The keyword arguments are all optional and required
980 parameters for the OAuth calls.
981 """
982 self.client_id = client_id
983 self.client_secret = client_secret
984 if type(scope) is list:
985 scope = ' '.join(scope)
986 self.scope = scope
987 self.user_agent = user_agent
988 self.auth_uri = auth_uri
989 self.token_uri = token_uri
990 self.params = {
991 'access_type': 'offline',
992 }
993 self.params.update(kwargs)
994 self.redirect_uri = None
995
997 """Returns a URI to redirect to the provider.
998
999 Args:
1000 redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
1001 a non-web-based application, or a URI that handles the callback from
1002 the authorization server.
1003
1004 If redirect_uri is 'urn:ietf:wg:oauth:2.0:oob' then pass in the
1005 generated verification code to step2_exchange,
1006 otherwise pass in the query parameters received
1007 at the callback uri to step2_exchange.
1008 """
1009
1010 self.redirect_uri = redirect_uri
1011 query = {
1012 'response_type': 'code',
1013 'client_id': self.client_id,
1014 'redirect_uri': redirect_uri,
1015 'scope': self.scope,
1016 }
1017 query.update(self.params)
1018 parts = list(urlparse.urlparse(self.auth_uri))
1019 query.update(dict(parse_qsl(parts[4])))
1020 parts[4] = urllib.urlencode(query)
1021 return urlparse.urlunparse(parts)
1022
1024 """Exhanges a code for OAuth2Credentials.
1025
1026 Args:
1027 code: string or dict, either the code as a string, or a dictionary
1028 of the query parameters to the redirect_uri, which contains
1029 the code.
1030 http: httplib2.Http, optional http instance to use to do the fetch
1031
1032 Returns:
1033 An OAuth2Credentials object that can be used to authorize requests.
1034
1035 Raises:
1036 FlowExchangeError if a problem occured exchanging the code for a
1037 refresh_token.
1038 """
1039
1040 if not (isinstance(code, str) or isinstance(code, unicode)):
1041 if 'code' not in code:
1042 if 'error' in code:
1043 error_msg = code['error']
1044 else:
1045 error_msg = 'No code was supplied in the query parameters.'
1046 raise FlowExchangeError(error_msg)
1047 else:
1048 code = code['code']
1049
1050 body = urllib.urlencode({
1051 'grant_type': 'authorization_code',
1052 'client_id': self.client_id,
1053 'client_secret': self.client_secret,
1054 'code': code,
1055 'redirect_uri': self.redirect_uri,
1056 'scope': self.scope,
1057 })
1058 headers = {
1059 'content-type': 'application/x-www-form-urlencoded',
1060 }
1061
1062 if self.user_agent is not None:
1063 headers['user-agent'] = self.user_agent
1064
1065 if http is None:
1066 http = httplib2.Http()
1067
1068 resp, content = http.request(self.token_uri, method='POST', body=body,
1069 headers=headers)
1070 if resp.status == 200:
1071
1072 d = simplejson.loads(content)
1073 access_token = d['access_token']
1074 refresh_token = d.get('refresh_token', None)
1075 token_expiry = None
1076 if 'expires_in' in d:
1077 token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
1078 seconds=int(d['expires_in']))
1079
1080 if 'id_token' in d:
1081 d['id_token'] = _extract_id_token(d['id_token'])
1082
1083 logger.info('Successfully retrieved access token: %s' % content)
1084 return OAuth2Credentials(access_token, self.client_id,
1085 self.client_secret, refresh_token, token_expiry,
1086 self.token_uri, self.user_agent,
1087 id_token=d.get('id_token', None))
1088 else:
1089 logger.info('Failed to retrieve access token: %s' % content)
1090 error_msg = 'Invalid response %s.' % resp['status']
1091 try:
1092 d = simplejson.loads(content)
1093 if 'error' in d:
1094 error_msg = d['error']
1095 except:
1096 pass
1097
1098 raise FlowExchangeError(error_msg)
1099
1101 """Create a Flow from a clientsecrets file.
1102
1103 Will create the right kind of Flow based on the contents of the clientsecrets
1104 file or will raise InvalidClientSecretsError for unknown types of Flows.
1105
1106 Args:
1107 filename: string, File name of client secrets.
1108 scope: string or list of strings, scope(s) to request.
1109 message: string, A friendly string to display to the user if the
1110 clientsecrets file is missing or invalid. If message is provided then
1111 sys.exit will be called in the case of an error. If message in not
1112 provided then clientsecrets.InvalidClientSecretsError will be raised.
1113
1114 Returns:
1115 A Flow object.
1116
1117 Raises:
1118 UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
1119 clientsecrets.InvalidClientSecretsError if the clientsecrets file is
1120 invalid.
1121 """
1122 try:
1123 client_type, client_info = clientsecrets.loadfile(filename)
1124 if client_type in [clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
1125 return OAuth2WebServerFlow(
1126 client_info['client_id'],
1127 client_info['client_secret'],
1128 scope,
1129 None,
1130 client_info['auth_uri'],
1131 client_info['token_uri'])
1132 except clientsecrets.InvalidClientSecretsError:
1133 if message:
1134 sys.exit(message)
1135 else:
1136 raise
1137 else:
1138 raise UnknownClientSecretsFlowError(
1139 'This OAuth 2.0 flow is unsupported: "%s"' * client_type)
1140