diff --git a/src/main/java/net/snowflake/client/core/SFLoginInput.java b/src/main/java/net/snowflake/client/core/SFLoginInput.java index 67a4b3aca..927638f99 100644 --- a/src/main/java/net/snowflake/client/core/SFLoginInput.java +++ b/src/main/java/net/snowflake/client/core/SFLoginInput.java @@ -50,6 +50,8 @@ public class SFLoginInput { private String privateKeyFilePwd; private String inFlightCtx; // Opaque string sent for Snowsight account activation + private boolean disableConsoleLogin = true; + // Additional headers to add for Snowsight. Map additionalHttpHeadersForSnowsight; @@ -64,6 +66,15 @@ SFLoginInput setServerUrl(String serverUrl) { return this; } + public boolean getDisableConsoleLogin() { + return disableConsoleLogin; + } + + SFLoginInput setDisableConsoleLogin(boolean disableConsoleLogin) { + this.disableConsoleLogin = disableConsoleLogin; + return this; + } + String getDatabaseName() { return databaseName; } diff --git a/src/main/java/net/snowflake/client/core/SFSession.java b/src/main/java/net/snowflake/client/core/SFSession.java index 7a48f1f11..9c10ebf1d 100644 --- a/src/main/java/net/snowflake/client/core/SFSession.java +++ b/src/main/java/net/snowflake/client/core/SFSession.java @@ -507,7 +507,12 @@ public synchronized void open() throws SFException, SnowflakeSQLException { .setApplication((String) connectionPropertiesMap.get(SFSessionProperty.APPLICATION)) .setServiceName(getServiceName()) .setOCSPMode(getOCSPMode()) - .setHttpClientSettingsKey(httpClientSettingsKey); + .setHttpClientSettingsKey(httpClientSettingsKey) + .setDisableConsoleLogin( + connectionPropertiesMap.get(SFSessionProperty.DISABLE_CONSOLE_LOGIN) != null + ? getBooleanValue( + connectionPropertiesMap.get(SFSessionProperty.DISABLE_CONSOLE_LOGIN)) + : true); // Enable or disable OOB telemetry based on connection parameter. Default is disabled. // The value may still change later when session parameters from the server are read. diff --git a/src/main/java/net/snowflake/client/core/SFSessionProperty.java b/src/main/java/net/snowflake/client/core/SFSessionProperty.java index 97a921c01..2a7687aa7 100644 --- a/src/main/java/net/snowflake/client/core/SFSessionProperty.java +++ b/src/main/java/net/snowflake/client/core/SFSessionProperty.java @@ -72,6 +72,7 @@ public enum SFSessionProperty { MAX_HTTP_RETRIES("maxHttpRetries", false, Integer.class), ENABLE_PUT_GET("enablePutGet", false, Boolean.class), + DISABLE_CONSOLE_LOGIN("disableConsoleLogin", false, Boolean.class), PUT_GET_MAX_RETRIES("putGetMaxRetries", false, Integer.class), diff --git a/src/main/java/net/snowflake/client/core/SessionUtil.java b/src/main/java/net/snowflake/client/core/SessionUtil.java index c2d1acc5d..3c416a8d4 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtil.java +++ b/src/main/java/net/snowflake/client/core/SessionUtil.java @@ -55,6 +55,7 @@ public class SessionUtil { private static final String SF_PATH_LOGIN_REQUEST = "/session/v1/login-request"; private static final String SF_PATH_TOKEN_REQUEST = "/session/token-request"; public static final String SF_PATH_AUTHENTICATOR_REQUEST = "/session/authenticator-request"; + public static final String SF_PATH_CONSOLE_LOGIN_REQUEST = "/console/login"; public static final String SF_QUERY_SESSION_DELETE = "delete"; diff --git a/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java b/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java index b18d1ebcb..f322e3b98 100644 --- a/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java +++ b/src/main/java/net/snowflake/client/core/SessionUtilExternalBrowser.java @@ -13,6 +13,7 @@ import java.net.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.util.*; import net.snowflake.client.jdbc.ErrorCode; @@ -215,6 +216,34 @@ private String getSSOUrl(int port) throws SFException, SnowflakeSQLException { } } + private String getConsoleLoginUrl(int port) throws SFException { + try { + proofKey = generateProofKey(); + String serverUrl = loginInput.getServerUrl(); + + URIBuilder consoleLoginUriBuilder = new URIBuilder(serverUrl); + consoleLoginUriBuilder.setPath(SessionUtil.SF_PATH_CONSOLE_LOGIN_REQUEST); + consoleLoginUriBuilder.addParameter("login_name", loginInput.getUserName()); + consoleLoginUriBuilder.addParameter("browser_mode_redirect_port", Integer.toString(port)); + consoleLoginUriBuilder.addParameter("proof_key", proofKey); + + String consoleLoginUrl = consoleLoginUriBuilder.build().toURL().toString(); + + logger.debug("console login url: {}", consoleLoginUrl); + + return consoleLoginUrl; + } catch (Exception ex) { + throw new SFException(ex, ErrorCode.INTERNAL_ERROR, ex.getMessage()); + } + } + + private String generateProofKey() { + SecureRandom secureRandom = new SecureRandom(); + byte[] randomness = new byte[32]; + secureRandom.nextBytes(randomness); + return Base64.getEncoder().encodeToString(randomness); + } + /** * Authenticate * @@ -227,13 +256,26 @@ void authenticate() throws SFException, SnowflakeSQLException { // main procedure int port = this.getLocalPort(ssocket); logger.debug("Listening localhost:{}", port); - String ssoUrl = getSSOUrl(port); - this.handlers.output( - "Initiating login request with your identity provider. A " - + "browser window should have opened for you to complete the " - + "login. If you can't see it, check existing browser windows, " - + "or your OS settings. Press CTRL+C to abort and try again..."); - this.handlers.openBrowser(ssoUrl); + + if (loginInput.getDisableConsoleLogin()) { + // Access GS to get SSO URL + String ssoUrl = getSSOUrl(port); + this.handlers.output( + "Initiating login request with your identity provider. A " + + "browser window should have opened for you to complete the " + + "login. If you can't see it, check existing browser windows, " + + "or your OS settings. Press CTRL+C to abort and try again..."); + this.handlers.openBrowser(ssoUrl); + } else { + // Multiple SAML way to do authentication via console login + String consoleLoginUrl = getConsoleLoginUrl(port); + this.handlers.output( + "Initiating login request with your identity provider(s). A " + + "browser window should have opened for you to complete the " + + "login. If you can't see it, check existing browser windows, " + + "or your OS settings. Press CTRL+C to abort and try again..."); + this.handlers.openBrowser(consoleLoginUrl); + } while (true) { Socket socket = ssocket.accept(); // start accepting the request diff --git a/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java b/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java index 6b0d15c24..0092b925f 100644 --- a/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java +++ b/src/test/java/net/snowflake/client/core/SessionUtilExternalBrowserTest.java @@ -229,6 +229,7 @@ private SFLoginInput initMockLoginInput() { .thenReturn(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name()); when(loginInput.getAccountName()).thenReturn("testaccount"); when(loginInput.getUserName()).thenReturn("testuser"); + when(loginInput.getDisableConsoleLogin()).thenReturn(true); return loginInput; } } diff --git a/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java b/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java index 22e846e5b..6a945fcc9 100644 --- a/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java +++ b/src/test/java/net/snowflake/client/jdbc/SSOConnectionTest.java @@ -288,6 +288,7 @@ private SFLoginInput initMockLoginInput() { .thenReturn(ClientAuthnDTO.AuthenticatorType.EXTERNALBROWSER.name()); when(loginInput.getAccountName()).thenReturn("testaccount"); when(loginInput.getUserName()).thenReturn("testuser"); + when(loginInput.getDisableConsoleLogin()).thenReturn(true); return loginInput; }