Package oauth2client :: Module appengine
[hide private]
[frames] | no frames]

Source Code for Module oauth2client.appengine

  1  # Copyright (C) 2010 Google Inc. 
  2  # 
  3  # Licensed under the Apache License, Version 2.0 (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 
  6  # 
  7  #      http://www.apache.org/licenses/LICENSE-2.0 
  8  # 
  9  # Unless required by applicable law or agreed to in writing, software 
 10  # distributed under the License is distributed on an "AS IS" BASIS, 
 11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 12  # See the License for the specific language governing permissions and 
 13  # limitations under the License. 
 14   
 15  """Utilities for Google App Engine 
 16   
 17  Utilities for making it easier to use OAuth 2.0 on Google App Engine. 
 18  """ 
 19   
 20  __author__ = 'jcgregorio@google.com (Joe Gregorio)' 
 21   
 22  import base64 
 23  import cgi 
 24  import httplib2 
 25  import logging 
 26  import os 
 27  import pickle 
 28  import time 
 29   
 30  from google.appengine.api import app_identity 
 31  from google.appengine.api import memcache 
 32  from google.appengine.api import users 
 33  from google.appengine.ext import db 
 34  from google.appengine.ext import ndb 
 35  from google.appengine.ext import webapp 
 36  from google.appengine.ext.webapp.util import login_required 
 37  from google.appengine.ext.webapp.util import run_wsgi_app 
 38  from oauth2client import clientsecrets 
 39  from oauth2client import util 
 40  from oauth2client import xsrfutil 
 41  from oauth2client.anyjson import simplejson 
 42  from oauth2client.client import AccessTokenRefreshError 
 43  from oauth2client.client import AssertionCredentials 
 44  from oauth2client.client import Credentials 
 45  from oauth2client.client import Flow 
 46  from oauth2client.client import OAuth2WebServerFlow 
 47  from oauth2client.client import Storage 
 48   
 49  logger = logging.getLogger(__name__) 
 50   
 51  OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns' 
 52   
 53  XSRF_MEMCACHE_ID = 'xsrf_secret_key' 
