OLD | NEW |
1 # Copyright 2014 Google Inc. All rights reserved. | 1 # Copyright (C) 2010 Google Inc. |
2 # | 2 # |
3 # Licensed under the Apache License, Version 2.0 (the "License"); | 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
4 # you may not use this file except in compliance with the License. | 4 # you may not use this file except in compliance with the License. |
5 # You may obtain a copy of the License at | 5 # You may obtain a copy of the License at |
6 # | 6 # |
7 # http://www.apache.org/licenses/LICENSE-2.0 | 7 # http://www.apache.org/licenses/LICENSE-2.0 |
8 # | 8 # |
9 # Unless required by applicable law or agreed to in writing, software | 9 # Unless required by applicable law or agreed to in writing, software |
10 # distributed under the License is distributed on an "AS IS" BASIS, | 10 # distributed under the License is distributed on an "AS IS" BASIS, |
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 # See the License for the specific language governing permissions and | 12 # See the License for the specific language governing permissions and |
13 # limitations under the License. | 13 # limitations under the License. |
14 | 14 |
15 """Utilities for Google App Engine | 15 """Utilities for Google App Engine |
16 | 16 |
17 Utilities for making it easier to use OAuth 2.0 on Google App Engine. | 17 Utilities for making it easier to use OAuth 2.0 on Google App Engine. |
18 """ | 18 """ |
19 | 19 |
20 __author__ = 'jcgregorio@google.com (Joe Gregorio)' | 20 __author__ = 'jcgregorio@google.com (Joe Gregorio)' |
21 | 21 |
| 22 import base64 |
22 import cgi | 23 import cgi |
23 import json | 24 import httplib2 |
24 import logging | 25 import logging |
25 import os | 26 import os |
26 import pickle | 27 import pickle |
27 import threading | 28 import threading |
28 | 29 import time |
29 import httplib2 | |
30 | 30 |
31 from google.appengine.api import app_identity | 31 from google.appengine.api import app_identity |
32 from google.appengine.api import memcache | 32 from google.appengine.api import memcache |
33 from google.appengine.api import users | 33 from google.appengine.api import users |
34 from google.appengine.ext import db | 34 from google.appengine.ext import db |
35 from google.appengine.ext import webapp | 35 from google.appengine.ext import webapp |
36 from google.appengine.ext.webapp.util import login_required | 36 from google.appengine.ext.webapp.util import login_required |
37 from google.appengine.ext.webapp.util import run_wsgi_app | 37 from google.appengine.ext.webapp.util import run_wsgi_app |
38 from oauth2client import GOOGLE_AUTH_URI | 38 from oauth2client import GOOGLE_AUTH_URI |
39 from oauth2client import GOOGLE_REVOKE_URI | 39 from oauth2client import GOOGLE_REVOKE_URI |
40 from oauth2client import GOOGLE_TOKEN_URI | 40 from oauth2client import GOOGLE_TOKEN_URI |
41 from oauth2client import clientsecrets | 41 from oauth2client import clientsecrets |
42 from oauth2client import util | 42 from oauth2client import util |
43 from oauth2client import xsrfutil | 43 from oauth2client import xsrfutil |
| 44 from oauth2client.anyjson import simplejson |
44 from oauth2client.client import AccessTokenRefreshError | 45 from oauth2client.client import AccessTokenRefreshError |
45 from oauth2client.client import AssertionCredentials | 46 from oauth2client.client import AssertionCredentials |
46 from oauth2client.client import Credentials | 47 from oauth2client.client import Credentials |
47 from oauth2client.client import Flow | 48 from oauth2client.client import Flow |
48 from oauth2client.client import OAuth2WebServerFlow | 49 from oauth2client.client import OAuth2WebServerFlow |
49 from oauth2client.client import Storage | 50 from oauth2client.client import Storage |
50 | 51 |
51 # TODO(dhermes): Resolve import issue. | 52 # TODO(dhermes): Resolve import issue. |
52 # This is a temporary fix for a Google internal issue. | 53 # This is a temporary fix for a Google internal issue. |
53 try: | 54 try: |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
151 generate and refresh its own access tokens. | 152 generate and refresh its own access tokens. |
152 """ | 153 """ |
153 | 154 |
154 @util.positional(2) | 155 @util.positional(2) |
155 def __init__(self, scope, **kwargs): | 156 def __init__(self, scope, **kwargs): |
156 """Constructor for AppAssertionCredentials | 157 """Constructor for AppAssertionCredentials |
157 | 158 |
158 Args: | 159 Args: |
159 scope: string or iterable of strings, scope(s) of the credentials being | 160 scope: string or iterable of strings, scope(s) of the credentials being |
160 requested. | 161 requested. |
161 **kwargs: optional keyword args, including: | |
162 service_account_id: service account id of the application. If None or | |
163 unspecified, the default service account for the app is used. | |
164 """ | 162 """ |
165 self.scope = util.scopes_to_string(scope) | 163 self.scope = util.scopes_to_string(scope) |
166 self._kwargs = kwargs | |
167 self.service_account_id = kwargs.get('service_account_id', None) | |
168 | 164 |
169 # Assertion type is no longer used, but still in the parent class signature. | 165 # Assertion type is no longer used, but still in the parent class signature. |
170 super(AppAssertionCredentials, self).__init__(None) | 166 super(AppAssertionCredentials, self).__init__(None) |
171 | 167 |
172 @classmethod | 168 @classmethod |
173 def from_json(cls, json_data): | 169 def from_json(cls, json): |
174 data = json.loads(json_data) | 170 data = simplejson.loads(json) |
175 return AppAssertionCredentials(data['scope']) | 171 return AppAssertionCredentials(data['scope']) |
176 | 172 |
177 def _refresh(self, http_request): | 173 def _refresh(self, http_request): |
178 """Refreshes the access_token. | 174 """Refreshes the access_token. |
179 | 175 |
180 Since the underlying App Engine app_identity implementation does its own | 176 Since the underlying App Engine app_identity implementation does its own |
181 caching we can skip all the storage hoops and just to a refresh using the | 177 caching we can skip all the storage hoops and just to a refresh using the |
182 API. | 178 API. |
183 | 179 |
184 Args: | 180 Args: |
185 http_request: callable, a callable that matches the method signature of | 181 http_request: callable, a callable that matches the method signature of |
186 httplib2.Http.request, used to make the refresh request. | 182 httplib2.Http.request, used to make the refresh request. |
187 | 183 |
188 Raises: | 184 Raises: |
189 AccessTokenRefreshError: When the refresh fails. | 185 AccessTokenRefreshError: When the refresh fails. |
190 """ | 186 """ |
191 try: | 187 try: |
192 scopes = self.scope.split() | 188 scopes = self.scope.split() |
193 (token, _) = app_identity.get_access_token( | 189 (token, _) = app_identity.get_access_token(scopes) |
194 scopes, service_account_id=self.service_account_id) | 190 except app_identity.Error, e: |
195 except app_identity.Error as e: | |
196 raise AccessTokenRefreshError(str(e)) | 191 raise AccessTokenRefreshError(str(e)) |
197 self.access_token = token | 192 self.access_token = token |
198 | 193 |
199 @property | |
200 def serialization_data(self): | |
201 raise NotImplementedError('Cannot serialize credentials for AppEngine.') | |
202 | |
203 def create_scoped_required(self): | |
204 return not self.scope | |
205 | |
206 def create_scoped(self, scopes): | |
207 return AppAssertionCredentials(scopes, **self._kwargs) | |
208 | |
209 | 194 |
210 class FlowProperty(db.Property): | 195 class FlowProperty(db.Property): |
211 """App Engine datastore Property for Flow. | 196 """App Engine datastore Property for Flow. |
212 | 197 |
213 Utility property that allows easy storage and retrieval of an | 198 Utility property that allows easy storage and retrieval of an |
214 oauth2client.Flow""" | 199 oauth2client.Flow""" |
215 | 200 |
216 # Tell what the user type is. | 201 # Tell what the user type is. |
217 data_type = Flow | 202 data_type = Flow |
218 | 203 |
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
442 | 427 |
443 Attempts to delete using the key_name stored on the object, whether or not | 428 Attempts to delete using the key_name stored on the object, whether or not |
444 the given key is in the datastore. | 429 the given key is in the datastore. |
445 """ | 430 """ |
446 if self._is_ndb(): | 431 if self._is_ndb(): |
447 ndb.Key(self._model, self._key_name).delete() | 432 ndb.Key(self._model, self._key_name).delete() |
448 else: | 433 else: |
449 entity_key = db.Key.from_path(self._model.kind(), self._key_name) | 434 entity_key = db.Key.from_path(self._model.kind(), self._key_name) |
450 db.delete(entity_key) | 435 db.delete(entity_key) |
451 | 436 |
452 @db.non_transactional(allow_existing=True) | |
453 def locked_get(self): | 437 def locked_get(self): |
454 """Retrieve Credential from datastore. | 438 """Retrieve Credential from datastore. |
455 | 439 |
456 Returns: | 440 Returns: |
457 oauth2client.Credentials | 441 oauth2client.Credentials |
458 """ | 442 """ |
459 credentials = None | 443 credentials = None |
460 if self._cache: | 444 if self._cache: |
461 json = self._cache.get(self._key_name) | 445 json = self._cache.get(self._key_name) |
462 if json: | 446 if json: |
463 credentials = Credentials.new_from_json(json) | 447 credentials = Credentials.new_from_json(json) |
464 if credentials is None: | 448 if credentials is None: |
465 entity = self._get_entity() | 449 entity = self._get_entity() |
466 if entity is not None: | 450 if entity is not None: |
467 credentials = getattr(entity, self._property_name) | 451 credentials = getattr(entity, self._property_name) |
468 if self._cache: | 452 if self._cache: |
469 self._cache.set(self._key_name, credentials.to_json()) | 453 self._cache.set(self._key_name, credentials.to_json()) |
470 | 454 |
471 if credentials and hasattr(credentials, 'set_store'): | 455 if credentials and hasattr(credentials, 'set_store'): |
472 credentials.set_store(self) | 456 credentials.set_store(self) |
473 return credentials | 457 return credentials |
474 | 458 |
475 @db.non_transactional(allow_existing=True) | |
476 def locked_put(self, credentials): | 459 def locked_put(self, credentials): |
477 """Write a Credentials to the datastore. | 460 """Write a Credentials to the datastore. |
478 | 461 |
479 Args: | 462 Args: |
480 credentials: Credentials, the credentials to store. | 463 credentials: Credentials, the credentials to store. |
481 """ | 464 """ |
482 entity = self._model.get_or_insert(self._key_name) | 465 entity = self._model.get_or_insert(self._key_name) |
483 setattr(entity, self._property_name, credentials) | 466 setattr(entity, self._property_name, credentials) |
484 entity.put() | 467 entity.put() |
485 if self._cache: | 468 if self._cache: |
486 self._cache.set(self._key_name, credentials.to_json()) | 469 self._cache.set(self._key_name, credentials.to_json()) |
487 | 470 |
488 @db.non_transactional(allow_existing=True) | |
489 def locked_delete(self): | 471 def locked_delete(self): |
490 """Delete Credential from datastore.""" | 472 """Delete Credential from datastore.""" |
491 | 473 |
492 if self._cache: | 474 if self._cache: |
493 self._cache.delete(self._key_name) | 475 self._cache.delete(self._key_name) |
494 | 476 |
495 self._delete_entity() | 477 self._delete_entity() |
496 | 478 |
497 | 479 |
498 class CredentialsModel(db.Model): | 480 class CredentialsModel(db.Model): |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
564 | 546 |
565 return uri | 547 return uri |
566 | 548 |
567 | 549 |
568 class OAuth2Decorator(object): | 550 class OAuth2Decorator(object): |
569 """Utility for making OAuth 2.0 easier. | 551 """Utility for making OAuth 2.0 easier. |
570 | 552 |
571 Instantiate and then use with oauth_required or oauth_aware | 553 Instantiate and then use with oauth_required or oauth_aware |
572 as decorators on webapp.RequestHandler methods. | 554 as decorators on webapp.RequestHandler methods. |
573 | 555 |
574 :: | 556 Example: |
575 | 557 |
576 decorator = OAuth2Decorator( | 558 decorator = OAuth2Decorator( |
577 client_id='837...ent.com', | 559 client_id='837...ent.com', |
578 client_secret='Qh...wwI', | 560 client_secret='Qh...wwI', |
579 scope='https://www.googleapis.com/auth/plus') | 561 scope='https://www.googleapis.com/auth/plus') |
580 | 562 |
| 563 |
581 class MainHandler(webapp.RequestHandler): | 564 class MainHandler(webapp.RequestHandler): |
| 565 |
582 @decorator.oauth_required | 566 @decorator.oauth_required |
583 def get(self): | 567 def get(self): |
584 http = decorator.http() | 568 http = decorator.http() |
585 # http is authorized with the user's Credentials and can be used | 569 # http is authorized with the user's Credentials and can be used |
586 # in API calls | 570 # in API calls |
587 | 571 |
588 """ | 572 """ |
589 | 573 |
590 def set_credentials(self, credentials): | 574 def set_credentials(self, credentials): |
591 self._tls.credentials = credentials | 575 self._tls.credentials = credentials |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
659 _storage_class: "Protected" keyword argument not typically provided to | 643 _storage_class: "Protected" keyword argument not typically provided to |
660 this constructor. A storage class to aid in storing a Credentials object | 644 this constructor. A storage class to aid in storing a Credentials object |
661 for a user in the datastore. Defaults to StorageByKeyName. | 645 for a user in the datastore. Defaults to StorageByKeyName. |
662 _credentials_class: "Protected" keyword argument not typically provided to | 646 _credentials_class: "Protected" keyword argument not typically provided to |
663 this constructor. A db or ndb Model class to hold credentials. Defaults | 647 this constructor. A db or ndb Model class to hold credentials. Defaults |
664 to CredentialsModel. | 648 to CredentialsModel. |
665 _credentials_property_name: "Protected" keyword argument not typically | 649 _credentials_property_name: "Protected" keyword argument not typically |
666 provided to this constructor. A string indicating the name of the field | 650 provided to this constructor. A string indicating the name of the field |
667 on the _credentials_class where a Credentials object will be stored. | 651 on the _credentials_class where a Credentials object will be stored. |
668 Defaults to 'credentials'. | 652 Defaults to 'credentials'. |
669 **kwargs: dict, Keyword arguments are passed along as kwargs to | 653 **kwargs: dict, Keyword arguments are be passed along as kwargs to the |
670 the OAuth2WebServerFlow constructor. | 654 OAuth2WebServerFlow constructor. |
671 | |
672 """ | 655 """ |
673 self._tls = threading.local() | 656 self._tls = threading.local() |
674 self.flow = None | 657 self.flow = None |
675 self.credentials = None | 658 self.credentials = None |
676 self._client_id = client_id | 659 self._client_id = client_id |
677 self._client_secret = client_secret | 660 self._client_secret = client_secret |
678 self._scope = util.scopes_to_string(scope) | 661 self._scope = util.scopes_to_string(scope) |
679 self._auth_uri = auth_uri | 662 self._auth_uri = auth_uri |
680 self._token_uri = token_uri | 663 self._token_uri = token_uri |
681 self._revoke_uri = revoke_uri | 664 self._revoke_uri = revoke_uri |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
808 | 791 |
809 def authorize_url(self): | 792 def authorize_url(self): |
810 """Returns the URL to start the OAuth dance. | 793 """Returns the URL to start the OAuth dance. |
811 | 794 |
812 Must only be called from with a webapp.RequestHandler subclassed method | 795 Must only be called from with a webapp.RequestHandler subclassed method |
813 that had been decorated with either @oauth_required or @oauth_aware. | 796 that had been decorated with either @oauth_required or @oauth_aware. |
814 """ | 797 """ |
815 url = self.flow.step1_get_authorize_url() | 798 url = self.flow.step1_get_authorize_url() |
816 return str(url) | 799 return str(url) |
817 | 800 |
818 def http(self, *args, **kwargs): | 801 def http(self): |
819 """Returns an authorized http instance. | 802 """Returns an authorized http instance. |
820 | 803 |
821 Must only be called from within an @oauth_required decorated method, or | 804 Must only be called from within an @oauth_required decorated method, or |
822 from within an @oauth_aware decorated method where has_credentials() | 805 from within an @oauth_aware decorated method where has_credentials() |
823 returns True. | 806 returns True. |
824 | |
825 Args: | |
826 *args: Positional arguments passed to httplib2.Http constructor. | |
827 **kwargs: Positional arguments passed to httplib2.Http constructor. | |
828 """ | 807 """ |
829 return self.credentials.authorize(httplib2.Http(*args, **kwargs)) | 808 return self.credentials.authorize(httplib2.Http()) |
830 | 809 |
831 @property | 810 @property |
832 def callback_path(self): | 811 def callback_path(self): |
833 """The absolute path where the callback will occur. | 812 """The absolute path where the callback will occur. |
834 | 813 |
835 Note this is the absolute path, not the absolute URI, that will be | 814 Note this is the absolute path, not the absolute URI, that will be |
836 calculated by the decorator at runtime. See callback_handler() for how this | 815 calculated by the decorator at runtime. See callback_handler() for how this |
837 should be used. | 816 should be used. |
838 | 817 |
839 Returns: | 818 Returns: |
840 The callback path as a string. | 819 The callback path as a string. |
841 """ | 820 """ |
842 return self._callback_path | 821 return self._callback_path |
843 | 822 |
844 | 823 |
845 def callback_handler(self): | 824 def callback_handler(self): |
846 """RequestHandler for the OAuth 2.0 redirect callback. | 825 """RequestHandler for the OAuth 2.0 redirect callback. |
847 | 826 |
848 Usage:: | 827 Usage: |
849 | |
850 app = webapp.WSGIApplication([ | 828 app = webapp.WSGIApplication([ |
851 ('/index', MyIndexHandler), | 829 ('/index', MyIndexHandler), |
852 ..., | 830 ..., |
853 (decorator.callback_path, decorator.callback_handler()) | 831 (decorator.callback_path, decorator.callback_handler()) |
854 ]) | 832 ]) |
855 | 833 |
856 Returns: | 834 Returns: |
857 A webapp.RequestHandler that handles the redirect back from the | 835 A webapp.RequestHandler that handles the redirect back from the |
858 server during the OAuth 2.0 dance. | 836 server during the OAuth 2.0 dance. |
859 """ | 837 """ |
(...skipping 13 matching lines...) Expand all Loading... |
873 user = users.get_current_user() | 851 user = users.get_current_user() |
874 decorator._create_flow(self) | 852 decorator._create_flow(self) |
875 credentials = decorator.flow.step2_exchange(self.request.params) | 853 credentials = decorator.flow.step2_exchange(self.request.params) |
876 decorator._storage_class( | 854 decorator._storage_class( |
877 decorator._credentials_class, None, | 855 decorator._credentials_class, None, |
878 decorator._credentials_property_name, user=user).put(credentials) | 856 decorator._credentials_property_name, user=user).put(credentials) |
879 redirect_uri = _parse_state_value(str(self.request.get('state')), | 857 redirect_uri = _parse_state_value(str(self.request.get('state')), |
880 user) | 858 user) |
881 | 859 |
882 if decorator._token_response_param and credentials.token_response: | 860 if decorator._token_response_param and credentials.token_response: |
883 resp_json = json.dumps(credentials.token_response) | 861 resp_json = simplejson.dumps(credentials.token_response) |
884 redirect_uri = util._add_query_parameter( | 862 redirect_uri = util._add_query_parameter( |
885 redirect_uri, decorator._token_response_param, resp_json) | 863 redirect_uri, decorator._token_response_param, resp_json) |
886 | 864 |
887 self.redirect(redirect_uri) | 865 self.redirect(redirect_uri) |
888 | 866 |
889 return OAuth2Handler | 867 return OAuth2Handler |
890 | 868 |
891 def callback_application(self): | 869 def callback_application(self): |
892 """WSGI application for handling the OAuth 2.0 redirect callback. | 870 """WSGI application for handling the OAuth 2.0 redirect callback. |
893 | 871 |
894 If you need finer grained control use `callback_handler` which returns just | 872 If you need finer grained control use `callback_handler` which returns just |
895 the webapp.RequestHandler. | 873 the webapp.RequestHandler. |
896 | 874 |
897 Returns: | 875 Returns: |
898 A webapp.WSGIApplication that handles the redirect back from the | 876 A webapp.WSGIApplication that handles the redirect back from the |
899 server during the OAuth 2.0 dance. | 877 server during the OAuth 2.0 dance. |
900 """ | 878 """ |
901 return webapp.WSGIApplication([ | 879 return webapp.WSGIApplication([ |
902 (self.callback_path, self.callback_handler()) | 880 (self.callback_path, self.callback_handler()) |
903 ]) | 881 ]) |
904 | 882 |
905 | 883 |
906 class OAuth2DecoratorFromClientSecrets(OAuth2Decorator): | 884 class OAuth2DecoratorFromClientSecrets(OAuth2Decorator): |
907 """An OAuth2Decorator that builds from a clientsecrets file. | 885 """An OAuth2Decorator that builds from a clientsecrets file. |
908 | 886 |
909 Uses a clientsecrets file as the source for all the information when | 887 Uses a clientsecrets file as the source for all the information when |
910 constructing an OAuth2Decorator. | 888 constructing an OAuth2Decorator. |
911 | 889 |
912 :: | 890 Example: |
913 | 891 |
914 decorator = OAuth2DecoratorFromClientSecrets( | 892 decorator = OAuth2DecoratorFromClientSecrets( |
915 os.path.join(os.path.dirname(__file__), 'client_secrets.json') | 893 os.path.join(os.path.dirname(__file__), 'client_secrets.json') |
916 scope='https://www.googleapis.com/auth/plus') | 894 scope='https://www.googleapis.com/auth/plus') |
917 | 895 |
| 896 |
918 class MainHandler(webapp.RequestHandler): | 897 class MainHandler(webapp.RequestHandler): |
| 898 |
919 @decorator.oauth_required | 899 @decorator.oauth_required |
920 def get(self): | 900 def get(self): |
921 http = decorator.http() | 901 http = decorator.http() |
922 # http is authorized with the user's Credentials and can be used | 902 # http is authorized with the user's Credentials and can be used |
923 # in API calls | 903 # in API calls |
924 | |
925 """ | 904 """ |
926 | 905 |
927 @util.positional(3) | 906 @util.positional(3) |
928 def __init__(self, filename, scope, message=None, cache=None, **kwargs): | 907 def __init__(self, filename, scope, message=None, cache=None): |
929 """Constructor | 908 """Constructor |
930 | 909 |
931 Args: | 910 Args: |
932 filename: string, File name of client secrets. | 911 filename: string, File name of client secrets. |
933 scope: string or iterable of strings, scope(s) of the credentials being | 912 scope: string or iterable of strings, scope(s) of the credentials being |
934 requested. | 913 requested. |
935 message: string, A friendly string to display to the user if the | 914 message: string, A friendly string to display to the user if the |
936 clientsecrets file is missing or invalid. The message may contain HTML | 915 clientsecrets file is missing or invalid. The message may contain HTML |
937 and will be presented on the web interface for any method that uses the | 916 and will be presented on the web interface for any method that uses the |
938 decorator. | 917 decorator. |
939 cache: An optional cache service client that implements get() and set() | 918 cache: An optional cache service client that implements get() and set() |
940 methods. See clientsecrets.loadfile() for details. | 919 methods. See clientsecrets.loadfile() for details. |
941 **kwargs: dict, Keyword arguments are passed along as kwargs to | |
942 the OAuth2WebServerFlow constructor. | |
943 """ | 920 """ |
944 client_type, client_info = clientsecrets.loadfile(filename, cache=cache) | 921 client_type, client_info = clientsecrets.loadfile(filename, cache=cache) |
945 if client_type not in [ | 922 if client_type not in [ |
946 clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]: | 923 clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]: |
947 raise InvalidClientSecretsError( | 924 raise InvalidClientSecretsError( |
948 "OAuth2Decorator doesn't support this OAuth 2.0 flow.") | 925 'OAuth2Decorator doesn\'t support this OAuth 2.0 flow.') |
949 constructor_kwargs = dict(kwargs) | 926 constructor_kwargs = { |
950 constructor_kwargs.update({ | 927 'auth_uri': client_info['auth_uri'], |
951 'auth_uri': client_info['auth_uri'], | 928 'token_uri': client_info['token_uri'], |
952 'token_uri': client_info['token_uri'], | 929 'message': message, |
953 'message': message, | 930 } |
954 }) | |
955 revoke_uri = client_info.get('revoke_uri') | 931 revoke_uri = client_info.get('revoke_uri') |
956 if revoke_uri is not None: | 932 if revoke_uri is not None: |
957 constructor_kwargs['revoke_uri'] = revoke_uri | 933 constructor_kwargs['revoke_uri'] = revoke_uri |
958 super(OAuth2DecoratorFromClientSecrets, self).__init__( | 934 super(OAuth2DecoratorFromClientSecrets, self).__init__( |
959 client_info['client_id'], client_info['client_secret'], | 935 client_info['client_id'], client_info['client_secret'], |
960 scope, **constructor_kwargs) | 936 scope, **constructor_kwargs) |
961 if message is not None: | 937 if message is not None: |
962 self._message = message | 938 self._message = message |
963 else: | 939 else: |
964 self._message = 'Please configure your application for OAuth 2.0.' | 940 self._message = 'Please configure your application for OAuth 2.0.' |
(...skipping 12 matching lines...) Expand all Loading... |
977 clientsecrets file is missing or invalid. The message may contain HTML and | 953 clientsecrets file is missing or invalid. The message may contain HTML and |
978 will be presented on the web interface for any method that uses the | 954 will be presented on the web interface for any method that uses the |
979 decorator. | 955 decorator. |
980 cache: An optional cache service client that implements get() and set() | 956 cache: An optional cache service client that implements get() and set() |
981 methods. See clientsecrets.loadfile() for details. | 957 methods. See clientsecrets.loadfile() for details. |
982 | 958 |
983 Returns: An OAuth2Decorator | 959 Returns: An OAuth2Decorator |
984 | 960 |
985 """ | 961 """ |
986 return OAuth2DecoratorFromClientSecrets(filename, scope, | 962 return OAuth2DecoratorFromClientSecrets(filename, scope, |
987 message=message, cache=cache) | 963 message=message, cache=cache) |
OLD | NEW |