Skip to content

Commit e609747

Browse files
SNOW-1647589: Fix NullPointerException when MFA is enabled in Okta and native Okta authentication is used (#1887)
Co-authored-by: Rafal Kowalski <[email protected]>
1 parent a35e5c7 commit e609747

File tree

5 files changed

+94
-2
lines changed

5 files changed

+94
-2
lines changed

src/main/java/net/snowflake/client/core/SessionUtil.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,17 +1555,23 @@ private static String federatedFlowStep3(
15551555
loginInput.getHttpClientSettingsKey(),
15561556
null);
15571557

1558-
logger.debug("User is authenticated against {}.", loginInput.getAuthenticator());
1559-
15601558
// session token is in the data field of the returned json response
15611559
final JsonNode jsonNode = mapper.readTree(idpResponse);
1560+
boolean isMfaEnabledInOkta = "MFA_REQUIRED".equals(jsonNode.get("status").asText());
1561+
if (isMfaEnabledInOkta) {
1562+
throw new SnowflakeSQLLoggedException(
1563+
null,
1564+
ErrorCode.OKTA_MFA_NOT_SUPPORTED.getMessageCode(),
1565+
SqlState.FEATURE_NOT_SUPPORTED);
1566+
}
15621567
oneTimeToken =
15631568
jsonNode.get("sessionToken") != null
15641569
? jsonNode.get("sessionToken").asText()
15651570
: jsonNode.get("cookieToken").asText();
15661571
} catch (IOException | URISyntaxException ex) {
15671572
handleFederatedFlowError(loginInput, ex);
15681573
}
1574+
logger.debug("User is authenticated against {}.", loginInput.getAuthenticator());
15691575
return oneTimeToken;
15701576
}
15711577

