From 3447893c4af3c14fd44e5f74158ec4e22b3ae734 Mon Sep 17 00:00:00 2001 From: lilgreenbird Date: Thu, 12 Oct 2023 12:22:47 -0700 Subject: [PATCH 1/4] randomize test table name (#2236) --- .../ResultSetsWithResiliencyTest.java | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) 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 b3d6fdc32..dfc1048bd 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java @@ -12,7 +12,6 @@ import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -36,19 +35,26 @@ @Tag(Constants.xSQLv11) @Tag(Constants.xAzureSQLDW) public class ResultSetsWithResiliencyTest extends AbstractTest { - static String tableName = AbstractSQLGenerator.escapeIdentifier("resilencyTestTable"); + static String tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("resiliencyTestTable")); static int numberOfRows = 10; - private static String callableStatementICROnDoneTestSp = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneTest_SP")); - private static String callableStatementICROnDoneErrorTestSp = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneErrorTest_SP")); + private static String callableStatementICROnDoneTestSp = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneTest_SP")); + private static String callableStatementICROnDoneErrorTestSp = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneErrorTest_SP")); private static String createClientCursorInitTableQuery = "create table %s (col1 int, col2 varchar(8000), col3 int identity(1,1))"; private static String createFetchBufferTableQuery = "create table %s (col1 int not null)"; private static String insertIntoFetchBufferTableQuery = "insert into %s (col1) values (%s);"; - private static final String clientCursorInitTable1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable1")); - private static final String clientCursorInitTable2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable2")); - private static final String clientCursorInitTable3 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable3")); - private static final String fetchBufferTestTable1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable1")); - private static final String fetchBufferTestTable2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable2")); + private static final String clientCursorInitTable1 = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable1")); + private static final String clientCursorInitTable2 = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable2")); + private static final String clientCursorInitTable3 = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable3")); + private static final String fetchBufferTestTable1 = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable1")); + private static final String fetchBufferTestTable2 = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable2")); private static final String clientCursorInitTableQuery1 = "select * from " + clientCursorInitTable1; private static final String clientCursorInitTableQuery2 = "select * from " + clientCursorInitTable2; private static final String clientCursorInitTableQuery3 = "select * from " + clientCursorInitTable3; @@ -169,8 +175,8 @@ public void testAdaptiveBufferingWithPartiallyBufferedResultSet() throws SQLExce public void testResultSetClientCursorInitializerOnDone() throws SQLException { try (Connection con = ResiliencyUtils.getConnection(connectionString); Statement stmt = con.createStatement()) { - boolean hasResults = stmt.execute(clientCursorInitTableQuery1+ "; " + clientCursorInitTableQuery2); - while(hasResults) { + boolean hasResults = stmt.execute(clientCursorInitTableQuery1 + "; " + clientCursorInitTableQuery2); + while (hasResults) { ResultSet rs = stmt.getResultSet(); while (rs.next()) {} hasResults = stmt.getMoreResults(); @@ -178,9 +184,8 @@ public void testResultSetClientCursorInitializerOnDone() throws SQLException { ResiliencyUtils.killConnection(con, connectionString, 1); - try (ResultSet rs = con.createStatement() - .executeQuery(clientCursorInitTableQuery3)) { - while(rs.next()) {} + try (ResultSet rs = con.createStatement().executeQuery(clientCursorInitTableQuery3)) { + while (rs.next()) {} } } } @@ -191,7 +196,7 @@ public void testResultSetErrorClientCursorInitializerOnDone() throws SQLExceptio try { boolean hasResults = stmt.execute(clientCursorInitTableQuery1 + "; " + errorQuery); - while(hasResults) { + while (hasResults) { ResultSet rs = stmt.getResultSet(); while (rs.next()) {} hasResults = stmt.getMoreResults(); @@ -205,8 +210,7 @@ public void testResultSetErrorClientCursorInitializerOnDone() throws SQLExceptio ResiliencyUtils.killConnection(con, connectionString, 1); - try (ResultSet rs = con.createStatement() - .executeQuery(clientCursorInitTableQuery3)) { + try (ResultSet rs = con.createStatement().executeQuery(clientCursorInitTableQuery3)) { while (rs.next()) {} } } @@ -238,10 +242,8 @@ public void testCallableStatementOnDone() throws SQLException { @Test public void testCallableStatementErrorOnDone() throws SQLException { - String errorCallableStmt = "{CALL " - + callableStatementICROnDoneErrorTestSp + " (?, ?)}"; - String validCallableStmt = "{CALL " - + callableStatementICROnDoneTestSp + " (?, ?)}"; + String errorCallableStmt = "{CALL " + callableStatementICROnDoneErrorTestSp + " (?, ?)}"; + String validCallableStmt = "{CALL " + callableStatementICROnDoneTestSp + " (?, ?)}"; try (Connection con = ResiliencyUtils.getConnection(connectionString)) { @@ -286,7 +288,7 @@ public void testResultSetFetchBufferOnDone() throws SQLException { try (Statement stmt = con.createStatement()) { boolean hasResults = stmt.execute(fetchBufferTableQuery1 + "; " + fetchBufferTableQuery2); - while(hasResults) { + while (hasResults) { ResultSet rs = stmt.getResultSet(); while (rs.next()) {} hasResults = stmt.getMoreResults(); @@ -326,7 +328,7 @@ public void testResultSetErrorFetchBufferOnDone() throws SQLException { try (Statement stmt = con.createStatement()) { boolean hasResults = stmt.execute(fetchBufferTableQuery1 + "; " + errorQuery); - while(hasResults) { + while (hasResults) { ResultSet rs = stmt.getResultSet(); while (rs.next()) {} hasResults = stmt.getMoreResults(); @@ -386,7 +388,7 @@ public void testMultipleResultSets() throws Exception { @Test public void testResultSetWithException() throws Exception { try (Connection c = ResiliencyUtils.getConnection(connectionString); Statement s = c.createStatement(); - ResultSet rs = s.executeQuery("SELECT 1/0")) { + ResultSet rs = s.executeQuery("SELECT 1/0")) { while (rs.next()) { ResiliencyUtils.killConnection(c, connectionString, 0); From a3178ba93231cc84fe7e57734eeb4bee0c805169 Mon Sep 17 00:00:00 2001 From: alagrede Date: Thu, 12 Oct 2023 22:46:29 +0200 Subject: [PATCH 2/4] Fix to ignore computed columns during BulkCopy (#1562) --- .../sqlserver/jdbc/SQLServerBulkCopy.java | 28 ++++++------- .../jdbc/SQLServerPreparedStatement.java | 3 ++ .../BatchExecutionWithBulkCopyTest.java | 40 +++++++++++++++++++ 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index c1e459283..afadf9445 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -1729,40 +1729,36 @@ private void getDestinationMetadata() throws SQLServerException { if (null != destinationTableMetadata) { rs = (SQLServerResultSet) destinationTableMetadata; } else { - stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting); + stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting); // Get destination metadata - rs = stmt.executeQueryInternal( - "sp_executesql N'SET FMTONLY ON SELECT * FROM " + escapedDestinationTableName + " '"); + rs = stmt.executeQueryInternal("sp_executesql N'SET FMTONLY ON SELECT * FROM " + escapedDestinationTableName + " '"); } - destColumnCount = rs.getMetaData().getColumnCount(); + int destColumnMetadataCount = rs.getMetaData().getColumnCount(); destColumnMetadata = new HashMap<>(); destCekTable = rs.getCekTable(); - if (!connection.getServerSupportsColumnEncryption()) { - metaDataQuery = "select collation_name from sys.columns where " + "object_id=OBJECT_ID('" - + escapedDestinationTableName + "') " + "order by column_id ASC"; - } else { - metaDataQuery = "select collation_name, encryption_type from sys.columns where " - + "object_id=OBJECT_ID('" + escapedDestinationTableName + "') " + "order by column_id ASC"; - } + metaDataQuery = "select * from sys.columns where " + "object_id=OBJECT_ID('" + escapedDestinationTableName + "') " + "order by column_id ASC"; try (SQLServerStatement statementMoreMetadata = (SQLServerStatement) connection.createStatement(); - SQLServerResultSet rsMoreMetaData = statementMoreMetadata.executeQueryInternal(metaDataQuery)) { - for (int i = 1; i <= destColumnCount; ++i) { + SQLServerResultSet rsMoreMetaData = statementMoreMetadata.executeQueryInternal(metaDataQuery)) { + for (int i = 1; i <= destColumnMetadataCount; ++i) { if (rsMoreMetaData.next()) { String bulkCopyEncryptionType = null; if (connection.getServerSupportsColumnEncryption()) { bulkCopyEncryptionType = rsMoreMetaData.getString("encryption_type"); } - destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i), - rsMoreMetaData.getString("collation_name"), bulkCopyEncryptionType)); + // Skip computed columns + if (!rsMoreMetaData.getBoolean("is_computed")) { + destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i), rsMoreMetaData.getString("collation_name"), + bulkCopyEncryptionType)); + } } else { destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i))); } } + destColumnCount = destColumnMetadata.size(); } } catch (SQLException e) { // Unable to retrieve metadata for destination diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 2a2adf12d..cfe578127 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -2142,6 +2142,9 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL CryptoMetadata cryptoMetadata = c.getCryptoMetadata(); int jdbctype; TypeInfo ti = c.getTypeInfo(); + if (ti.getUpdatability() == 0) { // Skip read only columns + continue; + } checkValidColumns(ti); if (null != cryptoMetadata) { jdbctype = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType().getIntValue(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java index dbccf4e7d..6e49da7d6 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java @@ -59,6 +59,7 @@ public class BatchExecutionWithBulkCopyTest extends AbstractTest { static String squareBracketTableName = RandomUtil.getIdentifier("BulkCopy]]]]test'"); static String doubleQuoteTableName = RandomUtil.getIdentifier("\"BulkCopy\"\"\"\"test\""); static String schemaTableName = "\"dbo\" . /*some comment */ " + squareBracketTableName; + static String tableNameBulkComputedCols = RandomUtil.getIdentifier("BulkCopyComputedCols"); private Object[] generateExpectedValues() { float randomFloat = RandomData.generateReal(false); @@ -760,6 +761,45 @@ public void testReverseColumnOrder() throws Exception { } } + @Test + @Tag(Constants.xAzureSQLDW) + @Tag(Constants.xSQLv12) + public void testComputedCols() throws Exception { + String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols) + " (id, json)" + + " values (?, ?)"; + + try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); + Statement stmt = (SQLServerStatement) connection.createStatement();) { + Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); + f1.setAccessible(true); + f1.set(connection, true); + + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols), stmt); + String createTable = "create table " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols) + + " (id nvarchar(100) not null, json nvarchar(max) not null," + + " vcol1 as json_value([json], '$.vcol1'), vcol2 as json_value([json], '$.vcol2'))"; + stmt.execute(createTable); + + String jsonValue = + "{\"vcol1\":\"" + UUID.randomUUID().toString() + "\",\"vcol2\":\"" + UUID.randomUUID().toString() + + "\" }"; + String idValue = UUID.randomUUID().toString(); + pstmt.setString(1, idValue); + pstmt.setString(2, jsonValue); + pstmt.addBatch(); + pstmt.executeBatch(); + + try (ResultSet rs = stmt.executeQuery( + "select * from " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols))) { + rs.next(); + + assertEquals(idValue, rs.getObject(1)); + assertEquals(jsonValue, rs.getObject(2)); + } + } + } + @BeforeAll public static void setupTests() throws Exception { setConnection(); From 3f0f1201622e815b76e3815a5249d8a27f346d9f Mon Sep 17 00:00:00 2001 From: zeotuan <48720253+zeotuan@users.noreply.github.com> Date: Tue, 17 Oct 2023 03:49:53 +1100 Subject: [PATCH 3/4] add useDefaultGSSCredential property (#2177) * add useDefaultGSSCredential property * Fix wrong expected useDefaultGSSCredential value * use default GSS if useDefaultGssCredential true * Fix awkward R_useDefaultGSSCredentialPropertyDescription SqlServerResource Co-authored-by: Jeffery Wasty * Update ISQLServerDataSource useDefaultGSSCredential doc string Co-authored-by: Jeffery Wasty * Revert white space changes * Added tests * Corrected credential cleanup; Addressed PR comments --------- Co-authored-by: Tuan Pham Co-authored-by: Jeffery Wasty Co-authored-by: Terry Chow --- build.gradle | 2 +- pom.xml | 2 +- .../sqlserver/jdbc/ISQLServerDataSource.java | 19 ++++++- .../sqlserver/jdbc/KerbAuthentication.java | 12 +++- ...umnEncryptionCertificateStoreProvider.java | 12 +--- .../sqlserver/jdbc/SQLServerConnection.java | 19 +++++-- .../sqlserver/jdbc/SQLServerDataSource.java | 11 ++++ .../sqlserver/jdbc/SQLServerDriver.java | 8 ++- .../sqlserver/jdbc/SQLServerResource.java | 1 + .../sqlserver/jdbc/KerberosTest.java | 55 ++++++++++++------- .../jdbc/SQLServerConnectionTest.java | 4 ++ .../sqlserver/jdbc/TestResource.java | 1 + .../sqlserver/testframework/Constants.java | 2 +- 13 files changed, 102 insertions(+), 46 deletions(-) diff --git a/build.gradle b/build.gradle index 13a17efe5..18cbe02a9 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ allprojects { test { useJUnitPlatform { - excludeTags (hasProperty('excludedGroups') ? excludedGroups : 'xSQLv15','xGradle','reqExternalSetup','NTLM','MSI','clientCertAuth','fedAuth') + excludeTags (hasProperty('excludedGroups') ? excludedGroups : 'xSQLv15','xGradle','reqExternalSetup','NTLM','MSI','clientCertAuth','fedAuth','kerberos') } } diff --git a/pom.xml b/pom.xml index ceb27f690..eb0d51670 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ 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) - Kerberos - - - - - For tests using Kerberos authentication (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) - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java index 019b79997..3bfc279a5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java @@ -594,9 +594,24 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { */ String getServerSpn(); + /** + * Sets the value to indicate whether useDefaultGSSCredential is enabled. + * + * @param enable + * true if useDefaultGSSCredential is enabled. Otherwise, false. + */ + void setUseDefaultGSSCredential(boolean enable); + + /** + * Returns the useDefaultGSSCredential. + * + * @return if enabled, return true. Otherwise, false. + */ + boolean getUseDefaultGSSCredential(); + /** * Sets the GSSCredential. - * + * * @param userCredential * the credential */ @@ -604,7 +619,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { /** * Returns the GSSCredential. - * + * * @return GSSCredential */ GSSCredential getGSSCredentials(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index b58bb9fae..4c3c91093 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -39,6 +39,7 @@ final class KerbAuthentication extends SSPIAuthentication { private LoginContext lc = null; private boolean isUserCreatedCredential = false; private GSSCredential peerCredentials = null; + private boolean useDefaultNativeGSSCredential = false; private GSSContext peerContext = null; static { @@ -63,6 +64,10 @@ private void initAuthInit() throws SQLServerException { // as it is. GSSName remotePeerName = manager.createName(spn, null); + if (useDefaultNativeGSSCredential) { + peerCredentials = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, kerberos, GSSCredential.INITIATE_ONLY); + } + if (null != peerCredentials) { peerContext = manager.createContext(remotePeerName, kerberos, peerCredentials, GSSContext.DEFAULT_LIFETIME); @@ -220,10 +225,11 @@ private byte[] initAuthHandShake(byte[] pin, boolean[] done) throws SQLServerExc * @param impersonatedUserCred */ KerbAuthentication(SQLServerConnection con, String address, int port, GSSCredential impersonatedUserCred, - boolean isUserCreated) { + boolean isUserCreated, boolean useDefaultNativeGSSCredential) { this(con, address, port); this.peerCredentials = impersonatedUserCred; this.isUserCreatedCredential = isUserCreated; + this.useDefaultNativeGSSCredential = useDefaultNativeGSSCredential; } byte[] generateClientContext(byte[] pin, boolean[] done) throws SQLServerException { @@ -235,9 +241,9 @@ byte[] generateClientContext(byte[] pin, boolean[] done) throws SQLServerExcepti void releaseClientContext() { try { - if (null != peerCredentials && !isUserCreatedCredential) { + if (null != peerCredentials && !isUserCreatedCredential && !useDefaultNativeGSSCredential) { peerCredentials.dispose(); - } else if (null != peerCredentials && isUserCreatedCredential) { + } else if (null != peerCredentials && (isUserCreatedCredential || useDefaultNativeGSSCredential)) { peerCredentials = null; } if (null != peerContext) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java index c0e2e2ad4..1765687be 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java @@ -19,8 +19,6 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL static final private java.util.logging.Logger windowsCertificateStoreLogger = java.util.logging.Logger .getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionCertificateStoreProvider"); - static boolean isWindows; - String name = "MSSQL_CERTIFICATE_STORE"; static final String LOCAL_MACHINE_DIRECTORY = "LocalMachine"; @@ -29,14 +27,6 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL private static final Lock lock = new ReentrantLock(); - static { - if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) { - isWindows = true; - } else { - isWindows = false; - } - } - /** * Constructs a SQLServerColumnEncryptionCertificateStoreProvider. */ @@ -67,7 +57,7 @@ public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryption windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(), "decryptColumnEncryptionKey", "Decrypting Column Encryption Key."); byte[] plainCek; - if (isWindows) { + if (SQLServerConnection.isWindows) { plainCek = decryptColumnEncryptionKeyWindows(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey); } else { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 2dfcdfb0c..66ad92b9d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -983,8 +983,10 @@ IdleConnectionResiliency getSessionRecovery() { /** global system ColumnEncryptionKeyStoreProviders */ static Map globalSystemColumnEncryptionKeyStoreProviders = new HashMap<>(); + static boolean isWindows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows"); + static { - if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) { + if (isWindows) { SQLServerColumnEncryptionCertificateStoreProvider provider = new SQLServerColumnEncryptionCertificateStoreProvider(); globalSystemColumnEncryptionKeyStoreProviders.put(provider.getName(), provider); } @@ -1426,6 +1428,9 @@ public static void clearUserTokenCache() { /** integrated authentication scheme */ private AuthenticationScheme intAuthScheme = AuthenticationScheme.NATIVE_AUTHENTICATION; + /** use default native GSS-API Credential flag */ + private boolean useDefaultGSSCredential = SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue(); + /** impersonated user credential */ private transient GSSCredential impersonatedUserCred; @@ -2480,6 +2485,11 @@ Connection connectInternal(Properties propsIn, impersonatedUserCred = (GSSCredential) activeConnectionProperties.get(sPropKey); isUserCreatedCredential = true; } + sPropKey = SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if(null != sPropValue && isWindows) { + useDefaultGSSCredential = isBooleanPropertyOn(sPropKey, sPropValue); + } } else if (intAuthScheme == AuthenticationScheme.NTLM) { String sPropKeyDomain = SQLServerDriverStringProperty.DOMAIN.toString(); String sPropValueDomain = activeConnectionProperties.getProperty(sPropKeyDomain); @@ -5097,9 +5107,9 @@ private void logon(LogonCommand command) throws SQLServerException { authentication = new AuthenticationJNI(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber()); } else if (AuthenticationScheme.JAVA_KERBEROS == intAuthScheme) { - if (null != impersonatedUserCred) { + if (null != impersonatedUserCred || useDefaultGSSCredential) { authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(), - currentConnectPlaceHolder.getPortNumber(), impersonatedUserCred, isUserCreatedCredential); + currentConnectPlaceHolder.getPortNumber(), impersonatedUserCred, isUserCreatedCredential, useDefaultGSSCredential); } else { authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber()); @@ -5800,8 +5810,7 @@ private SqlAuthenticationToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw } else if (authenticationString .equalsIgnoreCase(SqlAuthentication.ACTIVE_DIRECTORY_INTEGRATED.toString())) { // If operating system is windows and mssql-jdbc_auth is loaded then choose the DLL authentication. - if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows") - && AuthenticationJNI.isDllLoaded()) { + if (isWindows && AuthenticationJNI.isDllLoaded()) { try { FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated( fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 216ef569a..52182bbfd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -239,6 +239,17 @@ public GSSCredential getGSSCredentials() { SQLServerDriverObjectProperty.GSS_CREDENTIAL.getDefaultValue()); } + @Override + public void setUseDefaultGSSCredential(boolean enable) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), enable); + } + + @Override + public boolean getUseDefaultGSSCredential() { + return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), + SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()); + } + @Override public void setAccessToken(String accessToken) { setStringProperty(connectionProps, SQLServerDriverStringProperty.ACCESS_TOKEN.toString(), accessToken); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 966ffb079..fc4b1f73a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -694,7 +694,8 @@ enum SQLServerDriverBooleanProperty { USE_FMT_ONLY("useFmtOnly", false), SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY("sendTemporalDataTypesAsStringForBulkCopy", true), DELAY_LOADING_LOBS("delayLoadingLobs", true), - USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false); + USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false), + USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false); private final String name; private final boolean defaultValue; @@ -748,7 +749,7 @@ public final class SQLServerDriver implements java.sql.Driver { SQLServerDriverStringProperty.DATABASE_NAME.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString(), Boolean.toString(SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.getDefaultValue()), false, - new String[] {"true", "false"}), + TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.ENCRYPT.toString(), SQLServerDriverStringProperty.ENCRYPT.getDefaultValue(), false, new String[] {EncryptOption.FALSE.toString(), EncryptOption.NO.toString(), @@ -768,6 +769,9 @@ public final class SQLServerDriver implements java.sql.Driver { new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.toString(), Boolean.toString(SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.getDefaultValue()), false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()), false, + TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString(), SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.getDefaultValue(), false, new String[] {KeyStoreAuthentication.JAVA_KEYSTORE_PASSWORD.toString()}), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 9aa648779..effb8ae6d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -197,6 +197,7 @@ protected Object[][] getContents() { {"R_lastUpdateCountPropertyDescription", "Ensures that only the last update count is returned from an SQL statement passed to the server."}, {"R_disableStatementPoolingPropertyDescription", "Disables the statement pooling feature."}, {"R_integratedSecurityPropertyDescription", "Indicates whether Windows authentication will be used to connect to SQL Server."}, + {"R_useDefaultGSSCredentialPropertyDescription", "Indicates whether GSSCredential will be created using native GSS-API."}, {"R_authenticationSchemePropertyDescription", "The authentication scheme to be used for integrated authentication."}, {"R_lockTimeoutPropertyDescription", "The number of milliseconds to wait before the database reports a lock time-out."}, {"R_connectRetryCountPropertyDescription", "The number of reconnection attempts if there is a connection failure."}, diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java index 8755f468d..212839558 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java @@ -16,58 +16,47 @@ import java.util.HashMap; import java.util.Map; +@Tag(Constants.kerberos) @RunWith(JUnitPlatform.class) public class KerberosTest extends AbstractTest { private static String kerberosAuth = "KERBEROS"; + private static String authSchemeQuery = "select auth_scheme from sys.dm_exec_connections where session_id=@@spid"; @BeforeAll public static void setupTests() throws Exception { setConnection(); } - @Tag(Constants.Kerberos) @Test public void testUseDefaultJaasConfigConnectionStringPropertyTrue() throws Exception { String connectionStringUseDefaultJaasConfig = connectionStringKerberos + ";useDefaultJaasConfig=true;"; // Initial connection should succeed with default JAAS config - try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringUseDefaultJaasConfig)) { - ResultSet rs = conn.createStatement().executeQuery("select auth_scheme from sys.dm_exec_connections where session_id=@@spid"); - rs.next(); - Assertions.assertEquals(kerberosAuth, rs.getString(1)); - } + createKerberosConnection(connectionStringUseDefaultJaasConfig); // Attempt to overwrite JAAS config. Since useDefaultJaasConfig=true, this should have no effect // and subsequent connections should succeed. overwriteJaasConfig(); // New connection should successfully connect and continue to use the default JAAS config. - try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringUseDefaultJaasConfig)) { - ResultSet rs = conn.createStatement().executeQuery("select auth_scheme from sys.dm_exec_connections where session_id=@@spid"); - rs.next(); - Assertions.assertEquals(kerberosAuth, rs.getString(1)); - } + createKerberosConnection(connectionStringUseDefaultJaasConfig); } - @Tag(Constants.Kerberos) @Test public void testUseDefaultJaasConfigConnectionStringPropertyFalse() throws Exception { // useDefaultJaasConfig=false by default // Initial connection should succeed with default JAAS config - try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringKerberos)) { - ResultSet rs = conn.createStatement().executeQuery("select auth_scheme from sys.dm_exec_connections where session_id=@@spid"); - rs.next(); - Assertions.assertEquals(kerberosAuth, rs.getString(1)); - } + createKerberosConnection(connectionStringKerberos); // Overwrite JAAS config. Since useDefaultJaasConfig=false, overwriting should succeed and have an effect. // Subsequent connections will fail. overwriteJaasConfig(); // New connection should fail as it is attempting to connect using an overwritten JAAS config. - try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionStringKerberos)) { + try { + createKerberosConnection(connectionStringKerberos); Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); } catch (SQLServerException e) { Assertions.assertTrue(e.getMessage() @@ -75,6 +64,33 @@ public void testUseDefaultJaasConfigConnectionStringPropertyFalse() throws Excep } } + @Test + public void testUseDefaultNativeGSSCredential() throws Exception { + // This is a negative test. This test should fail as expected as the JVM arg "-Dsun.security.jgss.native=true" + // isn't provided. + String connectionString = connectionStringKerberos + ";useDefaultGSSCredential=true;"; + + try { + createKerberosConnection(connectionString); + Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } catch (SQLServerException e) { + Assertions.assertEquals(e.getCause().getMessage(), TestResource.getResource("R_kerberosNativeGSSFailure")); + } + } + + @Test + public void testBasicKerberosAuth() throws Exception { + createKerberosConnection(connectionStringKerberos); + } + + private static void createKerberosConnection(String connectionString) throws Exception { + try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionString)) { + ResultSet rs = conn.createStatement().executeQuery(authSchemeQuery); + rs.next(); + Assertions.assertEquals(kerberosAuth, rs.getString(1)); + } + } + /** * Overwrites the default JAAS config. Call before making a connection. */ @@ -84,7 +100,7 @@ private static void overwriteJaasConfig() { AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap<>()); Map configurationEntries = new HashMap<>(); - configurationEntries.put("KAFKA_CLIENT_CONTEXT_NAME", + configurationEntries.put("CLIENT_CONTEXT_NAME", new AppConfigurationEntry[] { kafkaClientConfigurationEntry }); Configuration.setConfiguration(new InternalConfiguration(configurationEntries)); } @@ -100,6 +116,5 @@ private static class InternalConfiguration extends Configuration { public AppConfigurationEntry[] getAppConfigurationEntry(String name) { return this.configurationEntries.get(name); } - } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index e4ff9bc8b..0ed94e3a6 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -201,6 +201,10 @@ public void testDataSource() throws SQLServerException { assertEquals(Boolean.toString(booleanPropValue), ds.getEncrypt(), TestResource.getResource("R_valuesAreDifferent")); + ds.setUseDefaultGSSCredential(booleanPropValue); + assertEquals(booleanPropValue, ds.getUseDefaultGSSCredential(), + TestResource.getResource("R_valuesAreDifferent")); + ds.setServerCertificate(stringPropValue); assertEquals(stringPropValue, ds.getServerCertificate(), TestResource.getResource("R_valuesAreDifferent")); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index c21f56101..9a4c4177b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -43,6 +43,7 @@ protected Object[][] getContents() { {"R_lengthTruncated", " The inserted length is truncated or not correct!"}, {"R_timeValueTruncated", " The time value is truncated or not correct!"}, {"R_invalidErrorMessage", "Invalid Error Message: "}, + {"R_kerberosNativeGSSFailure", "No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)"}, {"R_expectedFailPassed", "Expected failure did not fail"}, {"R_dataTypeNotFound", "Cannot find data type"}, {"R_illegalCharWktPosition", "Illegal character in Well-Known text at position {0}."}, {"R_illegalCharWkt", "Illegal Well-Known text. Please make sure Well-Known text is valid."}, diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java index 3b2d08721..92ba90811 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java @@ -42,7 +42,7 @@ private Constants() {} public static final String xAzureSQLDW = "xAzureSQLDW"; public static final String xAzureSQLMI = "xAzureSQLMI"; public static final String NTLM = "NTLM"; - public static final String Kerberos = "kerberos"; + public static final String kerberos = "kerberos"; public static final String MSI = "MSI"; public static final String reqExternalSetup = "reqExternalSetup"; public static final String clientCertAuth = "clientCertAuth"; From 7ce903f9829f19353874c70566c0675551970a26 Mon Sep 17 00:00:00 2001 From: Terry Chow <32403408+tkyc@users.noreply.github.com> Date: Mon, 16 Oct 2023 21:09:18 +0000 Subject: [PATCH 4/4] Tagged test with xSQLv11 (#2238) --- .../jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java index 6e49da7d6..353648dc6 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java @@ -763,6 +763,7 @@ public void testReverseColumnOrder() throws Exception { @Test @Tag(Constants.xAzureSQLDW) + @Tag(Constants.xSQLv11) @Tag(Constants.xSQLv12) public void testComputedCols() throws Exception { String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols) + " (id, json)"