Skip to content

Commit f0c9963

Browse files
committed
Added auto-auth flow.
1 parent f5fb54d commit f0c9963

File tree

7 files changed

+468
-148
lines changed

7 files changed

+468
-148
lines changed

gdrivefs/auto_auth.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import logging
2+
import threading
3+
import webbrowser
4+
import time
5+
import urlparse
6+
7+
import SocketServer
8+
import BaseHTTPServer
9+
import cStringIO
10+
11+
import gdrivefs.oauth_authorize
12+
import gdrivefs.conf
13+
14+
_LOGGER = logging.getLogger(__name__)
15+
16+
17+
class _HTTPRequest(BaseHTTPServer.BaseHTTPRequestHandler):
18+
def __init__(self, request_text):
19+
self.rfile = cStringIO.StringIO(request_text)
20+
self.raw_requestline = self.rfile.readline()
21+
self.error_code = self.error_message = None
22+
self.parse_request()
23+
24+
25+
class _WebserverMonitor(object):
26+
def __init__(self, filepath):
27+
self.__filepath = filepath
28+
29+
# Allows us to be in sync when starting and stopping the thread.
30+
self.__server_state_e = threading.Event()
31+
32+
self.__t = threading.Thread(target=self.__thread)
33+
self._port = None
34+
35+
# Signaled when the authorization response is received.
36+
self._request_state_e = threading.Event()
37+
38+
# Will be assigned with the response from Google.
39+
self._http_status_raw = None
40+
41+
def start(self):
42+
self.__t.start()
43+
44+
# Wait for the loop to change the event state.
45+
_LOGGER.debug("Waiting for thread to start.")
46+
self.__server_state_e.wait()
47+
48+
_LOGGER.debug("Server is now running.")
49+
50+
self.__server_state_e.clear()
51+
52+
def stop(self):
53+
assert \
54+
self.__server_state_e is not None, \
55+
"Thread doesn't appear to have ever been started."
56+
57+
assert \
58+
self.__t.is_alive() is True, \
59+
"Thread doesn't appear to be running."
60+
61+
self.__server_state_e.clear()
62+
self.__s.shutdown()
63+
64+
# Wait for the loop to change the event state.
65+
_LOGGER.debug("Waiting for thread to stop.")
66+
self.__server_state_e.wait()
67+
68+
_LOGGER.debug("Server is no longer running.")
69+
70+
self.__server_state_e.clear()
71+
72+
def __thread(self):
73+
"""Where the main loop lives."""
74+
75+
_LOGGER.debug("Webserver is starting.")
76+
77+
monitor = self
78+
79+
# Embedding this because it's so trivial.
80+
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
81+
def do_GET(self):
82+
83+
# We have the first line of the response with the authorization code
84+
# passed as a query argument.
85+
#
86+
# Example:
87+
#
88+
# GET /?code=4/clwm0rESq8sqeC-JxIcfiSdjh2593hLej9CZxAcbe1A HTTP/1.1
89+
#
90+
91+
# Use Python to parse the request. We need to add one newline for the
92+
# line and another for a subsequent blank line to terminate the block
93+
# and conform with the RFC.
94+
hr = _HTTPRequest(self.requestline + "\n\n")
95+
u = urlparse.urlparse(hr.path)
96+
arguments = urlparse.parse_qs(u.query)
97+
98+
# It's not an authorization response. Bail with the same error
99+
# the library would normally send for unhandled requests.
100+
if 'code' not in arguments:
101+
self.send_error(
102+
501,
103+
"Unsupported method ({}): {}".format(
104+
self.command, hr.path))
105+
106+
return
107+
108+
authcode = arguments['code'][0]
109+
_LOGGER.debug("Received authcode [{}]".format(authcode))
110+
111+
monitor._authcode = authcode
112+
113+
monitor._request_state_e.set()
114+
115+
self.send_response(200, message='OK')
116+
117+
self.send_header("Content-type", 'text/html')
118+
self.end_headers()
119+
120+
self.wfile.write("""\
121+
<html>
122+
<head></head>
123+
<body>
124+
GDFS authorization recorded.
125+
</body>
126+
</html>
127+
""")
128+
129+
def log_message(self, format, *args):
130+
pass
131+
132+
133+
class Server(SocketServer.TCPServer):
134+
def server_activate(self, *args, **kwargs):
135+
r = SocketServer.TCPServer.server_activate(self, *args, **kwargs)
136+
137+
# Sniff the port, now that we're running.
138+
monitor._port = self.server_address[1]
139+
140+
return r
141+
142+
# Our little webserver. (0) for the port will automatically assign it
143+
# to some unused port.
144+
binding = ('localhost', 0)
145+
self.__s = Server(binding, Handler)
146+
147+
_LOGGER.debug("Created server.")
148+
149+
# Signal the startup routine that we're starting.
150+
self.__server_state_e.set()
151+
152+
_LOGGER.debug("Running server.")
153+
self.__s.serve_forever()
154+
155+
_LOGGER.debug("Webserver is stopping.")
156+
157+
# Signal the startup routine that we're stopping.
158+
self.__server_state_e.set()
159+
160+
@property
161+
def port(self):
162+
assert \
163+
self._port is not None, \
164+
"Thread hasn't been started or a port hasn't been assigned."
165+
166+
return self._port
167+
168+
@property
169+
def request_state_e(self):
170+
return self._request_state_e
171+
172+
@property
173+
def authcode(self):
174+
return self._authcode
175+
176+
177+
class AutoAuth(object):
178+
"""Knows how to open the browser, authorize the application (prompting the
179+
user if necessary), redirect, receive the response, and store the
180+
credentials.
181+
"""
182+
183+
def get_and_write_creds(self):
184+
_LOGGER.info("Requesting authorization.")
185+
186+
creds_filepath = gdrivefs.conf.Conf.get('auth_cache_filepath')
187+
wm = _WebserverMonitor(creds_filepath)
188+
189+
# Start the webserver.
190+
wm.start()
191+
192+
# Open a browser window to request authorization.
193+
194+
redirect_uri = 'http://localhost:{}'.format(wm.port)
195+
oa = gdrivefs.oauth_authorize.OauthAuthorize(
196+
redirect_uri=redirect_uri)
197+
198+
url = oa.step1_get_auth_url()
199+
_LOGGER.debug("Opening browser: [{}]".format(url))
200+
201+
webbrowser.open(url)
202+
203+
# Wait for the response from Google. We implement this as a loop rather
204+
# than a blocking call so that the user can terminate this with a
205+
# simple break (in contract, blocking on an event makes us
206+
# unresponsive).
207+
208+
try:
209+
while 1:
210+
if wm.request_state_e.is_set() is True:
211+
break
212+
213+
time.sleep(1)
214+
except:
215+
raise
216+
else:
217+
authcode = wm.authcode
218+
finally:
219+
# Shutdown the webserver.
220+
wm.stop()
221+
222+
# Finish the authorization from our side and record.
223+
oa.step2_doexchange(authcode)
224+
225+
_LOGGER.info("Authorization complete.")

