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