22import logging
33import os
44import urllib .parse as urlparse
5+ import wsgiref .simple_server
6+ import wsgiref .util
57from pathlib import Path
8+ from threading import Thread
69
710import aiohttp
811import click
1417from ..utils import open_graphical_browser
1518from . import base
1619from . import dav
20+ from .google_helpers import _RedirectWSGIApp
21+ from .google_helpers import _WSGIRequestHandler
1722
1823logger = logging .getLogger (__name__ )
1924
@@ -54,6 +59,7 @@ def __init__(
5459 self ._client_id = client_id
5560 self ._client_secret = client_secret
5661 self ._token = None
62+ self ._redirect_uri = None
5763
5864 async def request (self , method , path , ** kwargs ):
5965 if not self ._token :
@@ -69,12 +75,18 @@ async def _save_token(self, token):
6975
7076 @property
7177 def _session (self ):
72- """Return a new OAuth session for requests."""
78+ """Return a new OAuth session for requests.
79+
80+ Accesses the self.redirect_uri field (str): the URI to redirect
81+ authentication to. Should be a loopback address for a local server that
82+ follows the process detailed in
83+ https://developers.google.com/identity/protocols/oauth2/native-app.
84+ """
7385
7486 return OAuth2Session (
7587 client_id = self ._client_id ,
7688 token = self ._token ,
77- redirect_uri = "urn:ietf:wg:oauth:2.0:oob" ,
89+ redirect_uri = self . _redirect_uri ,
7890 scope = self .scope ,
7991 auto_refresh_url = REFRESH_URL ,
8092 auto_refresh_kwargs = {
@@ -102,7 +114,18 @@ async def _init_token(self):
102114 # Some times a task stops at this `async`, and another continues the flow.
103115 # At this point, the user has already completed the flow, but is prompeted
104116 # for a second one.
117+ wsgi_app = _RedirectWSGIApp ("Successfully obtained token." )
118+ wsgiref .simple_server .WSGIServer .allow_reuse_address = False
119+ host = "127.0.0.1"
120+ local_server = wsgiref .simple_server .make_server (
121+ host , 0 , wsgi_app , handler_class = _WSGIRequestHandler
122+ )
123+ thread = Thread (target = local_server .handle_request )
124+ thread .start ()
125+ self ._redirect_uri = f"http://{ host } :{ local_server .server_port } "
105126 async with self ._session as session :
127+ # Fail fast if the address is occupied
128+
106129 authorization_url , state = session .authorization_url (
107130 TOKEN_URL ,
108131 # access_type and approval_prompt are Google specific
@@ -117,14 +140,23 @@ async def _init_token(self):
117140 logger .warning (str (e ))
118141
119142 click .echo ("Follow the instructions on the page." )
120- code = click .prompt ("Paste obtained code" )
143+ thread .join ()
144+ logger .debug ("server handled request!" )
121145
146+ # Note: using https here because oauthlib is very picky that
147+ # OAuth 2.0 should only occur over https.
148+ authorization_response = wsgi_app .last_request_uri .replace (
149+ "http" , "https" , 1
150+ )
151+ logger .debug (f"authorization_response: { authorization_response } " )
122152 self ._token = await session .fetch_token (
123153 REFRESH_URL ,
124- code = code ,
154+ authorization_response = authorization_response ,
125155 # Google specific extra param used for client authentication:
126156 client_secret = self ._client_secret ,
127157 )
158+ logger .debug (f"token: { self ._token } " )
159+ local_server .server_close ()
128160
129161 # FIXME: Ugly
130162 await self ._save_token (self ._token )
0 commit comments