Skip to content

Commit 246eb8f

Browse files
authored
SNOW-923398: improve robustness in handling authentication response (#1742)
1 parent 682c14b commit 246eb8f

File tree

3 files changed

+64
-21
lines changed

3 files changed

+64
-21
lines changed

DESCRIPTION.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
1010

1111
- v3.2.1(September 26,2023)
1212

13-
- Fixed a bug where url port and path were ignore in private link oscp retry.
13+
- Fixed a bug where url port and path were ignored in private link oscp retry.
1414
- Added thread safety in telemetry when instantiating multiple connections concurrently.
1515
- Bumped platformdirs dependency from >=2.6.0,<3.9.0 to >=2.6.0,<4.0.0.0 and made necessary changes to allow this.
1616
- Removed the deprecation warning from the vendored urllib3 about urllib3.contrib.pyopenssl deprecation.
17+
- Improved robustness in handling authentication response.
1718

1819
- v3.2.0(September 06,2023)
1920

src/snowflake/connector/auth/_auth.py

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,11 @@ def authenticate(
282282
)
283283

284284
# waiting for MFA authentication
285-
if ret["data"].get("nextAction") in (
285+
if ret["data"] and ret["data"].get("nextAction") in (
286286
"EXT_AUTHN_DUO_ALL",
287287
"EXT_AUTHN_DUO_PUSH_N_PASSCODE",
288288
):
289-
body["inFlightCtx"] = ret["data"]["inFlightCtx"]
289+
body["inFlightCtx"] = ret["data"].get("inFlightCtx")
290290
body["data"]["EXT_AUTHN_DUO_METHOD"] = "push"
291291
self.ret = {"message": "Timeout", "data": {}}
292292

@@ -310,9 +310,13 @@ def post_request_wrapper(self, url, headers, body) -> None:
310310
t.join(timeout=timeout)
311311

312312
ret = self.ret
313-
if ret and ret["data"].get("nextAction") == "EXT_AUTHN_SUCCESS":
313+
if (
314+
ret
315+
and ret["data"]
316+
and ret["data"].get("nextAction") == "EXT_AUTHN_SUCCESS"
317+
):
314318
body = copy.deepcopy(body_template)
315-
body["inFlightCtx"] = ret["data"]["inFlightCtx"]
319+
body["inFlightCtx"] = ret["data"].get("inFlightCtx")
316320
# final request to get tokens
317321
ret = self._rest._post_request(
318322
url,
@@ -321,7 +325,7 @@ def post_request_wrapper(self, url, headers, body) -> None:
321325
timeout=self._rest._connection.login_timeout,
322326
socket_timeout=self._rest._connection.login_timeout,
323327
)
324-
elif not ret or not ret["data"].get("token"):
328+
elif not ret or not ret["data"] or not ret["data"].get("token"):
325329
# not token is returned.
326330
Error.errorhandler_wrapper(
327331
self._rest._connection,
@@ -343,10 +347,10 @@ def post_request_wrapper(self, url, headers, body) -> None:
343347
)
344348
return session_parameters # required for unit test
345349

346-
elif ret["data"].get("nextAction") == "PWD_CHANGE":
350+
elif ret["data"] and ret["data"].get("nextAction") == "PWD_CHANGE":
347351
if callable(password_callback):
348352
body = copy.deepcopy(body_template)
349-
body["inFlightCtx"] = ret["data"]["inFlightCtx"]
353+
body["inFlightCtx"] = ret["data"].get("inFlightCtx")
350354
body["data"]["LOGIN_NAME"] = user
351355
body["data"]["PASSWORD"] = (
352356
auth_instance.password
@@ -411,41 +415,59 @@ def post_request_wrapper(self, url, headers, body) -> None:
411415
)
412416
else:
413417
logger.debug(
414-
"token = %s", "******" if ret["data"]["token"] is not None else "NULL"
418+
"token = %s",
419+
"******"
420+
if ret["data"] and ret["data"].get("token") is not None
421+
else "NULL",
415422
)
416423
logger.debug(
417424
"master_token = %s",
418-
"******" if ret["data"]["masterToken"] is not None else "NULL",
425+
"******"
426+
if ret["data"] and ret["data"].get("masterToken") is not None
427+
else "NULL",
419428
)
420429
logger.debug(
421430
"id_token = %s",
422-
"******" if ret["data"].get("idToken") is not None else "NULL",
431+
"******"
432+
if ret["data"] and ret["data"].get("idToken") is not None
433+
else "NULL",
423434
)
424435
logger.debug(
425436
"mfa_token = %s",
426-
"******" if ret["data"].get("mfaToken") is not None else "NULL",
437+
"******"
438+
if ret["data"] and ret["data"].get("mfaToken") is not None
439+
else "NULL",
427440
)
441+
if not ret["data"]:
442+
Error.errorhandler_wrapper(
443+
None,
444+
None,
445+
Error,
446+
{
447+
"msg": "There is no data in the returning response, please retry the operation."
448+
},
449+
)
428450
self._rest.update_tokens(
429-
ret["data"]["token"],
430-
ret["data"]["masterToken"],
451+
ret["data"].get("token"),
452+
ret["data"].get("masterToken"),
431453
master_validity_in_seconds=ret["data"].get("masterValidityInSeconds"),
432454
id_token=ret["data"].get("idToken"),
433455
mfa_token=ret["data"].get("mfaToken"),
434456
)
435457
self.write_temporary_credentials(
436458
self._rest._host, user, session_parameters, ret
437459
)
438-
if "sessionId" in ret["data"]:
439-
self._rest._connection._session_id = ret["data"]["sessionId"]
440-
if "sessionInfo" in ret["data"]:
441-
session_info = ret["data"]["sessionInfo"]
460+
if ret["data"] and "sessionId" in ret["data"]:
461+
self._rest._connection._session_id = ret["data"].get("sessionId")
462+
if ret["data"] and "sessionInfo" in ret["data"]:
463+
session_info = ret["data"].get("sessionInfo")
442464
self._rest._connection._database = session_info.get("databaseName")
443465
self._rest._connection._schema = session_info.get("schemaName")
444466
self._rest._connection._warehouse = session_info.get("warehouseName")
445467
self._rest._connection._role = session_info.get("roleName")
446-
if "parameters" in ret["data"]:
468+
if ret["data"] and "parameters" in ret["data"]:
447469
session_parameters.update(
448-
{p["name"]: p["value"] for p in ret["data"]["parameters"]}
470+
{p["name"]: p["value"] for p in ret["data"].get("parameters")}
449471
)
450472
self._rest._connection._update_parameters(session_parameters)
451473
return session_parameters

test/unit/test_auth.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import pytest
1313

14+
import snowflake.connector.errors
1415
from snowflake.connector.constants import OCSPMode
1516
from snowflake.connector.description import CLIENT_NAME, CLIENT_VERSION
1617
from snowflake.connector.network import SnowflakeRestful
@@ -102,7 +103,12 @@ def _mock_auth_mfa_rest_response_failure(url, headers, body, **kwargs):
102103
"inFlightCtx": "inFlightCtx",
103104
},
104105
}
105-
106+
elif mock_cnt == 2:
107+
ret = {
108+
"success": True,
109+
"message": None,
110+
"data": None,
111+
}
106112
mock_cnt += 1
107113
return ret
108114

@@ -126,6 +132,12 @@ def _mock_auth_mfa_rest_response_timeout(url, headers, body, **kwargs):
126132
elif mock_cnt == 1:
127133
time.sleep(10) # should timeout while here
128134
ret = {}
135+
elif mock_cnt == 2:
136+
ret = {
137+
"success": True,
138+
"message": None,
139+
"data": None,
140+
}
129141

130142
mock_cnt += 1
131143
return ret
@@ -168,6 +180,14 @@ def test_auth_mfa(next_action: str):
168180
auth.authenticate(auth_instance, account, user, timeout=1)
169181
assert rest._connection.errorhandler.called # error
170182

183+
# ret["data"] is none
184+
with pytest.raises(snowflake.connector.errors.Error):
185+
mock_cnt = 2
186+
rest = _init_rest(application, _mock_auth_mfa_rest_response_timeout)
187+
auth = Auth(rest)
188+
auth_instance = AuthByDefault(password)
189+
auth.authenticate(auth_instance, account, user)
190+
171191

172192
def _mock_auth_password_change_rest_response(url, headers, body, **kwargs):
173193
"""Test successful case."""

0 commit comments

Comments
 (0)