args["openid.mode"] = u"check_authentication"
url = self._OPENID_ENDPOINT
if http_client is None:
- http_client = httpclient.AsyncHTTPClient()
+ http_client = self.get_auth_http_client()
http_client.fetch(url, self.async_callback(
self._on_authentication_verified, callback),
method="POST", body=urllib.urlencode(args))
user["claimed_id"] = claimed_id
callback(user)
+ def get_auth_http_client(self):
+ """Returns the AsyncHTTPClient instance to be used for auth requests.
+
+ May be overridden by subclasses to use an http client other than
+ the default.
+ """
+ return httpclient.AsyncHTTPClient()
+
class OAuthMixin(object):
"""Abstract implementation of OAuth.
if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False):
raise Exception("This service does not support oauth_callback")
if http_client is None:
- http_client = httpclient.AsyncHTTPClient()
+ http_client = self.get_auth_http_client()
if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a":
http_client.fetch(
self._oauth_request_token_url(callback_uri=callback_uri,
if oauth_verifier:
token["verifier"] = oauth_verifier
if http_client is None:
- http_client = httpclient.AsyncHTTPClient()
+ http_client = self.get_auth_http_client()
http_client.fetch(self._oauth_access_token_url(token),
self.async_callback(self._on_access_token, callback))
base_args["oauth_signature"] = signature
return base_args
+ def get_auth_http_client(self):
+ """Returns the AsyncHTTPClient instance to be used for auth requests.
+
+ May be overridden by subclasses to use an http client other than
+ the default.
+ """
+ return httpclient.AsyncHTTPClient()
+
class OAuth2Mixin(object):
"""Abstract implementation of OAuth v 2."""
_OAUTH_AUTHORIZE_URL = "http://api.twitter.com/oauth/authorize"
_OAUTH_AUTHENTICATE_URL = "http://api.twitter.com/oauth/authenticate"
_OAUTH_NO_CALLBACKS = False
+ _TWITTER_BASE_URL = "http://api.twitter.com/1"
def authenticate_redirect(self, callback_uri=None):
"""Just like authorize_redirect(), but auto-redirects if authorized.
This is generally the right interface to use if you are using
Twitter for single-sign on.
"""
- http = httpclient.AsyncHTTPClient()
+ http = self.get_auth_http_client()
http.fetch(self._oauth_request_token_url(callback_uri=callback_uri), self.async_callback(
self._on_request_token, self._OAUTH_AUTHENTICATE_URL, None))
# usual pattern: http://search.twitter.com/search.json
url = path
else:
- url = "http://api.twitter.com/1" + path + ".json"
+ url = self._TWITTER_BASE_URL + path + ".json"
# Add the OAuth resource request signature if we have credentials
if access_token:
all_args = {}
if args:
url += "?" + urllib.urlencode(args)
callback = self.async_callback(self._on_twitter_request, callback)
- http = httpclient.AsyncHTTPClient()
+ http = self.get_auth_http_client()
if post_args is not None:
http.fetch(url, method="POST", body=urllib.urlencode(post_args),
callback=callback)
def _oauth_get_user(self, access_token, callback):
callback = self.async_callback(self._parse_user_response, callback)
self.twitter_request(
- "/users/show/" + access_token["screen_name"],
+ "/users/show/" + escape.native_str(access_token[b("screen_name")]),
access_token=access_token, callback=callback)
def _parse_user_response(self, callback, user):
if args:
url += "?" + urllib.urlencode(args)
callback = self.async_callback(self._on_friendfeed_request, callback)
- http = httpclient.AsyncHTTPClient()
+ http = self.get_auth_http_client()
if post_args is not None:
http.fetch(url, method="POST", body=urllib.urlencode(post_args),
callback=callback)
break
token = self.get_argument("openid." + oauth_ns + ".request_token", "")
if token:
- http = httpclient.AsyncHTTPClient()
+ 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))
args["sig"] = self._signature(args)
url = "http://api.facebook.com/restserver.php?" + \
urllib.urlencode(args)
- http = httpclient.AsyncHTTPClient()
+ http = self.get_auth_http_client()
http.fetch(url, callback=self.async_callback(
self._parse_response, callback))
body = body.encode("utf-8")
return hashlib.md5(body).hexdigest()
+ def get_auth_http_client(self):
+ """Returns the AsyncHTTPClient instance to be used for auth requests.
+
+ May be overridden by subclasses to use an http client other than
+ the default.
+ """
+ return httpclient.AsyncHTTPClient()
+
class FacebookGraphMixin(OAuth2Mixin):
"""Facebook authentication using the new Graph API and OAuth2."""
self.finish()
"""
- http = httpclient.AsyncHTTPClient()
+ http = self.get_auth_http_client()
args = {
"redirect_uri": redirect_uri,
"code": code,
if all_args:
url += "?" + urllib.urlencode(all_args)
callback = self.async_callback(self._on_facebook_request, callback)
- http = httpclient.AsyncHTTPClient()
+ http = self.get_auth_http_client()
if post_args is not None:
http.fetch(url, method="POST", body=urllib.urlencode(post_args),
callback=callback)
return
callback(escape.json_decode(response.body))
+ def get_auth_http_client(self):
+ """Returns the AsyncHTTPClient instance to be used for auth requests.
+
+ May be overridden by subclasses to use an http client other than
+ the default.
+ """
+ return httpclient.AsyncHTTPClient()
+
def _oauth_signature(consumer_token, method, url, parameters={}, token=None):
"""Calculates the HMAC-SHA1 OAuth signature for the given request.
from __future__ import absolute_import, division, with_statement
-from tornado.auth import OpenIdMixin, OAuthMixin, OAuth2Mixin
+from tornado.auth import OpenIdMixin, OAuthMixin, OAuth2Mixin, TwitterMixin
from tornado.escape import json_decode
from tornado.testing import AsyncHTTPTestCase, LogTrapTestCase
from tornado.util import b
self.authorize_redirect()
+class TwitterClientLoginHandler(RequestHandler, TwitterMixin):
+ def initialize(self, test):
+ self._OAUTH_REQUEST_TOKEN_URL = test.get_url('/oauth1/server/request_token')
+ self._OAUTH_ACCESS_TOKEN_URL = test.get_url('/twitter/server/access_token')
+ self._OAUTH_AUTHORIZE_URL = test.get_url('/oauth1/server/authorize')
+ self._TWITTER_BASE_URL = test.get_url('/twitter/api')
+
+ @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)
+
+ def get_auth_http_client(self):
+ return self.settings['http_client']
+
+
+class TwitterServerAccessTokenHandler(RequestHandler):
+ def get(self):
+ self.write('oauth_token=hjkl&oauth_token_secret=vbnm&screen_name=foo')
+
+class TwitterServerShowUserHandler(RequestHandler):
+ def get(self, screen_name):
+ self.write(dict(screen_name=screen_name, name=screen_name.capitalize()))
+
class AuthTest(AsyncHTTPTestCase, LogTrapTestCase):
def get_app(self):
return Application(
dict(version='1.0a')),
('/oauth2/client/login', OAuth2ClientLoginHandler, dict(test=self)),
+ ('/twitter/client/login', TwitterClientLoginHandler, dict(test=self)),
+
# simulated servers
('/openid/server/authenticate', OpenIdServerAuthenticateHandler),
('/oauth1/server/request_token', OAuth1ServerRequestTokenHandler),
('/oauth1/server/access_token', OAuth1ServerAccessTokenHandler),
+
+ ('/twitter/server/access_token', TwitterServerAccessTokenHandler),
+ (r'/twitter/api/users/show/(.*)\.json', TwitterServerShowUserHandler),
],
- http_client=self.http_client)
+ http_client=self.http_client,
+ twitter_consumer_key='test_twitter_consumer_key',
+ twitter_consumer_secret='test_twitter_consumer_secret')
def test_openid_redirect(self):
response = self.fetch('/openid/client/login', follow_redirects=False)
response = self.fetch('/oauth2/client/login', follow_redirects=False)
self.assertEqual(response.code, 302)
self.assertTrue('/oauth2/server/authorize?' in response.headers['Location'])
+
+ def test_twitter_redirect(self):
+ # Same as test_oauth10a_redirect
+ response = self.fetch('/twitter/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_twitter_get_user(self):
+ response = self.fetch(
+ '/twitter/client/login?oauth_token=zxcv',
+ headers={'Cookie': '_oauth_request_token=enhjdg==|MTIzNA=='})
+ response.rethrow()
+ parsed = json_decode(response.body)
+ self.assertEqual(parsed,
+ {u'access_token': {u'key': u'hjkl',
+ u'screen_name': u'foo',
+ u'secret': u'vbnm'},
+ u'name': u'Foo',
+ u'screen_name': u'foo',
+ u'username': u'foo'})