FITBIT_CONSUMER_KEY and FITBIT_CONSUMER_SECRET must be defined with the values
given by Fitbit application registration process.
-Extended permissions are supported by defining FITBIT_EXTENDED_PERMISSIONS
-setting, it must be a list of values to request.
-
-By default account id and token expiration time are stored in extra_data
-field, check OAuthBackend class for details on how to extend it.
+By default account id, username and token expiration time are stored in
+extra_data field, check OAuthBackend class for details on how to extend it.
"""
-import logging
-logger = logging.getLogger('buttekicker')
-
-import cgi
-import urllib
+try:
+ from urlparse import parse_qs
+ parse_qs # placate pyflakes
+except ImportError:
+ # fall back for Python 2.5
+ from cgi import parse_qs
from django.conf import settings
from django.utils import simplejson
-from django.contrib.auth import authenticate
-
-from social_auth.backends import BaseOAuth, OAuthBackend, USERNAME
+from oauth2 import Token
+from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, USERNAME
# Fitbit configuration
-FITBIT_SERVER = 'api.fitbit.com'
-FITBIT_REQUEST_TOKEN_URL = 'https://%s/oauth/request_token' % FITBIT_SERVER
-FITBIT_AUTHORIZATION_URL = 'https://%s/oauth/authorize' % FITBIT_SERVER
-FITBIT_ACCESS_TOKEN_URL = 'https://%s/oauth/access_token' % FITBIT_SERVER
-FITBIT_API_URL = 'https://api.%s' % FITBIT_SERVER
+FITBIT_SERVER = 'https://api.fitbit.com'
+FITBIT_REQUEST_TOKEN_URL = '%s/oauth/request_token' % FITBIT_SERVER
+FITBIT_AUTHORIZATION_URL = '%s/oauth/authorize' % FITBIT_SERVER
+FITBIT_ACCESS_TOKEN_URL = '%s/oauth/access_token' % FITBIT_SERVER
EXPIRES_NAME = getattr(settings, 'SOCIAL_AUTH_EXPIRATION', 'expires')
"""Fitbit OAuth authentication backend"""
name = 'fitbit'
# Default extra data to store
- EXTRA_DATA = [('id', 'id'), ('expires', EXPIRES_NAME)]
+ EXTRA_DATA = [('id', 'id'),
+ ('username', 'username'),
+ ('expires', EXPIRES_NAME)]
def get_user_details(self, response):
"""Return user details from Fitbit account"""
- return {USERNAME: response.get('login'),
- 'email': response.get('email') or '',
- 'first_name': response.get('name')}
+ return {USERNAME: response.get('id'),
+ 'email': '',
+ 'first_name': response.get('fullname')}
-class FitbitAuth(BaseOAuth):
- """Fitbit OAuth mechanism"""
- AUTH_BACKEND = FitbitBackend
- def auth_url(self):
- """Returns redirect url"""
- args = {'client_id': settings.FITBIT_CONSUMER_KEY,
- 'redirect_uri': self.redirect_uri}
- if hasattr(settings, 'FITBIT_EXTENDED_PERMISSIONS'):
- args['scope'] = ','.join(settings.FITBIT_EXTENDED_PERMISSIONS)
- args.update(self.auth_extra_arguments())
- return FITBIT_AUTHORIZATION_URL + '?' + urllib.urlencode(args)
-
- def auth_complete(self, *args, **kwargs):
- """Returns user, might be logged in"""
- if 'code' in self.data:
- url = FITBIT_ACCESS_TOKEN_URL + '?' + \
- urllib.urlencode({'client_id': settings.FITBIT_CONSUMER_KEY,
- 'redirect_uri': self.redirect_uri,
- 'client_secret': settings.FITBIT_CONSUMER_SECRET,
- 'code': self.data['code']})
- response = cgi.parse_qs(urllib.urlopen(url).read())
- if response.get('error'):
- error = self.data.get('error') or 'unknown error'
- raise ValueError('Authentication error: %s' % error)
- access_token = response['access_token'][0]
- data = self.user_data(access_token)
- if data is not None:
- if 'error' in data:
- error = self.data.get('error') or 'unknown error'
- raise ValueError('Authentication error: %s' % error)
- data['access_token'] = access_token
- kwargs.update({'response': data, FitbitBackend.name: True})
- return authenticate(*args, **kwargs)
- else:
- error = self.data.get('error') or 'unknown error'
- raise ValueError('Authentication error: %s' % error)
+class FitbitAuth(ConsumerBasedOAuth):
+ """Fitbit OAuth authentication mechanism"""
+ AUTHORIZATION_URL = FITBIT_AUTHORIZATION_URL
+ REQUEST_TOKEN_URL = FITBIT_REQUEST_TOKEN_URL
+ ACCESS_TOKEN_URL = FITBIT_ACCESS_TOKEN_URL
+ SERVER_URL = FITBIT_SERVER
+ AUTH_BACKEND = FitbitBackend
+ SETTINGS_KEY_NAME = 'FITBIT_CONSUMER_KEY'
+ SETTINGS_SECRET_NAME = 'FITBIT_CONSUMER_SECRET'
+
+ def access_token(self, token):
+ """Return request for access token value"""
+ # Fitbit is a bit different - it passes user information along with
+ # the access token, so temporarily store it to vie the user_data
+ # method easy access later in the flow!
+ request = self.oauth_request(token, self.ACCESS_TOKEN_URL)
+ response = self.fetch_response(request)
+ token = Token.from_string(response)
+ params = parse_qs(response)
+
+ token.user_nsid = params['user_nsid'][0] if 'user_nsid' in params \
+ else None
+ token.fullname = params['fullname'][0] if 'fullname' in params \
+ else None
+ token.username = params['username'][0] if 'username' in params \
+ else None
+ return token
def user_data(self, access_token):
"""Loads user data from service"""
- params = {'access_token': access_token}
- url = FITBIT_API_URL + '/user?' + urllib.urlencode(params)
- try:
- return simplejson.load(urllib.urlopen(url))
- except ValueError:
- return None
-
- @classmethod
- def enabled(cls):
- """Return backend enabled status by checking basic settings"""
- return all(hasattr(settings, name) for name in
- ('FITBIT_CONSUMER_KEY',
- 'FITBIT_CONSUMER_SECRET'))
+ return {
+ 'id': access_token.user_nsid,
+ 'username': access_token.username,
+ 'fullname': access_token.fullname,
+ }
# Backend definition