@@ -33,19 +33,9 @@ def obtain_auth_code(listen_port, auth_uri=None): # Historically only used in t
33
33
).get ("code" )
34
34
35
35
36
- def _browse (auth_uri ):
36
+ def _browse (auth_uri ): # throws ImportError, possibly webbrowser.Error in future
37
37
import webbrowser # Lazy import. Some distro may not have this.
38
- controller = webbrowser .get () # Get a default controller
39
- # Some Linux Distro does not setup default browser properly,
40
- # so we try to explicitly use some popular browser, if we found any.
41
- for browser in ["chrome" , "firefox" , "safari" , "windows-default" ]:
42
- try :
43
- controller = webbrowser .get (browser )
44
- break
45
- except webbrowser .Error :
46
- pass # This browser is not installed. Try next one.
47
- logger .info ("Please open a browser on THIS device to visit: %s" % auth_uri )
48
- controller .open (auth_uri )
38
+ return webbrowser .open (auth_uri ) # Use default browser. Customizable by $BROWSER
49
39
50
40
51
41
def _qs2kv (qs ):
@@ -130,14 +120,16 @@ def get_port(self):
130
120
return self ._server .server_address [1 ]
131
121
132
122
def get_auth_response (self , auth_uri = None , timeout = None , state = None ,
133
- welcome_template = None , success_template = None , error_template = None ):
134
- """Wait and return the auth response, or None when timeout.
123
+ welcome_template = None , success_template = None , error_template = None ,
124
+ auth_uri_callback = None ,
125
+ ):
126
+ """Wait and return the auth response. Raise RuntimeError when timeout.
135
127
136
128
:param str auth_uri:
137
129
If provided, this function will try to open a local browser.
138
130
:param int timeout: In seconds. None means wait indefinitely.
139
131
:param str state:
140
- You may provide the state you used in auth_url ,
132
+ You may provide the state you used in auth_uri ,
141
133
then we will use it to validate incoming response.
142
134
:param str welcome_template:
143
135
If provided, your end user will see it instead of the auth_uri.
@@ -152,6 +144,10 @@ def get_auth_response(self, auth_uri=None, timeout=None, state=None,
152
144
The page will be displayed when authentication encountered error.
153
145
Placeholders can be any of these:
154
146
https://tools.ietf.org/html/rfc6749#section-5.2
147
+ :param callable auth_uri_callback:
148
+ A function with the shape of lambda auth_uri: ...
149
+ When a browser was unable to be launch, this function will be called,
150
+ so that the app could tell user to manually visit the auth_uri.
155
151
:return:
156
152
The auth response of the first leg of Auth Code flow,
157
153
typically {"code": "...", "state": "..."} or {"error": "...", ...}
@@ -164,8 +160,31 @@ def get_auth_response(self, auth_uri=None, timeout=None, state=None,
164
160
logger .debug ("Abort by visit %s" , abort_uri )
165
161
self ._server .welcome_page = Template (welcome_template or "" ).safe_substitute (
166
162
auth_uri = auth_uri , abort_uri = abort_uri )
167
- if auth_uri :
168
- _browse (welcome_uri if welcome_template else auth_uri )
163
+ if auth_uri : # Now attempt to open a local browser to visit it
164
+ _uri = welcome_uri if welcome_template else auth_uri
165
+ logger .info ("Open a browser on this device to visit: %s" % _uri )
166
+ browser_opened = False
167
+ try :
168
+ browser_opened = _browse (_uri )
169
+ except : # Had to use broad except, because the potential
170
+ # webbrowser.Error is purposely undefined outside of _browse().
171
+ # Absorb and proceed. Because browser could be manually run elsewhere.
172
+ logger .exception ("_browse(...) unsuccessful" )
173
+ if not browser_opened :
174
+ if not auth_uri_callback :
175
+ logger .warning (
176
+ "Found no browser in current environment. "
177
+ "If this program is being run inside a container "
178
+ "which has access to host network "
179
+ "(i.e. started by `docker run --net=host -it ...`), "
180
+ "you can use browser on host to visit the following link. "
181
+ "Otherwise, this auth attempt would either timeout "
182
+ "(current timeout setting is {timeout}) "
183
+ "or be aborted by CTRL+C. Auth URI: {auth_uri}" .format (
184
+ auth_uri = _uri , timeout = timeout ))
185
+ else : # Then it is the auth_uri_callback()'s job to inform the user
186
+ auth_uri_callback (_uri )
187
+
169
188
self ._server .success_template = Template (success_template or
170
189
"Authentication completed. You can close this window now." )
171
190
self ._server .error_template = Template (error_template or
0 commit comments