54 55 56 -def _safe_html(s):
57 """Escape text to make it safe to display. 58 59 Args: 60 s: string, The text to escape. 61 62 Returns: 63 The escaped text as a string. 64 """ 65 return cgi.escape(s, quote=1).replace("'", ''')
66
67 68 -class InvalidClientSecretsError(Exception):
69 """The client_secrets.json file is malformed or missing required fields."""
70
71 72 -class InvalidXsrfTokenError(Exception):
73 """The XSRF token is invalid or expired."""
74
75 76 -class SiteXsrfSecretKey(db.Model):
77 """Storage for the sites XSRF secret key. 78 79 There will only be one instance stored of this model, the one used for the 80 site. 81 """ 82 secret = db.StringProperty()
83
84 85 -class SiteXsrfSecretKeyNDB(ndb.Model):
86 """NDB Model for storage for the sites XSRF secret key. 87 88 Since this model uses the same kind as SiteXsrfSecretKey, it can be used 89 interchangeably. This simply provides an NDB model for interacting with the 90 same data the DB model interacts with. 91 92 There should only be one instance stored of this model, the one used for the 93 site. 94 """ 95 secret = ndb.StringProperty() 96 97 @classmethod
98 - def _get_kind(cls):
99 """Return the kind name for this class.""" 100 return 'SiteXsrfSecretKey'
101
102 103 -def _generate_new_xsrf_secret_key():
104 """Returns a random XSRF secret key. 105 """ 106 return os.urandom(16).encode("hex")
107
108 109 -def xsrf_secret_key():
110 """Return the secret key for use for XSRF protection. 111 112 If the Site entity does not have a secret key, this method will also create 113 one and persist it. 114 115 Returns: 116 The secret key. 117 """ 118 secret = memcache.get(XSRF_MEMCACHE_ID, namespace=OAUTH2CLIENT_NAMESPACE) 119 if not secret: 120 # Load the one and only instance of SiteXsrfSecretKey. 121 model = SiteXsrfSecretKey.get_or_insert(key_name='site') 122 if not model.secret: 123 model.secret = _generate_new_xsrf_secret_key() 124 model.put() 125 secret = model.secret 126 memcache.add(XSRF_MEMCACHE_ID, secret, namespace=OAUTH2CLIENT_NAMESPACE) 127 128 return str(secret)
129
130 131 -class AppAssertionCredentials(AssertionCredentials):
132 """Credentials object for App Engine Assertion Grants 133 134 This object will allow an App Engine application to identify itself to Google 135 and other OAuth 2.0 servers that can verify assertions. It can be used for the 136 purpose of accessing data stored under an account assigned to the App Engine 137 application itself. 138 139 This credential does not require a flow to instantiate because it represents 140 a two legged flow, and therefore has all of the required information to 141 generate and refresh its own access tokens. 142 """ 143 144 @util.positional(2)
145 - def __init__(self, scope, **kwargs):
146 """Constructor for AppAssertionCredentials 147 148 Args: 149 scope: string or iterable of strings, scope(s) of the credentials being 150 requested. 151 """ 152 self.scope = util.scopes_to_string(scope) 153 154 super(AppAssertionCredentials, self).__init__( 155 'ignored' # assertion_type is ignore in this subclass. 156 )
157 158 @classmethod
159 - def from_json(cls, json):
160 data = simplejson.loads(json) 161 return AppAssertionCredentials(data['scope'])
162
163 - def _refresh(self, http_request):
164 """Refreshes the access_token. 165 166 Since the underlying App Engine app_identity implementation does its own 167 caching we can skip all the storage hoops and just to a refresh using the 168 API. 169 170 Args: 171 http_request: callable, a callable that matches the method signature of 172 httplib2.Http.request, used to make the refresh request. 173 174 Raises: 175 AccessTokenRefreshError: When the refresh fails. 176 """ 177 try: 178 scopes = self.scope.split() 179 (token, _) = app_identity.get_access_token(scopes) 180 except app_identity.Error, e: 181 raise AccessTokenRefreshError(str(e)) 182 self.access_token = token
183
184 185 -class FlowProperty(db.Property):
186 """App Engine datastore Property for Flow. 187 188 Utility property that allows easy storage and retrieval of an 189 oauth2client.Flow""" 190 191 # Tell what the user type is. 192 data_type = Flow 193 194 # For writing to datastore.
195 - def get_value_for_datastore(self, model_instance):
196 flow = super(FlowProperty, 197 self).get_value_for_datastore(model_instance) 198 return db.Blob(pickle.dumps(flow))
199 200 # For reading from datastore.
201 - def make_value_from_datastore(self, value):
202 if value is None: 203 return None 204 return pickle.loads(value)
205
206 - def validate(self, value):
207 if value is not None and not isinstance(value, Flow): 208 raise db.BadValueError('Property %s must be convertible ' 209 'to a FlowThreeLegged instance (%s)' % 210 (self.name, value)) 211 return super(FlowProperty, self).validate(value)
212
213 - def empty(self, value):
214 return not value
215
216 217 -class FlowNDBProperty(ndb.PickleProperty):
218 """App Engine NDB datastore Property for Flow. 219 220 Serves the same purpose as the DB FlowProperty, but for NDB models. Since 221 PickleProperty inherits from BlobProperty, the underlying representation of 222 the data in the datastore will be the same as in the DB case. 223 224 Utility property that allows easy storage and retrieval of an 225 oauth2client.Flow 226 """ 227
228 - def _validate(self, value):
229 """Validates a value as a proper Flow object. 230 231 Args: 232 value: A value to be set on the property. 233 234 Raises: 235 TypeError if the value is not an instance of Flow. 236 """ 237 logger.info('validate: Got type %s', type(value)) 238 if value is not None and not isinstance(value, Flow): 239 raise TypeError('Property %s must be convertible to a flow ' 240 'instance; received: %s.' % (self._name, value))
241
242 243 -class CredentialsProperty(db.Property):
244 """App Engine datastore Property for Credentials. 245 246 Utility property that allows easy storage and retrieval of 247 oath2client.Credentials 248 """ 249 250 # Tell what the user type is. 251 data_type = Credentials 252 253 # For writing to datastore.
254 - def get_value_for_datastore(self, model_instance):
255 logger.info("get: Got type " + str(type(model_instance))) 256 cred = super(CredentialsProperty, 257 self).get_value_for_datastore(model_instance) 258 if cred is None: 259 cred = '' 260 else: 261 cred = cred.to_json() 262 return db.Blob(cred)
263 264 # For reading from datastore.
265 - def make_value_from_datastore(self, value):
266 logger.info("make: Got type " + str(type(value))) 267 if value is None: 268 return None 269 if len(value) == 0: 270 return None 271 try: 272 credentials = Credentials.new_from_json(value) 273 except ValueError: 274 credentials = None 275 return credentials
276
277 - def validate(self, value):
278 value = super(CredentialsProperty, self).validate(value) 279 logger.info("validate: Got type " + str(type(value))) 280 if value is not None and not isinstance(value, Credentials): 281 raise db.BadValueError('Property %s must be convertible ' 282 'to a Credentials instance (%s)' % 283 (self.name, value)) 284 #if value is not None and not isinstance(value, Credentials): 285 # return None 286 return value
287
288 289 # TODO(dhermes): Turn this into a JsonProperty and overhaul the Credentials 290 # and subclass mechanics to use new_from_dict, to_dict, 291 # from_dict, etc. 292 -class CredentialsNDBProperty(ndb.BlobProperty):
293 """App Engine NDB datastore Property for Credentials. 294 295 Serves the same purpose as the DB CredentialsProperty, but for NDB models. 296 Since CredentialsProperty stores data as a blob and this inherits from 297 BlobProperty, the data in the datastore will be the same as in the DB case. 298 299 Utility property that allows easy storage and retrieval of Credentials and 300 subclasses. 301 """
302 - def _validate(self, value):
303 """Validates a value as a proper credentials object. 304 305 Args: 306 value: A value to be set on the property. 307 308 Raises: 309 TypeError if the value is not an instance of Credentials. 310 """ 311 logger.info('validate: Got type %s', type(value)) 312 if value is not None and not isinstance(value, Credentials): 313 raise TypeError('Property %s must be convertible to a credentials ' 314 'instance; received: %s.' % (self._name, value))
315
316 - def _to_base_type(self, value):
317 """Converts our validated value to a JSON serialized string. 318 319 Args: 320 value: A value to be set in the datastore. 321 322 Returns: 323 A JSON serialized version of the credential, else '' if value is None. 324 """ 325 if value is None: 326 return '' 327 else: 328 return value.to_json()
329
330 - def _from_base_type(self, value):
331 """Converts our stored JSON string back to the desired type. 332 333 Args: 334 value: A value from the datastore to be converted to the desired type. 335 336 Returns: 337 A deserialized Credentials (or subclass) object, else None if the 338 value can't be parsed. 339 """ 340 if not value: 341 return None 342 try: 343 # Uses the from_json method of the implied class of value 344 credentials = Credentials.new_from_json(value) 345 except ValueError: 346 credentials = None 347 return credentials
348
349 350 -class StorageByKeyName(Storage):
351 """Store and retrieve a credential to and from the App Engine datastore. 352 353 This Storage helper presumes the Credentials have been stored as a 354 CredentialsProperty or CredentialsNDBProperty on a datastore model class, and 355 that entities are stored by key_name. 356 """ 357 358 @util.positional(4)
359 - def __init__(self, model, key_name, property_name, cache=None):
360 """Constructor for Storage. 361 362 Args: 363 model: db.Model or ndb.Model, model class 364 key_name: string, key name for the entity that has the credentials 365 property_name: string, name of the property that is a CredentialsProperty 366 or CredentialsNDBProperty. 367 cache: memcache, a write-through cache to put in front of the datastore. 368 If the model you are using is an NDB model, using a cache will be 369 redundant since the model uses an instance cache and memcache for you. 370 """ 371 self._model = model 372 self._key_name = key_name 373 self._property_name = property_name 374 self._cache = cache
375
376 - def _is_ndb(self):
377 """Determine whether the model of the instance is an NDB model. 378 379 Returns: 380 Boolean indicating whether or not the model is an NDB or DB model. 381 """ 382 # issubclass will fail if one of the arguments is not a class, only need 383 # worry about new-style classes since ndb and db models are new-style 384 if isinstance(self._model, type): 385 if issubclass(self._model, ndb.Model): 386 return True 387 elif issubclass(self._model, db.Model): 388 return False 389 390 raise TypeError('Model class not an NDB or DB model: %s.' % (self._model,))
391
392 - def _get_entity(self):
393 """Retrieve entity from datastore. 394 395 Uses a different model method for db or ndb models. 396 397 Returns: 398 Instance of the model corresponding to the current storage object 399 and stored using the key name of the storage object. 400 """ 401 if self._is_ndb(): 402 return self._model.get_by_id(self._key_name) 403 else: 404 return self._model.get_by_key_name(self._key_name)
405
406 - def _delete_entity(self):
407 """Delete entity from datastore. 408 409 Attempts to delete using the key_name stored on the object, whether or not 410 the given key is in the datastore. 411 """ 412 if self._is_ndb(): 413 ndb.Key(self._model, self._key_name).delete() 414 else: 415 entity_key = db.Key.from_path(self._model.kind(), self._key_name) 416 db.delete(entity_key)
417
418 - def locked_get(self):
419 """Retrieve Credential from datastore. 420 421 Returns: 422 oauth2client.Credentials 423 """ 424 if self._cache: 425 json = self._cache.get(self._key_name) 426 if json: 427 return Credentials.new_from_json(json) 428 429 credentials = None 430 entity = self._get_entity() 431 if entity is not None: 432 credentials = getattr(entity, self._property_name) 433 if credentials and hasattr(credentials, 'set_store'): 434 credentials.set_store(self) 435 if self._cache: 436 self._cache.set(self._key_name, credentials.to_json()) 437 438 return credentials
439
440 - def locked_put(self, credentials):
441 """Write a Credentials to the datastore. 442 443 Args: 444 credentials: Credentials, the credentials to store. 445 """ 446 entity = self._model.get_or_insert(self._key_name) 447 setattr(entity, self._property_name, credentials) 448 entity.put() 449 if self._cache: 450 self._cache.set(self._key_name, credentials.to_json())
451
452 - def locked_delete(self):
453 """Delete Credential from datastore.""" 454 455 if self._cache: 456 self._cache.delete(self._key_name) 457 458 self._delete_entity()
459
460 461 -class CredentialsModel(db.Model):
462 """Storage for OAuth 2.0 Credentials 463 464 Storage of the model is keyed by the user.user_id(). 465 """ 466 credentials = CredentialsProperty()
467
468 469 -class CredentialsNDBModel(ndb.Model):
470 """NDB Model for storage of OAuth 2.0 Credentials 471 472 Since this model uses the same kind as CredentialsModel and has a property 473 which can serialize and deserialize Credentials correctly, it can be used 474 interchangeably with a CredentialsModel to access, insert and delete the same 475 entities. This simply provides an NDB model for interacting with the 476 same data the DB model interacts with. 477 478 Storage of the model is keyed by the user.user_id(). 479 """ 480 credentials = CredentialsNDBProperty() 481 482 @classmethod
483 - def _get_kind(cls):
484 """Return the kind name for this class.""" 485 return 'CredentialsModel'
486
487 488 -def _build_state_value(request_handler, user):
489 """Composes the value for the 'state' parameter. 490 491 Packs the current request URI and an XSRF token into an opaque string that 492 can be passed to the authentication server via the 'state' parameter. 493 494 Args: 495 request_handler: webapp.RequestHandler, The request. 496 user: google.appengine.api.users.User, The current user. 497 498 Returns: 499 The state value as a string. 500 """ 501 uri = request_handler.request.url 502 token = xsrfutil.generate_token(xsrf_secret_key(), user.user_id(), 503 action_id=str(uri)) 504 return uri + ':' + token
505
506 507 -def _parse_state_value(state, user):
508 """Parse the value of the 'state' parameter. 509 510 Parses the value and validates the XSRF token in the state parameter. 511 512 Args: 513 state: string, The value of the state parameter. 514 user: google.appengine.api.users.User, The current user. 515 516 Raises: 517 InvalidXsrfTokenError: if the XSRF token is invalid. 518 519 Returns: 520 The redirect URI. 521 """ 522 uri, token = state.rsplit(':', 1) 523 if not xsrfutil.validate_token(xsrf_secret_key(), token, user.user_id(), 524 action_id=uri): 525 raise InvalidXsrfTokenError() 526 527 return uri
528
529 530 -class OAuth2Decorator(object):
531 """Utility for making OAuth 2.0 easier. 532 533 Instantiate and then use with oauth_required or oauth_aware 534 as decorators on webapp.RequestHandler methods. 535 536 Example: 537 538 decorator = OAuth2Decorator( 539 client_id='837...ent.com', 540 client_secret='Qh...wwI', 541 scope='https://www.googleapis.com/auth/plus') 542 543 544 class MainHandler(webapp.RequestHandler): 545 546 @decorator.oauth_required 547 def get(self): 548 http = decorator.http() 549 # http is authorized with the user's Credentials and can be used 550 # in API calls 551 552 """ 553 554 @util.positional(4)
555 - def __init__(self, client_id, client_secret, scope, 556 auth_uri='https://accounts.google.com/o/oauth2/auth', 557 token_uri='https://accounts.google.com/o/oauth2/token', 558 user_agent=None, 559 message=None, 560 callback_path='/oauth2callback', 561 **kwargs):
562 563 """Constructor for OAuth2Decorator 564 565 Args: 566 client_id: string, client identifier. 567 client_secret: string client secret. 568 scope: string or iterable of strings, scope(s) of the credentials being 569 requested. 570 auth_uri: string, URI for authorization endpoint. For convenience 571 defaults to Google's endpoints but any OAuth 2.0 provider can be used. 572 token_uri: string, URI for token endpoint. For convenience 573 defaults to Google's endpoints but any OAuth 2.0 provider can be used. 574 user_agent: string, User agent of your application, default to None. 575 message: Message to display if there are problems with the OAuth 2.0 576 configuration. The message may contain HTML and will be presented on the 577 web interface for any method that uses the decorator. 578 callback_path: string, The absolute path to use as the callback URI. Note 579 that this must match up with the URI given when registering the 580 application in the APIs Console. 581 **kwargs: dict, Keyword arguments are be passed along as kwargs to the 582 OAuth2WebServerFlow constructor. 583 """ 584 self.flow = None 585 self.credentials = None 586 self._client_id = client_id 587 self._client_secret = client_secret 588 self._scope = util.scopes_to_string(scope) 589 self._auth_uri = auth_uri 590 self._token_uri = token_uri 591 self._user_agent = user_agent 592 self._kwargs = kwargs 593 self._message = message 594 self._in_error = False 595 self._callback_path = callback_path
596
597 - def _display_error_message(self, request_handler):
598 request_handler.response.out.write('<html><body>') 599 request_handler.response.out.write(_safe_html(self._message)) 600 request_handler.response.out.write('</body></html>')
601
602 - def oauth_required(self, method):
603 """Decorator that starts the OAuth 2.0 dance. 604 605 Starts the OAuth dance for the logged in user if they haven't already 606 granted access for this application. 607 608 Args: 609 method: callable, to be decorated method of a webapp.RequestHandler 610 instance. 611 """ 612 613 def check_oauth(request_handler, *args, **kwargs): 614 if self._in_error: 615 self._display_error_message(request_handler) 616 return 617 618 user = users.get_current_user() 619 # Don't use @login_decorator as this could be used in a POST request. 620 if not user: 621 request_handler.redirect(users.create_login_url( 622 request_handler.request.uri)) 623 return 624 625 self._create_flow(request_handler) 626 627 # Store the request URI in 'state' so we can use it later 628 self.flow.params['state'] = _build_state_value(request_handler, user) 629 self.credentials = StorageByKeyName( 630 CredentialsModel, user.user_id(), 'credentials').get() 631 632 if not self.has_credentials(): 633 return request_handler.redirect(self.authorize_url()) 634 try: 635 return method(request_handler, *args, **kwargs) 636 except AccessTokenRefreshError: 637 return request_handler.redirect(self.authorize_url())
638 639 return check_oauth
640
641 - def _create_flow(self, request_handler):
642 """Create the Flow object. 643 644 The Flow is calculated lazily since we don't know where this app is 645 running until it receives a request, at which point redirect_uri can be 646 calculated and then the Flow object can be constructed. 647 648 Args: 649 request_handler: webapp.RequestHandler, the request handler. 650 """ 651 if self.flow is None: 652 redirect_uri = request_handler.request.relative_url( 653 self._callback_path) # Usually /oauth2callback 654 self.flow = OAuth2WebServerFlow(self._client_id, self._client_secret, 655 self._scope, redirect_uri=redirect_uri, 656 user_agent=self._user_agent, 657 auth_uri=self._auth_uri, 658 token_uri=self._token_uri, **self._kwargs)
659 660
661 - def oauth_aware(self, method):
662 """Decorator that sets up for OAuth 2.0 dance, but doesn't do it. 663 664 Does all the setup for the OAuth dance, but doesn't initiate it. 665 This decorator is useful if you want to create a page that knows 666 whether or not the user has granted access to this application. 667 From within a method decorated with @oauth_aware the has_credentials() 668 and authorize_url() methods can be called. 669 670 Args: 671 method: callable, to be decorated method of a webapp.RequestHandler 672 instance. 673 """ 674 675 def setup_oauth(request_handler, *args, **kwargs): 676 if self._in_error: 677 self._display_error_message(request_handler) 678 return 679 680 user = users.get_current_user() 681 # Don't use @login_decorator as this could be used in a POST request. 682 if not user: 683 request_handler.redirect(users.create_login_url( 684 request_handler.request.uri)) 685 return 686 687 self._create_flow(request_handler) 688 689 self.flow.params['state'] = _build_state_value(request_handler, user) 690 self.credentials = StorageByKeyName( 691 CredentialsModel, user.user_id(), 'credentials').get() 692 return method(request_handler, *args, **kwargs)
693 return setup_oauth 694
695 - def has_credentials(self):
696 """True if for the logged in user there are valid access Credentials. 697 698 Must only be called from with a webapp.RequestHandler subclassed method 699 that had been decorated with either @oauth_required or @oauth_aware. 700 """ 701 return self.credentials is not None and not self.credentials.invalid
702
703 - def authorize_url(self):
704 """Returns the URL to start the OAuth dance. 705 706 Must only be called from with a webapp.RequestHandler subclassed method 707 that had been decorated with either @oauth_required or @oauth_aware. 708 """ 709 url = self.flow.step1_get_authorize_url() 710 return str(url)
711
712 - def http(self):
713 """Returns an authorized http instance. 714 715 Must only be called from within an @oauth_required decorated method, or 716 from within an @oauth_aware decorated method where has_credentials() 717 returns True. 718 """ 719 return self.credentials.authorize(httplib2.Http())
720 721 @property
722 - def callback_path(self):
723 """The absolute path where the callback will occur. 724 725 Note this is the absolute path, not the absolute URI, that will be 726 calculated by the decorator at runtime. See callback_handler() for how this 727 should be used. 728 729 Returns: 730 The callback path as a string. 731 """ 732 return self._callback_path
733 734
735 - def callback_handler(self):
736 """RequestHandler for the OAuth 2.0 redirect callback. 737 738 Usage: 739 app = webapp.WSGIApplication([ 740 ('/index', MyIndexHandler), 741 ..., 742 (decorator.callback_path, decorator.callback_handler()) 743 ]) 744 745 Returns: 746 A webapp.RequestHandler that handles the redirect back from the 747 server during the OAuth 2.0 dance. 748 """ 749 decorator = self 750 751 class OAuth2Handler(webapp.RequestHandler): 752 """Handler for the redirect_uri of the OAuth 2.0 dance.""" 753 754 @login_required 755 def get(self): 756 error = self.request.get('error') 757 if error: 758 errormsg = self.request.get('error_description', error) 759 self.response.out.write( 760 'The authorization request failed: %s' % _safe_html(errormsg)) 761 else: 762 user = users.get_current_user() 763 decorator._create_flow(self) 764 credentials = decorator.flow.step2_exchange(self.request.params) 765 StorageByKeyName( 766 CredentialsModel, user.user_id(), 'credentials').put(credentials) 767 redirect_uri = _parse_state_value(str(self.request.get('state')), 768 user) 769 self.redirect(redirect_uri)
770 771 return OAuth2Handler 772
773 - def callback_application(self):
774 """WSGI application for handling the OAuth 2.0 redirect callback. 775 776 If you need finer grained control use `callback_handler` which returns just 777 the webapp.RequestHandler. 778 779 Returns: 780 A webapp.WSGIApplication that handles the redirect back from the 781 server during the OAuth 2.0 dance. 782 """ 783 return webapp.WSGIApplication([ 784 (self.callback_path, self.callback_handler()) 785 ])
786
787 788 -class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
789 """An OAuth2Decorator that builds from a clientsecrets file. 790 791 Uses a clientsecrets file as the source for all the information when 792 constructing an OAuth2Decorator. 793 794 Example: 795 796 decorator = OAuth2DecoratorFromClientSecrets( 797 os.path.join(os.path.dirname(__file__), 'client_secrets.json') 798 scope='https://www.googleapis.com/auth/plus') 799 800 801 class MainHandler(webapp.RequestHandler): 802 803 @decorator.oauth_required 804 def get(self): 805 http = decorator.http() 806 # http is authorized with the user's Credentials and can be used 807 # in API calls 808 """ 809 810 @util.positional(3)
811 - def __init__(self, filename, scope, message=None, cache=None):
812 """Constructor 813 814 Args: 815 filename: string, File name of client secrets. 816 scope: string or iterable of strings, scope(s) of the credentials being 817 requested. 818 message: string, A friendly string to display to the user if the 819 clientsecrets file is missing or invalid. The message may contain HTML 820 and will be presented on the web interface for any method that uses the 821 decorator. 822 cache: An optional cache service client that implements get() and set() 823 methods. See clientsecrets.loadfile() for details. 824 """ 825 client_type, client_info = clientsecrets.loadfile(filename, cache=cache) 826 if client_type not in [ 827 clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]: 828 raise InvalidClientSecretsError( 829 'OAuth2Decorator doesn\'t support this OAuth 2.0 flow.') 830 super(OAuth2DecoratorFromClientSecrets, self).__init__( 831 client_info['client_id'], 832 client_info['client_secret'], 833 scope, 834 auth_uri=client_info['auth_uri'], 835 token_uri=client_info['token_uri'], 836 message=message) 837 if message is not None: 838 self._message = message 839 else: 840 self._message = "Please configure your application for OAuth 2.0"
841
842 843 @util.positional(2) 844 -def oauth2decorator_from_clientsecrets(filename, scope, 845 message=None, cache=None):
846 """Creates an OAuth2Decorator populated from a clientsecrets file. 847 848 Args: 849 filename: string, File name of client secrets. 850 scope: string or list of strings, scope(s) of the credentials being 851 requested. 852 message: string, A friendly string to display to the user if the 853 clientsecrets file is missing or invalid. The message may contain HTML and 854 will be presented on the web interface for any method that uses the 855 decorator. 856 cache: An optional cache service client that implements get() and set() 857 methods. See clientsecrets.loadfile() for details. 858 859 Returns: An OAuth2Decorator 860 861 """ 862 return OAuth2DecoratorFromClientSecrets(filename, scope, 863 message=message, cache=cache)
864