.. testoutput::
:hide:
-
-.. versionchanged:: 4.0
- 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
import base64
import binascii
-import functools
import hashlib
import hmac
import time
+import urllib.parse
import uuid
-import warnings
-from tornado.concurrent import (Future, _non_deprecated_return_future,
- future_set_exc_info, chain_future,
- future_set_result_unless_cancelled)
from tornado import gen
from tornado import httpclient
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 unicode_type, ArgReplacer, PY3
-
-if PY3:
- import urllib.parse as urlparse
- import urllib.parse as urllib_parse
- long = int
-else:
- import urlparse
- import urllib as urllib_parse
+from tornado.util import unicode_type
class AuthError(Exception):
pass
-def _auth_future_to_callback(callback, future):
- try:
- result = future.result()
- except AuthError as e:
- gen_log.warning(str(e))
- result = None
- callback(result)
-
-
-def _auth_return_future(f):
- """Similar to tornado.concurrent.return_future, but uses the auth
- module's legacy callback interface.
-
- Note that when using this decorator the ``callback`` parameter
- inside the function will actually be a future.
-
- .. deprecated:: 5.1
- Will be removed in 6.0.
- """
- replacer = ArgReplacer(f, 'callback')
-
- @functools.wraps(f)
- def wrapper(*args, **kwargs):
- future = Future()
- callback, args, kwargs = replacer.replace(future, args, kwargs)
- if callback is not None:
- warnings.warn("callback arguments are deprecated, use the returned Future instead",
- DeprecationWarning)
- future.add_done_callback(
- functools.partial(_auth_future_to_callback, callback))
-
- def handle_exception(typ, value, tb):
- if future.done():
- return False
- else:
- future_set_exc_info(future, (typ, value, tb))
- return True
- with ExceptionStackContext(handle_exception, delay_warning=True):
- f(*args, **kwargs)
- return future
- return wrapper
-
-
class OpenIdMixin(object):
"""Abstract implementation of OpenID and Attribute Exchange.
* ``_OPENID_ENDPOINT``: the identity provider's URI.
"""
- @_non_deprecated_return_future
def authenticate_redirect(self, callback_uri=None,
- ax_attrs=["name", "email", "language", "username"],
- callback=None):
+ ax_attrs=["name", "email", "language", "username"]):
"""Redirects to the authentication URL for this service.
After authentication, the service will redirect back to the given
all those attributes for your app, you can request fewer with
the ax_attrs keyword argument.
- .. versionchanged:: 3.1
- Returns a `.Future` and takes an optional callback. These are
- not strictly necessary as this method is synchronous,
- but they are supplied for consistency with
- `OAuthMixin.authorize_redirect`.
-
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument and returned awaitable will be removed
- in Tornado 6.0; this will be an ordinary synchronous function.
+ The ``callback`` argument was removed and this method no
+ longer returns an awaitable object. It is now an ordinary
+ synchronous function.
"""
callback_uri = callback_uri or self.request.uri
args = self._openid_args(callback_uri, ax_attrs=ax_attrs)
- self.redirect(self._OPENID_ENDPOINT + "?" + urllib_parse.urlencode(args))
- callback()
+ self.redirect(self._OPENID_ENDPOINT + "?" + urllib.parse.urlencode(args))
- @_auth_return_future
- def get_authenticated_user(self, callback, http_client=None):
+ async def get_authenticated_user(self, http_client=None):
"""Fetches the authenticated user data upon redirect.
This method should be called by the handler that receives the
The result of this method will generally be used to set a cookie.
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned
+ awaitable object instead.
"""
# Verify the OpenID response via direct request to the OP
args = dict((k, v[-1]) for k, v in self.request.arguments.items())
url = self._OPENID_ENDPOINT
if http_client is None:
http_client = self.get_auth_http_client()
- fut = http_client.fetch(url, method="POST", body=urllib_parse.urlencode(args))
- fut.add_done_callback(functools.partial(
- self._on_authentication_verified, callback))
+ resp = await http_client.fetch(url, method="POST", body=urllib.parse.urlencode(args))
+ return self._on_authentication_verified(resp)
def _openid_args(self, callback_uri, ax_attrs=[], oauth_scope=None):
- url = urlparse.urljoin(self.request.full_url(), callback_uri)
+ url = urllib.parse.urljoin(self.request.full_url(), callback_uri)
args = {
"openid.ns": "http://specs.openid.net/auth/2.0",
"openid.claimed_id":
"openid.identity":
"http://specs.openid.net/auth/2.0/identifier_select",
"openid.return_to": url,
- "openid.realm": urlparse.urljoin(url, '/'),
+ "openid.realm": urllib.parse.urljoin(url, '/'),
"openid.mode": "checkid_setup",
}
if ax_attrs:
})
return args
- def _on_authentication_verified(self, future, response_fut):
- try:
- response = response_fut.result()
- except Exception as e:
- future.set_exception(AuthError(
- "Error response %s" % e))
- return
+ def _on_authentication_verified(self, response):
if b"is_valid:true" not in response.body:
- future.set_exception(AuthError(
- "Invalid OpenID response: %s" % response.body))
- return
+ raise AuthError("Invalid OpenID response: %s" % response.body)
# Make sure we got back at least an email from attribute exchange
ax_ns = None
claimed_id = self.get_argument("openid.claimed_id", None)
if claimed_id:
user["claimed_id"] = claimed_id
- future_set_result_unless_cancelled(future, user)
+ return user
def get_auth_http_client(self):
"""Returns the `.AsyncHTTPClient` instance to be used for auth requests.
Subclasses must also override the `_oauth_get_user_future` and
`_oauth_consumer_token` methods.
"""
- @_non_deprecated_return_future
- def authorize_redirect(self, callback_uri=None, extra_params=None,
- http_client=None, callback=None):
+ async def authorize_redirect(self, callback_uri=None, extra_params=None,
+ http_client=None):
"""Redirects the user to obtain OAuth authorization for this service.
The ``callback_uri`` may be omitted if you have previously
Now returns a `.Future` and takes an optional callback, for
compatibility with `.gen.coroutine`.
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned
+ awaitable object instead.
"""
if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False):
if http_client is None:
http_client = self.get_auth_http_client()
if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
- fut = http_client.fetch(
+ response = await http_client.fetch(
self._oauth_request_token_url(callback_uri=callback_uri,
extra_params=extra_params))
- fut.add_done_callback(functools.partial(
- self._on_request_token,
- self._OAUTH_AUTHORIZE_URL,
- callback_uri,
- callback))
else:
- fut = http_client.fetch(self._oauth_request_token_url())
- fut.add_done_callback(
- functools.partial(
- self._on_request_token, self._OAUTH_AUTHORIZE_URL,
- callback_uri,
- callback))
-
- @_auth_return_future
- def get_authenticated_user(self, callback, http_client=None):
+ response = await http_client.fetch(self._oauth_request_token_url())
+ self._on_request_token(self._OAUTH_AUTHORIZE_URL, callback_uri, response)
+
+ async def get_authenticated_user(self, http_client=None):
"""Gets the OAuth authorized user and access token.
This method should be called from the handler for your
also contain other fields such as ``name``, depending on the service
used.
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned
+ awaitable object instead.
"""
- future = callback
request_key = escape.utf8(self.get_argument("oauth_token"))
oauth_verifier = self.get_argument("oauth_verifier", None)
request_cookie = self.get_cookie("_oauth_request_token")
if not request_cookie:
- future.set_exception(AuthError(
- "Missing OAuth request token cookie"))
- return
+ raise AuthError("Missing OAuth request token cookie")
self.clear_cookie("_oauth_request_token")
cookie_key, cookie_secret = [
base64.b64decode(escape.utf8(i)) for i in request_cookie.split("|")]
if cookie_key != request_key:
- future.set_exception(AuthError(
- "Request token does not match cookie"))
- return
+ raise AuthError("Request token does not match cookie")
token = dict(key=cookie_key, secret=cookie_secret)
if oauth_verifier:
token["verifier"] = oauth_verifier
if http_client is None:
http_client = self.get_auth_http_client()
- fut = http_client.fetch(self._oauth_access_token_url(token))
- fut.add_done_callback(functools.partial(self._on_access_token, callback))
+ response = await http_client.fetch(self._oauth_access_token_url(token))
+ access_token = _oauth_parse_response(response.body)
+ user = await self._oauth_get_user_future(access_token)
+ if not user:
+ raise AuthError("Error getting user")
+ user["access_token"] = access_token
+ return user
def _oauth_request_token_url(self, callback_uri=None, extra_params=None):
consumer_token = self._oauth_consumer_token()
if callback_uri == "oob":
args["oauth_callback"] = "oob"
elif callback_uri:
- args["oauth_callback"] = urlparse.urljoin(
+ args["oauth_callback"] = urllib.parse.urljoin(
self.request.full_url(), callback_uri)
if extra_params:
args.update(extra_params)
signature = _oauth_signature(consumer_token, "GET", url, args)
args["oauth_signature"] = signature
- return url + "?" + urllib_parse.urlencode(args)
-
- def _on_request_token(self, authorize_url, callback_uri, callback,
- response_fut):
- try:
- response = response_fut.result()
- except Exception as e:
- raise Exception("Could not get request token: %s" % e)
+ return url + "?" + urllib.parse.urlencode(args)
+
+ def _on_request_token(self, authorize_url, callback_uri, response):
request_token = _oauth_parse_response(response.body)
data = (base64.b64encode(escape.utf8(request_token["key"])) + b"|" +
base64.b64encode(escape.utf8(request_token["secret"])))
self.set_cookie("_oauth_request_token", data)
args = dict(oauth_token=request_token["key"])
if callback_uri == "oob":
- self.finish(authorize_url + "?" + urllib_parse.urlencode(args))
- callback()
+ self.finish(authorize_url + "?" + urllib.parse.urlencode(args))
return
elif callback_uri:
- args["oauth_callback"] = urlparse.urljoin(
+ args["oauth_callback"] = urllib.parse.urljoin(
self.request.full_url(), callback_uri)
- self.redirect(authorize_url + "?" + urllib_parse.urlencode(args))
- callback()
+ self.redirect(authorize_url + "?" + urllib.parse.urlencode(args))
def _oauth_access_token_url(self, request_token):
consumer_token = self._oauth_consumer_token()
request_token)
args["oauth_signature"] = signature
- return url + "?" + urllib_parse.urlencode(args)
+ return url + "?" + urllib.parse.urlencode(args)
- def _on_access_token(self, future, response_fut):
- try:
- response = response_fut.result()
- except Exception:
- future.set_exception(AuthError("Could not fetch access token"))
- return
-
- access_token = _oauth_parse_response(response.body)
- fut = self._oauth_get_user_future(access_token)
- fut = gen.convert_yielded(fut)
- fut.add_done_callback(
- 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.
"""
raise NotImplementedError()
- @_non_deprecated_return_future
- def _oauth_get_user_future(self, access_token, callback):
+ async def _oauth_get_user_future(self, access_token):
"""Subclasses must override this to get basic information about the
user.
- Should return a `.Future` whose result is a dictionary
+ Should be a coroutine whose result is a dictionary
containing information about the user, which may have been
retrieved by using ``access_token`` to make a request to the
service.
The access token will be added to the returned dictionary to make
the result of `get_authenticated_user`.
- For backwards compatibility, the callback-based ``_oauth_get_user``
- method is also supported.
-
.. versionchanged:: 5.1
Subclasses may also define this method with ``async def``.
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``_oauth_get_user`` fallback is deprecated and support for it
- will be removed in 6.0.
+ A synchronous fallback to ``_oauth_get_user`` was removed.
"""
- warnings.warn("_oauth_get_user is deprecated, override _oauth_get_user_future instead",
- DeprecationWarning)
- # By default, call the old-style _oauth_get_user, but new code
- # should override this method instead.
- self._oauth_get_user(access_token, callback)
-
- def _oauth_get_user(self, access_token, callback):
raise NotImplementedError()
- def _on_oauth_get_user(self, access_token, future, user_future):
- if user_future.exception() is not None:
- future.set_exception(user_future.exception())
- return
- user = user_future.result()
- if not user:
- future.set_exception(AuthError("Error getting user"))
- return
- user["access_token"] = access_token
- future_set_result_unless_cancelled(future, user)
-
def _oauth_request_parameters(self, url, access_token, parameters={},
method="GET"):
"""Returns the OAuth parameters as a dict for the given request.
* ``_OAUTH_AUTHORIZE_URL``: The service's authorization url.
* ``_OAUTH_ACCESS_TOKEN_URL``: The service's access token url.
"""
- @_non_deprecated_return_future
def authorize_redirect(self, redirect_uri=None, client_id=None,
client_secret=None, extra_params=None,
- callback=None, scope=None, response_type="code"):
+ scope=None, response_type="code"):
"""Redirects the user to obtain OAuth authorization for this service.
Some providers require that you register a redirect URL with
``get_authenticated_user`` in the handler for your
redirect URL to complete the authorization process.
- .. versionchanged:: 3.1
- Returns a `.Future` and takes an optional callback. These are
- not strictly necessary as this method is synchronous,
- but they are supplied for consistency with
- `OAuthMixin.authorize_redirect`.
-
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument and returned awaitable will be removed
- in Tornado 6.0; this will be an ordinary synchronous function.
+ The ``callback`` argument and returned awaitable were removed;
+ this is now an ordinary synchronous function.
"""
args = {
"redirect_uri": redirect_uri,
args['scope'] = ' '.join(scope)
self.redirect(
url_concat(self._OAUTH_AUTHORIZE_URL, args))
- callback()
def _oauth_request_token_url(self, redirect_uri=None, client_id=None,
client_secret=None, code=None,
args.update(extra_params)
return url_concat(url, args)
- @_auth_return_future
- def oauth2_request(self, url, callback, access_token=None,
- post_args=None, **args):
+ async def oauth2_request(self, url, access_token=None,
+ post_args=None, **args):
"""Fetches the given URL auth an OAuth2 access token.
If the request is a POST, ``post_args`` should be provided. Query
.. versionadded:: 4.3
- .. deprecated:: 5.1
+ .. versionchanged::: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned awaitable object instead.
"""
all_args = {}
if access_token:
all_args.update(args)
if all_args:
- url += "?" + urllib_parse.urlencode(all_args)
- callback = functools.partial(self._on_oauth2_request, callback)
+ url += "?" + urllib.parse.urlencode(all_args)
http = self.get_auth_http_client()
if post_args is not None:
- fut = http.fetch(url, method="POST", body=urllib_parse.urlencode(post_args))
+ response = await http.fetch(url, method="POST", body=urllib.parse.urlencode(post_args))
else:
- fut = http.fetch(url)
- fut.add_done_callback(callback)
-
- def _on_oauth2_request(self, future, response_fut):
- try:
- response = response_fut.result()
- except Exception as e:
- future.set_exception(AuthError("Error response %s" % e))
- return
-
- future_set_result_unless_cancelled(future, escape.json_decode(response.body))
+ response = await http.fetch(url)
+ return escape.json_decode(response.body)
def get_auth_http_client(self):
"""Returns the `.AsyncHTTPClient` instance to be used for auth requests.
_OAUTH_NO_CALLBACKS = False
_TWITTER_BASE_URL = "https://api.twitter.com/1.1"
- @_non_deprecated_return_future
- def authenticate_redirect(self, callback_uri=None, callback=None):
+ async def authenticate_redirect(self, callback_uri=None):
"""Just like `~OAuthMixin.authorize_redirect`, but
auto-redirects if authorized.
Now returns a `.Future` and takes an optional callback, for
compatibility with `.gen.coroutine`.
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned
+ awaitable object instead.
"""
http = self.get_auth_http_client()
- http.fetch(self._oauth_request_token_url(callback_uri=callback_uri),
- functools.partial(
- self._on_request_token, self._OAUTH_AUTHENTICATE_URL,
- None, callback))
-
- @_auth_return_future
- def twitter_request(self, path, callback=None, access_token=None,
- post_args=None, **args):
+ response = await http.fetch(self._oauth_request_token_url(callback_uri=callback_uri))
+ self._on_request_token(self._OAUTH_AUTHENTICATE_URL, None, response)
+
+ async def twitter_request(self, path, access_token=None, post_args=None, **args):
"""Fetches the given API path, e.g., ``statuses/user_timeline/btaylor``
The path should not include the format or API version number.
.. testoutput::
:hide:
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned
+ awaitable object instead.
"""
if path.startswith('http:') or path.startswith('https:'):
# Raw urls are useful for e.g. search which doesn't follow the
url, access_token, all_args, method=method)
args.update(oauth)
if args:
- url += "?" + urllib_parse.urlencode(args)
+ url += "?" + urllib.parse.urlencode(args)
http = self.get_auth_http_client()
- http_callback = functools.partial(self._on_twitter_request, callback, url)
if post_args is not None:
- fut = http.fetch(url, method="POST", body=urllib_parse.urlencode(post_args))
+ response = await http.fetch(url, method="POST", body=urllib.parse.urlencode(post_args))
else:
- fut = http.fetch(url)
- fut.add_done_callback(http_callback)
-
- def _on_twitter_request(self, future, url, response_fut):
- try:
- response = response_fut.result()
- except Exception as e:
- future.set_exception(AuthError(
- "Error response %s fetching %s" % (e, url)))
- return
- future_set_result_unless_cancelled(future, escape.json_decode(response.body))
+ response = await http.fetch(url)
+ return escape.json_decode(response.body)
def _oauth_consumer_token(self):
self.require_setting("twitter_consumer_key", "Twitter OAuth")
key=self.settings["twitter_consumer_key"],
secret=self.settings["twitter_consumer_secret"])
- @gen.coroutine
- def _oauth_get_user_future(self, access_token):
- user = yield self.twitter_request(
+ async def _oauth_get_user_future(self, access_token):
+ user = await self.twitter_request(
"/account/verify_credentials",
access_token=access_token)
if user:
user["username"] = user["screen_name"]
- raise gen.Return(user)
+ return user
class GoogleOAuth2Mixin(OAuth2Mixin):
_OAUTH_NO_CALLBACKS = False
_OAUTH_SETTINGS_KEY = 'google_oauth'
- @_auth_return_future
- def get_authenticated_user(self, redirect_uri, code, callback):
+ async def get_authenticated_user(self, redirect_uri, code):
"""Handles the login for the Google user, returning an access token.
The result is a dictionary containing an ``access_token`` field
.. testoutput::
:hide:
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned awaitable object instead.
""" # noqa: E501
http = self.get_auth_http_client()
- body = urllib_parse.urlencode({
+ body = urllib.parse.urlencode({
"redirect_uri": redirect_uri,
"code": code,
"client_id": self.settings[self._OAUTH_SETTINGS_KEY]['key'],
"grant_type": "authorization_code",
})
- fut = http.fetch(self._OAUTH_ACCESS_TOKEN_URL,
- method="POST",
- headers={'Content-Type': 'application/x-www-form-urlencoded'},
- body=body)
- fut.add_done_callback(functools.partial(self._on_access_token, callback))
-
- def _on_access_token(self, future, response_fut):
- """Callback function for the exchange to the access token."""
- try:
- response = response_fut.result()
- except Exception as e:
- future.set_exception(AuthError('Google auth error: %s' % str(e)))
- return
-
- args = escape.json_decode(response.body)
- future_set_result_unless_cancelled(future, args)
+ response = await http.fetch(self._OAUTH_ACCESS_TOKEN_URL,
+ method="POST",
+ headers={'Content-Type': 'application/x-www-form-urlencoded'},
+ body=body)
+ return escape.json_decode(response.body)
class FacebookGraphMixin(OAuth2Mixin):
_OAUTH_NO_CALLBACKS = False
_FACEBOOK_BASE_URL = "https://graph.facebook.com"
- @_auth_return_future
- def get_authenticated_user(self, redirect_uri, client_id, client_secret,
- code, callback, extra_fields=None):
+ async def get_authenticated_user(self, redirect_uri, client_id, client_secret,
+ code, extra_fields=None):
"""Handles the login for the Facebook user, returning a user object.
Example usage:
The ``session_expires`` field was updated to support changes made to the
Facebook API in March 2017.
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned awaitable object instead.
"""
http = self.get_auth_http_client()
args = {
if extra_fields:
fields.update(extra_fields)
- fut = http.fetch(self._oauth_request_token_url(**args))
- fut.add_done_callback(functools.partial(self._on_access_token, redirect_uri, client_id,
- client_secret, callback, fields))
-
- @gen.coroutine
- def _on_access_token(self, redirect_uri, client_id, client_secret,
- future, fields, response_fut):
- try:
- response = response_fut.result()
- except Exception as e:
- future.set_exception(AuthError('Facebook auth error: %s' % str(e)))
- return
-
+ response = await http.fetch(self._oauth_request_token_url(**args))
args = escape.json_decode(response.body)
session = {
"access_token": args.get("access_token"),
"expires_in": args.get("expires_in")
}
- user = yield self.facebook_request(
+ user = await self.facebook_request(
path="/me",
access_token=session["access_token"],
appsecret_proof=hmac.new(key=client_secret.encode('utf8'),
)
if user is None:
- future_set_result_unless_cancelled(future, None)
- return
+ return None
fieldmap = {}
for field in fields:
# This should change in Tornado 5.0.
fieldmap.update({"access_token": session["access_token"],
"session_expires": str(session.get("expires_in"))})
- future_set_result_unless_cancelled(future, fieldmap)
+ return fieldmap
- @_auth_return_future
- def facebook_request(self, path, callback, access_token=None,
- post_args=None, **args):
+ async def facebook_request(self, path, access_token=None, post_args=None, **args):
"""Fetches the given relative API path, e.g., "/btaylor/picture"
If the request is a POST, ``post_args`` should be provided. Query
.. versionchanged:: 3.1
Added the ability to override ``self._FACEBOOK_BASE_URL``.
- .. deprecated:: 5.1
+ .. versionchanged:: 6.0
- The ``callback`` argument is deprecated and will be removed in 6.0.
- Use the returned awaitable object instead.
+ The ``callback`` argument was removed. Use the returned awaitable object instead.
"""
url = self._FACEBOOK_BASE_URL + path
- # Thanks to the _auth_return_future decorator, our "callback"
- # argument is a Future, which we cannot pass as a callback to
- # oauth2_request. Instead, have oauth2_request return a
- # future and chain them together.
- oauth_future = self.oauth2_request(url, access_token=access_token,
- post_args=post_args, **args)
- chain_future(oauth_future, callback)
+ return await self.oauth2_request(url, access_token=access_token,
+ post_args=post_args, **args)
def _oauth_signature(consumer_token, method, url, parameters={}, token=None):
See http://oauth.net/core/1.0/#signing_process
"""
- parts = urlparse.urlparse(url)
+ parts = urllib.parse.urlparse(url)
scheme, netloc, path = parts[:3]
normalized_url = scheme.lower() + "://" + netloc.lower() + path
See http://oauth.net/core/1.0a/#signing_process
"""
- parts = urlparse.urlparse(url)
+ parts = urllib.parse.urlparse(url)
scheme, netloc, path = parts[:3]
normalized_url = scheme.lower() + "://" + netloc.lower() + path
for k, v in sorted(parameters.items())))
base_string = "&".join(_oauth_escape(e) for e in base_elems)
- key_elems = [escape.utf8(urllib_parse.quote(consumer_token["secret"], safe='~'))]
- key_elems.append(escape.utf8(urllib_parse.quote(token["secret"], safe='~') if token else ""))
+ key_elems = [escape.utf8(urllib.parse.quote(consumer_token["secret"], safe='~'))]
+ key_elems.append(escape.utf8(urllib.parse.quote(token["secret"], safe='~') if token else ""))
key = b"&".join(key_elems)
hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1)
def _oauth_escape(val):
if isinstance(val, unicode_type):
val = val.encode("utf-8")
- return urllib_parse.quote(val, safe="~")
+ return urllib.parse.quote(val, safe="~")
def _oauth_parse_response(body):
# have never seen anyone use non-ascii. Leave the response in a byte
# string for python 2, and use utf8 on python 3.
body = escape.native_str(body)
- p = urlparse.parse_qs(body, keep_blank_values=False)
+ p = urllib.parse.parse_qs(body, keep_blank_values=False)
token = dict(key=p["oauth_token"][0], secret=p["oauth_token_secret"][0])
# Add the extra parameters the Provider included to the token
from __future__ import absolute_import, division, print_function
-import warnings
-
from tornado.auth import (
- AuthError, OpenIdMixin, OAuthMixin, OAuth2Mixin,
+ OpenIdMixin, OAuthMixin, OAuth2Mixin,
GoogleOAuth2Mixin, FacebookGraphMixin, TwitterMixin,
)
-from tornado.concurrent import Future
from tornado.escape import json_decode
from tornado import gen
+from tornado.httpclient import HTTPClientError
from tornado.httputil import url_concat
-from tornado.log import gen_log
-from tornado.testing import AsyncHTTPTestCase, ExpectLog
-from tornado.test.util import ignore_deprecation
-from tornado.web import RequestHandler, Application, asynchronous, HTTPError
-
-
-class OpenIdClientLoginHandlerLegacy(RequestHandler, OpenIdMixin):
- def initialize(self, test):
- self._OPENID_ENDPOINT = test.get_url('/openid/server/authenticate')
-
- with ignore_deprecation():
- @asynchronous
- def get(self):
- if self.get_argument('openid.mode', None):
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', DeprecationWarning)
- self.get_authenticated_user(
- self.on_user, http_client=self.settings['http_client'])
- return
- res = self.authenticate_redirect()
- assert isinstance(res, Future)
- assert res.done()
-
- def on_user(self, user):
- if user is None:
- raise Exception("user is None")
- self.finish(user)
+from tornado.testing import AsyncHTTPTestCase
+from tornado.web import RequestHandler, Application, HTTPError
class OpenIdClientLoginHandler(RequestHandler, OpenIdMixin):
self.finish(user)
return
res = self.authenticate_redirect()
- assert isinstance(res, Future)
- assert res.done()
+ assert res is None
class OpenIdServerAuthenticateHandler(RequestHandler):
self.write('is_valid:true')
-class OAuth1ClientLoginHandlerLegacy(RequestHandler, OAuthMixin):
- def initialize(self, test, version):
- self._OAUTH_VERSION = version
- self._OAUTH_REQUEST_TOKEN_URL = test.get_url('/oauth1/server/request_token')
- self._OAUTH_AUTHORIZE_URL = test.get_url('/oauth1/server/authorize')
- self._OAUTH_ACCESS_TOKEN_URL = test.get_url('/oauth1/server/access_token')
-
- def _oauth_consumer_token(self):
- return dict(key='asdf', secret='qwer')
-
- with ignore_deprecation():
- @asynchronous
- def get(self):
- if self.get_argument('oauth_token', None):
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', DeprecationWarning)
- self.get_authenticated_user(
- self.on_user, http_client=self.settings['http_client'])
- return
- res = self.authorize_redirect(http_client=self.settings['http_client'])
- assert isinstance(res, Future)
-
- def on_user(self, user):
- if user is None:
- raise Exception("user is None")
- 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 OAuth1ClientLoginHandler(RequestHandler, OAuthMixin):
def initialize(self, test, version):
self._OAUTH_VERSION = version
def get(self):
res = self.authorize_redirect()
- assert isinstance(res, Future)
- assert res.done()
+ assert res is None
class FacebookClientLoginHandler(RequestHandler, FacebookGraphMixin):
return self.settings['http_client']
-class TwitterClientLoginHandlerLegacy(TwitterClientHandler):
- with ignore_deprecation():
- @asynchronous
- def get(self):
- if self.get_argument("oauth_token", None):
- self.get_authenticated_user(self.on_user)
- return
- self.authorize_redirect()
-
- def on_user(self, user):
- if user is None:
- raise Exception("user is None")
- self.finish(user)
-
-
class TwitterClientLoginHandler(TwitterClientHandler):
@gen.coroutine
def get(self):
response = yield self.twitter_request(
'/users/show/%s' % self.get_argument('name'),
access_token=dict(key='hjkl', secret='vbnm'))
- except AuthError:
+ except HTTPClientError:
+ # TODO(bdarnell): Should we catch HTTP errors and
+ # transform some of them (like 403s) into AuthError?
self.set_status(500)
self.finish('error from twitter request')
else:
return Application(
[
# test endpoints
- ('/legacy/openid/client/login', OpenIdClientLoginHandlerLegacy, dict(test=self)),
('/openid/client/login', OpenIdClientLoginHandler, dict(test=self)),
- ('/legacy/oauth10/client/login', OAuth1ClientLoginHandlerLegacy,
- dict(test=self, version='1.0')),
('/oauth10/client/login', OAuth1ClientLoginHandler,
dict(test=self, version='1.0')),
('/oauth10/client/request_params',
OAuth1ClientRequestParametersHandler,
dict(version='1.0')),
- ('/legacy/oauth10a/client/login', OAuth1ClientLoginHandlerLegacy,
- dict(test=self, version='1.0a')),
('/oauth10a/client/login', OAuth1ClientLoginHandler,
dict(test=self, version='1.0a')),
('/oauth10a/client/login_coroutine',
('/facebook/client/login', FacebookClientLoginHandler, dict(test=self)),
- ('/legacy/twitter/client/login', TwitterClientLoginHandlerLegacy, dict(test=self)),
('/twitter/client/login', TwitterClientLoginHandler, dict(test=self)),
('/twitter/client/login_gen_coroutine',
TwitterClientLoginGenCoroutineHandler, dict(test=self)),
facebook_api_key='test_facebook_api_key',
facebook_secret='test_facebook_secret')
- def test_openid_redirect_legacy(self):
- response = self.fetch('/legacy/openid/client/login', follow_redirects=False)
- self.assertEqual(response.code, 302)
- self.assertTrue(
- '/openid/server/authenticate?' in response.headers['Location'])
-
- def test_openid_get_user_legacy(self):
- response = self.fetch('/legacy/openid/client/login?openid.mode=blah'
- '&openid.ns.ax=http://openid.net/srv/ax/1.0'
- '&openid.ax.type.email=http://axschema.org/contact/email'
- '&openid.ax.value.email=foo@example.com')
- response.rethrow()
- parsed = json_decode(response.body)
- self.assertEqual(parsed["email"], "foo@example.com")
-
def test_openid_redirect(self):
response = self.fetch('/openid/client/login', follow_redirects=False)
self.assertEqual(response.code, 302)
parsed = json_decode(response.body)
self.assertEqual(parsed["email"], "foo@example.com")
- def test_oauth10_redirect_legacy(self):
- response = self.fetch('/legacy/oauth10/client/login', follow_redirects=False)
- self.assertEqual(response.code, 302)
- self.assertTrue(response.headers['Location'].endswith(
- '/oauth1/server/authorize?oauth_token=zxcv'))
- # the cookie is base64('zxcv')|base64('1234')
- self.assertTrue(
- '_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'],
- response.headers['Set-Cookie'])
-
def test_oauth10_redirect(self):
response = self.fetch('/oauth10/client/login', follow_redirects=False)
self.assertEqual(response.code, 302)
'_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'],
response.headers['Set-Cookie'])
- def test_oauth10_get_user_legacy(self):
- with ignore_deprecation():
- response = self.fetch(
- '/legacy/oauth10/client/login?oauth_token=zxcv',
- headers={'Cookie': '_oauth_request_token=enhjdg==|MTIzNA=='})
- response.rethrow()
- parsed = json_decode(response.body)
- self.assertEqual(parsed['email'], 'foo@example.com')
- self.assertEqual(parsed['access_token'], dict(key='uiop', secret='5678'))
-
def test_oauth10_get_user(self):
response = self.fetch(
'/oauth10/client/login?oauth_token=zxcv',
self.assertTrue('oauth_nonce' in parsed)
self.assertTrue('oauth_signature' in parsed)
- def test_oauth10a_redirect_legacy(self):
- response = self.fetch('/legacy/oauth10a/client/login', follow_redirects=False)
- self.assertEqual(response.code, 302)
- self.assertTrue(response.headers['Location'].endswith(
- '/oauth1/server/authorize?oauth_token=zxcv'))
- # the cookie is base64('zxcv')|base64('1234')
- self.assertTrue(
- '_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'],
- response.headers['Set-Cookie'])
-
- def test_oauth10a_get_user_legacy(self):
- with ignore_deprecation():
- response = self.fetch(
- '/legacy/oauth10a/client/login?oauth_token=zxcv',
- headers={'Cookie': '_oauth_request_token=enhjdg==|MTIzNA=='})
- response.rethrow()
- parsed = json_decode(response.body)
- self.assertEqual(parsed['email'], 'foo@example.com')
- self.assertEqual(parsed['access_token'], dict(key='uiop', secret='5678'))
-
def test_oauth10a_redirect(self):
response = self.fetch('/oauth10a/client/login', follow_redirects=False)
self.assertEqual(response.code, 302)
'_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'],
response.headers['Set-Cookie'])
- def test_twitter_redirect_legacy(self):
- self.base_twitter_redirect('/legacy/twitter/client/login')
-
def test_twitter_redirect(self):
self.base_twitter_redirect('/twitter/client/login')