2
2
import logging
3
3
import os
4
4
import urllib .parse as urlparse
5
+ import wsgiref .simple_server
6
+ import wsgiref .util
5
7
from pathlib import Path
8
+ from threading import Thread
6
9
7
10
import aiohttp
8
11
import click
14
17
from ..utils import open_graphical_browser
15
18
from . import base
16
19
from . import dav
20
+ from .google_helpers import _RedirectWSGIApp
21
+ from .google_helpers import _WSGIRequestHandler
17
22
18
23
logger = logging .getLogger (__name__ )
19
24
@@ -54,6 +59,7 @@ def __init__(
54
59
self ._client_id = client_id
55
60
self ._client_secret = client_secret
56
61
self ._token = None
62
+ self ._redirect_uri = None
57
63
58
64
async def request (self , method , path , ** kwargs ):
59
65
if not self ._token :
@@ -69,12 +75,18 @@ async def _save_token(self, token):
69
75
70
76
@property
71
77
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
+ """
73
85
74
86
return OAuth2Session (
75
87
client_id = self ._client_id ,
76
88
token = self ._token ,
77
- redirect_uri = "urn:ietf:wg:oauth:2.0:oob" ,
89
+ redirect_uri = self . _redirect_uri ,
78
90
scope = self .scope ,
79
91
auto_refresh_url = REFRESH_URL ,
80
92
auto_refresh_kwargs = {
@@ -102,7 +114,18 @@ async def _init_token(self):
102
114
# Some times a task stops at this `async`, and another continues the flow.
103
115
# At this point, the user has already completed the flow, but is prompeted
104
116
# 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 } "
105
126
async with self ._session as session :
127
+ # Fail fast if the address is occupied
128
+
106
129
authorization_url , state = session .authorization_url (
107
130
TOKEN_URL ,
108
131
# access_type and approval_prompt are Google specific
@@ -117,14 +140,23 @@ async def _init_token(self):
117
140
logger .warning (str (e ))
118
141
119
142
click .echo ("Follow the instructions on the page." )
120
- code = click .prompt ("Paste obtained code" )
143
+ thread .join ()
144
+ logger .debug ("server handled request!" )
121
145
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 } " )
122
152
self ._token = await session .fetch_token (
123
153
REFRESH_URL ,
124
- code = code ,
154
+ authorization_response = authorization_response ,
125
155
# Google specific extra param used for client authentication:
126
156
client_secret = self ._client_secret ,
127
157
)
158
+ logger .debug (f"token: { self ._token } " )
159
+ local_server .server_close ()
128
160
129
161
# FIXME: Ugly
130
162
await self ._save_token (self ._token )
0 commit comments