src/main/java/net/snowflake/client/jdbc/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public enum ErrorCode {
8484
OAUTH_CLIENT_CREDENTIALS_FLOW_ERROR(200069, SqlState.CONNECTION_EXCEPTION),
8585
OAUTH_REFRESH_TOKEN_FLOW_ERROR(200070, SqlState.CONNECTION_EXCEPTION),
8686
WORKLOAD_IDENTITY_FLOW_ERROR(200071, SqlState.CONNECTION_EXCEPTION),
87+
OKTA_MFA_NOT_SUPPORTED(200072, SqlState.FEATURE_NOT_SUPPORTED),
8788
FILE_TRANSFER_ERROR(253000, SqlState.SYSTEM_ERROR),
8889
DOWNLOAD_ERROR(253002, SqlState.SYSTEM_ERROR),
8990
UPLOAD_ERROR(253003, SqlState.SYSTEM_ERROR),

src/main/resources/net/snowflake/client/jdbc/jdbc_error_messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ Error message={3}, Extended error info={4}
9090
200069=Error during OAuth Client Credentials authentication: {0}
9191
200070=Error during obtaining OAuth access token using refresh token: {0}
9292
200071=Error during Workload Identity authentication: {0}
93+
200072=MFA enabled in Okta is not supported with this authenticator type. \
94+
Please use 'externalbrowser' instead or a different authentication method.
9395
253000=Error during file transfer: {0}
9496
253003=Error during file upload to stage: {0}
9597
253002=Error during file download to stage: {0}

src/test/java/net/snowflake/client/core/SessionUtilWiremockIT.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.hamcrest.Matchers.equalTo;
55
import static org.hamcrest.Matchers.greaterThan;
66
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
7+
import static org.junit.jupiter.api.Assertions.assertThrows;
78
import static org.junit.jupiter.api.Assertions.fail;
89

910
import java.util.Comparator;
@@ -15,6 +16,7 @@
1516
import net.snowflake.client.jdbc.BaseWiremockTest;
1617
import net.snowflake.client.jdbc.ErrorCode;
1718
import net.snowflake.client.jdbc.SnowflakeSQLException;
19+
import net.snowflake.client.jdbc.SnowflakeSQLLoggedException;
1820
import org.junit.jupiter.api.Tag;
1921
import org.junit.jupiter.api.Test;
2022

@@ -32,6 +34,8 @@ public class SessionUtilWiremockIT extends BaseWiremockTest {
3234
"/wiremock/mappings/session/session-util-wiremock-it-multiple-429-in-federated-step-3.json";
3335
private static final String MULTIPLE_429_IN_FEDERATED_STEP_4 =
3436
"/wiremock/mappings/session/session-util-wiremock-it-multiple-429-in-federated-step-4.json";
37+
private static final String UNSUPPORTED_MFA_IN_FEDERATED_STEP_3 =
38+
"/wiremock/mappings/session/session-util-wiremock-it-unsupported-mfa-in-federated-step-3.json";
3539

3640
/**
3741
* Minimum spacing we expect between consecutive requests, in milliseconds - associated with
@@ -231,6 +235,31 @@ public void testOktaRetriesUntilTimeoutThenRaisesAuthTimeoutExceptionWhen429InFe
231235
ALLOWED_DIFFERENCE_BETWEEN_LOGIN_TIMEOUT_AND_ACTUAL_DURATION_IN_MS);
232236
}
233237

238+
@Test
239+
public void testErrorHandlingWhenOktaReturnsUnsupportedMfaInFederatedStep3() throws Throwable {
240+
// GIVEN
241+
Map<String, Object> placeholders = new HashMap<>();
242+
placeholders.put("{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}", WIREMOCK_HOST_WITH_HTTPS_AND_PORT);
243+
String wireMockMapping =
244+
getWireMockMappingFromFile(UNSUPPORTED_MFA_IN_FEDERATED_STEP_3, placeholders);
245+
importMapping(wireMockMapping);
246+
247+
setCustomTrustStorePropertyPath();
248+
249+
SFLoginInput loginInput = createOktaLoginInputBase();
250+
Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();
251+
252+
SnowflakeSQLLoggedException thrown =
253+
assertThrows(
254+
SnowflakeSQLLoggedException.class,
255+
() -> SessionUtil.openSession(loginInput, connectionPropertiesMap, "ALL"));
256+
assertThat(thrown.getErrorCode(), equalTo(ErrorCode.OKTA_MFA_NOT_SUPPORTED.getMessageCode()));
257+
assertThat(
258+
thrown.getMessage(),
259+
equalTo(
260+
"MFA enabled in Okta is not supported with this authenticator type. Please use 'externalbrowser' instead or a different authentication method."));
261+
}
262+
234263
private void assertThatTotalLoginTimeoutIsKeptWhenRetrying(
235264
List<MinimalServeEvent> requestEvents, long loginTimeout, long allowedDifferenceInMs) {
236265
final int SECONDS_TO_MS_FACTOR = 1000;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"mappings": [
3+
{
4+
"scenarioName": "Unsupported MFA in federated step 3",
5+
"request": {
6+
"method": "POST",
7+
"urlPath": "/session/authenticator-request",
8+
"queryParameters": {
9+
"request_guid": {
10+
"matches": ".*"
11+
}
12+
}
13+
},
14+
"response": {
15+
"status": 200,
16+
"headers": {
17+
"Content-Type": "application/json"
18+
},
19+
"jsonBody": {
20+
"data": {
21+
"tokenUrl": "{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/api/v1/authn",
22+
"ssoUrl": "{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml",
23+
"proofKey": null
24+
},
25+
"code": null,
26+
"message": null,
27+
"success": true
28+
}
29+
}
30+
},
31+
{
32+
"scenarioName": "Unsupported MFA in federated step 3",
33+
"request": {
34+
"method": "POST",
35+
"urlPath": "/okta-stub/vanity-url/api/v1/authn"
36+
},
37+
"response": {
38+
"status": 200,
39+
"headers": {
40+
"Content-Type": "application/json"
41+
},
42+
"jsonBody": {
43+
"status": "MFA_REQUIRED",
44+
"sessionToken": null,
45+
"cookieToken": null
46+
}
47+
}
48+
}
49+
],
50+
"importOptions": {
51+
"duplicatePolicy": "IGNORE",
52+
"deleteAllNotInImport": true
53+
}
54+
}

0 commit comments

Comments
 (0)