Skip to content

Commit b727a96

Browse files
lilgreenbirdJeffery-Wasty
authored andcommitted
Updates for running tests with managed identity (#2416)
Signed-off-by: Jeff Wasty <[email protected]> # Conflicts: # src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java # src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java # src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java # src/test/java/com/microsoft/sqlserver/jdbc/connection/TimeoutTest.java # src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java # src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java
1 parent 6d5c519 commit b727a96

File tree

14 files changed

+1204
-168
lines changed

14 files changed

+1204
-168
lines changed

src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ protected static void createTable(String tableName, String cekName, String table
328328
TestUtils.dropTableIfExists(tableName, stmt);
329329
sql = String.format(createSql, tableName, sql);
330330
stmt.execute(sql);
331-
stmt.execute("DBCC FREEPROCCACHE");
331+
TestUtils.freeProcCache(stmt);
332332
} catch (SQLException e) {
333333
fail(e.getMessage());
334334
}
@@ -362,7 +362,7 @@ protected static void createPrecisionTable(String tableName, String table[][], S
362362
}
363363
sql = String.format(createSql, tableName, sql);
364364
stmt.execute(sql);
365-
stmt.execute("DBCC FREEPROCCACHE");
365+
TestUtils.freeProcCache(stmt);
366366
} catch (SQLException e) {
367367
fail(e.getMessage());
368368
}
@@ -390,7 +390,7 @@ protected static void createScaleTable(String tableName, String table[][], Strin
390390

391391
sql = String.format(createSql, tableName, sql);
392392
stmt.execute(sql);
393-
stmt.execute("DBCC FREEPROCCACHE");
393+
TestUtils.freeProcCache(stmt);
394394
} catch (SQLException e) {
395395
fail(e.getMessage());
396396
}

src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2200,7 +2200,7 @@ protected static void createDateTableCallableStatement(String cekName) throws SQ
22002200
SQLServerStatement stmt = (SQLServerStatement) con.createStatement()) {
22012201
TestUtils.dropTableIfExists(DATE_TABLE_AE, stmt);
22022202
stmt.execute(sql);
2203-
stmt.execute("DBCC FREEPROCCACHE");
2203+
TestUtils.freeProcCache(stmt);
22042204
} catch (SQLException e) {
22052205
fail(e.getMessage());
22062206
}

src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java

Lines changed: 285 additions & 14 deletions
Large diffs are not rendered by default.

src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ protected Object[][] getContents() {
4343
{"R_lengthTruncated", " The inserted length is truncated or not correct!"},
4444
{"R_timeValueTruncated", " The time value is truncated or not correct!"},
4545
{"R_invalidErrorMessage", "Invalid Error Message: "},
46+
{"R_kerberosNativeGSSFailure",
47+
"No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)"},
4648
{"R_expectedFailPassed", "Expected failure did not fail"}, {"R_dataTypeNotFound", "Cannot find data type"},
4749
{"R_illegalCharWktPosition", "Illegal character in Well-Known text at position {0}."},
4850
{"R_illegalCharWkt", "Illegal Well-Known text. Please make sure Well-Known text is valid."},
@@ -54,14 +56,23 @@ protected Object[][] getContents() {
5456
{"R_createDropAlterTableFailed", "Create/drop/alter table with preparedStatement failed!"},
5557
{"R_grantFailed", "grant table with preparedStatement failed!"},
5658
{"R_connectionIsClosed", "The connection is closed."},
59+
{"R_noNamedAndIndexedParameters",
60+
"Cannot specify both named and indexed parameters when 'useFlexibleCallableStatements=false'"},
61+
{"R_unknownOutputParameter",
62+
"Cannot acquire output parameter value by name. No parameter index was associated with the output parameter name. If acquiring output parameter by name, verify that the output parameter was initially registered by name."},
5763
{"R_ConnectionURLNull", "The connection URL is null."},
5864
{"R_connectionIsNotClosed", "The connection is not closed."},
5965
{"R_invalidExceptionMessage", "Invalid exception message"},
66+
{"R_invalidClientSecret", "AADSTS7000215: Invalid client secret provided"},
67+
{"R_invalidCertFields",
68+
"Error reading certificate, please verify the location of the certificate.signed fields invalid"},
69+
{"R_invalidAADAuth", "Failed to authenticate the user {0} in Active Directory (Authentication={1})"},
6070
{"R_failedValidate", "failed to validate values in $0} "}, {"R_tableNotDropped", "table not dropped. "},
6171
{"R_connectionReset", "Connection reset"}, {"R_unknownException", "Unknown exception"},
6272
{"R_deadConnection", "Dead connection should be invalid"},
6373
{"R_wrongExceptionMessage", "Wrong exception message"}, {"R_wrongSqlState", "Wrong sql state"},
6474
{"R_parameterNotDefined", "Parameter {0} was not defined"},
75+
{"R_notValidParameterForProcedure", "{0} is not a parameter for procedure {1}."},
6576
{"R_unexpectedExceptionContent", "Unexpected content in exception message"},
6677
{"R_connectionClosed", "The connection has been closed"},
6778
{"R_conversionFailed", "Conversion failed when converting {0} to {1} data type"},
@@ -201,5 +212,14 @@ protected Object[][] getContents() {
201212
{"R_objectNullOrEmpty", "The {0} is null or empty."},
202213
{"R_cekDecryptionFailed", "Failed to decrypt a column encryption key using key store provider: {0}."},
203214
{"R_connectTimedOut", "connect timed out"},
204-
{"R_sessionKilled", "Cannot continue the execution because the session is in the kill state"}};
215+
{"R_sharedTimerStopOnNoRef", "SharedTimer should be stopped after all references are removed."},
216+
{"R_sessionKilled", "Cannot continue the execution because the session is in the kill state"},
217+
{"R_failedFedauth", "Failed to acquire fedauth token: "},
218+
{"R_noLoginModulesConfiguredForJdbcDriver",
219+
"javax.security.auth.login.LoginException (No LoginModules configured for SQLJDBCDriver)"},
220+
{"R_unexpectedThreadCount", "Thread count is higher than expected."},
221+
{"R_expectedClassDoesNotMatchActualClass",
222+
"Expected column class {0} does not match actual column class {1} for column {2}."},
223+
{"R_loginFailedMI", "Login failed for user '<token-identified principal>'"},
224+
{"R_MInotAvailable", "Managed Identity authentication is not available"},};
205225
}

