strava_auth.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/env python
  2. """
  3. A basic authorization server. Run this with your Strava Client ID and Client Secret and access from your
  4. browser (because the Strava OAuth page uses javascript) in order to get a resulting access token. That access
  5. token can then be used to initialize a Client that can read (and/or write) data from the Strava API.
  6. You must run this from a virtualenv that has stravalib installed.
  7. Example Usage:
  8. (env) shell$ python strava_auth.py --port=8000 --client-id=123 --client-secret=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
  9. Then connect in your browser to http://localhost:8000/
  10. The redirected response (from Strava) will deliver a code that can be exchanged for a token. The access token will be
  11. presented in the browser after the exchange. Save this value into your config (e.g. into your test.ini) to run
  12. functional tests.
  13. """
  14. import os
  15. import sys
  16. import argparse
  17. import logging
  18. from socketserver import ThreadingTCPServer
  19. from http.server import SimpleHTTPRequestHandler
  20. from urllib.parse import urlparse, parse_qs
  21. from stravalib import Client
  22. if getattr(sys, 'frozen', False):
  23. # If we're running as a pyinstaller bundle
  24. SCRIPT_DIR = os.path.dirname(sys.executable)
  25. else:
  26. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
  27. class StravaAuthHTTPServer(ThreadingTCPServer):
  28. def __init__(self, server_address, RequestHandlerClass, client_id, client_secret):
  29. ThreadingTCPServer.__init__(self, server_address, RequestHandlerClass)
  30. self.logger = logging.getLogger('auth_server.http')
  31. self.client_id = client_id
  32. self.client_secret = client_secret
  33. class RequestHandler(SimpleHTTPRequestHandler):
  34. def do_GET(self):
  35. client = Client()
  36. if self.path.startswith('/authorization'):
  37. self.send_response(200)
  38. self.send_header("Content-type", "text/html")
  39. self.end_headers()
  40. code = parse_qs(urlparse(self.path).query).get('code')
  41. if code:
  42. code = code[0]
  43. token_response = client.exchange_code_for_token(client_id=self.server.client_id,
  44. client_secret=self.server.client_secret,
  45. code=code)
  46. access_token = token_response['access_token']
  47. refresh_token = token_response['refresh_token']
  48. expires_at = token_response['expires_at']
  49. self.server.logger.info("Exchanged code {} for access token {}".format(code, access_token))
  50. self.wfile.write("<html><head><script>function download() {".encode())
  51. self.wfile.write("var text = `{}\n".format(self.server.client_id).encode())
  52. self.wfile.write("{}\n".format(self.server.client_secret).encode())
  53. self.wfile.write("{}\n".format(access_token).encode())
  54. self.wfile.write("{}\n".format(refresh_token).encode())
  55. self.wfile.write("{}\n`;".format(expires_at).encode())
  56. self.wfile.write("var pom = document.createElement('a');".encode())
  57. self.wfile.write("pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));".encode())
  58. self.wfile.write("pom.setAttribute('download', 'strava_token.txt');".encode())
  59. self.wfile.write("pom.style.display = 'none'; document.body.appendChild(pom);".encode())
  60. self.wfile.write("pom.click(); document.body.removeChild(pom); }".encode())
  61. self.wfile.write("</script></head><body>Access token obtained successfully<br><br>".encode())
  62. self.wfile.write("<button onclick=\"download()\">Download</button></body></html>".encode())
  63. with open('%s/strava_token.txt' % SCRIPT_DIR, 'w') as f:
  64. f.write(str(self.server.client_id) + '\n')
  65. f.write(self.server.client_secret + '\n')
  66. f.write(access_token + '\n')
  67. f.write(refresh_token + '\n')
  68. f.write(str(expires_at) + '\n')
  69. else:
  70. self.server.logger.error("No code param received.")
  71. self.wfile.write("ERROR: No code param recevied.\n".encode())
  72. else:
  73. url = client.authorization_url(client_id=self.server.client_id,
  74. redirect_uri='http://localhost:{}/authorization'.format(self.server.server_address[1]),
  75. scope=['activity:write'])
  76. self.send_response(302)
  77. self.send_header("Content-type", "text/plain")
  78. self.send_header('Location', url)
  79. self.end_headers()
  80. self.wfile.write("Redirect to URL: {}\n".format(url).encode())
  81. def main(port, client_id, client_secret):
  82. logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(message)s')
  83. logger = logging.getLogger('auth_responder')
  84. logger.info('Listening on localhost:%s' % port)
  85. server = StravaAuthHTTPServer(('', port), RequestHandler, client_id=client_id, client_secret=client_secret)
  86. server.serve_forever()
  87. if __name__ == "__main__":
  88. parser = argparse.ArgumentParser(description="Run a local web server to receive authorization responses from Strava.")
  89. parser.add_argument('-p', '--port', help='Which port to bind to',
  90. action='store', type=int, default=8000)
  91. parser.add_argument('--client-id', help='Strava API Client ID',
  92. action='store', type=int, required=True)
  93. parser.add_argument('--client-secret', help='Strava API Client Secret',
  94. action='store', required=True)
  95. args = parser.parse_args()
  96. main(port=args.port, client_id=args.client_id, client_secret=args.client_secret)