diff --git a/pom.xml b/pom.xml index ad2156bc0..89740e8f8 100644 --- a/pom.xml +++ b/pom.xml @@ -50,19 +50,21 @@ xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - - xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse - xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance - NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default) - reqExternalSetup - For tests requiring external setup (excluded by default) - clientCertAuth - - For tests requiring client certificate authentication setup (excluded by default) + NTLM - - - For tests using NTLM Authentication mode (excluded by default) + kerberos - - - - For tests using Kerberos authentication (excluded by default) + reqExternalSetup - For tests requiring external setup (excluded by default) + clientCertAuth - - For tests requiring client certificate authentication + setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - - + requireSecret - For tests requiring setting up secrets manually - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default testing enabled with SQL Server 2019 (SQLv15) --> - xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth - + xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos,requireSecret 6.0.0 - 4.3.6 + 4.9.2 1.4.3 5.0.0 4.9.3 @@ -232,7 +234,30 @@ test - + + + central + https://sqlclientdrivers.pkgs.visualstudio.com/public/_packaging/mssql-jdbc/maven/v1 + + true + + + true + + + + + + central + https://sqlclientdrivers.pkgs.visualstudio.com/public/_packaging/mssql-jdbc/maven/v1 + + true + + + true + + + jre8 diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java index dbd270451..ca71de69e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -177,17 +177,25 @@ static void setAEConnectionString(String serverName, String url, String protocol if (!isSqlLinux() && null != serverName && null != url && null != protocol) { enclaveProperties = "serverName=" + serverName + ";" + Constants.ENCLAVE_ATTESTATIONURL + "=" + url + ";" + Constants.ENCLAVE_ATTESTATIONPROTOCOL + "=" + protocol; - AETestConnectionString = connectionString + ";sendTimeAsDateTime=false" + ";columnEncryptionSetting=enabled" - + ";" + enclaveProperties; + AETestConnectionString = connectionString + ";sendTimeAsDateTime=false;columnEncryptionSetting=enabled;" + + enclaveProperties; // show progress if testing multiple servers if (enclaveServer.length > 1) { System.out.println("Testing enclave: " + enclaveProperties); } + + // remove the password in connection string + // this is necessary as updateDataSource will only use 1st occurrence + String password = getConfiguredProperty("enclaveServerPassword"); + AETestConnectionString = TestUtils.addOrOverrideProperty(AETestConnectionString, Constants.PASSWORD, + password); } else { - AETestConnectionString = connectionString + ";sendTimeAsDateTime=false" - + ";columnEncryptionSetting=enabled"; + AETestConnectionString = connectionString + ";sendTimeAsDateTime=false;columnEncryptionSetting=enabled;"; } + + // TODO: update AE test servers to support + AETestConnectionString += ";encrypt=false;trustServerCertificate=true;"; } @BeforeAll diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java index d8552ad3d..6dd177ab5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java @@ -57,6 +57,7 @@ @Tag(Constants.xSQLv12) @Tag(Constants.xAzureSQLDB) @Tag(Constants.xAzureSQLDW) +@Tag(Constants.reqExternalSetup) public class BulkCopySendTemporalDataTypesAsStringAETest extends AESetup { static String inputFile = "BulkCopyCSVSendTemporalDataTypesAsStringForBulkCopy.csv"; static String encoding = "UTF-8"; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java index 7fd94b498..0b9f0a4f0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java @@ -51,6 +51,7 @@ @Tag(Constants.xSQLv12) @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) +@Tag(Constants.reqExternalSetup) public class CallableStatementTest extends AESetup { private static String multiStatementsProcedure = AbstractSQLGenerator diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java index 9b136afd3..b142cd1dd 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/EnclaveTest.java @@ -42,6 +42,7 @@ @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) @Tag(Constants.reqExternalSetup) +@Tag(Constants.requireSecret) public class EnclaveTest extends AESetup { /** * Tests basic connection. diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java index d4c2771c5..e54011872 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java @@ -29,10 +29,6 @@ import com.azure.identity.ClientSecretCredentialBuilder; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -64,6 +60,7 @@ @Tag(Constants.xSQLv12) @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) +@Tag(Constants.reqExternalSetup) public class JDBCEncryptionDecryptionTest extends AESetup { private boolean nullable = false; @@ -105,8 +102,7 @@ public void testJksName(String serverName, String url, String protocol) throws E public void testAkvName(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - SQLServerColumnEncryptionAzureKeyVaultProvider akv = new SQLServerColumnEncryptionAzureKeyVaultProvider( - applicationClientID, applicationKey); + SQLServerColumnEncryptionAzureKeyVaultProvider akv = akvProvider; String keystoreName = "keystoreName"; akv.setName(keystoreName); assertTrue(akv.getName().equals(keystoreName), "AKV name: " + akv.getName() + " keystoreName: " + keystoreName); @@ -136,6 +132,7 @@ public void testBadJks(String serverName, String url, String protocol) throws Ex @SuppressWarnings("unused") @ParameterizedTest @MethodSource("enclaveParams") + @Tag(Constants.requireSecret) public void testBadAkvCallback(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); @@ -209,8 +206,7 @@ public void testJksBadEncryptColumnEncryptionKey(String serverName, String url, public void testAkvBadEncryptColumnEncryptionKey(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - SQLServerColumnEncryptionAzureKeyVaultProvider akv = null; - akv = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey); + SQLServerColumnEncryptionAzureKeyVaultProvider akv = akvProvider; // null encryptedColumnEncryptionKey try { @@ -288,8 +284,7 @@ public void testJksDecryptColumnEncryptionKey(String serverName, String url, Str public void testAkvDecryptColumnEncryptionKey(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); - SQLServerColumnEncryptionAzureKeyVaultProvider akv = null; - akv = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey); + SQLServerColumnEncryptionAzureKeyVaultProvider akv = akvProvider; // null akvpath try { @@ -2268,6 +2263,7 @@ void testNumerics(SQLServerStatement stmt, String cekName, String[][] table, Str @ParameterizedTest @MethodSource("enclaveParams") @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testAkvNameWithAuthCallback(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); @@ -2286,6 +2282,7 @@ public void testAkvNameWithAuthCallback(String serverName, String url, String pr @ParameterizedTest @MethodSource("enclaveParams") @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testAkvNameWithTokenCredential(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); @@ -2307,6 +2304,7 @@ public void testAkvNameWithTokenCredential(String serverName, String url, String @ParameterizedTest @MethodSource("enclaveParams") @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testAkvBadEncryptColumnEncryptionKeyWithAuthCallback(String serverName, String url, String protocol) throws Exception { setAEConnectionString(serverName, url, protocol); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java index 7eec04b70..88cfa2593 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MSITest.java @@ -18,6 +18,10 @@ import java.util.Map; import java.util.Properties; +import com.azure.identity.CredentialUnavailableException; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -43,6 +47,7 @@ */ @RunWith(JUnitPlatform.class) @Tag(Constants.MSI) +@Tag(Constants.requireSecret) public class MSITest extends AESetup { /* @@ -365,11 +370,20 @@ private void testNumericAKV(String connStr) throws SQLException { @BeforeEach public void registerAKVProvider() throws Exception { + try { // unregister the custom providers registered in AESetup SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); Map map = new HashMap(); - if (null != applicationClientID && null != applicationKey) { + if (null != akvProviderManagedClientId) { + System.out.println("ManagedIdentityCredential: registering akvProvider"); + + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(credential); + map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + System.out.println("ManagedIdentityCredential: registered akvProvider"); + } else if (null != applicationClientID && null != applicationKey) { File file = null; try { file = new File(Constants.MSSQL_JDBC_PROPERTIES); @@ -386,8 +400,12 @@ public void registerAKVProvider() throws Exception { file.delete(); } } + System.out.println("applicationClientID: registered akvProvider"); } SQLServerConnection.registerColumnEncryptionKeyStoreProviders(map); + } catch (Exception e) { + System.out.println("MSITest registerAKVProvider exception: " +e.getMessage()); + } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java index b597c2052..3852b606e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java @@ -35,6 +35,8 @@ import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionKeyStoreProvider; @@ -58,6 +60,7 @@ @Tag(Constants.xSQLv12) @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) +@Tag(Constants.reqExternalSetup) public class MultiUserAKVTest extends AESetup { private static Map requiredKeyStoreProvider = new HashMap<>(); @@ -99,7 +102,7 @@ public static void testCleanUp() throws Exception { tempMap.put(Constants.CUSTOM_KEYSTORE_NAME, jksProvider); } - if (null != akvProvider && null != applicationClientID && null != applicationKey) { + if (null != akvProvider) { tempMap.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); } @@ -111,7 +114,7 @@ public static void testCleanUp() throws Exception { @Test @Tag(Constants.reqExternalSetup) public void decryptedCekIsCachedDuringDecryption() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (null == provider) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -151,8 +154,9 @@ public void decryptedCekIsCachedDuringDecryption() throws Exception { @Test @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void signatureVerificationResultIsCachedDuringVerification() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -184,7 +188,7 @@ public void signatureVerificationResultIsCachedDuringVerification() throws Excep @Test @Tag(Constants.reqExternalSetup) public void cekCacheEntryIsEvictedAfterTtlExpires() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -212,7 +216,7 @@ public void cekCacheEntryIsEvictedAfterTtlExpires() throws Exception { @Test @Tag(Constants.reqExternalSetup) public void cekCacheShouldBeDisabledWhenAkvProviderIsRegisteredGlobally() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -256,8 +260,9 @@ public void cekCacheShouldBeDisabledWhenAkvProviderIsRegisteredGlobally() throws @Test @Tag(Constants.reqExternalSetup) + @Tag(Constants.requireSecret) public void testLocalCekCacheIsScopedToProvider() throws Exception { - SQLServerColumnEncryptionAzureKeyVaultProvider provider = createAKVProvider(); + SQLServerColumnEncryptionAzureKeyVaultProvider provider = akvProvider; if (provider == null) { fail(TestResource.getResource("R_AKVProviderNull")); @@ -268,6 +273,8 @@ public void testLocalCekCacheIsScopedToProvider() throws Exception { fail((new MessageFormat(TestResource.getResource("R_objectNullOrEmpty"))).format(msgArg)); } + SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); + SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); Map providerMap = new HashMap(); providerMap.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); @@ -325,7 +332,7 @@ public void testLocalCekCacheIsScopedToProvider() throws Exception { } fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLServerException ex) { - assertTrue(ex.getMessage().contains("AADSTS700016")); + org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("AADSTS700016"), ex.getMessage()); } } finally { dropObject(AETestConnectionString, "TABLE", customProviderTableName); @@ -608,31 +615,4 @@ private int getCacheSize(String methodName, return (int) method.invoke(provider); } - - private SQLServerColumnEncryptionAzureKeyVaultProvider createAKVProvider() throws Exception { - - SQLServerColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = null; - - if (null != applicationClientID && null != applicationKey) { - File file = null; - try { - file = new File(Constants.MSSQL_JDBC_PROPERTIES); - try (OutputStream os = new FileOutputStream(file);) { - Properties props = new Properties(); - // Append to the list of hardcoded endpoints - props.setProperty(Constants.AKV_TRUSTED_ENDPOINTS_KEYWORD, ";vault.azure.net"); - props.store(os, ""); - } - azureKeyVaultProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, - applicationKey); - - } finally { - if (null != file) { - file.delete(); - } - } - } - - return azureKeyVaultProvider; - } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java index 5343b1fcd..162fe8cef 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java @@ -42,6 +42,7 @@ @Tag(Constants.xSQLv12) @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) +@Tag(Constants.reqExternalSetup) public class PrecisionScaleTest extends AESetup { private static java.util.Date date = null; private static int offsetFromGMT = 0; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java index c30dc0e50..1644bfc59 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java @@ -29,6 +29,7 @@ @Tag(Constants.xSQLv12) @Tag(Constants.xAzureSQLDW) @Tag(Constants.xAzureSQLDB) +@Tag(Constants.reqExternalSetup) public class RegressionAlwaysEncryptedTest extends AESetup { static String numericTable[][] = {{"Bit", "bit"}, {"Tinyint", "tinyint"}, {"Smallint", "smallint"},}; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index 5330d3db0..d1b2e7177 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -644,13 +644,22 @@ public void testIncorrectDatabase() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } @Test public void testIncorrectUserName() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long timerStart = 0; long timerEnd = 0; final long milsecs = threshHoldForNoRetryInMilliseconds; @@ -668,13 +677,22 @@ public void testIncorrectUserName() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_loginFailed"))); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_loginFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } @Test public void testIncorrectPassword() throws SQLException { + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + long timerStart = 0; long timerEnd = 0; final long milsecs = threshHoldForNoRetryInMilliseconds; @@ -692,7 +710,12 @@ public void testIncorrectPassword() throws SQLException { assertTrue(timeDiff <= milsecs, form.format(msgArgs)); } } catch (Exception e) { - assertTrue(e.getMessage().contains(TestResource.getResource("R_loginFailed"))); + assertTrue( + e.getMessage().contains(TestResource.getResource("R_loginFailed")) + || (TestUtils.getProperty(connectionString, "msiClientId") != null + && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())), + e.getMessage()); timerEnd = System.currentTimeMillis(); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index dd664bc72..3182654f9 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -199,7 +199,9 @@ protected Object[][] getContents() { {"R_AKVProviderNull", "The Azure key store provider is null."}, {"R_objectNullOrEmpty", "The {0} is null or empty."}, {"R_cekDecryptionFailed", "Failed to decrypt a column encryption key using key store provider: {0}."}, + {"R_failedFedauth", "Failed to acquire fedauth token: "}, {"R_connectTimedOut", "connect timed out"}, {"R_queryCanceled", "The query was canceled."}, + {"R_loginFailedMI", "Login failed for user ''"}, {"R_sessionKilled", "Cannot continue the execution because the session is in the kill state"}}; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java index ba084cb8f..cde1e6c31 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java @@ -168,87 +168,103 @@ public void testConnectRetryBadServer() { // Test connect retry for database error @Test public void testConnectRetryServerError() { - long timerEnd = 0; + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + + long totalTime = 0; long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry + long timeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry - int connectRetryCount = new Random().nextInt(256); - int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" + connectRetryCount - + ";connectRetryInterval=" + connectRetryInterval)) { + + ";loginTimeout=" + timeout + ";connectRetryCount=" + 1 + ";connectRetryInterval=" + interval + + ";transparentNetworkIPResolution=false")) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + totalTime = System.currentTimeMillis() - timerStart; + + assertTrue((e.getMessage().toLowerCase().contains( + TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) || (TestUtils.getProperty( + connectionString, "msiClientId") != null && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); - timerEnd = System.currentTimeMillis(); } - - // connect + all retries should always be <= loginTimeout - verifyTimeout(timerEnd - timerStart, defaultTimeout); } // Test connect retry for database error using Datasource @Test public void testConnectRetryServerErrorDS() { - long timerEnd = 0; - long timerStart = System.currentTimeMillis(); + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); - // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry - int connectRetryCount = new Random().nextInt(256); - int connectRetryInterval = new Random().nextInt(defaultTimeout) + 1; + long totalTime = 0; + long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry + long loginTimeout = defaultTimeout * 2; // long loginTimeout to accommodate the long interval + // non existent database with interval < loginTimeout this will generate a 4060 transient error and retry 1 time SQLServerDataSource ds = new SQLServerDataSource(); String connectStr = TestUtils.addOrOverrideProperty(connectionString, "database", - RandomUtil.getIdentifier("database")) + ";logintimeout=" + defaultTimeout + ";connectRetryCount=" - + connectRetryCount + ";connectRetryInterval=" + connectRetryInterval; + RandomUtil.getIdentifier("database")) + ";logintimeout=" + loginTimeout + ";connectRetryCount=1" + + ";connectRetryInterval=" + interval; updateDataSource(connectStr, ds); try (Connection con = PrepUtil.getConnection(connectStr)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); - timerEnd = System.currentTimeMillis(); + totalTime = System.currentTimeMillis() - timerStart; } - // connect + all retries should always be <= loginTimeout - verifyTimeout(timerEnd - timerStart, defaultTimeout); + // 1 retry should be at least 1 interval long but < 2 intervals + assertTrue(TimeUnit.SECONDS.toMillis(interval) < totalTime, + "interval: " + TimeUnit.SECONDS.toMillis(interval) + " total time: " + totalTime); + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(2 * interval), + "total time: " + totalTime + " 2 * interval: " + TimeUnit.SECONDS.toMillis(2 * interval)); } // Test connect retry for database error with loginTimeout @Test public void testConnectRetryTimeout() { - long timerEnd = 0; + long totalTime = 0; long timerStart = System.currentTimeMillis(); + int interval = defaultTimeout; // long interval so we can tell if there was a retry int loginTimeout = 2; - // non existent server with very short loginTimeout so there is no time to do all retries + // non existent database with very short loginTimeout so there is no time to do any retry try (Connection con = PrepUtil.getConnection( TestUtils.addOrOverrideProperty(connectionString, "database", RandomUtil.getIdentifier("database")) - + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" - + (new Random().nextInt(defaultTimeout - 1) + 1) + ";loginTimeout=" + loginTimeout)) { + + "connectRetryCount=" + (new Random().nextInt(256)) + ";connectRetryInterval=" + interval + + ";loginTimeout=" + loginTimeout)) { fail(TestResource.getResource("R_shouldNotConnect")); } catch (Exception e) { - assertTrue((e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase"))) - || ((isSqlAzure() || isSqlAzureDW()) - ? e.getMessage().contains( - TestResource.getResource("R_connectTimedOut")) - : false), + totalTime = System.currentTimeMillis() - timerStart; + + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) ? e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase()) : false), e.getMessage()); - timerEnd = System.currentTimeMillis(); } - verifyTimeout(timerEnd - timerStart, loginTimeout); + // if there was a retry then it would take at least 1 interval long, so if < interval means there were no retries + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(interval), + "total time: " + totalTime + " interval: " + TimeUnit.SECONDS.toMillis(interval)); } @Test diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/XADataSourceTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/XADataSourceTest.java index 7b867f23a..0fe9a1e12 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/XADataSourceTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/XADataSourceTest.java @@ -7,7 +7,6 @@ import javax.sql.XAConnection; -import com.microsoft.sqlserver.jdbc.TestUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -22,11 +21,10 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.reqExternalSetup) public class XADataSourceTest extends AbstractTest { - private static String connectionUrlSSL = connectionString + ";encrypt=true;trustServerCertificate=false;"; + private static String connectionUrlSSL = connectionString; @BeforeAll public static void setupTests() throws Exception { - connectionString = TestUtils.addOrOverrideProperty(connectionString,"trustServerCertificate", "true"); setConnection(); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java index 6b8509978..cde66ae2e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConcurrentLoginTest.java @@ -23,6 +23,7 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) +@Tag(Constants.requireSecret) public class ConcurrentLoginTest extends FedauthCommon { final AtomicReference throwableRef = new AtomicReference(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionSuspensionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionSuspensionTest.java index c23d6f813..559da4074 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionSuspensionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ConnectionSuspensionTest.java @@ -31,6 +31,7 @@ @RunWith(JUnitPlatform.class) @Tag("slow") @Tag(Constants.fedAuth) +@Tag(Constants.requireSecret) public class ConnectionSuspensionTest extends FedauthCommon { static String charTable = TestUtils.escapeSingleQuotes( diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java index 0630c05c5..efa35e500 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/ErrorMessageTest.java @@ -26,6 +26,7 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) +@Tag(Constants.requireSecret) public class ErrorMessageTest extends FedauthCommon { String badUserName = "abc" + azureUserName; @@ -402,8 +403,11 @@ public void testADPasswordWrongPasswordWithConnectionStringUserName() throws SQL assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && (e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") - || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY))); + && e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") + || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY) + || e.getCause().getCause().getMessage().contains(ERR_FAULT_ID3342) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED) + || e.getMessage().contains(ERR_FAULT_AUTH_FAIL)); } } @@ -427,8 +431,11 @@ public void testADPasswordWrongPasswordWithDatasource() throws SQLException { assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && (e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") - || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY))); + && e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") + || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY) + || e.getCause().getCause().getMessage().contains(ERR_FAULT_ID3342) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED) + || e.getMessage().contains(ERR_FAULT_AUTH_FAIL)); } } @@ -446,8 +453,11 @@ public void testADPasswordWrongPasswordWithConnectionStringUser() throws SQLExce assertTrue(INVALID_EXCEPTION_MSG + ": " + e.getMessage(), e.getMessage() .contains(ERR_MSG_FAILED_AUTHENTICATE + " the user " + azureUserName + " in Active Directory (Authentication=ActiveDirectoryPassword).") - && (e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") - || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY))); + && e.getCause().getCause().getMessage().toLowerCase().contains("invalid username or password") + || e.getCause().getCause().getMessage().contains(ERR_MSG_SIGNIN_TOO_MANY) + || e.getCause().getCause().getMessage().contains(ERR_FAULT_ID3342) + || e.getMessage().contains(ERR_MSG_REQUEST_THROTTLED) + || e.getMessage().contains(ERR_FAULT_AUTH_FAIL)); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java index de29d9158..d9b3be8c3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthCommon.java @@ -7,7 +7,15 @@ import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenRequestContext; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; +import com.microsoft.aad.msal4j.ClientCredentialFactory; +import com.microsoft.aad.msal4j.ClientCredentialParameters; +import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.aad.msal4j.MsalThrottlingException; import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.UserNamePasswordParameters; import java.sql.Connection; @@ -42,11 +50,7 @@ public class FedauthCommon extends AbstractTest { static String azureUserName = null; static String azurePassword = null; static String azureGroupUserName = null; - static String azureAADPrincipialId = null; - static String azureAADPrincipialSecret = null; - static boolean enableADIntegrated = false; - static String spn = null; static String stsurl = null; static String fedauthClientId = null; @@ -72,6 +76,8 @@ public class FedauthCommon extends AbstractTest { static final String ERR_MSG_HAS_CLOSED = TestResource.getResource("R_hasClosed"); static final String ERR_MSG_HAS_BEEN_CLOSED = TestResource.getResource("R_hasBeenClosed"); static final String ERR_MSG_SIGNIN_TOO_MANY = TestResource.getResource("R_signinTooManyTimes"); + static final String ERR_FAULT_ID3342 = "FaultMessage: ID3242"; + static final String ERR_FAULT_AUTH_FAIL = "FaultMessage: Authentication Failure"; static final String ERR_MSG_NOT_AUTH_AND_IS = TestUtils.R_BUNDLE .getString("R_SetAuthenticationWhenIntegratedSecurityTrue"); static final String ERR_MSG_NOT_AUTH_AND_USER_PASSWORD = TestUtils.R_BUNDLE @@ -80,6 +86,8 @@ public class FedauthCommon extends AbstractTest { static final String ERR_MSG_RESULTSET_IS_CLOSED = TestUtils.R_BUNDLE.getString("R_resultsetClosed"); static final String ERR_MSG_SOCKET_CLOSED = TestResource.getResource("R_socketClosed"); static final String ERR_TCPIP_CONNECTION = TestResource.getResource("R_tcpipConnectionToHost"); + static final String ERR_MSG_REQUEST_THROTTLED = "Request was throttled"; + static final String ERR_FAILED_FEDAUTH = TestResource.getResource("R_failedFedauth"); enum SqlAuthentication { NotSpecified, @@ -124,8 +132,6 @@ public static void getConfigs() throws Exception { azureUserName = getConfiguredProperty("azureUserName"); azurePassword = getConfiguredProperty("azurePassword"); azureGroupUserName = getConfiguredProperty("azureGroupUserName"); - azureAADPrincipialId = getConfiguredProperty("AADSecurePrincipalId"); - azureAADPrincipialSecret = getConfiguredProperty("AADSecurePrincipalSecret"); String prop = getConfiguredProperty("enableADIntegrated"); enableADIntegrated = (null != prop && prop.equalsIgnoreCase("true")) ? true : false; @@ -153,22 +159,50 @@ public static void getConfigs() throws Exception { * */ static void getFedauthInfo() { - try { + int retry = 0; + long interval = THROTTLE_RETRY_INTERVAL; + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + + while (retry <= THROTTLE_RETRY_COUNT) { + try { + TokenRequestContext requestContext = new TokenRequestContext() + .setScopes(Collections.singletonList(spn + "/.default")); + + AccessToken token = credential.getToken(requestContext).block(); + + if (token != null) { + secondsBeforeExpiration = TimeUnit.MILLISECONDS + .toSeconds(token.getExpiresAt().toInstant().toEpochMilli() - new Date().getTime()); + accessToken = token.getToken(); + } - final PublicClientApplication clientApplication = PublicClientApplication.builder(fedauthClientId) - .executorService(Executors.newFixedThreadPool(1)).authority(stsurl).build(); - final CompletableFuture future = clientApplication - .acquireToken(UserNamePasswordParameters.builder(Collections.singleton(spn + "/.default"), - azureUserName, azurePassword.toCharArray()).build()); + retry = THROTTLE_RETRY_COUNT + 1; + } catch (MsalThrottlingException te) { + interval = te.retryInMs(); + if (!checkForRetry(te, retry++, interval)) { + te.printStackTrace(); + fail(ERR_FAILED_FEDAUTH + "no more retries: " + te.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + fail(ERR_FAILED_FEDAUTH + e.getMessage()); + } + } + } - final IAuthenticationResult authenticationResult = future.get(); + static boolean checkForRetry(Exception e, int retry, long interval) { + if (retry > THROTTLE_RETRY_COUNT) { + return false; + } + try { + Thread.sleep(interval); + } catch (InterruptedException ex) { + e.printStackTrace(); - secondsBeforeExpiration = TimeUnit.MILLISECONDS - .toSeconds(authenticationResult.expiresOnDate().getTime() - new Date().getTime()); - accessToken = authenticationResult.accessToken(); - } catch (Exception e) { - fail(e.getMessage()); + fail(ERR_FAILED_FEDAUTH + ex.getMessage()); } + return true; } void testUserName(Connection conn, String user, SqlAuthentication authentication) throws SQLException { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java index 8d8cb5b5d..ec239e6b1 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthTest.java @@ -17,6 +17,7 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.text.MessageFormat; import java.util.Properties; import org.junit.jupiter.api.AfterAll; @@ -36,6 +37,7 @@ @RunWith(JUnitPlatform.class) @Tag(Constants.fedAuth) +@Tag(Constants.requireSecret) public class FedauthTest extends FedauthCommon { static String charTable = TestUtils .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("JDBC_FedAuthTest"))); @@ -253,10 +255,11 @@ public void testCorrectAccessTokenDS() throws SQLException { * @deprecated */ @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalAuthDeprecated() { String url = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" - + SqlAuthentication.ActiveDirectoryServicePrincipal + ";AADSecurePrincipalId=" + azureAADPrincipialId - + ";AADSecurePrincipalSecret=" + azureAADPrincipialSecret; + + SqlAuthentication.ActiveDirectoryServicePrincipal + ";AADSecurePrincipalId=" + applicationClientID + + ";AADSecurePrincipalSecret=" + applicationKey; String urlEncrypted = url + ";encrypt=true;trustServerCertificate=true;"; SQLServerDataSource ds = new SQLServerDataSource(); updateDataSource(url, ds); @@ -275,10 +278,11 @@ public void testAADServicePrincipalAuthDeprecated() { * encryption. */ @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalAuth() { String url = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" - + SqlAuthentication.ActiveDirectoryServicePrincipal + ";Username=" + azureAADPrincipialId + ";Password=" - + azureAADPrincipialSecret; + + SqlAuthentication.ActiveDirectoryServicePrincipal + ";Username=" + applicationClientID + ";Password=" + + applicationKey; String urlEncrypted = url + ";encrypt=true;trustServerCertificate=true;"; SQLServerDataSource ds = new SQLServerDataSource(); updateDataSource(url, ds); @@ -296,36 +300,36 @@ public void testAADServicePrincipalAuth() { * Test invalid connection property combinations when using AAD Service Principal Authentication. */ @Test + @Tag(Constants.requireSecret) public void testAADServicePrincipalAuthWrong() { String baseUrl = "jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";authentication=" + SqlAuthentication.ActiveDirectoryServicePrincipal + ";"; // Wrong AADSecurePrincipalSecret provided. - String url = baseUrl + "AADSecurePrincipalId=" + azureAADPrincipialId + ";AADSecurePrincipalSecret=wrongSecret"; + String url = baseUrl + "AADSecurePrincipalId=" + applicationClientID + ";AADSecurePrincipalSecret=wrongSecret"; validateException(url, "R_MSALExecution"); // Wrong AADSecurePrincipalId provided. - url = baseUrl + "AADSecurePrincipalId=wrongId;AADSecurePrincipalSecret=" + azureAADPrincipialSecret; + url = baseUrl + "AADSecurePrincipalId=wrongId;AADSecurePrincipalSecret=" + applicationKey; validateException(url, "R_MSALExecution"); // AADSecurePrincipalSecret/password not provided. - url = baseUrl + "AADSecurePrincipalId=" + azureAADPrincipialId; + url = baseUrl + "AADSecurePrincipalId=" + applicationClientID; validateException(url, "R_NoUserPasswordForActiveServicePrincipal"); - url = baseUrl + "Username=" + azureAADPrincipialId; + url = baseUrl + "Username=" + applicationClientID; validateException(url, "R_NoUserPasswordForActiveServicePrincipal"); // AADSecurePrincipalId/username not provided. - url = baseUrl + "AADSecurePrincipalSecret=" + azureAADPrincipialSecret; + url = baseUrl + "AADSecurePrincipalSecret=" + applicationKey; validateException(url, "R_NoUserPasswordForActiveServicePrincipal"); - url = baseUrl + "password=" + azureAADPrincipialSecret; + url = baseUrl + "password=" + applicationKey; validateException(url, "R_NoUserPasswordForActiveServicePrincipal"); // Both AADSecurePrincipalId/username and AADSecurePrincipalSecret/password not provided. validateException(baseUrl, "R_NoUserPasswordForActiveServicePrincipal"); // both username/password and AADSecurePrincipalId/AADSecurePrincipalSecret provided - url = baseUrl + "Username=" + azureAADPrincipialId + ";password=" + azureAADPrincipialSecret - + ";AADSecurePrincipalId=" + azureAADPrincipialId + ";AADSecurePrincipalSecret=" - + azureAADPrincipialSecret; + url = baseUrl + "Username=" + applicationClientID + ";password=" + applicationKey + ";AADSecurePrincipalId=" + + applicationClientID + ";AADSecurePrincipalSecret=" + applicationKey; validateException(url, "R_BothUserPasswordandDeprecated"); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java index a34d4ee32..47478d2fc 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/FedauthWithAE.java @@ -23,6 +23,8 @@ import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider; @@ -282,16 +284,17 @@ private SQLServerColumnEncryptionKeyStoreProvider setupKeyStoreProvider_JKS() th private SQLServerColumnEncryptionKeyStoreProvider setupKeyStoreProvider_AKV() throws SQLServerException { SQLServerConnection.unregisterColumnEncryptionKeyStoreProviders(); - return registerAKVProvider( - new SQLServerColumnEncryptionAzureKeyVaultProvider(applicationClientID, applicationKey)); + return registerAKVProvider(); } - private SQLServerColumnEncryptionKeyStoreProvider registerAKVProvider( - SQLServerColumnEncryptionKeyStoreProvider provider) throws SQLServerException { - Map map1 = new HashMap(); - map1.put(provider.getName(), provider); - SQLServerConnection.registerColumnEncryptionKeyStoreProviders(map1); - return provider; + private SQLServerColumnEncryptionKeyStoreProvider registerAKVProvider() throws SQLServerException { + Map map = new HashMap(); + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(credential); + map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + SQLServerConnection.registerColumnEncryptionKeyStoreProviders(map); + return akvProvider; } private void createCMK(String cmkName, String keyStoreName, String keyPath, Statement stmt) throws SQLException { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/PooledConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/PooledConnectionTest.java index 884b6c276..6dc98ee2a 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/PooledConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fedauth/PooledConnectionTest.java @@ -39,6 +39,7 @@ @RunWith(JUnitPlatform.class) @Tag("slow") @Tag(Constants.fedAuth) +@Tag(Constants.requireSecret) public class PooledConnectionTest extends FedauthCommon { static String charTable = TestUtils.escapeSingleQuotes( diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java index a486e3c27..9a02cd956 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java @@ -62,13 +62,18 @@ public void fipsTrustServerCertificateTest() throws Exception { */ @Test public void fipsEncryptTest() throws Exception { + // test doesn't apply to managed identity as encrypt is set to on by default + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null && !(auth.equalsIgnoreCase("ActiveDirectoryManagedIdentity") + || auth.equalsIgnoreCase("ActiveDirectoryMSI"))); + Properties props = buildConnectionProperties(); props.setProperty(Constants.ENCRYPT, Boolean.FALSE.toString()); try (Connection con = PrepUtil.getConnection(connectionString, props)) { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidEncrypt")); + TestResource.getResource("R_invalidTrustCert") + ": " + e.getMessage()); } } @@ -116,6 +121,11 @@ public void fipsDataSourcePropertyTest() throws Exception { */ @Test public void fipsDatSourceEncrypt() { + // test doesn't apply to managed identity as encrypt is set to on by default + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null && !(auth.equalsIgnoreCase("ActiveDirectoryManagedIdentity") + || auth.equalsIgnoreCase("ActiveDirectoryMSI"))); + SQLServerDataSource ds = new SQLServerDataSource(); setDataSourceProperties(ds); ds.setEncrypt(false); @@ -124,7 +134,7 @@ public void fipsDatSourceEncrypt() { Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLException e) { Assertions.assertTrue(e.getMessage().contains(TestResource.getResource("R_invalidFipsConfig")), - TestResource.getResource("R_invalidEncrypt")); + TestResource.getResource("R_invalidEncrypt") + ": " + e.getMessage()); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java index 3a6c7dd08..6204ec61c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/BasicConnectionTest.java @@ -49,15 +49,36 @@ public void testBasicReconnectDefault() throws SQLException { } @Test - public void testBasicConnectionAAD() throws SQLException { - String azureServer = getConfiguredProperty("azureServer"); - String azureDatabase = getConfiguredProperty("azureDatabase"); - String azureUserName = getConfiguredProperty("azureUserName"); - String azurePassword = getConfiguredProperty("azurePassword"); - org.junit.Assume.assumeTrue(azureServer != null && !azureServer.isEmpty()); - - basicReconnect("jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";user=" + azureUserName - + ";password=" + azurePassword + ";loginTimeout=30;Authentication=ActiveDirectoryPassword"); + @Tag(Constants.fedAuth) + public void testBasicConnectionAAD() throws Exception { + // retry since this could fail due to server throttling + int retry = 1; + while (retry <= THROTTLE_RETRY_COUNT) { + try { + String azureServer = getConfiguredProperty("azureServer"); + String azureDatabase = getConfiguredProperty("azureDatabase"); + String azureUserName = getConfiguredProperty("azureUserName"); + String azurePassword = getConfiguredProperty("azurePassword"); + org.junit.Assume.assumeTrue(azureServer != null && !azureServer.isEmpty()); + + basicReconnect("jdbc:sqlserver://" + azureServer + ";database=" + azureDatabase + ";user=" + + azureUserName + ";password=" + azurePassword + + ";loginTimeout=90;Authentication=ActiveDirectoryPassword"); + retry = THROTTLE_RETRY_COUNT + 1; + } catch (Exception e) { + if (e.getMessage().matches(TestUtils.formatErrorMsg("R_crClientAllRecoveryAttemptsFailed"))) { + System.out.println(e.getMessage() + ". Recovery failed, retry #" + retry + " in " + + THROTTLE_RETRY_INTERVAL + " ms"); + + Thread.sleep(THROTTLE_RETRY_INTERVAL); + retry++; + } else { + e.printStackTrace(); + + fail(e.getMessage()); + } + } + } } @Test diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java index e2206deb9..1a4f9dd4b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ReflectiveTests.java @@ -77,13 +77,18 @@ public void testDefaultTimeout() throws SQLException { } /* - * Default retry count is 1. Expect timeout to be just above login timeout. + * Default retry count is 1 (for non-Azure). Expect timeout to be just above login timeout. */ @Test + @Tag(Constants.xAzureSQLDB) + @Tag(Constants.xAzureSQLDW) public void testDefaultRetry() throws SQLException { Map m = new HashMap<>(); m.put("loginTimeout", "5"); - timeoutVariations(m, 6000, Optional.empty()); + + // ensure count is not set to something else as this test assumes exactly just 1 retry + // this is only true for non-Azure as retry counts gets auto changed for Azure servers + timeoutVariations(m, 15000, Optional.empty()); } /* @@ -109,7 +114,7 @@ public void testQueryTimeout() throws SQLException { m.put("queryTimeout", "10"); m.put("loginTimeout", "65535"); m.put("connectRetryCount", "1"); - timeoutVariations(m, 12000, Optional.empty()); + timeoutVariations(m, 14000, Optional.empty()); } /* @@ -123,7 +128,7 @@ public void testValidRetryWindow() throws SQLException { m.put("loginTimeout", "5"); m.put("connectRetryCount", "2"); m.put("connectRetryInterval", "10"); - timeoutVariations(m, 25000, Optional.of("R_crClientAllRecoveryAttemptsFailed")); + timeoutVariations(m, 28000, Optional.of("R_crClientAllRecoveryAttemptsFailed")); } @Test diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java index 1147cbe79..663f9d599 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java @@ -167,93 +167,6 @@ public void testAdaptiveBufferingWithPartiallyBufferedResultSet() throws SQLExce } } - /* - * Test killing a session while retrieving result set should result in exception thrown - */ - @Test - public void testKillSession() throws Exception { - // setup test with big tables - String table1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("killSessionTestTable1")); - String table2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("killSessionTestTable2")); - String table3 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("killSessionTestTable3")); - - try (Connection c = DriverManager.getConnection(connectionString); Statement s = c.createStatement(); - PreparedStatement ps1 = c.prepareStatement("INSERT INTO " + table1 + " values (?)"); - PreparedStatement ps2 = c.prepareStatement("INSERT INTO " + table2 + " values (?)"); - PreparedStatement ps3 = c.prepareStatement("INSERT INTO " + table3 + " values (?)")) { - TestUtils.dropTableIfExists(table1, s); - TestUtils.dropTableIfExists(table2, s); - TestUtils.dropTableIfExists(table3, s); - s.execute("CREATE TABLE " + table1 - + " (ID int primary key IDENTITY(1,1) NOT NULL, NAME varchar(255) NOT NULL); CREATE TABLE " + table2 - + " (ID int primary key IDENTITY(1,1) NOT NULL, NAME varchar(255) NOT NULL); CREATE TABLE " + table3 - + "( ID int primary key IDENTITY(1,1) NOT NULL, NAME varchar(255) NOT NULL);"); - - for (int i = 0; i < 1000; i++) { - ps1.setString(1, "value" + i); - ps2.setString(1, "value" + i); - ps3.setString(1, "value" + i); - - ps1.addBatch(); - ps2.addBatch(); - ps3.addBatch(); - } - - ps1.executeBatch(); - ps2.executeBatch(); - ps3.executeBatch(); - - c.commit(); - - try (Connection c2 = DriverManager.getConnection(connectionString)) { - int sessionId = ResiliencyUtils.getSessionId(c2); - - Runnable r1 = () -> { - try { - ResiliencyUtils.killConnection(sessionId, connectionString, c2, 10); - } catch (Exception e) { - fail(e.getMessage());; - } - }; - - Thread t1 = new Thread(r1); - t1.start(); - - // execute query which takes a long time and kill session in another thread - try (PreparedStatement ps = c2.prepareStatement("SELECT e1.* FROM " + table1 + " e1, " + table2 - + " e2, " + table3 + " e3, " + table1 - + " e4 where e1.name = 'abc' or e2.name = 'def'or e3.name = 'ghi' or e4.name = 'xxx' and e1.name not in (select name FROM " - + table2 + ") and e2.name not in (select name FROM " + table1 - + " ) and e3.name not in (SELECT name FROM " + table2 - + ") and e4.name not in (SELECT name FROM " + table3 + ");"); - ResultSet rs = ps.executeQuery()) { - - fail(TestResource.getResource("R_expectedExceptionNotThrown")); - } catch (SQLException e) { - // may get different error message depending on SQL servers. - // Local servers will report a TDS error where as Azure servers will have a DONE error - if (!(e.getMessage().matches(TestUtils.formatErrorMsg("R_serverError")) - || e.getMessage().contains(TestResource.getResource("R_sessionKilled")) - || e.getMessage().contains(TestResource.getResource("R_connectionReset")))) { - e.printStackTrace(); - } - - assertTrue( - e.getMessage().matches(TestUtils.formatErrorMsg("R_serverError")) - || e.getMessage().contains(TestResource.getResource("R_sessionKilled")) - || e.getMessage().contains(TestResource.getResource("R_connectionReset")), - e.getMessage()); - } - t1.join(); - - } finally { - TestUtils.dropTableIfExists(table1, s); - TestUtils.dropTableIfExists(table2, s); - TestUtils.dropTableIfExists(table3, s); - } - } - } - @Test public void testResultSetClientCursorInitializerOnDone() throws SQLException { try (Connection con = ResiliencyUtils.getConnection(connectionString); Statement stmt = con.createStatement()) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java index 834e6da49..5318236a8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/SQLServerErrorTest.java @@ -43,6 +43,11 @@ public static void setupTests() throws Exception { @Test @Tag(Constants.xAzureSQLDW) public void testLoginFailedError() { + // test to remove password only valid for password auth + String auth = TestUtils.getProperty(connectionString, "authentication"); + org.junit.Assume.assumeTrue(auth != null + && (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword"))); + SQLServerDataSource ds = new SQLServerDataSource(); ds.setURL(connectionString); ds.setLoginTimeout(loginTimeOutInSeconds); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index 2bacd129b..9df82e517 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -32,6 +32,11 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import com.azure.identity.ClientSecretCredential; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.identity.ManagedIdentityCredential; +import com.azure.identity.ManagedIdentityCredentialBuilder; +import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.sqlserver.jdbc.ISQLServerDataSource; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionAzureKeyVaultProvider; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider; @@ -60,6 +65,7 @@ public abstract class AbstractTest { protected static String applicationKey = null; protected static String tenantID; protected static String[] keyIDs = null; + protected static String akvProviderManagedClientId = null; protected static String[] enclaveServer = null; protected static String[] enclaveAttestationUrl = null; @@ -108,6 +114,9 @@ public abstract class AbstractTest { protected static boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + protected static final int THROTTLE_RETRY_COUNT = 3; // max number of throttling retries + protected static final int THROTTLE_RETRY_INTERVAL = 60000; // default throttling retry interval in ms + public static Properties properties = null; /** @@ -133,6 +142,9 @@ public static void setup() throws Exception { applicationClientID = getConfiguredProperty("applicationClientID"); applicationKey = getConfiguredProperty("applicationKey"); + + akvProviderManagedClientId = getConfiguredProperty("akvProviderManagedClientId"); + tenantID = getConfiguredProperty("tenantID"); trustServerCertificate = getConfiguredProperty("trustServerCertificate", "true"); @@ -177,7 +189,12 @@ public static void setup() throws Exception { map.put(Constants.CUSTOM_KEYSTORE_NAME, jksProvider); } - if (null == akvProvider && null != applicationClientID && null != applicationKey) { + if (null == akvProvider && null != akvProviderManagedClientId) { + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder() + .clientId(akvProviderManagedClientId).build(); + akvProvider = new SQLServerColumnEncryptionAzureKeyVaultProvider(credential); + map.put(Constants.AZURE_KEY_VAULT_NAME, akvProvider); + } else if (null == akvProvider && null != applicationClientID && null != applicationKey) { File file = null; try { file = new File(Constants.MSSQL_JDBC_PROPERTIES); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java index acac806cb..92efb79ca 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java @@ -43,6 +43,7 @@ private Constants() {} public static final String reqExternalSetup = "reqExternalSetup"; public static final String clientCertAuth = "clientCertAuth"; public static final String fedAuth = "fedAuth"; + public static final String requireSecret = "requireSecret"; public static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current(); public static final Logger LOGGER = Logger.getLogger("AbstractTest");