]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
auth: Remove deprecated callback interfaces
authorBen Darnell <ben@bendarnell.com>
Fri, 6 Jul 2018 22:23:22 +0000 (18:23 -0400)
committerBen Darnell <ben@bendarnell.com>
Sat, 14 Jul 2018 20:58:48 +0000 (16:58 -0400)
tornado/auth.py
tornado/test/auth_test.py

index ab1a8503a3c7ce3a3c555fa0f58dd2333edf2125..07a3f5d70e52244a19efa3d6d71e4b245b9c8f6e 100644 (file)
@@ -54,93 +54,29 @@ Example usage for Google OAuth:
 .. 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.
 
@@ -148,10 +84,8 @@ class OpenIdMixin(object):
 
     * ``_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
@@ -162,24 +96,17 @@ class OpenIdMixin(object):
         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
@@ -190,10 +117,10 @@ class OpenIdMixin(object):
 
         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())
@@ -201,12 +128,11 @@ class OpenIdMixin(object):
         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":
@@ -214,7 +140,7 @@ class OpenIdMixin(object):
             "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:
@@ -253,17 +179,9 @@ class OpenIdMixin(object):
             })
         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
@@ -316,7 +234,7 @@ class OpenIdMixin(object):
         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.
@@ -343,9 +261,8 @@ class OAuthMixin(object):
     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
@@ -367,10 +284,10 @@ class OAuthMixin(object):
            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):
@@ -378,24 +295,14 @@ class OAuthMixin(object):
         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
@@ -406,33 +313,33 @@ class OAuthMixin(object):
         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()
@@ -448,7 +355,7 @@ class OAuthMixin(object):
             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)
@@ -457,28 +364,21 @@ class OAuthMixin(object):
             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()
@@ -502,20 +402,8 @@ class OAuthMixin(object):
                                          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.
@@ -524,12 +412,11 @@ class OAuthMixin(object):
         """
         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.
@@ -537,38 +424,16 @@ class OAuthMixin(object):
         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.
@@ -617,10 +482,9 @@ class OAuth2Mixin(object):
     * ``_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
@@ -629,16 +493,10 @@ class OAuth2Mixin(object):
         ``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,
@@ -651,7 +509,6 @@ class OAuth2Mixin(object):
             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,
@@ -667,9 +524,8 @@ class OAuth2Mixin(object):
             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
@@ -699,10 +555,9 @@ class OAuth2Mixin(object):
 
         .. 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:
@@ -710,23 +565,13 @@ class OAuth2Mixin(object):
             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.
@@ -778,8 +623,7 @@ class TwitterMixin(OAuthMixin):
     _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.
 
@@ -790,20 +634,16 @@ class TwitterMixin(OAuthMixin):
            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.
@@ -840,10 +680,10 @@ class TwitterMixin(OAuthMixin):
         .. 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
@@ -861,23 +701,13 @@ class TwitterMixin(OAuthMixin):
                 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")
@@ -886,14 +716,13 @@ class TwitterMixin(OAuthMixin):
             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):
@@ -920,8 +749,7 @@ 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
@@ -959,13 +787,12 @@ class GoogleOAuth2Mixin(OAuth2Mixin):
         .. 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'],
@@ -973,22 +800,11 @@ class GoogleOAuth2Mixin(OAuth2Mixin):
             "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):
@@ -998,9 +814,8 @@ 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:
@@ -1042,10 +857,9 @@ class FacebookGraphMixin(OAuth2Mixin):
            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 = {
@@ -1060,26 +874,14 @@ class FacebookGraphMixin(OAuth2Mixin):
         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'),
@@ -1089,8 +891,7 @@ class FacebookGraphMixin(OAuth2Mixin):
         )
 
         if user is None:
-            future_set_result_unless_cancelled(future, None)
-            return
+            return None
 
         fieldmap = {}
         for field in fields:
@@ -1102,11 +903,9 @@ class FacebookGraphMixin(OAuth2Mixin):
         # 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
@@ -1153,19 +952,13 @@ class FacebookGraphMixin(OAuth2Mixin):
         .. 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):
@@ -1173,7 +966,7 @@ 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
 
@@ -1197,7 +990,7 @@ def _oauth10a_signature(consumer_token, method, url, parameters={}, token=None):
 
     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
 
@@ -1208,8 +1001,8 @@ def _oauth10a_signature(consumer_token, method, url, parameters={}, token=None):
                                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)
@@ -1219,7 +1012,7 @@ def _oauth10a_signature(consumer_token, method, url, parameters={}, token=None):
 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):
@@ -1227,7 +1020,7 @@ 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
index 6b3cb16f519be6d35859bb7d557a211907f9c54b..ee5ee7a7b6bda30e50990cffbea1b1f458a03f23 100644 (file)
@@ -6,43 +6,16 @@
 
 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):
@@ -58,8 +31,7 @@ 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):
@@ -69,41 +41,6 @@ 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
@@ -180,8 +117,7 @@ class OAuth2ClientLoginHandler(RequestHandler, OAuth2Mixin):
 
     def get(self):
         res = self.authorize_redirect()
-        assert isinstance(res, Future)
-        assert res.done()
+        assert res is None
 
 
 class FacebookClientLoginHandler(RequestHandler, FacebookGraphMixin):
@@ -227,21 +163,6 @@ class TwitterClientHandler(RequestHandler, TwitterMixin):
         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):
@@ -275,7 +196,9 @@ class TwitterClientShowUserHandler(TwitterClientHandler):
             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:
@@ -318,17 +241,12 @@ class AuthTest(AsyncHTTPTestCase):
         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',
@@ -341,7 +259,6 @@ class AuthTest(AsyncHTTPTestCase):
 
                 ('/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)),
@@ -366,21 +283,6 @@ class AuthTest(AsyncHTTPTestCase):
             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)
@@ -396,16 +298,6 @@ class AuthTest(AsyncHTTPTestCase):
         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)
@@ -416,16 +308,6 @@ class AuthTest(AsyncHTTPTestCase):
             '_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',
@@ -444,26 +326,6 @@ class AuthTest(AsyncHTTPTestCase):
         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)
@@ -524,9 +386,6 @@ class AuthTest(AsyncHTTPTestCase):
             '_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')