mirror of
https://github.com/sstent/containers.git
synced 2025-12-06 08:01:51 +00:00
159 lines
5.9 KiB
Python
159 lines
5.9 KiB
Python
#!/usr/bin/env python
|
|
|
|
"""
|
|
Note: This file was adapted from the unoffiicial Python Fitbit client Git repo:
|
|
https://raw.githubusercontent.com/orcasgit/python-fitbit/master/gather_keys_oauth2.py
|
|
"""
|
|
import cherrypy
|
|
import os
|
|
import sys
|
|
import threading
|
|
import traceback
|
|
import webbrowser
|
|
import json
|
|
|
|
from urllib.parse import urlparse
|
|
# import urllib.parse as urlparse
|
|
from base64 import b64encode
|
|
from fitbit.api import Fitbit, FitbitOauth2Client
|
|
from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError
|
|
|
|
CLIENT_DETAILS_FILE = 'client_details.json' # configuration for for the client
|
|
USER_DETAILS_FILE = 'user_details.json' # user details file
|
|
|
|
|
|
class OAuth2Server:
|
|
def __init__(self, client_id, client_secret,
|
|
redirect_uri='http://127.0.0.1:8080/'):
|
|
""" Initialize the FitbitOauth2Client """
|
|
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.fitbit = Fitbit(
|
|
client_id,
|
|
client_secret,
|
|
redirect_uri=redirect_uri,
|
|
timeout=10,
|
|
)
|
|
|
|
self.redirect_uri = redirect_uri
|
|
self.oauth = FitbitOauth2Client(client_id, client_secret)
|
|
|
|
|
|
def headless_authorize(self):
|
|
"""
|
|
Authorize without a display using only TTY.
|
|
"""
|
|
url, _ = self.oauth.authorize_token_url(redirect_uri=self.redirect_uri)
|
|
# Ask the user to open this url on a system with browser
|
|
print('\n-------------------------------------------------------------------------')
|
|
print('\t\tOpen the below URL in your browser\n')
|
|
print(url)
|
|
print('\n-------------------------------------------------------------------------\n')
|
|
print('NOTE: After authenticating on Fitbit website, you will redirected to a URL which ')
|
|
print('throws an ERROR. This is expected! Just copy the full redirected here.\n')
|
|
redirected_url = input('Full redirected URL: ')
|
|
params = urlparse.parse_qs(urlparse.urlparse(redirected_url).query)
|
|
print(params['code'][0])
|
|
self.authenticate_code(code=params['code'][0])
|
|
|
|
def browser_authorize(self):
|
|
"""
|
|
Open a browser to the authorization url and spool up a CherryPy
|
|
server to accept the response
|
|
"""
|
|
url, _ = self.fitbit.client.authorize_token_url()
|
|
# Open the web browser in a new thread for command-line browser support
|
|
threading.Timer(1, webbrowser.open, args=(url,)).start()
|
|
print()
|
|
print('URL for authenticating is:')
|
|
print(url)
|
|
print()
|
|
|
|
# Same with redirect_uri hostname and port.
|
|
urlparams = urlparse(self.redirect_uri)
|
|
cherrypy.config.update({'server.socket_host': '0.0.0.0',
|
|
'server.socket_port': urlparams.port})
|
|
|
|
cherrypy.quickstart(self)
|
|
|
|
def authenticate_code(self, code=None):
|
|
"""
|
|
Final stage of authentication using the code from Fitbit.
|
|
"""
|
|
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')
|
|
|
|
@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.fitbit.client.fetch_access_token(code)
|
|
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
|
|
|
|
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()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if not (len(sys.argv) == 3):
|
|
print("Arguments: client_id and client_secret")
|
|
sys.exit(1)
|
|
|
|
client_id = sys.argv[1]
|
|
client_secret = sys.argv[2]
|
|
server = OAuth2Server(client_id, client_secret)
|
|
# server.headless_authorize()
|
|
server.browser_authorize()
|
|
|
|
profile = server.fitbit.user_profile_get()
|
|
print('You are authorized to access data for the user: {}'.format(
|
|
profile['user']['fullName']))
|
|
|
|
print('TOKEN\n=====\n')
|
|
for key, value in server.fitbit.client.session.token.items():
|
|
print('{} = {}'.format(key, value))
|
|
|
|
print("Writing client details to file for usage on next collection.")
|
|
client_details = {'client_id': client_id, 'client_secret': client_secret} # Details of application
|
|
with open(CLIENT_DETAILS_FILE, 'w') as f:
|
|
json.dump(client_details, f)
|
|
|
|
print("Writing user details to file for usage on next collection.")
|
|
with open(USER_DETAILS_FILE, 'w') as f:
|
|
json.dump(server.fitbit.client.session.token, f)
|
|
|