gdrivefs/config/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
IS_DEBUG = bool(int(os.environ.get('GD_DEBUG', '0')))
44
DO_LOG_FUSE_MESSAGES = bool(int(os.environ.get('GD_DO_LOG_FUSE_MESSAGES', '0')))
5+
DEFAULT_CREDENTIALS_FILEPATH = os.path.expandvars('$HOME/.gdfs/creds')

gdrivefs/config/log.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,33 @@
66

77
logger = logging.getLogger()
88

9-
if gdrivefs.config.IS_DEBUG:
10-
logger.setLevel(logging.DEBUG)
11-
else:
12-
logger.setLevel(logging.WARNING)
9+
def configure(is_debug=gdrivefs.config.IS_DEBUG):
10+
if is_debug:
11+
logger.setLevel(logging.DEBUG)
12+
else:
13+
logger.setLevel(logging.WARNING)
1314

14-
def _configure_syslog():
15-
facility = logging.handlers.SysLogHandler.LOG_LOCAL0
16-
sh = logging.handlers.SysLogHandler(facility=facility)
17-
formatter = logging.Formatter('GD: %(name)-12s %(levelname)-7s %(message)s')
18-
sh.setFormatter(formatter)
19-
logger.addHandler(sh)
15+
def _configure_syslog():
16+
facility = logging.handlers.SysLogHandler.LOG_LOCAL0
17+
sh = logging.handlers.SysLogHandler(facility=facility)
18+
formatter = logging.Formatter('GD: %(name)-12s %(levelname)-7s %(message)s')
19+
sh.setFormatter(formatter)
20+
logger.addHandler(sh)
2021

21-
def _configure_file():
22-
filepath = os.environ.get('GD_LOG_FILEPATH', '/tmp/gdrivefs.log')
23-
fh = logging.FileHandler(filepath)
24-
formatter = logging.Formatter('%(asctime)s [%(name)s %(levelname)s] %(message)s')
25-
fh.setFormatter(formatter)
26-
logger.addHandler(fh)
22+
def _configure_file():
23+
filepath = os.environ.get('GD_LOG_FILEPATH', '/tmp/gdrivefs.log')
24+
fh = logging.FileHandler(filepath)
25+
formatter = logging.Formatter('%(asctime)s [%(name)s %(levelname)s] %(message)s')
26+
fh.setFormatter(formatter)
27+
logger.addHandler(fh)
2728

28-
def _configure_console():
29-
sh = logging.StreamHandler()
30-
formatter = logging.Formatter('%(asctime)s [%(name)s %(levelname)s] %(message)s')
31-
sh.setFormatter(formatter)
32-
logger.addHandler(sh)
29+
def _configure_console():
30+
sh = logging.StreamHandler()
31+
formatter = logging.Formatter('%(asctime)s [%(name)s %(levelname)s] %(message)s')
32+
sh.setFormatter(formatter)
33+
logger.addHandler(sh)
3334

34-
if gdrivefs.config.IS_DEBUG is True:
35-
# _configure_file()
36-
_configure_console()
37-
else:
38-
_configure_syslog()
35+
if is_debug is True:
36+
_configure_console()
37+
else:
38+
_configure_syslog()

0 commit comments

Comments
 (0)