Switch to OAuth2 for Fitbit.
This commit is contained in:
141
auth_fitbit.py
141
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 = """
|
||||
<h1>You are now authorized to access the Fitbit API!</h1>
|
||||
<br/><h3>You can close this window</h3>"""
|
||||
self.failure_html = """
|
||||
<h1>ERROR: %s</h1><br/><h3>You can close this window</h3>%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.</br>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 = '<pre>%s</pre>' % ('\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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user