scope=['profile', 'email'],
response_type='code',
extra_params={'approval_prompt': 'auto'})
+
+.. versionchanged:: 3.3
+ All of the callback interfaces in this module are now guaranteed
+ to run their callback with an argument of ``None`` on error.
+ Previously some functions would do this while others would simply
+ terminate the request on their own. This change also ensures that
+ errors are more consistently reported through the ``Future`` interfaces.
"""
from __future__ import absolute_import, division, print_function, with_statement
from tornado import escape
from tornado.httputil import url_concat
from tornado.log import gen_log
+from tornado.stack_context import ExceptionStackContext
from tornado.util import bytes_type, u, unicode_type, ArgReplacer
try:
if callback is not None:
future.add_done_callback(
functools.partial(_auth_future_to_callback, callback))
- f(*args, **kwargs)
+ def handle_exception(typ, value, tb):
+ if future.done():
+ return False
+ else:
+ future.set_exc_info((typ, value, tb))
+ return True
+ with ExceptionStackContext(handle_exception):
+ f(*args, **kwargs)
return future
return wrapper
url = self._OPENID_ENDPOINT
if http_client is None:
http_client = self.get_auth_http_client()
- http_client.fetch(url, self.async_callback(
+ http_client.fetch(url, functools.partial(
self._on_authentication_verified, callback),
method="POST", body=urllib_parse.urlencode(args))
http_client.fetch(
self._oauth_request_token_url(callback_uri=callback_uri,
extra_params=extra_params),
- self.async_callback(
+ functools.partial(
self._on_request_token,
self._OAUTH_AUTHORIZE_URL,
callback_uri,
else:
http_client.fetch(
self._oauth_request_token_url(),
- self.async_callback(
+ functools.partial(
self._on_request_token, self._OAUTH_AUTHORIZE_URL,
callback_uri,
callback))
if http_client is None:
http_client = self.get_auth_http_client()
http_client.fetch(self._oauth_access_token_url(token),
- self.async_callback(self._on_access_token, callback))
+ functools.partial(self._on_access_token, callback))
def _oauth_request_token_url(self, callback_uri=None, extra_params=None):
consumer_token = self._oauth_consumer_token()
access_token = _oauth_parse_response(response.body)
self._oauth_get_user_future(access_token).add_done_callback(
- self.async_callback(self._on_oauth_get_user, access_token, future))
+ functools.partial(self._on_oauth_get_user, access_token, future))
def _oauth_consumer_token(self):
"""Subclasses must override this to return their OAuth consumer keys.
"""
http = self.get_auth_http_client()
http.fetch(self._oauth_request_token_url(callback_uri=callback_uri),
- self.async_callback(
+ functools.partial(
self._on_request_token, self._OAUTH_AUTHENTICATE_URL,
None, callback))
if args:
url += "?" + urllib_parse.urlencode(args)
http = self.get_auth_http_client()
- http_callback = self.async_callback(self._on_twitter_request, callback)
+ http_callback = functools.partial(self._on_twitter_request, callback)
if post_args is not None:
http.fetch(url, method="POST", body=urllib_parse.urlencode(post_args),
callback=http_callback)
args.update(oauth)
if args:
url += "?" + urllib_parse.urlencode(args)
- callback = self.async_callback(self._on_friendfeed_request, callback)
+ callback = functools.partial(self._on_friendfeed_request, callback)
http = self.get_auth_http_client()
if post_args is not None:
http.fetch(url, method="POST", body=urllib_parse.urlencode(post_args),
http = self.get_auth_http_client()
token = dict(key=token, secret="")
http.fetch(self._oauth_access_token_url(token),
- self.async_callback(self._on_access_token, callback))
+ functools.partial(self._on_access_token, callback))
else:
chain_future(OpenIdMixin.get_authenticated_user(self),
callback)
})
http.fetch(self._OAUTH_ACCESS_TOKEN_URL,
- self.async_callback(self._on_access_token, callback),
+ functools.partial(self._on_access_token, callback),
method="POST", headers={'Content-Type': 'application/x-www-form-urlencoded'}, body=body)
def _on_access_token(self, future, response):
@tornado.web.asynchronous
def get(self):
if self.get_argument("session", None):
- self.get_authenticated_user(self.async_callback(self._on_auth))
+ self.get_authenticated_user(self._on_auth)
return
yield self.authenticate_redirect()
session = escape.json_decode(self.get_argument("session"))
self.facebook_request(
method="facebook.users.getInfo",
- callback=self.async_callback(
+ callback=functools.partial(
self._on_get_user_info, callback, session),
session_key=session["session_key"],
uids=session["uid"],
def get(self):
self.facebook_request(
method="stream.get",
- callback=self.async_callback(self._on_stream),
+ callback=self._on_stream,
session_key=self.current_user["session_key"])
def _on_stream(self, stream):
url = "http://api.facebook.com/restserver.php?" + \
urllib_parse.urlencode(args)
http = self.get_auth_http_client()
- http.fetch(url, callback=self.async_callback(
+ http.fetch(url, callback=functools.partial(
self._parse_response, callback))
def _on_get_user_info(self, callback, session, users):
fields.update(extra_fields)
http.fetch(self._oauth_request_token_url(**args),
- self.async_callback(self._on_access_token, redirect_uri, client_id,
+ functools.partial(self._on_access_token, redirect_uri, client_id,
client_secret, callback, fields))
def _on_access_token(self, redirect_uri, client_id, client_secret,
self.facebook_request(
path="/me",
- callback=self.async_callback(
+ callback=functools.partial(
self._on_get_user_info, future, session, fields),
access_token=session["access_token"],
fields=",".join(fields)
if all_args:
url += "?" + urllib_parse.urlencode(all_args)
- callback = self.async_callback(self._on_facebook_request, callback)
+ callback = functools.partial(self._on_facebook_request, callback)
http = self.get_auth_http_client()
if post_args is not None:
http.fetch(url, method="POST", body=urllib_parse.urlencode(post_args),
self.finish(user)
def _oauth_get_user(self, access_token, callback):
+ if self.get_argument('fail_in_get_user', None):
+ raise Exception("failing in get_user")
if access_token != dict(key='uiop', secret='5678'):
raise Exception("incorrect access token %r" % access_token)
callback(dict(email='foo@example.com'))
+class OAuth1ClientLoginCoroutineHandler(OAuth1ClientLoginHandler):
+ """Replaces OAuth1ClientLoginCoroutineHandler's get() with a coroutine."""
+ @gen.coroutine
+ def get(self):
+ if self.get_argument('oauth_token', None):
+ # Ensure that any exceptions are set on the returned Future,
+ # not simply thrown into the surrounding StackContext.
+ try:
+ yield self.get_authenticated_user()
+ except Exception as e:
+ self.set_status(503)
+ self.write("got exception: %s" % e)
+ else:
+ yield self.authorize_redirect()
+
+
class OAuth1ClientRequestParametersHandler(RequestHandler, OAuthMixin):
def initialize(self, version):
self._OAUTH_VERSION = version
dict(version='1.0')),
('/oauth10a/client/login', OAuth1ClientLoginHandler,
dict(test=self, version='1.0a')),
+ ('/oauth10a/client/login_coroutine',
+ OAuth1ClientLoginCoroutineHandler,
+ dict(test=self, version='1.0a')),
('/oauth10a/client/request_params',
OAuth1ClientRequestParametersHandler,
dict(version='1.0a')),
self.assertTrue('oauth_nonce' in parsed)
self.assertTrue('oauth_signature' in parsed)
+ def test_oauth10a_get_user_coroutine_exception(self):
+ response = self.fetch(
+ '/oauth10a/client/login_coroutine?oauth_token=zxcv&fail_in_get_user=true',
+ headers={'Cookie': '_oauth_request_token=enhjdg==|MTIzNA=='})
+ self.assertEqual(response.code, 503)
+
def test_oauth2_redirect(self):
response = self.fetch('/oauth2/client/login', follow_redirects=False)
self.assertEqual(response.code, 302)