diff --git a/auth_fitbit.py b/auth_fitbit.py index a1a591e..ad01d7a 100755 --- a/auth_fitbit.py +++ b/auth_fitbit.py @@ -1,78 +1,109 @@ #!/usr/bin/env python """ -This was taken, and modified from python-fitbit/gather_keys_cli.py, +This was taken, and modified from python-fitbit/gather_keys_oauth2.py, License reproduced below. -------------------------- -The MIT License +Copyright 2012-2015 ORCAS -Copyright (c) 2007 Leah Culver + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + http://www.apache.org/licenses/LICENSE-2.0 -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -Example consumer. This is not recommended for production. -Instead, you'll want to create your own subclass of OAuthClient -or find one that works with your web framework. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. """ import os import sys +import threading +import traceback import webbrowser import yaml -from fitbit.api import FitbitOauthClient +from base64 import b64encode +import cherrypy +from fitbit.api import FitbitOauth2Client +from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError +from requests_oauthlib import OAuth2Session -def gather_keys(): - # setup - client = FitbitOauthClient(CLIENT_KEY, CLIENT_SECRET) +class OAuth2Server: + def __init__(self, client_id, client_secret, + redirect_uri='http://localhost:8080/'): + """ Initialize the FitbitOauth2Client """ + self.redirect_uri = redirect_uri + self.success_html = """ +

You are now authorized to access the Fitbit API!

+

You can close this window

""" + self.failure_html = """ +

ERROR: %s


You can close this window

%s""" + self.oauth = FitbitOauth2Client(client_id, client_secret) - # get request token - token = client.fetch_request_token() + def browser_authorize(self): + """ + Open a browser to the authorization url and spool up a CherryPy + server to accept the response + """ + url, _ = self.oauth.authorize_token_url(redirect_uri=self.redirect_uri) + # Open the web browser in a new thread for command-line browser support + threading.Timer(1, webbrowser.open, args=(url,)).start() + cherrypy.quickstart(self) - stderr = os.dup(2) - os.close(2) - os.open(os.devnull, os.O_RDWR) - webbrowser.open(client.authorize_token_url()) - os.dup2(stderr, 2) - try: - verifier = raw_input('Verifier: ') - except NameError: - # Python 3.x - verifier = input('Verifier: ') + @cherrypy.expose + def index(self, state, code=None, error=None): + """ + Receive a Fitbit response containing a verification code. Use the code + to fetch the access_token. + """ + error = None + if code: + try: + self.oauth.fetch_access_token(code, self.redirect_uri) + except MissingTokenError: + error = self._fmt_failure( + 'Missing access token parameter.
Please check that ' + 'you are using the correct client_secret') + except MismatchingStateError: + error = self._fmt_failure('CSRF Warning! Mismatching state') + else: + error = self._fmt_failure('Unknown error while authenticating') + # Use a thread to shutdown cherrypy so we can return HTML first + self._shutdown_cherrypy() + return error if error else self.success_html - # get access token - token = client.fetch_access_token(verifier) - return token + def _fmt_failure(self, message): + tb = traceback.format_tb(sys.exc_info()[2]) + tb_html = '
%s
' % ('\n'.join(tb)) if tb else '' + return self.failure_html % (message, tb_html) + def _shutdown_cherrypy(self): + """ Shutdown cherrypy in one second, if it's running """ + if cherrypy.engine.state == cherrypy.engine.states.STARTED: + threading.Timer(1, cherrypy.engine.exit).start() + + +def main(): + if not (len(sys.argv) == 3): + print("Arguments 'client ID', 'client secret' are required") + sys.exit(1) + client_id = sys.argv[1] + client_secret = sys.argv[2] + + server = OAuth2Server(client_id, client_secret) + server.browser_authorize() + + credentials = dict( + client_id=client_id, + client_secret=client_secret, + access_token=server.oauth.token['access_token'], + refresh_token=server.oauth.token['refresh_token']) + yaml.dump(credentials, open('fitbit.yaml', 'w')) if __name__ == '__main__': - if not (len(sys.argv) == 3): - print("Arguments 'client key', 'client secret' are required") - sys.exit(1) - CLIENT_KEY = sys.argv[1] - CLIENT_SECRET = sys.argv[2] - - keys = gather_keys() - credentials = dict( - client_key=CLIENT_KEY, - client_secret=CLIENT_SECRET, - resource_owner_key=keys['oauth_token'].encode('ascii'), - resource_owner_secret=keys['oauth_token_secret'].encode('ascii')) - yaml.dump(credentials, open('fitbit.yaml', 'w')) + main() diff --git a/requirements.txt b/requirements.txt index 5f5c972..60d43c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ PyYAML==3.11 -fitbit==0.1.2 +fitbit==0.2.2 google-api-python-client==1.3.1 httplib2==0.9 oauth2client==1.4.3 @@ -8,9 +8,10 @@ pyasn1==0.1.7 pyasn1-modules==0.0.5 python-dateutil==2.3 requests==2.5.0 -requests-oauthlib==0.4.2 +requests-oauthlib==0.6.1 rsa==3.1.4 simplejson==3.6.5 six==1.8.0 uritemplate==0.6 wsgiref==0.1.2 +cherrypy==5.4.0