Skip to content

Commit

Permalink
Merge branch 'master' into SNOW-1848734-prevent-unnecessary-string-cr…
Browse files Browse the repository at this point in the history
…eation-in-structured-types-get-object
  • Loading branch information
sfc-gh-mkubik authored Dec 9, 2024
2 parents 601842e + 84deebc commit f6b74e3
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .github/workflows/parameters_aws_auth_tests.json.gpg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
� �.T��Q�Q���_鞋ftS�o��C���e���ǜ�'�����W����"�#Z8\1j�^?+��h�qGR�,�=���������I�zc8K��=Z��p2��+�kU5�wxƳ
R��W���ܗ*�p��W�\J�v���½|/u��^8��%���J���@L+��.������e��#Z��~���W� �^C�v�}�ے����]4pk�^r'
�hŚ>���;X:�7"@�;~Z�Ezkv�2�nNS0��9��=�U��w�D0�8<����o�������f�o&��3��r�:_��"K�=����:= \9 ��X��(\��J\ʒ'�?��l�w��?�)�t�*[���pP8g�͠���\te�>q��z���4�?�gj�����78*u�-"�*'{ih|�/L�þ #S��b�蕮�i�J$�LF�c*A��B�n��&q=r�jbn�6Ih
Rd�쌔���Di5��X��,���+�zh�ʻ��9��Oe<>[u�>����v��s��cf?c�l�Ջ,rS�rh�Ι�
Expand Down
13 changes: 13 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ timestamps {
}

jobDefinitions.put('JDBC-AIX-Unit', { build job: 'JDBC-AIX-UnitTests', parameters: [ string(name: 'BRANCH', value: scmInfo.GIT_BRANCH ) ] } )
jobDefinitions.put('Test Authentication', {
withCredentials([
string(credentialsId: 'sfctest0-parameters-secret', variable: 'PARAMETERS_SECRET'),
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c', variable: 'NEXUS_PASSWORD')
]) {
sh '''\
|#!/bin/bash
|set -e
|ci/test_authentication.sh
'''.stripMargin()
}
})

stage('Test') {
parallel (jobDefinitions)
}
Expand Down
20 changes: 20 additions & 0 deletions ci/container/test_authentication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash -e

set -o pipefail

export WORKSPACE=${WORKSPACE:-/mnt/workspace}
export SOURCE_ROOT=${SOURCE_ROOT:-/mnt/host}
MVNW_EXE=$SOURCE_ROOT/mvnw

AUTH_PARAMETER_FILE=./.github/workflows/parameters_aws_auth_tests.json
eval $(jq -r '.authtestparams | to_entries | map("export \(.key)=\(.value|tostring)")|.[]' $AUTH_PARAMETER_FILE)

$MVNW_EXE -DjenkinsIT \
-Djava.io.tmpdir=$WORKSPACE \
-Djacoco.skip.instrument=true \
-Dskip.unitTests=true \
-DintegrationTestSuites=AuthenticationTestSuite \
-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \
-Dnot-self-contained-jar \
verify \
--batch-mode --show-version
16 changes: 16 additions & 0 deletions ci/test_authentication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash -e

set -o pipefail
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export WORKSPACE=${WORKSPACE:-/tmp}
export INTERNAL_REPO=nexus.int.snowflakecomputing.com:8086

source $THIS_DIR/scripts/login_internal_docker.sh
gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json "$THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json.gpg"

docker run \
-v $(cd $THIS_DIR/.. && pwd):/mnt/host \
-v $WORKSPACE:/mnt/workspace \
--rm \
nexus.int.snowflakecomputing.com:8086/docker/snowdrivers-test-external-browser-jdbc:1 \
"/mnt/host/ci/container/test_authentication.sh"
1 change: 1 addition & 0 deletions parent-pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
<relocationBase>net/snowflake/client/jdbc/internal</relocationBase>
<shadeBase>net.snowflake.client.jdbc.internal</shadeBase>
<shadeNativeBase>net_snowflake_client_jdbc_internal</shadeNativeBase>
<skip.unitTests>false</skip.unitTests>
<slf4j.version>2.0.13</slf4j.version>
<snowflake.common.version>5.1.4</snowflake.common.version>
<integrationTestSuites>UnitTestSuite</integrationTestSuites>
Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,7 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>${skip.unitTests}</skipTests>
<argLine>--add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/sun.util.calendar=ALL-UNNAMED --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED</argLine>
</configuration>
</plugin>
Expand Down Expand Up @@ -1214,6 +1215,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<test>UnitTestSuite</test>
<skipTests>${skip.unitTests}</skipTests>
</configuration>
<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.snowflake.client.authentication;

import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetEnv;

import java.util.Properties;

public class AuthConnectionParameters {

static final String SSO_USER = systemGetEnv("SNOWFLAKE_AUTH_TEST_BROWSER_USER");
static final String HOST = systemGetEnv("SNOWFLAKE_AUTH_TEST_HOST");
static final String SSO_PASSWORD = systemGetEnv("SNOWFLAKE_AUTH_TEST_OKTA_PASS");

static Properties getBaseConnectionParameters() {
Properties properties = new Properties();
properties.put("host", HOST);
properties.put("port", systemGetEnv("SNOWFLAKE_AUTH_TEST_PORT"));
properties.put("role", systemGetEnv("SNOWFLAKE_AUTH_TEST_ROLE"));
properties.put("account", systemGetEnv("SNOWFLAKE_AUTH_TEST_ACCOUNT"));
properties.put("db", systemGetEnv("SNOWFLAKE_AUTH_TEST_DATABASE"));
properties.put("schema", systemGetEnv("SNOWFLAKE_AUTH_TEST_SCHEMA"));
properties.put("warehouse", systemGetEnv("SNOWFLAKE_AUTH_TEST_WAREHOUSE"));
return properties;
}

static Properties getExternalBrowserConnectionParameters() {
Properties properties = getBaseConnectionParameters();
properties.put("user", SSO_USER);
properties.put("authenticator", "externalbrowser");
return properties;
}

static Properties getStoreIDTokenConnectionParameters() {
Properties properties = getExternalBrowserConnectionParameters();
properties.put("CLIENT_STORE_TEMPORARY_CREDENTIAL", true);
return properties;
}
}
114 changes: 114 additions & 0 deletions src/test/java/net/snowflake/client/authentication/AuthTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package net.snowflake.client.authentication;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import net.snowflake.client.core.SessionUtil;
import net.snowflake.client.jdbc.SnowflakeConnectionV1;
import net.snowflake.client.jdbc.SnowflakeSQLException;

public class AuthTest {

private Exception exception;
private String idToken;
private final boolean runAuthTestsManually;

public AuthTest() {
this.runAuthTestsManually = Boolean.parseBoolean(System.getenv("RUN_AUTH_TESTS_MANUALLY"));
}

public Thread getConnectAndExecuteSimpleQueryThread(Properties props, String sessionParameters) {
return new Thread(() -> connectAndExecuteSimpleQuery(props, sessionParameters));
}

public Thread getConnectAndExecuteSimpleQueryThread(Properties props) {
return new Thread(() -> connectAndExecuteSimpleQuery(props, null));
}

public void verifyExceptionIsThrown(String message) {
assertThat("Expected exception not thrown", this.exception.getMessage(), is(message));
}

public void verifyExceptionIsNotThrown() {
assertThat("Unexpected exception thrown", this.exception, nullValue());
}

public void connectAndProvideCredentials(Thread provideCredentialsThread, Thread connectThread)
throws InterruptedException {
if (runAuthTestsManually) {
connectThread.start();
connectThread.join();
} else {
provideCredentialsThread.start();
connectThread.start();
provideCredentialsThread.join();
connectThread.join();
}
}

public void provideCredentials(String scenario, String login, String password) {
try {
String provideBrowserCredentialsPath = "/externalbrowser/provideBrowserCredentials.js";
ProcessBuilder processBuilder =
new ProcessBuilder("node", provideBrowserCredentialsPath, scenario, login, password);
Process process = processBuilder.start();
process.waitFor(15, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public void cleanBrowserProcesses() {
if (!runAuthTestsManually) {
String cleanBrowserProcessesPath = "/externalbrowser/cleanBrowserProcesses.js";
ProcessBuilder processBuilder = new ProcessBuilder("node", cleanBrowserProcessesPath);
try {
Process process = processBuilder.start();
process.waitFor(15, TimeUnit.SECONDS);
} catch (InterruptedException | IOException e) {
throw new RuntimeException(e);
}
}
}

public static void deleteIdToken() {
SessionUtil.deleteIdTokenCache(
AuthConnectionParameters.HOST, AuthConnectionParameters.SSO_USER);
}

public void connectAndExecuteSimpleQuery(Properties props, String sessionParameters) {
String url = String.format("jdbc:snowflake://%s:%s", props.get("host"), props.get("port"));
if (sessionParameters != null) {
url += "?" + sessionParameters;
}
try (Connection con = DriverManager.getConnection(url, props);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select 1")) {
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
saveToken(con);
} catch (SQLException e) {
this.exception = e;
}
}

private void saveToken(Connection con) throws SnowflakeSQLException {
SnowflakeConnectionV1 sfcon = (SnowflakeConnectionV1) con;
this.idToken = sfcon.getSfSession().getIdToken();
}

public String getIdToken() {
return idToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package net.snowflake.client.authentication;

import static net.snowflake.client.authentication.AuthConnectionParameters.getExternalBrowserConnectionParameters;

import java.io.IOException;
import java.util.Properties;
import net.snowflake.client.category.TestTags;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag(TestTags.AUTHENTICATION)
class ExternalBrowserIT {

String login = AuthConnectionParameters.SSO_USER;
String password = AuthConnectionParameters.SSO_PASSWORD;
AuthTest authTest = new AuthTest();

@BeforeEach
public void setUp() throws IOException {
AuthTest.deleteIdToken();
}

@AfterEach
public void tearDown() {
authTest.cleanBrowserProcesses();
AuthTest.deleteIdToken();
}

@Test
void shouldAuthenticateUsingExternalBrowser() throws InterruptedException {
Thread provideCredentialsThread =
new Thread(() -> authTest.provideCredentials("success", login, password));
Thread connectThread =
authTest.getConnectAndExecuteSimpleQueryThread(getExternalBrowserConnectionParameters());

authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread);
authTest.verifyExceptionIsNotThrown();
}

@Test
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);

authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread);
authTest.verifyExceptionIsThrown(
"The user you were trying to authenticate as differs from the user currently logged in at the IDP.");
}

@Test
void shouldThrowErrorForWrongCredentials() throws InterruptedException {
String login = "itsnotanaccount.com";
String password = "fakepassword";
Thread provideCredentialsThread =
new Thread(() -> authTest.provideCredentials("fail", login, password));
Thread connectThread =
authTest.getConnectAndExecuteSimpleQueryThread(
getExternalBrowserConnectionParameters(), "BROWSER_RESPONSE_TIMEOUT=10");

authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread);
authTest.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));
Thread connectThread =
authTest.getConnectAndExecuteSimpleQueryThread(
getExternalBrowserConnectionParameters(), "BROWSER_RESPONSE_TIMEOUT=1");

authTest.connectAndProvideCredentials(provideCredentialsThread, connectThread);
authTest.verifyExceptionIsThrown(
"JDBC driver encountered communication error. Message: External browser authentication failed within timeout of 1000 milliseconds.");
}
}
Loading

0 comments on commit f6b74e3

Please sign in to comment.