Skip to content

Commit 1bebb02

Browse files
committed
Merge branch 'auth-code-flow-in-local-container' into dev
2 parents da61f80 + 770bab3 commit 1bebb02

File tree

2 files changed

+38
-17
lines changed

2 files changed

+38
-17
lines changed

oauth2cli/authcode.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,9 @@ def obtain_auth_code(listen_port, auth_uri=None): # Historically only used in t
3333
).get("code")
3434

3535

36-
def _browse(auth_uri):
36+
def _browse(auth_uri): # throws ImportError, possibly webbrowser.Error in future
3737
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
4939

5040

5141
def _qs2kv(qs):
@@ -130,14 +120,16 @@ def get_port(self):
130120
return self._server.server_address[1]
131121

132122
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.
135127
136128
:param str auth_uri:
137129
If provided, this function will try to open a local browser.
138130
:param int timeout: In seconds. None means wait indefinitely.
139131
: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,
141133
then we will use it to validate incoming response.
142134
:param str welcome_template:
143135
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,
152144
The page will be displayed when authentication encountered error.
153145
Placeholders can be any of these:
154146
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.
155151
:return:
156152
The auth response of the first leg of Auth Code flow,
157153
typically {"code": "...", "state": "..."} or {"error": "...", ...}
@@ -164,8 +160,31 @@ def get_auth_response(self, auth_uri=None, timeout=None, state=None,
164160
logger.debug("Abort by visit %s", abort_uri)
165161
self._server.welcome_page = Template(welcome_template or "").safe_substitute(
166162
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+
169188
self._server.success_template = Template(success_template or
170189
"Authentication completed. You can close this window now.")
171190
self._server.error_template = Template(error_template or

oauth2cli/oauth2.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ def obtain_token_by_browser(
580580
welcome_template=None,
581581
success_template=None,
582582
auth_params=None,
583+
auth_uri_callback=None,
583584
**kwargs):
584585
"""A native app can use this method to obtain token via a local browser.
585586
@@ -637,6 +638,7 @@ def obtain_token_by_browser(
637638
timeout=timeout,
638639
welcome_template=welcome_template,
639640
success_template=success_template,
641+
auth_uri_callback=auth_uri_callback,
640642
)
641643
except PermissionError:
642644
if 0 < listen_port < 1024:

0 commit comments

Comments
 (0)