Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(230)

Side by Side Diff: third_party/google-endpoints/oauth2client/appengine.py

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

Powered by Google App Engine
This is Rietveld 408576698