From 871df20e256b83e9c6a1ba6df11e841b004a5261 Mon Sep 17 00:00:00 2001 From: David Szmolka <69192509+sfc-gh-dszmolka@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:10:11 +0100 Subject: [PATCH 1/2] NO-SNOW change jira assignee, labels, priority (#2005) --- .github/workflows/jira_issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jira_issue.yml b/.github/workflows/jira_issue.yml index 92501da8f..c22fa9cf3 100644 --- a/.github/workflows/jira_issue.yml +++ b/.github/workflows/jira_issue.yml @@ -37,7 +37,7 @@ jobs: summary: '${{ github.event.issue.title }}' description: | ${{ github.event.issue.body }} \\ \\ _Created from GitHub Action_ for ${{ github.event.issue.html_url }} - fields: '{ "customfield_11401": {"id": "14723"}, "assignee": {"id": "712020:3c0352b5-63f7-4e26-9afe-38f6f9f0f4c5"}, "components":[{"id":"19281"}] }' + fields: '{ "customfield_11401": {"id": "14723"}, "assignee": {"id": "712020:e1f41916-da57-4fe8-b317-116d5229aa51"}, "components":[{"id":"19281"}], "labels": ["oss"], "priority": {"id": "10001"} }' - name: Update GitHub Issue uses: ./jira/gajira-issue-update From b3a276316189f0611426130c8b30b1ca6ff18733 Mon Sep 17 00:00:00 2001 From: Adam Kolodziejczyk Date: Tue, 17 Dec 2024 10:31:39 +0100 Subject: [PATCH 2/2] SNOW-1739611 add oauth and okta automated tests (#1994) --- .../AuthConnectionParameters.java | 16 ++++ .../{AuthTest.java => AuthTestHelper.java} | 4 +- ...erIT.java => ExternalBrowserLatestIT.java} | 43 ++++----- .../{IdTokenIT.java => IdTokenLatestIT.java} | 34 +++---- .../client/authentication/OauthLatestIT.java | 94 +++++++++++++++++++ .../authentication/OktaAuthLatestIT.java | 74 +++++++++++++++ .../client/core/HttpUtilLatestIT.java | 6 ++ .../snowflake/client/jdbc/ConnectionIT.java | 38 -------- 8 files changed, 231 insertions(+), 78 deletions(-) rename src/test/java/net/snowflake/client/authentication/{AuthTest.java => AuthTestHelper.java} (98%) rename src/test/java/net/snowflake/client/authentication/{ExternalBrowserIT.java => ExternalBrowserLatestIT.java} (58%) rename src/test/java/net/snowflake/client/authentication/{IdTokenIT.java => IdTokenLatestIT.java} (62%) create mode 100644 src/test/java/net/snowflake/client/authentication/OauthLatestIT.java create mode 100644 src/test/java/net/snowflake/client/authentication/OktaAuthLatestIT.java diff --git a/src/test/java/net/snowflake/client/authentication/AuthConnectionParameters.java b/src/test/java/net/snowflake/client/authentication/AuthConnectionParameters.java index b30fc25fe..39b8c1766 100644 --- a/src/test/java/net/snowflake/client/authentication/AuthConnectionParameters.java +++ b/src/test/java/net/snowflake/client/authentication/AuthConnectionParameters.java @@ -34,4 +34,20 @@ static Properties getStoreIDTokenConnectionParameters() { properties.put("CLIENT_STORE_TEMPORARY_CREDENTIAL", true); return properties; } + + static Properties getOktaConnectionParameters() { + Properties properties = getBaseConnectionParameters(); + properties.put("user", SSO_USER); + properties.put("password", SSO_PASSWORD); + properties.put("authenticator", systemGetEnv("SNOWFLAKE_AUTH_TEST_OAUTH_URL")); + return properties; + } + + static Properties getOauthConnectionParameters(String token) { + Properties properties = getBaseConnectionParameters(); + properties.put("user", SSO_USER); + properties.put("authenticator", "OAUTH"); + properties.put("token", token); + return properties; + } } diff --git a/src/test/java/net/snowflake/client/authentication/AuthTest.java b/src/test/java/net/snowflake/client/authentication/AuthTestHelper.java similarity index 98% rename from src/test/java/net/snowflake/client/authentication/AuthTest.java rename to src/test/java/net/snowflake/client/authentication/AuthTestHelper.java index b7ad13052..6a5f8e6e9 100644 --- a/src/test/java/net/snowflake/client/authentication/AuthTest.java +++ b/src/test/java/net/snowflake/client/authentication/AuthTestHelper.java @@ -18,13 +18,13 @@ import net.snowflake.client.jdbc.SnowflakeConnectionV1; import net.snowflake.client.jdbc.SnowflakeSQLException; -public class AuthTest { +public class AuthTestHelper { private Exception exception; private String idToken; private final boolean runAuthTestsManually; - public AuthTest() { + public AuthTestHelper() { this.runAuthTestsManually = Boolean.parseBoolean(System.getenv("RUN_AUTH_TESTS_MANUALLY")); } diff --git a/src/test/java/net/snowflake/client/authentication/ExternalBrowserIT.java b/src/test/java/net/snowflake/client/authentication/ExternalBrowserLatestIT.java similarity index 58% rename from src/test/java/net/snowflake/client/authentication/ExternalBrowserIT.java rename to src/test/java/net/snowflake/client/authentication/ExternalBrowserLatestIT.java index aba5398e0..2fc52213a 100644 --- a/src/test/java/net/snowflake/client/authentication/ExternalBrowserIT.java +++ b/src/test/java/net/snowflake/client/authentication/ExternalBrowserLatestIT.java @@ -11,32 +11,33 @@ import org.junit.jupiter.api.Test; @Tag(TestTags.AUTHENTICATION) -class ExternalBrowserIT { +class ExternalBrowserLatestIT { String login = AuthConnectionParameters.SSO_USER; String password = AuthConnectionParameters.SSO_PASSWORD; - AuthTest authTest = new AuthTest(); + AuthTestHelper authTestHelper = new AuthTestHelper(); @BeforeEach public void setUp() throws IOException { - AuthTest.deleteIdToken(); + AuthTestHelper.deleteIdToken(); } @AfterEach public void tearDown() { - authTest.cleanBrowserProcesses(); - AuthTest.deleteIdToken(); + authTestHelper.cleanBrowserProcesses(); + AuthTestHelper.deleteIdToken(); } @Test void shouldAuthenticateUsingExternalBrowser() throws InterruptedException { Thread provideCredentialsThread = - new Thread(() -> authTest.provideCredentials("success", login, password)); + new Thread(() -> authTestHelper.provideCredentials("success", login, password)); Thread connectThread = - authTest.getConnectAndExecuteSimpleQueryThread(getExternalBrowserConnectionParameters()); + authTestHelper.getConnectAndExecuteSimpleQueryThread( + getExternalBrowserConnectionParameters()); - authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread); - authTest.verifyExceptionIsNotThrown(); + authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread); + authTestHelper.verifyExceptionIsNotThrown(); } @Test @@ -44,11 +45,11 @@ void shouldThrowErrorForMismatchedUsername() throws InterruptedException { Properties properties = getExternalBrowserConnectionParameters(); properties.put("user", "differentUsername"); Thread provideCredentialsThread = - new Thread(() -> authTest.provideCredentials("success", login, password)); - Thread connectThread = authTest.getConnectAndExecuteSimpleQueryThread(properties); + new Thread(() -> authTestHelper.provideCredentials("success", login, password)); + Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties); - authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread); - authTest.verifyExceptionIsThrown( + authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread); + authTestHelper.verifyExceptionIsThrown( "The user you were trying to authenticate as differs from the user currently logged in at the IDP."); } @@ -57,26 +58,26 @@ void shouldThrowErrorForWrongCredentials() throws InterruptedException { String login = "itsnotanaccount.com"; String password = "fakepassword"; Thread provideCredentialsThread = - new Thread(() -> authTest.provideCredentials("fail", login, password)); + new Thread(() -> authTestHelper.provideCredentials("fail", login, password)); Thread connectThread = - authTest.getConnectAndExecuteSimpleQueryThread( + authTestHelper.getConnectAndExecuteSimpleQueryThread( getExternalBrowserConnectionParameters(), "BROWSER_RESPONSE_TIMEOUT=10"); - authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread); - authTest.verifyExceptionIsThrown( + authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread); + authTestHelper.verifyExceptionIsThrown( "JDBC driver encountered communication error. Message: External browser authentication failed within timeout of 10000 milliseconds."); } @Test void shouldThrowErrorForBrowserTimeout() throws InterruptedException { Thread provideCredentialsThread = - new Thread(() -> authTest.provideCredentials("timeout", login, password)); + new Thread(() -> authTestHelper.provideCredentials("timeout", login, password)); Thread connectThread = - authTest.getConnectAndExecuteSimpleQueryThread( + authTestHelper.getConnectAndExecuteSimpleQueryThread( getExternalBrowserConnectionParameters(), "BROWSER_RESPONSE_TIMEOUT=1"); - authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread); - authTest.verifyExceptionIsThrown( + authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread); + authTestHelper.verifyExceptionIsThrown( "JDBC driver encountered communication error. Message: External browser authentication failed within timeout of 1000 milliseconds."); } } diff --git a/src/test/java/net/snowflake/client/authentication/IdTokenIT.java b/src/test/java/net/snowflake/client/authentication/IdTokenLatestIT.java similarity index 62% rename from src/test/java/net/snowflake/client/authentication/IdTokenIT.java rename to src/test/java/net/snowflake/client/authentication/IdTokenLatestIT.java index 61a739e58..4e7f22146 100644 --- a/src/test/java/net/snowflake/client/authentication/IdTokenIT.java +++ b/src/test/java/net/snowflake/client/authentication/IdTokenLatestIT.java @@ -17,34 +17,34 @@ @Tag(TestTags.AUTHENTICATION) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class IdTokenIT { +class IdTokenLatestIT { String login = AuthConnectionParameters.SSO_USER; String password = AuthConnectionParameters.SSO_PASSWORD; - AuthTest authTest = new AuthTest(); + AuthTestHelper authTestHelper = new AuthTestHelper(); private static String firstToken; @BeforeAll public static void globalSetUp() { - AuthTest.deleteIdToken(); + AuthTestHelper.deleteIdToken(); } @AfterEach public void tearDown() { - authTest.cleanBrowserProcesses(); + authTestHelper.cleanBrowserProcesses(); } @Test @Order(1) void shouldAuthenticateUsingExternalBrowserAndSaveToken() throws InterruptedException { Thread provideCredentialsThread = - new Thread(() -> authTest.provideCredentials("success", login, password)); + new Thread(() -> authTestHelper.provideCredentials("success", login, password)); Thread connectThread = - authTest.getConnectAndExecuteSimpleQueryThread(getStoreIDTokenConnectionParameters()); + authTestHelper.getConnectAndExecuteSimpleQueryThread(getStoreIDTokenConnectionParameters()); - authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread); - authTest.verifyExceptionIsNotThrown(); - firstToken = authTest.getIdToken(); + authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread); + authTestHelper.verifyExceptionIsNotThrown(); + firstToken = authTestHelper.getIdToken(); assertThat("Id token was not saved", firstToken, notNullValue()); } @@ -52,23 +52,23 @@ void shouldAuthenticateUsingExternalBrowserAndSaveToken() throws InterruptedExce @Order(2) void shouldAuthenticateUsingTokenWithoutBrowser() { verifyFirstTokenWasSaved(); - authTest.connectAndExecuteSimpleQuery(getStoreIDTokenConnectionParameters(), null); - authTest.verifyExceptionIsNotThrown(); + authTestHelper.connectAndExecuteSimpleQuery(getStoreIDTokenConnectionParameters(), null); + authTestHelper.verifyExceptionIsNotThrown(); } @Test @Order(3) void shouldOpenBrowserAgainWhenTokenIsDeleted() throws InterruptedException { verifyFirstTokenWasSaved(); - AuthTest.deleteIdToken(); + AuthTestHelper.deleteIdToken(); Thread provideCredentialsThread = - new Thread(() -> authTest.provideCredentials("success", login, password)); + new Thread(() -> authTestHelper.provideCredentials("success", login, password)); Thread connectThread = - authTest.getConnectAndExecuteSimpleQueryThread(getStoreIDTokenConnectionParameters()); + authTestHelper.getConnectAndExecuteSimpleQueryThread(getStoreIDTokenConnectionParameters()); - authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread); - authTest.verifyExceptionIsNotThrown(); - String secondToken = authTest.getIdToken(); + authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread); + authTestHelper.verifyExceptionIsNotThrown(); + String secondToken = authTestHelper.getIdToken(); assertThat("Id token was not saved", secondToken, notNullValue()); assertThat("Id token was not updated", secondToken, not(firstToken)); } diff --git a/src/test/java/net/snowflake/client/authentication/OauthLatestIT.java b/src/test/java/net/snowflake/client/authentication/OauthLatestIT.java new file mode 100644 index 000000000..9e4b8f9e9 --- /dev/null +++ b/src/test/java/net/snowflake/client/authentication/OauthLatestIT.java @@ -0,0 +1,94 @@ +package net.snowflake.client.authentication; + +import static net.snowflake.client.authentication.AuthConnectionParameters.getOauthConnectionParameters; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.snowflake.client.category.TestTags; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestTags.AUTHENTICATION) +public class OauthLatestIT { + + AuthTestHelper authTestHelper; + + @BeforeEach + public void setUp() throws IOException { + authTestHelper = new AuthTestHelper(); + } + + @Test + void shouldAuthenticateUsingOauth() throws IOException { + authTestHelper.connectAndExecuteSimpleQuery(getOauthConnectionParameters(getToken()), null); + authTestHelper.verifyExceptionIsNotThrown(); + } + + @Test + void shouldThrowErrorForInvalidToken() { + authTestHelper.connectAndExecuteSimpleQuery(getOauthConnectionParameters("invalidToken"), null); + authTestHelper.verifyExceptionIsThrown("Invalid OAuth access token. "); + } + + @Test + void shouldThrowErrorForMismatchedOauthUsername() throws IOException { + Properties properties = getOauthConnectionParameters(getToken()); + properties.put("user", "differentUsername"); + authTestHelper.connectAndExecuteSimpleQuery(properties, null); + authTestHelper.verifyExceptionIsThrown( + "The user you were trying to authenticate as differs from the user tied to the access token."); + } + + private String getToken() throws IOException { + List data = + Stream.of( + "username=" + System.getenv("SNOWFLAKE_AUTH_TEST_OKTA_USER"), + "password=" + System.getenv("SNOWFLAKE_AUTH_TEST_OKTA_PASS"), + "grant_type=password", + "scope=session:role:" + System.getenv("SNOWFLAKE_AUTH_TEST_ROLE").toLowerCase()) + .collect(Collectors.toList()); + + String auth = + System.getenv("SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_ID") + + ":" + + System.getenv("SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_SECRET"); + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8)); + + URL url = new URL(System.getenv("SNOWFLAKE_AUTH_TEST_OAUTH_URL")); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty( + "Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); + connection.setRequestProperty("Authorization", "Basic " + encodedAuth); + connection.setDoOutput(true); + + try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) { + out.writeBytes(String.join("&", data)); + out.flush(); + } + + int responseCode = connection.getResponseCode(); + assertThat("Failed to get access token, response code: " + responseCode, responseCode, is(200)); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode; + try (InputStream inputStream = connection.getInputStream()) { + jsonNode = mapper.readTree(inputStream); + } + return jsonNode.get("access_token").asText(); + } +} diff --git a/src/test/java/net/snowflake/client/authentication/OktaAuthLatestIT.java b/src/test/java/net/snowflake/client/authentication/OktaAuthLatestIT.java new file mode 100644 index 000000000..3c274aded --- /dev/null +++ b/src/test/java/net/snowflake/client/authentication/OktaAuthLatestIT.java @@ -0,0 +1,74 @@ +package net.snowflake.client.authentication; + +import static net.snowflake.client.authentication.AuthConnectionParameters.SSO_USER; +import static net.snowflake.client.authentication.AuthConnectionParameters.getOktaConnectionParameters; + +import java.io.IOException; +import java.util.Properties; +import net.snowflake.client.category.TestTags; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestTags.AUTHENTICATION) +class OktaAuthLatestIT { + + AuthTestHelper authTestHelper; + + @BeforeEach + public void setUp() throws IOException { + authTestHelper = new AuthTestHelper(); + } + + @Test + void shouldAuthenticateUsingOkta() { + authTestHelper.connectAndExecuteSimpleQuery(getOktaConnectionParameters(), null); + authTestHelper.verifyExceptionIsNotThrown(); + } + + @Test + void shouldAuthenticateUsingOktaWithOktaUsernameParam() { + Properties properties = getOktaConnectionParameters(); + properties.replace("user", "differentUsername"); + authTestHelper.connectAndExecuteSimpleQuery(properties, "oktausername=" + SSO_USER); + authTestHelper.verifyExceptionIsNotThrown(); + } + + @Test + void shouldThrowErrorForWrongOktaCredentials() { + Properties properties = getOktaConnectionParameters(); + properties.put("user", "invalidUsername"); + properties.put("password", "fakepassword"); + authTestHelper.connectAndExecuteSimpleQuery(properties, null); + authTestHelper.verifyExceptionIsThrown( + "JDBC driver encountered communication error. Message: HTTP status=401."); + } + + @Test + void shouldThrowErrorForWrongOktaCredentialsInOktaUsernameParam() { + Properties properties = getOktaConnectionParameters(); + properties.replace("user", "differentUsername"); + authTestHelper.connectAndExecuteSimpleQuery(properties, "oktausername=invalidUser"); + authTestHelper.verifyExceptionIsThrown( + "JDBC driver encountered communication error. Message: HTTP status=401."); + } + + @Test + void shouldThrowErrorForWrongOktaUrl() { + Properties properties = getOktaConnectionParameters(); + properties.put("authenticator", "https://invalid.okta.com/"); + authTestHelper.connectAndExecuteSimpleQuery(properties, null); + authTestHelper.verifyExceptionIsThrown( + "The specified authenticator is not accepted by your Snowflake account configuration. Please contact your local system administrator to get the correct URL to use."); + } + + @Test + @Disabled // todo SNOW-1852279 implement error handling for invalid URL + void shouldThrowErrorForWrongUrlWithoutOktaPath() { + Properties properties = getOktaConnectionParameters(); + properties.put("authenticator", "https://invalid.abc.com/"); + authTestHelper.connectAndExecuteSimpleQuery(properties, null); + authTestHelper.verifyExceptionIsThrown("todo"); + } +} diff --git a/src/test/java/net/snowflake/client/core/HttpUtilLatestIT.java b/src/test/java/net/snowflake/client/core/HttpUtilLatestIT.java index 00c318227..40eab5835 100644 --- a/src/test/java/net/snowflake/client/core/HttpUtilLatestIT.java +++ b/src/test/java/net/snowflake/client/core/HttpUtilLatestIT.java @@ -14,6 +14,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -23,6 +24,11 @@ public class HttpUtilLatestIT { private static final String HANG_WEBSERVER_ADDRESS = "http://localhost:12345/hang"; + @BeforeEach + public void resetHttpClientsCache() { + HttpUtil.httpClient.clear(); + } + /** Added in > 3.14.5 */ @Test public void shouldGetDefaultConnectionAndSocketTimeouts() { diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java index 9d99e01a1..7066e31c7 100644 --- a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java @@ -44,7 +44,6 @@ import java.util.concurrent.Executors; import net.snowflake.client.TestUtil; import net.snowflake.client.annotations.DontRunOnGithubActions; -import net.snowflake.client.annotations.RunOnTestaccountNotOnGithubActions; import net.snowflake.client.category.TestTags; import net.snowflake.client.core.SFSession; import net.snowflake.common.core.SqlState; @@ -837,43 +836,6 @@ public void testResultSetsClosedByStatement() throws SQLException { assertTrue(rs4.isClosed()); } - @Test - @RunOnTestaccountNotOnGithubActions - public void testOKTAConnection() throws Throwable { - Map params = getConnectionParameters(); - Properties properties = new Properties(); - properties.put("user", params.get("ssoUser")); - properties.put("password", params.get("ssoPassword")); - properties.put("ssl", params.get("ssl")); - properties.put("authenticator", "https://snowflakecomputing.okta.com/"); - - DriverManager.getConnection( - String.format( - "jdbc:snowflake://%s.reg.snowflakecomputing.com:%s/", - params.get("account"), params.get("port")), - properties); - } - - @Test - @RunOnTestaccountNotOnGithubActions - public void testOKTAConnectionWithOktauserParam() throws Throwable { - Map params = getConnectionParameters(); - Properties properties = new Properties(); - properties.put("user", "test"); - properties.put("password", params.get("ssoPassword")); - properties.put("ssl", params.get("ssl")); - properties.put( - "authenticator", - String.format( - "https://snowflakecomputing.okta.com;oktausername=%s;", params.get("ssoUser"))); - - DriverManager.getConnection( - String.format( - "jdbc:snowflake://%s.reg.snowflakecomputing.com:%s/", - params.get("account"), params.get("port")), - properties); - } - @Test public void testValidateDefaultParameters() throws Throwable { Map params = getConnectionParameters();