Skip to content

Commit 3c990cb

Browse files
author
mhaderman
authored
Merge pull request #1 from mhaderman/dev
Dev
2 parents 80a7984 + 7d4d62b commit 3c990cb

File tree

6 files changed

+486
-3
lines changed

6 files changed

+486
-3
lines changed

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
/**
3434
* SQLServerConnectionPoolProxy is a wrapper around SQLServerConnection object. When returning a connection object from PooledConnection.getConnection
35-
* we returnt this proxy per SPEC.
35+
* we return this proxy per SPEC.
3636
* <p>
3737
* This class's public functions need to be kept identical to the SQLServerConnection's.
3838
* <p>
@@ -114,7 +114,7 @@ public void commit() throws SQLServerException {
114114
}
115115

116116
/**
117-
* Rollback a transcation.
117+
* Rollback a transaction.
118118
*
119119
* @throws SQLServerException
120120
* if no transaction exists or if the connection is in auto-commit mode.

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java

+54-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ private class MetaInfo {
366366
*/
367367
private MetaInfo parseStatement(String sql,
368368
String sTableMarker) {
369-
StringTokenizer st = new StringTokenizer(sql, " ,", true);
369+
StringTokenizer st = new StringTokenizer(sql, " ,\r\n", true);
370370

371371
/* Find the table */
372372

@@ -375,6 +375,10 @@ private MetaInfo parseStatement(String sql,
375375
while (st.hasMoreTokens()) {
376376
String sToken = st.nextToken().trim();
377377

378+
if(sToken.contains("*/")){
379+
sToken = removeCommentsInTheBeginning(sToken, 0, 0, "/*", "*/");
380+
}
381+
378382
if (sToken.equalsIgnoreCase(sTableMarker)) {
379383
if (st.hasMoreTokens()) {
380384
metaTable = escapeParse(st, st.nextToken());
@@ -409,6 +413,18 @@ private MetaInfo parseStatement(String sql) throws SQLServerException {
409413
if (st.hasMoreTokens()) {
410414
String sToken = st.nextToken().trim();
411415

416+
// filter out multiple line comments in the beginning of the query
417+
if (sToken.contains("/*")) {
418+
String sqlWithoutCommentsInBeginning = removeCommentsInTheBeginning(sql, 0, 0, "/*", "*/");
419+
return parseStatement(sqlWithoutCommentsInBeginning);
420+
}
421+
422+
// filter out single line comments in the beginning of the query
423+
if (sToken.contains("--")) {
424+
String sqlWithoutCommentsInBeginning = removeCommentsInTheBeginning(sql, 0, 0, "--", "\n");
425+
return parseStatement(sqlWithoutCommentsInBeginning);
426+
}
427+
412428
if (sToken.equalsIgnoreCase("INSERT"))
413429
return parseStatement(sql, "INTO"); // INTO marks the table name
414430

@@ -424,6 +440,40 @@ private MetaInfo parseStatement(String sql) throws SQLServerException {
424440

425441
return null;
426442
}
443+
444+
private String removeCommentsInTheBeginning(String sql,
445+
int startCommentMarkCount,
446+
int endCommentMarkCount,
447+
String startMark,
448+
String endMark) {
449+
int startCommentMarkIndex = sql.indexOf(startMark);
450+
int endCommentMarkIndex = sql.indexOf(endMark);
451+
452+
if (-1 == startCommentMarkIndex) {
453+
startCommentMarkIndex = Integer.MAX_VALUE;
454+
}
455+
if (-1 == endCommentMarkIndex) {
456+
endCommentMarkIndex = Integer.MAX_VALUE;
457+
}
458+
459+
// Base case. startCommentMarkCount is guaranteed to be bigger than 0 because the method is called when /* occurs
460+
if (startCommentMarkCount == endCommentMarkCount) {
461+
if (startCommentMarkCount != 0 && endCommentMarkCount != 0) {
462+
return sql;
463+
}
464+
}
465+
466+
// filter out first start comment mark
467+
if (startCommentMarkIndex < endCommentMarkIndex) {
468+
String sqlWithoutCommentsInBeginning = sql.substring(startCommentMarkIndex + startMark.length());
469+
return removeCommentsInTheBeginning(sqlWithoutCommentsInBeginning, ++startCommentMarkCount, endCommentMarkCount, startMark, endMark);
470+
}
471+
// filter out first end comment mark
472+
else {
473+
String sqlWithoutCommentsInBeginning = sql.substring(endCommentMarkIndex + endMark.length());
474+
return removeCommentsInTheBeginning(sqlWithoutCommentsInBeginning, startCommentMarkCount, ++endCommentMarkCount, startMark, endMark);
475+
}
476+
}
427477

428478
String parseThreePartNames(String threeName) throws SQLServerException {
429479
int noofitems = 0;
@@ -581,6 +631,9 @@ private void checkClosed() throws SQLServerException {
581631
catch (SQLException e) {
582632
SQLServerException.makeFromDriverError(con, stmtParent, e.toString(), null, false);
583633
}
634+
catch(StringIndexOutOfBoundsException e){
635+
SQLServerException.makeFromDriverError(con, stmtParent, e.toString(), null, false);
636+
}
584637
}
585638

586639
public boolean isWrapperFor(Class<?> iface) throws SQLException {

src/samples/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ The following samples are available:
3131

3232
7. sparse
3333
* **SparseColumns** - how to detect column sets. It also shows a technique for parsing a column set's XML output, to get data from the sparse columns.
34+
35+
8. constrained
36+
* **ConstrainedSample** - how to connect with Kerberos constrained delegation using an impersonated credential.
3437

3538

3639
## Running Samples

src/samples/constrained/pom.xml

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.microsoft.sqlserver.jdbc</groupId>
8+
<artifactId>constrained</artifactId>
9+
<version>0.0.1</version>
10+
11+
<packaging>jar</packaging>
12+
13+
<name>${project.artifactId}</name>
14+
<url>https://github.com/Microsoft/mssql-jdbc/tree/master/src/samples</url>
15+
16+
<properties>
17+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18+
</properties>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>com.microsoft.sqlserver</groupId>
23+
<artifactId>mssql-jdbc</artifactId>
24+
<version>6.1.5.jre8-preview</version>
25+
</dependency>
26+
</dependencies>
27+
28+
<profiles>
29+
<profile>
30+
<id>ConstrainedSample</id>
31+
<build>
32+
<finalName>ConstrainedSample</finalName>
33+
34+
<plugins>
35+
<plugin>
36+
<groupId>org.codehaus.mojo</groupId>
37+
<artifactId>exec-maven-plugin</artifactId>
38+
<version>1.6.0</version>
39+
<configuration>
40+
<mainClass>ConstrainedSample</mainClass>
41+
</configuration>
42+
</plugin>
43+
</plugins>
44+
</build>
45+
</profile>
46+
</profiles>
47+
48+
<build>
49+
<plugins>
50+
<plugin>
51+
<groupId>org.apache.maven.plugins</groupId>
52+
<artifactId>maven-compiler-plugin</artifactId>
53+
<version>3.6.0</version>
54+
<configuration>
55+
<source>1.8</source>
56+
<target>1.8</target>
57+
</configuration>
58+
</plugin>
59+
60+
<plugin>
61+
<groupId>org.apache.maven.plugins</groupId>
62+
<artifactId>maven-resources-plugin</artifactId>
63+
<version>3.0.2</version>
64+
</plugin>
65+
</plugins>
66+
</build>
67+
68+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import java.security.PrivilegedActionException;
2+
import java.security.PrivilegedExceptionAction;
3+
import java.sql.Connection;
4+
import java.sql.DriverManager;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
import java.util.Properties;
8+
9+
import javax.security.auth.Subject;
10+
import javax.security.auth.login.LoginException;
11+
import javax.security.auth.spi.LoginModule;
12+
13+
import org.ietf.jgss.GSSCredential;
14+
import org.ietf.jgss.GSSException;
15+
import org.ietf.jgss.GSSManager;
16+
import org.ietf.jgss.GSSName;
17+
import org.ietf.jgss.Oid;
18+
19+
import com.sun.security.jgss.ExtendedGSSCredential;
20+
21+
/**
22+
*
23+
* Sample of constrained delegation connection.
24+
*
25+
* An intermediate service is necessary to impersonate the client. This service needs to be configured with the
26+
* options:
27+
* "Trust this user for delegation to specified services only"
28+
* "Use any authentication protocol"
29+
*
30+
*/
31+
public class ConstrainedSample {
32+
33+
// Connection properties
34+
private static final String DRIVER_CLASS_NAME ="com.microsoft.sqlserver.jdbc.SQLServerDriver";
35+
private static final String CONNECTION_URI = "jdbc:sqlserver:// URI of the SQLServer";
36+
37+
private static final String TARGET_USER_NAME = "User to be impersonated";
38+
39+
// Impersonation service properties
40+
private static final String SERVICE_PRINCIPAL = "SPN";
41+
private static final String KEYTAB_ROUTE = "Route to the keytab file";
42+
43+
private static final Properties driverProperties;
44+
private static Oid krb5Oid;
45+
46+
private static Subject serviceSubject;
47+
48+
static {
49+
50+
driverProperties = new Properties();
51+
driverProperties.setProperty("integratedSecurity", "true");
52+
driverProperties.setProperty("authenticationScheme", "JavaKerberos");
53+
54+
try {
55+
krb5Oid = new Oid("1.2.840.113554.1.2.2");
56+
} catch (GSSException e) {
57+
System.out.println("Error creating Oid: " + e);
58+
System.exit(-1);
59+
}
60+
}
61+
62+
public static void main(String... args) throws Exception {
63+
64+
Class.forName(DRIVER_CLASS_NAME).getConstructor().newInstance();
65+
System.out.println("Service subject: " + doInitialLogin());
66+
67+
// Get impersonated user credentials thanks S4U2self mechanism
68+
GSSCredential impersonatedUserCreds = impersonate();
69+
System.out.println("Credentials for " + TARGET_USER_NAME + ": " + impersonatedUserCreds);
70+
71+
// Create a connection for target service thanks S4U2proxy mechanism
72+
try (Connection con = createConnection(impersonatedUserCreds)) {
73+
System.out.println("Connection succesfully: " + con);
74+
}
75+
76+
}
77+
78+
/**
79+
*
80+
* Authenticate the intermediate server that is going to impersonate the client
81+
*
82+
* @return a subject for the intermediate server with the keytab credentials
83+
* @throws PrivilegedActionException in case of failure
84+
*/
85+
private static Subject doInitialLogin() throws PrivilegedActionException {
86+
serviceSubject = new Subject();
87+
88+
LoginModule krb5Module;
89+
try {
90+
krb5Module = (LoginModule) Class.forName("com.sun.security.auth.module.Krb5LoginModule").getConstructor()
91+
.newInstance();
92+
} catch (Exception e) {
93+
System.out.print("Error loading Krb5LoginModule module: " + e);
94+
throw new PrivilegedActionException(e);
95+
}
96+
97+
System.setProperty("sun.security.krb5.debug", String.valueOf(true));
98+
99+
Map<String, String> options = new HashMap<>();
100+
options.put("useKeyTab", "true");
101+
options.put("storeKey", "true");
102+
options.put("doNotPrompt", "true");
103+
options.put("keyTab", KEYTAB_ROUTE);
104+
options.put("principal", SERVICE_PRINCIPAL);
105+
options.put("debug", "true");
106+
options.put("isInitiator", "true");
107+
108+
Map<String, String> sharedState = new HashMap<>(0);
109+
110+
krb5Module.initialize(serviceSubject, null, sharedState, options);
111+
try {
112+
krb5Module.login();
113+
krb5Module.commit();
114+
} catch (LoginException e) {
115+
System.out.print("Error authenticating with Kerberos: " + e);
116+
try {
117+
krb5Module.abort();
118+
} catch (LoginException e1) {
119+
System.out.print("Error aborting Kerberos authentication: " + e1);
120+
throw new PrivilegedActionException(e);
121+
}
122+
throw new PrivilegedActionException(e);
123+
}
124+
125+
return serviceSubject;
126+
}
127+
128+
/**
129+
* Generate the impersonated user credentials thanks to the S4U2self mechanism
130+
*
131+
* @return the client impersonated GSSCredential
132+
* @throws PrivilegedActionException in case of failure
133+
*/
134+
private static GSSCredential impersonate() throws PrivilegedActionException {
135+
return Subject.doAs(serviceSubject, (PrivilegedExceptionAction<GSSCredential>) () -> {
136+
GSSManager manager = GSSManager.getInstance();
137+
138+
GSSCredential self = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, krb5Oid,
139+
GSSCredential.INITIATE_ONLY);
140+
GSSName user = manager.createName(TARGET_USER_NAME, GSSName.NT_USER_NAME);
141+
return ((ExtendedGSSCredential) self).impersonate(user);
142+
});
143+
}
144+
145+
/**
146+
* Obtains a connection using an impersonated credential
147+
*
148+
* @param impersonatedUserCredential impersonated user credentials
149+
* @return a connection to the SQL Server opened using the given impersonated credential
150+
* @throws PrivilegedActionException in case of failure
151+
*/
152+
private static Connection createConnection(final GSSCredential impersonatedUserCredential)
153+
throws PrivilegedActionException {
154+
155+
return Subject.doAs(new Subject(), (PrivilegedExceptionAction<Connection>) () -> {
156+
driverProperties.put("gsscredential", impersonatedUserCredential);
157+
return DriverManager.getConnection(CONNECTION_URI, driverProperties);
158+
});
159+
}
160+
161+
}

0 commit comments

Comments
 (0)