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