diff --git a/parent-pom.xml b/parent-pom.xml
index 40a3656dd..b5093fd97 100644
--- a/parent-pom.xml
+++ b/parent-pom.xml
@@ -21,7 +21,7 @@
4.4.16
1.5.6-5
17.0.0
- 9.3
+ 9.6
1.8.1
4.2.0
1.12.655
@@ -60,7 +60,7 @@
3.1.0
5.13.0
2.8.1
- 2.4.9
+ 2.5.1
4.13.2
5.11.1
1.11.1
@@ -69,7 +69,7 @@
2.2.0
4.11.0
4.1.115.Final
- 9.37.3
+ 9.40
11.20.1
0.31.1
1.0-alpha-9-stable-1
diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java
index 3a52b02e4..d9e58e9ab 100644
--- a/src/main/java/net/snowflake/client/core/SessionUtil.java
+++ b/src/main/java/net/snowflake/client/core/SessionUtil.java
@@ -140,6 +140,8 @@ public class SessionUtil {
public static int DEFAULT_CLIENT_PREFETCH_THREADS = 4;
public static int MIN_CLIENT_CHUNK_SIZE = 48;
public static int MAX_CLIENT_CHUNK_SIZE = 160;
+ private static final int DEFAULT_BROWSER_AUTHORIZATION_TIMEOUT_SECONDS = 180;
+
public static Map JVM_PARAMS_TO_PARAMS =
Stream.of(
new String[][] {
@@ -281,7 +283,8 @@ static SFLoginOutput openSession(
"passing clientSecret is required for OAUTH_AUTHORIZATION_CODE_FLOW authentication");
OauthAccessTokenProvider accessTokenProvider =
new AuthorizationCodeFlowAccessTokenProvider(
- new SessionUtilExternalBrowser.DefaultAuthExternalBrowserHandlers());
+ new SessionUtilExternalBrowser.DefaultAuthExternalBrowserHandlers(),
+ DEFAULT_BROWSER_AUTHORIZATION_TIMEOUT_SECONDS);
String oauthAccessToken = accessTokenProvider.getAccessToken(loginInput);
loginInput.setAuthenticator(AuthenticatorType.OAUTH.name());
loginInput.setToken(oauthAccessToken);
diff --git a/src/main/java/net/snowflake/client/core/auth/oauth/AuthorizationCodeFlowAccessTokenProvider.java b/src/main/java/net/snowflake/client/core/auth/oauth/AuthorizationCodeFlowAccessTokenProvider.java
index 32e27e844..bcbb3ac7f 100644
--- a/src/main/java/net/snowflake/client/core/auth/oauth/AuthorizationCodeFlowAccessTokenProvider.java
+++ b/src/main/java/net/snowflake/client/core/auth/oauth/AuthorizationCodeFlowAccessTokenProvider.java
@@ -1,6 +1,6 @@
package net.snowflake.client.core.auth.oauth;
-import static net.snowflake.client.core.SessionUtilExternalBrowser.*;
+import static net.snowflake.client.core.SessionUtilExternalBrowser.AuthExternalBrowserHandlers;
import com.amazonaws.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -52,13 +52,14 @@ public class AuthorizationCodeFlowAccessTokenProvider implements OauthAccessToke
private static final String REDIRECT_URI_ENDPOINT = "/snowflake/oauth-redirect";
public static final String SESSION_ROLE_SCOPE = "session:role";
- public static int AUTHORIZE_REDIRECT_TIMEOUT_MINUTES = 2;
-
private final AuthExternalBrowserHandlers browserHandler;
private final ObjectMapper objectMapper = new ObjectMapper();
+ private final int browserAuthorizationTimeoutSeconds;
- public AuthorizationCodeFlowAccessTokenProvider(AuthExternalBrowserHandlers browserHandler) {
+ public AuthorizationCodeFlowAccessTokenProvider(
+ AuthExternalBrowserHandlers browserHandler, int browserAuthorizationTimeoutSeconds) {
this.browserHandler = browserHandler;
+ this.browserAuthorizationTimeoutSeconds = browserAuthorizationTimeoutSeconds;
}
@Override
@@ -75,21 +76,25 @@ private AuthorizationCode requestAuthorizationCode(
URI authorizeRequestURI = request.toURI();
CompletableFuture codeFuture =
setupRedirectURIServerForAuthorizationCode(loginInput.getRedirectUriPort());
+ logger.debug(
+ "Waiting for authorization code on "
+ + buildRedirectURI(loginInput.getRedirectUriPort())
+ + "...");
letUserAuthorizeViaBrowser(authorizeRequestURI);
- String code = codeFuture.get(AUTHORIZE_REDIRECT_TIMEOUT_MINUTES, TimeUnit.MINUTES);
+ String code = codeFuture.get(this.browserAuthorizationTimeoutSeconds, TimeUnit.SECONDS);
return new AuthorizationCode(code);
} catch (Exception e) {
if (e instanceof TimeoutException) {
- logger.error(
- "Authorization request timed out. Did not receive authorization code back to the redirect URI");
+ throw new RuntimeException(
+ "Authorization request timed out. Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.",
+ e);
}
throw new RuntimeException(e);
}
}
private String exchangeAuthorizationCodeForAccessToken(
- SFLoginInput loginInput, AuthorizationCode authorizationCode, CodeVerifier pkceVerifier)
- throws SFException {
+ SFLoginInput loginInput, AuthorizationCode authorizationCode, CodeVerifier pkceVerifier) {
try {
TokenRequest request = buildTokenRequest(loginInput, authorizationCode, pkceVerifier);
String tokenResponse =
@@ -126,6 +131,7 @@ private static CompletableFuture setupRedirectURIServerForAuthorizationC
String authorizationCode =
extractAuthorizationCodeFromQueryParameters(exchange.getRequestURI().getQuery());
if (!StringUtils.isNullOrEmpty(authorizationCode)) {
+ logger.debug("Received authorization code on redirect URI");
accessTokenFuture.complete(authorizationCode);
httpServer.stop(0);
}
diff --git a/src/test/java/net/snowflake/client/jdbc/OauthAuthorizationCodeFlowLatestIT.java b/src/test/java/net/snowflake/client/jdbc/OauthAuthorizationCodeFlowLatestIT.java
index cbcb8649f..d085e0d7f 100644
--- a/src/test/java/net/snowflake/client/jdbc/OauthAuthorizationCodeFlowLatestIT.java
+++ b/src/test/java/net/snowflake/client/jdbc/OauthAuthorizationCodeFlowLatestIT.java
@@ -1,6 +1,6 @@
package net.snowflake.client.jdbc;
-import static net.snowflake.client.core.SessionUtilExternalBrowser.*;
+import static net.snowflake.client.core.SessionUtilExternalBrowser.AuthExternalBrowserHandlers;
import java.io.IOException;
import java.net.URI;
@@ -26,7 +26,7 @@
@Tag(TestTags.CORE)
public class OauthAuthorizationCodeFlowLatestIT extends BaseWiremockTest {
- public static final String SUCCESSFUL_FLOW_SCENARIO_MAPPINGS =
+ private static final String SUCCESSFUL_FLOW_SCENARIO_MAPPINGS =
"{\n"
+ " \"mappings\": [\n"
+ " {\n"
@@ -81,6 +81,81 @@ public class OauthAuthorizationCodeFlowLatestIT extends BaseWiremockTest {
+ " }\n"
+ "}";
+ public static final String BROWSER_TIMEOUT_SCENARIO_MAPPING =
+ "{\n"
+ + " \"mappings\": [\n"
+ + " {\n"
+ + " \"scenarioName\": \"Browser Authorization timeout\",\n"
+ + " \"request\": {\n"
+ + " \"urlPathPattern\": \"/oauth/authorize.*\",\n"
+ + " \"method\": \"GET\"\n"
+ + " },\n"
+ + " \"response\": {\n"
+ + " \"status\": 200,\n"
+ + " \"fixedDelayMilliseconds\": 5000\n"
+ + " }\n"
+ + " }\n"
+ + " ],\n"
+ + " \"importOptions\": {\n"
+ + " \"duplicatePolicy\": \"IGNORE\",\n"
+ + " \"deleteAllNotInImport\": true\n"
+ + " }\n"
+ + "}";
+
+ public static final String TOKEN_REQUEST_ERROR_SCENARIO_MAPPING =
+ "{\n"
+ + " \"mappings\": [\n"
+ + " {\n"
+ + " \"scenarioName\": \"OAuth token request error\",\n"
+ + " \"requiredScenarioState\": \"Started\",\n"
+ + " \"newScenarioState\": \"Authorized\",\n"
+ + " \"request\": {\n"
+ + " \"urlPathPattern\": \"/oauth/authorize.*\",\n"
+ + " \"method\": \"GET\"\n"
+ + " },\n"
+ + " \"response\": {\n"
+ + " \"status\": 200\n"
+ + " },\n"
+ + " \"serveEventListeners\": [\n"
+ + " {\n"
+ + " \"name\": \"webhook\",\n"
+ + " \"parameters\": {\n"
+ + " \"method\": \"GET\",\n"
+ + " \"url\": \"http://localhost:8001/snowflake/oauth-redirect?code=123\"\n"
+ + " }\n"
+ + " }\n"
+ + " ]\n"
+ + " },\n"
+ + " {\n"
+ + " \"scenarioName\": \"OAuth token request error\",\n"
+ + " \"requiredScenarioState\": \"Authorized\",\n"
+ + " \"newScenarioState\": \"Token request error\",\n"
+ + " \"request\": {\n"
+ + " \"urlPathPattern\": \"/oauth/token-request.*\",\n"
+ + " \"method\": \"POST\",\n"
+ + " \"headers\": {\n"
+ + " \"Authorization\": {\n"
+ + " \"contains\": \"Basic\"\n"
+ + " },\n"
+ + " \"Content-Type\": {\n"
+ + " \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"bodyPatterns\": [{\n"
+ + " \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8001%2Fsnowflake%2Foauth-redirect&code_verifier=\"\n"
+ + " }]\n"
+ + " },\n"
+ + " \"response\": {\n"
+ + " \"status\": 400\n"
+ + " }\n"
+ + " }\n"
+ + " ],\n"
+ + " \"importOptions\": {\n"
+ + " \"duplicatePolicy\": \"IGNORE\",\n"
+ + " \"deleteAllNotInImport\": true\n"
+ + " }\n"
+ + "}";
+
private static final Logger log =
LoggerFactory.getLogger(OauthAuthorizationCodeFlowLatestIT.class);
@@ -93,13 +168,41 @@ public void successfulFlowScenario() throws SFException {
SFLoginInput loginInput = createLoginInputStub();
OauthAccessTokenProvider provider =
- new AuthorizationCodeFlowAccessTokenProvider(wiremockProxyRequestBrowserHandler);
+ new AuthorizationCodeFlowAccessTokenProvider(wiremockProxyRequestBrowserHandler, 30);
String accessToken = provider.getAccessToken(loginInput);
Assertions.assertTrue(StringUtils.isNotBlank(accessToken));
Assertions.assertEquals("access-token-123", accessToken);
}
+ @Test
+ public void browserTimeoutFlowScenario() {
+ importMapping(BROWSER_TIMEOUT_SCENARIO_MAPPING);
+ SFLoginInput loginInput = createLoginInputStub();
+
+ OauthAccessTokenProvider provider =
+ new AuthorizationCodeFlowAccessTokenProvider(wiremockProxyRequestBrowserHandler, 1);
+ RuntimeException e =
+ Assertions.assertThrows(RuntimeException.class, () -> provider.getAccessToken(loginInput));
+ Assertions.assertEquals(
+ "Authorization request timed out. Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.",
+ e.getMessage());
+ }
+
+ @Test
+ public void tokenRequestErrorFlowScenario() {
+ importMapping(TOKEN_REQUEST_ERROR_SCENARIO_MAPPING);
+ SFLoginInput loginInput = createLoginInputStub();
+
+ OauthAccessTokenProvider provider =
+ new AuthorizationCodeFlowAccessTokenProvider(wiremockProxyRequestBrowserHandler, 30);
+ RuntimeException e =
+ Assertions.assertThrows(RuntimeException.class, () -> provider.getAccessToken(loginInput));
+ Assertions.assertEquals(
+ "net.snowflake.client.jdbc.SnowflakeSQLException: JDBC driver encountered communication error. Message: HTTP status=400.",
+ e.getMessage());
+ }
+
private SFLoginInput createLoginInputStub() {
SFLoginInput loginInputStub = new SFLoginInput();
loginInputStub.setServerUrl(String.format("http://%s:%d/", WIREMOCK_HOST, wiremockHttpPort));
diff --git a/thin_public_pom.xml b/thin_public_pom.xml
index eeb42d0f0..b8527cade 100644
--- a/thin_public_pom.xml
+++ b/thin_public_pom.xml
@@ -55,11 +55,11 @@
3.1.0
5.13.0
2.8.1
- 2.4.9
+ 2.5.1
1.15.3
2.2.0
4.1.115.Final
- 9.37.3
+ 9.40.0
UTF-8
UTF-8
2.0.13