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