src/test/java/com/microsoft/sqlserver/jdbc/TestUtils.java

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import java.io.File;
1414
import java.io.FileInputStream;
1515
import java.io.FileOutputStream;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.lang.reflect.Field;
1619
import java.net.URI;
1720
import java.security.KeyStore;
1821
import java.security.cert.CertificateFactory;
@@ -26,11 +29,27 @@
2629
import java.util.ArrayList;
2730
import java.util.Arrays;
2831
import java.util.Calendar;
32+
import java.util.Date;
33+
import java.util.HashSet;
2934
import java.util.List;
3035
import java.util.Locale;
36+
import java.util.Properties;
3137
import java.util.ResourceBundle;
32-
38+
import java.util.Set;
39+
import java.util.concurrent.CompletableFuture;
40+
import java.util.concurrent.ExecutorService;
41+
import java.util.concurrent.Executors;
42+
import java.util.concurrent.TimeUnit;
43+
44+
import org.junit.Assert;
45+
46+
import com.microsoft.aad.msal4j.ClientCredentialFactory;
47+
import com.microsoft.aad.msal4j.ClientCredentialParameters;
48+
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
49+
import com.microsoft.aad.msal4j.IAuthenticationResult;
50+
import com.microsoft.aad.msal4j.IClientCredential;
3351
import com.microsoft.sqlserver.testframework.AbstractSQLGenerator;
52+
import com.microsoft.sqlserver.testframework.Constants;
3453
import com.microsoft.sqlserver.testframework.PrepUtil;
3554
import com.microsoft.sqlserver.testframework.sqlType.SqlBigInt;
3655
import com.microsoft.sqlserver.testframework.sqlType.SqlBinary;
@@ -75,6 +94,97 @@ public final class TestUtils {
7594
static final int ENGINE_EDITION_FOR_SQL_AZURE_DW = 6;
7695
static final int ENGINE_EDITION_FOR_SQL_AZURE_MI = 8;
7796

97+
public static final int TEST_TOKEN_EXPIRY_SECONDS = 120; // token expiry time in secs
98+
99+
public static String ACCESS_TOKEN_CALLBACK = null;
100+
101+
static String applicationKey;
102+
static String applicationClientID;
103+
104+
static {
105+
try (InputStream input = new FileInputStream(Constants.CONFIG_PROPERTIES_FILE)) {
106+
Properties configProperties = new Properties();
107+
configProperties.load(input);
108+
applicationKey = configProperties.getProperty("applicationKey");
109+
applicationClientID = configProperties.getProperty("applicationClientID");
110+
} catch (IOException e) {
111+
// No config file found
112+
}
113+
}
114+
115+
public static boolean expireTokenToggle = false;
116+
117+
public static final SQLServerAccessTokenCallback accessTokenCallback = new SQLServerAccessTokenCallback() {
118+
@Override
119+
public SqlAuthenticationToken getAccessToken(String spn, String stsurl) {
120+
String scope = spn + "/.default";
121+
Set<String> scopes = new HashSet<>();
122+
scopes.add(scope);
123+
124+
try {
125+
ExecutorService executorService = Executors.newSingleThreadExecutor();
126+
IClientCredential credential = ClientCredentialFactory.createFromSecret(applicationKey);
127+
ConfidentialClientApplication clientApplication = ConfidentialClientApplication
128+
.builder(applicationClientID, credential).executorService(executorService).authority(stsurl)
129+
.build();
130+
CompletableFuture<IAuthenticationResult> future = clientApplication
131+
.acquireToken(ClientCredentialParameters.builder(scopes).build());
132+
133+
IAuthenticationResult authenticationResult = future.get();
134+
String accessToken = authenticationResult.accessToken();
135+
long expiresOn = authenticationResult.expiresOnDate().getTime();
136+
137+
ACCESS_TOKEN_CALLBACK = accessToken;
138+
139+
if (expireTokenToggle) {
140+
Date now = new Date();
141+
long minutesToExpireWithin = TEST_TOKEN_EXPIRY_SECONDS * 1000; // Expire within 2 minutes
142+
return new SqlAuthenticationToken(accessToken, now.getTime() + minutesToExpireWithin);
143+
} else {
144+
return new SqlAuthenticationToken(accessToken, expiresOn);
145+
}
146+
} catch (Exception e) {
147+
fail(TestResource.getResource("R_unexpectedException") + e.getMessage());
148+
}
149+
return null;
150+
}
151+
};
152+
153+
public static void setAccessTokenExpiry(Object con, String accessToken) {
154+
Field fedAuthTokenField;
155+
try {
156+
fedAuthTokenField = SQLServerConnection.class.getDeclaredField("fedAuthToken");
157+
fedAuthTokenField.setAccessible(true);
158+
159+
Date newExpiry = new Date(
160+
System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(TEST_TOKEN_EXPIRY_SECONDS));
161+
SqlAuthenticationToken newFedAuthToken = new SqlAuthenticationToken(accessToken, newExpiry);
162+
fedAuthTokenField.set(con, newFedAuthToken);
163+
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
164+
Assert.fail("Failed to set token expiry: " + e.getMessage());
165+
}
166+
}
167+
168+
public static void setAccessTokenExpiry(Object con) {
169+
Field fedAuthTokenField;
170+
Field wrappedConnection;
171+
try {
172+
fedAuthTokenField = SQLServerConnection.class.getDeclaredField("fedAuthToken");
173+
fedAuthTokenField.setAccessible(true);
174+
175+
wrappedConnection = SQLServerConnectionPoolProxy.class.getDeclaredField("wrappedConnection");
176+
wrappedConnection.setAccessible(true);
177+
Object wrappedConnectionObj = wrappedConnection.get(con);
178+
179+
Date newExpiry = new Date(
180+
System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(TEST_TOKEN_EXPIRY_SECONDS));
181+
SqlAuthenticationToken newFedAuthToken = new SqlAuthenticationToken(ACCESS_TOKEN_CALLBACK, newExpiry);
182+
fedAuthTokenField.set(wrappedConnectionObj, newFedAuthToken);
183+
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
184+
Assert.fail("Failed to set token expiry: " + e.getMessage());
185+
}
186+
}
187+
78188
private TestUtils() {}
79189

80190
/**
@@ -127,6 +237,15 @@ public static boolean isAzureMI(Connection con) {
127237
return ((SQLServerConnection) con).isAzureMI();
128238
}
129239

240+
/**
241+
* Checks if connection is established to Azure Synapse OnDemand server
242+
*
243+
*/
244+
public static boolean isAzureSynapseOnDemand(Connection con) {
245+
isAzure(con);
246+
return ((SQLServerConnection) con).isAzureSynapseOnDemandEndpoint();
247+
}
248+
130249
/**
131250
* Checks if connection is established to server that supports AEv2.
132251
*
@@ -300,6 +419,18 @@ public static void dropTableIfExists(String tableName, java.sql.Statement stmt)
300419
dropObjectIfExists(tableName, "U", stmt);
301420
}
302421

422+
public static void dropTableWithSchemaIfExists(String tableNameWithSchema,
423+
java.sql.Statement stmt) throws SQLException {
424+
stmt.execute(
425+
"IF OBJECT_ID('" + tableNameWithSchema + "', 'U') IS NOT NULL DROP TABLE " + tableNameWithSchema + ";");
426+
}
427+
428+
public static void dropProcedureWithSchemaIfExists(String procedureWithSchema,
429+
java.sql.Statement stmt) throws SQLException {
430+
stmt.execute("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" + procedureWithSchema
431+
+ "') AND type in (N'P', N'PC')) DROP PROCEDURE " + procedureWithSchema + ";");
432+
}
433+
303434
/**
304435
* Deletes the contents of a table.
305436
*
@@ -376,7 +507,8 @@ public static void dropTypeIfExists(String typeName, java.sql.Statement stmt) th
376507
* @throws SQLException
377508
*/
378509
public static void dropUserDefinedTypeIfExists(String typeName, Statement stmt) throws SQLException {
379-
stmt.executeUpdate("IF EXISTS (select * from sys.types where name = '" + escapeSingleQuotes(typeName) + "') DROP TYPE " + typeName);
510+
stmt.executeUpdate("IF EXISTS (select * from sys.types where name = '" + escapeSingleQuotes(typeName)
511+
+ "') DROP TYPE " + typeName);
380512
}
381513

382514
/**
@@ -403,7 +535,31 @@ public static void dropDatabaseIfExists(String databaseName, String connectionSt
403535
*/
404536
public static void dropSchemaIfExists(String schemaName, Statement stmt) throws SQLException {
405537
stmt.execute("if EXISTS (SELECT * FROM sys.schemas where name = '" + escapeSingleQuotes(schemaName)
406-
+ "') drop schema " + AbstractSQLGenerator.escapeIdentifier(schemaName));
538+
+ "') DROP SCHEMA" + AbstractSQLGenerator.escapeIdentifier(schemaName));
539+
}
540+
541+
/**
542+
* mimic "DROP USER..."
543+
*
544+
* @param userName
545+
* @param stmt
546+
* @throws SQLException
547+
*/
548+
public static void dropUserIfExists(String userName, Statement stmt) throws SQLException {
549+
stmt.execute("IF EXISTS (SELECT * FROM sys.sysusers where name = '" + escapeSingleQuotes(userName)
550+
+ "') DROP USER " + AbstractSQLGenerator.escapeIdentifier(userName));
551+
}
552+
553+
/**
554+
* mimic "DROP LOGIN..."
555+
*
556+
* @param userName
557+
* @param stmt
558+
* @throws SQLException
559+
*/
560+
public static void dropLoginIfExists(String userName, Statement stmt) throws SQLException {
561+
stmt.execute("IF EXISTS (SELECT * FROM sys.sysusers where name = '" + escapeSingleQuotes(userName)
562+
+ "') DROP LOGIN " + AbstractSQLGenerator.escapeIdentifier(userName));
407563
}
408564

