Skip to content

Commit 65a0746

Browse files
sfc-gh-fpawlowskisfc-gh-turbaszek
authored andcommitted
[async] Applied #2538 to async code
1 parent 1a2af3d commit 65a0746

File tree

2 files changed

+112
-14
lines changed

2 files changed

+112
-14
lines changed

src/snowflake/connector/aio/auth/_webbrowser.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,26 @@ async def prepare(
135135
return
136136

137137
print(
138-
"Initiating login request with your identity provider. A "
139-
"browser window should have opened for you to complete the "
140-
"login. If you can't see it, check existing browser windows, "
141-
"or your OS settings. Press CTRL+C to abort and try again..."
138+
"Initiating login request with your identity provider. Press CTRL+C to abort and try again..."
142139
)
143140

144141
logger.debug("step 2: open a browser")
145142
print(f"Going to open: {sso_url} to authenticate...")
146-
if not self._webbrowser.open_new(sso_url):
143+
browser_opened = self._webbrowser.open_new(sso_url)
144+
if browser_opened:
145+
print(
146+
"A browser window should have opened for you to complete the "
147+
"login. If you can't see it, check existing browser windows, "
148+
"or your OS settings."
149+
)
150+
151+
if (
152+
browser_opened
153+
or os.getenv("SNOWFLAKE_AUTH_FORCE_SERVER", "False").lower() == "true"
154+
):
155+
logger.debug("step 3: accept SAML token")
156+
await self._receive_saml_token(conn, socket_connection)
157+
else:
147158
print(
148159
"We were unable to open a browser window for you, "
149160
"please open the url above manually then paste the "
@@ -165,9 +176,6 @@ async def prepare(
165176
},
166177
)
167178
return
168-
else:
169-
logger.debug("step 3: accept SAML token")
170-
await self._receive_saml_token(conn, socket_connection)
171179
finally:
172180
socket_connection.close()
173181

test/unit/aio/test_auth_webbrowser_async.py

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,7 @@ async def test_auth_webbrowser_fail_webbrowser(
312312
)
313313
captured = capsys.readouterr()
314314
assert captured.out == (
315-
"Initiating login request with your identity provider. A browser window "
316-
"should have opened for you to complete the login. If you can't see it, "
317-
"check existing browser windows, or your OS settings. Press CTRL+C to "
315+
"Initiating login request with your identity provider. Press CTRL+C to "
318316
f"abort and try again...\nGoing to open: {REF_SSO_URL if disable_console_login else REF_CONSOLE_LOGIN_SSO_URL} to authenticate...\nWe were unable to open a browser window for "
319317
"you, please open the url above manually then paste the URL you "
320318
"are redirected to into the terminal.\n"
@@ -381,10 +379,10 @@ async def test_auth_webbrowser_fail_webserver(_, capsys, disable_console_login):
381379
)
382380
captured = capsys.readouterr()
383381
assert captured.out == (
384-
"Initiating login request with your identity provider. A browser window "
382+
"Initiating login request with your identity provider. Press CTRL+C to "
383+
f"abort and try again...\nGoing to open: {REF_SSO_URL if disable_console_login else REF_CONSOLE_LOGIN_SSO_URL} to authenticate...\nA browser window "
385384
"should have opened for you to complete the login. If you can't see it, "
386-
"check existing browser windows, or your OS settings. Press CTRL+C to "
387-
f"abort and try again...\nGoing to open: {REF_SSO_URL if disable_console_login else REF_CONSOLE_LOGIN_SSO_URL} to authenticate...\n"
385+
"check existing browser windows, or your OS settings.\n"
388386
)
389387
assert rest._connection.errorhandler.called # an error
390388
assert auth.assertion_content is None
@@ -873,6 +871,98 @@ async def test_auth_webbrowser_socket_reuseport_option_not_set_with_no_flag(
873871
assert auth.assertion_content == ref_token
874872

875873

874+
@pytest.mark.parametrize("force_auth_server", [True, False])
875+
@patch("secrets.token_bytes", return_value=PROOF_KEY)
876+
async def test_auth_webbrowser_force_auth_server(_, monkeypatch, force_auth_server):
877+
"""Authentication by WebBrowser with SNOWFLAKE_AUTH_FORCE_SERVER environment variable."""
878+
ref_token = "MOCK_TOKEN"
879+
rest = _init_rest(REF_SSO_URL, REF_PROOF_KEY, disable_console_login=True)
880+
881+
# Set environment variable
882+
if force_auth_server:
883+
monkeypatch.setenv("SNOWFLAKE_AUTH_FORCE_SERVER", "true")
884+
else:
885+
monkeypatch.delenv("SNOWFLAKE_AUTH_FORCE_SERVER", raising=False)
886+
887+
# mock socket
888+
recv_func = recv_setup([successful_web_callback(ref_token)])
889+
mock_socket_pkg = _init_socket()
890+
891+
# mock webbrowser - simulate browser failing to open
892+
mock_webbrowser = MagicMock()
893+
mock_webbrowser.open_new.return_value = False
894+
895+
# Mock select.select to return socket client
896+
with mock.patch(
897+
"select.select", return_value=([mock_socket_pkg.return_value], [], [])
898+
):
899+
auth = AuthByWebBrowser(
900+
application=APPLICATION,
901+
webbrowser_pkg=mock_webbrowser,
902+
socket_pkg=mock_socket_pkg,
903+
)
904+
905+
if force_auth_server:
906+
# When SNOWFLAKE_AUTH_FORCE_SERVER is true, should continue with server flow even if browser fails
907+
with mock.patch.object(
908+
auth._event_loop,
909+
"sock_accept",
910+
side_effect=_mock_event_loop_sock_accept(),
911+
), mock.patch.object(
912+
auth._event_loop, "sock_sendall", return_value=None
913+
), mock.patch.object(
914+
auth._event_loop,
915+
"sock_recv",
916+
side_effect=_mock_event_loop_sock_recv(recv_func),
917+
):
918+
await auth.prepare(
919+
conn=rest._connection,
920+
authenticator=AUTHENTICATOR,
921+
service_name=SERVICE_NAME,
922+
account=ACCOUNT,
923+
user=USER,
924+
password=PASSWORD,
925+
)
926+
assert not rest._connection.errorhandler.called # no error
927+
assert auth.assertion_content == ref_token
928+
body = {"data": {}}
929+
await auth.update_body(body)
930+
assert body["data"]["TOKEN"] == ref_token
931+
assert body["data"]["AUTHENTICATOR"] == EXTERNAL_BROWSER_AUTHENTICATOR
932+
assert body["data"]["PROOF_KEY"] == REF_PROOF_KEY
933+
else:
934+
# When SNOWFLAKE_AUTH_FORCE_SERVER is false/unset, should fall back to manual URL input
935+
with patch(
936+
"builtins.input",
937+
return_value=f"http://example.com/sso?token={ref_token}",
938+
), mock.patch.object(
939+
auth._event_loop,
940+
"sock_accept",
941+
side_effect=_mock_event_loop_sock_accept(),
942+
), mock.patch.object(
943+
auth._event_loop, "sock_sendall", return_value=None
944+
), mock.patch.object(
945+
auth._event_loop,
946+
"sock_recv",
947+
side_effect=_mock_event_loop_sock_recv(recv_func),
948+
):
949+
await auth.prepare(
950+
conn=rest._connection,
951+
authenticator=AUTHENTICATOR,
952+
service_name=SERVICE_NAME,
953+
account=ACCOUNT,
954+
user=USER,
955+
password=PASSWORD,
956+
)
957+
assert not rest._connection.errorhandler.called # no error
958+
assert auth.assertion_content == ref_token
959+
body = {"data": {}}
960+
await auth.update_body(body)
961+
assert body["data"]["TOKEN"] == ref_token
962+
assert body["data"]["AUTHENTICATOR"] == EXTERNAL_BROWSER_AUTHENTICATOR
963+
assert body["data"]["PROOF_KEY"] == REF_PROOF_KEY
964+
965+
876966
@pytest.mark.parametrize("authenticator", ["EXTERNALBROWSER", "externalbrowser"])
877967
async def test_externalbrowser_authenticator_is_case_insensitive(
878968
monkeypatch, authenticator

0 commit comments

Comments
 (0)