Created
January 5, 2013 21:50
-
-
Save pdarche/4463867 to your computer and use it in GitHub Desktop.
First pass as a ( currently non-functional ) Fitbit authentication mixin for Tronado and a test handler class
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################### Fitbit Mixin ################### | |
import urllib | |
import tornado.auth | |
import tornado.httputil | |
import logging | |
class FitbitMixin(tornado.auth.OAuthMixin): | |
"""Fitbit OAuth authentication. | |
To authenticate with Fitbit, register your application with | |
Fitbit at https://dev.fitbit.com/apps. Then copy your Consumer Key and | |
Consumer Secret to the application settings 'fitbit_consumer_key' and | |
'fitbit_consumer_secret'. Use this Mixin on the handler for the URL | |
you registered as your application's Callback URL. | |
When your application is set up, you can use this Mixin like this | |
to authenticate the user with Twitter and get access to their stream:: | |
class FitbitHandler(tornado.web.RequestHandler, | |
tornado.auth.FitbitMixin): | |
@tornado.web.asynchronous | |
def get(self): | |
if self.get_argument("oauth_token", None): | |
self.get_authenticated_user(self.async_callback(self._on_auth)) | |
return | |
self.authorize_redirect() | |
def _on_auth(self, user): | |
if not user: | |
raise tornado.web.HTTPError(500, "Fitbit auth failed") | |
# Save the user using, e.g., set_secure_cookie() | |
The user object returned by get_authenticated_user() includes the | |
attributes 'user-id', 'name', and all of the custom Fitbit user | |
attributes describe at | |
https://wiki.fitbit.com/display/API/API-Get-User-Info | |
in addition to 'access_token'. You should save the access token with | |
the user; it is required to make requests on behalf of the user later | |
with fitbit_request(). | |
""" | |
_OAUTH_REQUEST_TOKEN_URL = "http://api.fitbit.com/oauth/request_token" | |
_OAUTH_ACCESS_TOKEN_URL = "http://api.fitbit.com/oauth/access_token" | |
_OAUTH_AUTHORIZE_URL = "http://api.fitbit.com/oauth/authorize" | |
_OAUTH_AUTHENTICATE_URL = "http://api.fitbit.com/oauth/authenticate" | |
_OAUTH_NO_CALLBACKS = False | |
_FITBIT_BASE_URL = "https://api.fitbit.com/1" | |
def authenticate_redirect(self, callback_uri=None): | |
"""Just like authorize_redirect(), but auto-redirects if authorized.""" | |
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)) | |
def fitbit_request(self, path, callback, access_token=None, | |
post_args=None, **args): | |
"""Fetches the given API path, e.g., "/user/-/activities/log/steps/date/today/7d" | |
The path should not include the format (we automatically append | |
".json" and parse the JSON output). | |
If the request is a POST, post_args should be provided. Query | |
string arguments should be given as keyword arguments. | |
All the Fitbit methods are documented at | |
https://wiki.fitbit.com/display/API/Fitbit+API | |
Many methods require an OAuth access token which you can obtain | |
through authorize_redirect() and get_authenticated_user(). The | |
user returned through that process includes an 'access_token' | |
attribute that can be used to make authenticated requests via | |
this method. Example usage:: | |
class MainHandler(tornado.web.RequestHandler, | |
tornado.auth.FitbitMixin): | |
@tornado.web.authenticated | |
@tornado.web.asynchronous | |
def get(self): | |
self.fitbit_request( | |
"/user/-/activities/log/steps/date/today/7d", | |
access_token=user["access_token"], | |
callback=self.async_callback(self._on_post)) | |
def _on_post(self, new_entry): | |
if not new_entry: | |
# Call failed; perhaps missing permission? | |
self.authorize_redirect() | |
return | |
self.finish("Posted a message!") | |
""" | |
if path.startswith('http:') or path.startswith('https:'): | |
# Raw urls are useful for e.g. search which doesn't follow the | |
# usual pattern: http://search.twitter.com/search.json | |
url = path | |
else: | |
url = self._FITBIT_BASE_URL + path + ".json" | |
# Add the OAuth resource request signature if we have credentials | |
if access_token: | |
all_args = {} | |
all_args.update(args) | |
all_args.update(post_args or {}) | |
method = "POST" if post_args is not None else "GET" | |
oauth = self._oauth_request_parameters( | |
url, access_token, all_args, method=method) | |
args.update(oauth) | |
if args: | |
url += "?" + urllib.urlencode(args) | |
callback = self.async_callback(self._on_fitbit_request, callback) | |
http = self.get_auth_http_client() | |
if post_args is not None: | |
http.fetch(url, method="POST", body=urllib.urlencode(args), | |
callback=callback) | |
else: | |
http.fetch(url, callback=callback) | |
def _on_fitbit_request(self, callback, response): | |
if response.error: | |
logging.warning("Error response %s fetching %s", response.error, | |
response.request.url) | |
# print "Error response %s fetching %s", response.error,response.request.url | |
callback(None) | |
return | |
callback(escape.json_decode(response.body)) | |
def _oauth_consumer_token(self): | |
self.require_setting("fitbit_consumer_key", "Fitbit OAuth") | |
self.require_setting("fitbit_consumer_secret", "Fitbit OAuth") | |
return dict( | |
key=self.settings["fitbit_consumer_key"], | |
secret=self.settings["fitbit_consumer_secret"]) | |
def _oauth_get_user(self, access_token, callback): | |
callback = self.async_callback(self._parse_user_response, callback) | |
self.fitbit_request( | |
str("/user/" + access_token["encoded_user_id"] + "/profile"), | |
access_token=access_token, | |
# post_args={"Authorization" : "OAuth"}, | |
callback=callback | |
) | |
def _parse_user_response(self, callback, user): | |
if user: | |
user["username"] = user["encoded_user_id"] | |
callback(user) | |
################### Test Handler Class ################### | |
class FitbitHandler(tornado.web.RequestHandler, fitbit.FitbitMixin): | |
@tornado.web.asynchronous | |
def get(self): | |
oAuthToken = self.get_secure_cookie('fitbit_oauth_token') | |
oAuthSecret = self.get_secure_cookie('fitbit_oauth_secret') | |
userID = self.get_secure_cookie('fitbit_user_id') | |
if self.get_argument('oauth_token', None): | |
self.get_authenticated_user(self.async_callback(self._fitbit_on_auth)) | |
return | |
elif oAuthToken and oAuthSecret: | |
accessToken = { | |
'key': oAuthToken, | |
'secret': oAuthSecret | |
} | |
self.fitbit_request('/users/show', | |
access_token = accessToken, | |
user_id = userID, | |
callback = self.async_callback(self._fitbit_on_user) | |
) | |
return | |
self.authorize_redirect() | |
def _fitbit_on_auth(self, user): | |
if not user: | |
self.clear_all_cookies() | |
raise tornado.web.HTTPError(500, 'Fitbit authentication failed') | |
self.set_secure_cookie('fitbit_user_id', str(user['user_id'])) | |
self.set_secure_cookie('fitbit_oauth_token', user['access_token']['key']) | |
self.set_secure_cookie('fitbit_oauth_secret', user['access_token']['secret']) | |
self.redirect('/') | |
def _fitbit_on_user(self, user): | |
if not user: | |
self.clear_all_cookies() | |
raise tornado.web.HTTPError(500, "Couldn't retrieve user information") | |
self.render('index.html', user=user) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment