| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Handle gdata spreadsheet authentication.""" |
| |
| import BaseHTTPServer |
| import httplib |
| import os |
| import Queue |
| import threading |
| import socket |
| import time |
| |
| import gdata.gauth |
| import gdata.spreadsheets.client |
| |
| # Local port of http daemon for receiving the refresh token from the redirected |
| # url returned by authentication server. |
| START_PORT = 12345 |
| ACCESS_CODE_Q = Queue.Queue() |
| |
| # Token storage |
| TOKEN_STORAGE_NAME = '%s/.spreadsheets.oauth2.dat' % os.getenv('HOME') |
| |
| # Application's CLIENT_ID and SECRET for OAuthToken which is created from |
| # google's api console's API access - https://code.google.com/apis/console |
| CLIENT_ID = '657833351030.apps.googleusercontent.com' |
| CLIENT_SECRET = 'h72FzPdzfbN3I4U3M3l1DSiT' |
| |
| USER_AGENT = 'Pressure Calibration Data Collector' |
| SCOPES = 'https://spreadsheets.google.com/feeds/ https://docs.google.com/feeds/' |
| RETRY = 10 |
| |
| |
| class AuthenticationHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| """Authentication handler class.""" |
| |
| def do_QUIT(self): |
| """Do QUIT reuqest.""" |
| self.send_response(200) |
| self.end_headers() |
| self.server.stop = True |
| |
| def do_GET(self): # pylint: disable=g-bad-name |
| """Do GET request.""" |
| self.send_response(200) |
| self.send_header('Content-type', 'text/html') |
| self.end_headers() |
| self.port = START_PORT |
| query_str = self.path[2:] |
| if query_str.startswith('code='): |
| self.wfile.write('Spreadsheet authentication complete, ' |
| 'please back to command prompt.') |
| ACCESS_CODE_Q.put(query_str[5:]) |
| return |
| if query_str.startswith('error='): |
| print "Ouch, error: '%s'." % query_str[6:] |
| raise Exception("Exception during approval process: '%s'." % |
| query_str[6:]) |
| |
| def log_message(self, format, *args): # pylint: disable=redefined-builtin |
| pass |
| |
| |
| class AuthenticationHTTPD(BaseHTTPServer.HTTPServer): |
| """ Handle redirected response from authentication server.""" |
| def serve_forever(self, poll_interval=0.5): |
| poll_interval = poll_interval |
| self.stop = False |
| while not self.stop: |
| self.handle_request() |
| |
| |
| class AuthenticationServer(threading.Thread): |
| """Authentication http server thread.""" |
| |
| def __init__(self): |
| self.authserver = None |
| self.started = False |
| threading.Thread.__init__(self) |
| |
| def run(self): |
| for ports in range(START_PORT, START_PORT + RETRY): |
| self.port = ports |
| try: |
| self.authserver = AuthenticationHTTPD(('', self.port), |
| AuthenticationHandler) |
| self.started = True |
| self.authserver.serve_forever() |
| return |
| |
| except socket.error, se: |
| # port is busy, there must be another instance running... |
| if self.port == START_PORT + RETRY - 1: |
| raise se |
| else: |
| continue # keep trying new ports |
| |
| except KeyboardInterrupt: |
| print '^C received, shutting down authentication server.' |
| self.stop = True |
| return # out of retry loop |
| |
| except Exception: |
| self.stop = True |
| return # out of retry loop |
| |
| def get_port(self): |
| """Get the running port number.""" |
| for retry in xrange(RETRY): |
| if self.started and self.authserver: |
| return self.port |
| else: |
| time.sleep(retry * 2) |
| continue |
| |
| |
| class SpreadsheetAuthorizer: |
| """ Handle gdata api authentication for spreadsheet client.""" |
| def __init__(self): |
| self.lock = threading.Lock() |
| self.refresh_token = None |
| self.redirect_url = None |
| self.httpd_auth = None |
| self.port = None |
| |
| def _start_server(self): |
| """Start http daemon for handling refresh token.""" |
| if not self.httpd_auth or not self.httpd_auth.isAlive(): |
| ### Starting webserver if necessary |
| self.httpd_auth = AuthenticationServer() |
| self.httpd_auth.start() |
| self.port = self.httpd_auth.get_port() |
| |
| def _stop_server(self): |
| """Stop http daemon.""" |
| if self.httpd_auth: |
| try: |
| conn = httplib.HTTPConnection('localhost:%s' % self.port) |
| conn.request('QUIT', '/') |
| conn.getresponse() |
| time.sleep(1) |
| del self.httpd_auth |
| self.httpd_auth = None |
| except Exception, e: |
| print "Failed to quit local auth server...'%s'." % e |
| return |
| |
| def authorize(self, ss_client): |
| """Authorize the spreadsheet client. |
| |
| @param ss_client: spreadsheet client |
| """ |
| self._read_refresh_token() |
| token = gdata.gauth.OAuth2Token(CLIENT_ID, |
| CLIENT_SECRET, |
| SCOPES, |
| USER_AGENT, |
| refresh_token = self.refresh_token) |
| try: |
| if not self.refresh_token: |
| self._start_server() |
| token = gdata.gauth.OAuth2Token(CLIENT_ID, |
| CLIENT_SECRET, |
| SCOPES, |
| USER_AGENT) |
| redirect_url = 'http://localhost:' + str(self.port) |
| url = token.generate_authorize_url(redirect_url) |
| print ('\nPlease open the following URL and use @chromium.org' |
| 'account for authentication and authorization of the' |
| 'spreadsheet access:\n' + url) |
| print 'Waiting for you to authenticate...' |
| while ACCESS_CODE_Q.empty(): |
| time.sleep(.25) |
| access_code = ACCESS_CODE_Q.get() |
| print 'ACCESS CODE is ' + access_code |
| token.get_access_token(access_code) |
| self.refresh_token = token.refresh_token |
| print 'REFRESH TOKEN is ' + self.refresh_token |
| self._write_refresh_token() |
| self._stop_server() |
| token.authorize(ss_client) |
| return True |
| except IOError: |
| print "ERROR!!!!!!!!!!!!!!!!" |
| return False |
| |
| def _read_refresh_token(self): |
| """Read refresh token from storage.""" |
| try: |
| self.lock.acquire() |
| token_file = open(TOKEN_STORAGE_NAME, 'r') |
| self.refresh_token = token_file.readline().strip() |
| token_file.close() |
| self.lock.release() |
| return self.refresh_token |
| except IOError: |
| self.lock.release() |
| return None |
| |
| def _write_refresh_token(self): |
| """Write refresh token into storage.""" |
| try: |
| self.lock.acquire() |
| token_descriptor = os.open(TOKEN_STORAGE_NAME, |
| os.O_CREAT | os.O_WRONLY | os.O_TRUNC, |
| 0600) |
| token_file = os.fdopen(token_descriptor, 'w') |
| token_file.write(self.refresh_token + '\n') |
| token_file.close() |
| self.lock.release() |
| except (IOError, OSError): |
| self.lock.release() |
| print 'Error, can not write refresh token\n' |