409565
/**
@@ -964,4 +1120,27 @@ private static java.security.cert.Certificate getCertificate(String certname) th
9641120
return cf.generateCertificate(is);
9651121
}
9661122
}
1123+
1124+
public static String getConnectionID(
1125+
SQLServerPooledConnection pc) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
1126+
Class<?> pooledConnection = Class.forName("com.microsoft.sqlserver.jdbc.SQLServerPooledConnection");
1127+
Class<?> connection = Class.forName("com.microsoft.sqlserver.jdbc.SQLServerConnection");
1128+
1129+
Field physicalConnection = pooledConnection.getDeclaredField("physicalConnection");
1130+
Field traceID = connection.getDeclaredField("traceID");
1131+
1132+
physicalConnection.setAccessible(true);
1133+
traceID.setAccessible(true);
1134+
1135+
SQLServerConnection conn = (SQLServerConnection) physicalConnection.get(pc);
1136+
return (String) traceID.get(conn);
1137+
}
1138+
1139+
public static void freeProcCache(Statement stmt) {
1140+
try {
1141+
stmt.execute("DBCC FREEPROCCACHE");
1142+
} catch (Exception e) {
1143+
// ignore error - some tests fails due to permission issues from managed identity, this does not seem to affect tests
1144+
}
1145+
}
9671146
}

src/test/java/com/microsoft/sqlserver/jdbc/connection/PoolingTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ public void testConnectionPoolClose() throws SQLException {
154154

155155
@Test
156156
public void testConnectionPoolClientConnectionId() throws SQLException {
157+
String auth = TestUtils.getProperty(connectionString, "authentication");
158+
org.junit.Assume.assumeTrue(auth != null
159+
&& (auth.equalsIgnoreCase("SqlPassword") || auth.equalsIgnoreCase("ActiveDirectoryPassword")));
160+
157161
SQLServerXADataSource ds = new SQLServerXADataSource();
158162
ds.setURL(connectionString);
159163
PooledConnection pc = null;

0 commit comments

Comments
 (0)