Skip to content

Commit 984dbc9

Browse files
committed
Add support for Connection.abort
Include JDBC security requirements for both Connection.abort and Connection.setNetworkTimeout methods. Closes: #71
1 parent 9392133 commit 984dbc9

File tree

3 files changed

+208
-2
lines changed

3 files changed

+208
-2
lines changed

src/main/java/org/tarantool/jdbc/SQLConnection.java

+25-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.sql.SQLFeatureNotSupportedException;
2828
import java.sql.SQLNonTransientConnectionException;
2929
import java.sql.SQLNonTransientException;
30+
import java.sql.SQLPermission;
3031
import java.sql.SQLWarning;
3132
import java.sql.SQLXML;
3233
import java.sql.Savepoint;
@@ -42,6 +43,7 @@
4243
import java.util.concurrent.Executor;
4344
import java.util.concurrent.Future;
4445
import java.util.concurrent.TimeoutException;
46+
import java.util.concurrent.atomic.AtomicBoolean;
4547
import java.util.function.Function;
4648

4749
/**
@@ -51,6 +53,9 @@
5153
*/
5254
public class SQLConnection implements TarantoolConnection {
5355

56+
private static final SQLPermission CALL_ABORT_PERMISSION = new SQLPermission("callAbort");
57+
private static final SQLPermission SET_NETWORK_TIMEOUT_PERMISSION = new SQLPermission("setNetworkTimeout");
58+
5459
private static final int UNSET_HOLDABILITY = 0;
5560
private static final String PING_QUERY = "SELECT 1";
5661

@@ -60,6 +65,8 @@ public class SQLConnection implements TarantoolConnection {
6065
private DatabaseMetaData cachedMetadata;
6166
private int resultSetHoldability = UNSET_HOLDABILITY;
6267

68+
private final AtomicBoolean isClosed = new AtomicBoolean(false);
69+
6370
public SQLConnection(String url, Properties properties) throws SQLException {
6471
this.url = url;
6572
this.properties = properties;
@@ -205,6 +212,12 @@ public boolean getAutoCommit() throws SQLException {
205212

206213
@Override
207214
public void close() throws SQLException {
215+
if (isClosed.compareAndSet(false, true)) {
216+
closeInternal();
217+
}
218+
}
219+
220+
private void closeInternal() {
208221
client.close();
209222
}
210223

@@ -234,7 +247,7 @@ public void rollback(Savepoint savepoint) throws SQLException {
234247

235248
@Override
236249
public boolean isClosed() throws SQLException {
237-
return client.isClosed();
250+
return isClosed.get() || client.isClosed();
238251
}
239252

240253
@Override
@@ -417,6 +430,7 @@ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLExc
417430
if (milliseconds < 0) {
418431
throw new SQLException("Network timeout cannot be negative.");
419432
}
433+
SET_NETWORK_TIMEOUT_PERMISSION.checkGuard(this);
420434
client.setOperationTimeout(milliseconds);
421435
}
422436

@@ -515,7 +529,16 @@ public void abort(Executor executor) throws SQLException {
515529
if (isClosed()) {
516530
return;
517531
}
518-
throw new SQLFeatureNotSupportedException();
532+
if (executor == null) {
533+
throw new SQLNonTransientException(
534+
"Executor cannot be null",
535+
SQLStates.INVALID_PARAMETER_VALUE.getSqlState()
536+
);
537+
}
538+
CALL_ABORT_PERMISSION.checkGuard(this);
539+
if (isClosed.compareAndSet(false, true)) {
540+
executor.execute(this::closeInternal);
541+
}
519542
}
520543

521544
@Override

src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java

+33
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import static org.junit.jupiter.api.Assertions.assertNotNull;
66
import static org.junit.jupiter.api.Assertions.assertThrows;
77
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
import static org.junit.jupiter.api.Assertions.fail;
89
import static org.tarantool.TestAssumptions.assumeMinimalServerVersion;
910

1011
import org.tarantool.ServerVersion;
1112
import org.tarantool.TarantoolTestHelper;
13+
import org.tarantool.util.SQLStates;
1214

1315
import org.junit.jupiter.api.AfterAll;
1416
import org.junit.jupiter.api.AfterEach;
@@ -26,8 +28,10 @@
2628
import java.sql.SQLClientInfoException;
2729
import java.sql.SQLException;
2830
import java.sql.SQLFeatureNotSupportedException;
31+
import java.sql.SQLNonTransientException;
2932
import java.sql.Statement;
3033
import java.util.Map;
34+
import java.util.concurrent.Executors;
3135

3236
public class JdbcConnectionIT {
3337

@@ -456,5 +460,34 @@ void testSetClientInfoProperties() {
456460
assertEquals(ClientInfoStatus.REASON_UNKNOWN_PROPERTY, failedProperties.get(targetProperty));
457461
}
458462

463+
@Test
464+
void testConnectionAbort() throws SQLException {
465+
assertFalse(conn.isClosed());
466+
try (Statement statement = conn.createStatement()) {
467+
conn.abort(Executors.newSingleThreadExecutor());
468+
assertTrue(conn.isClosed());
469+
SQLNonTransientException exception = assertThrows(
470+
SQLNonTransientException.class,
471+
() -> statement.executeQuery("SELECT 1")
472+
);
473+
assertEquals(exception.getMessage(), "Statement is closed.");
474+
}
475+
}
476+
477+
@Test
478+
void testAlreadyClosedConnectionAbort() throws SQLException {
479+
conn.close();
480+
try {
481+
conn.abort(Executors.newSingleThreadExecutor());
482+
} catch (SQLException cause) {
483+
fail("Unexpected error", cause);
484+
}
485+
}
486+
487+
@Test
488+
void testNullParameterConnectionAbort() {
489+
SQLException exception = assertThrows(SQLException.class, () -> conn.abort(null));
490+
assertEquals(SQLStates.INVALID_PARAMETER_VALUE.getSqlState(), exception.getSQLState());
491+
}
459492
}
460493

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package org.tarantool.jdbc;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.tarantool.TestAssumptions.assumeMinimalServerVersion;
6+
7+
import org.tarantool.ServerVersion;
8+
import org.tarantool.TarantoolTestHelper;
9+
10+
import org.junit.jupiter.api.AfterAll;
11+
import org.junit.jupiter.api.AfterEach;
12+
import org.junit.jupiter.api.BeforeAll;
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.security.Permission;
17+
import java.sql.Connection;
18+
import java.sql.DriverManager;
19+
import java.sql.SQLException;
20+
import java.util.EnumSet;
21+
import java.util.concurrent.Executors;
22+
23+
public class JdbcSecurityIT {
24+
25+
private static TarantoolTestHelper testHelper;
26+
27+
private Connection connection;
28+
private SecurityManager originalSecurityManager;
29+
30+
@BeforeAll
31+
public static void setupEnv() {
32+
testHelper = new TarantoolTestHelper("jdbc-security-it");
33+
testHelper.createInstance();
34+
testHelper.startInstance();
35+
}
36+
37+
@AfterAll
38+
public static void teardownEnv() {
39+
testHelper.stopInstance();
40+
}
41+
42+
@BeforeEach
43+
public void setUpTest() throws SQLException {
44+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_1);
45+
connection = DriverManager.getConnection(SqlTestUtils.makeDefaultJdbcUrl());
46+
originalSecurityManager = System.getSecurityManager();
47+
}
48+
49+
@AfterEach
50+
public void tearDownTest() throws SQLException {
51+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_1);
52+
if (connection != null) {
53+
connection.close();
54+
}
55+
System.setSecurityManager(originalSecurityManager);
56+
}
57+
58+
@Test
59+
void testDeniedConnectionAbort() {
60+
EnumSet<JdbcPermission> exclusions = EnumSet.of(JdbcPermission.CALL_ABORT);
61+
System.setSecurityManager(new JdbcSecurityManager(true, exclusions));
62+
63+
SecurityException securityException = assertThrows(
64+
SecurityException.class,
65+
() -> connection.abort(Executors.newSingleThreadExecutor())
66+
);
67+
assertEquals(securityException.getMessage(), "Permission callAbort is not allowed");
68+
}
69+
70+
@Test
71+
void testDeniedSetConnectionTimeout() {
72+
EnumSet<JdbcPermission> exclusions = EnumSet.of(JdbcPermission.SET_NETWORK_TIMEOUT);
73+
System.setSecurityManager(new JdbcSecurityManager(true, exclusions));
74+
75+
SecurityException securityException = assertThrows(
76+
SecurityException.class,
77+
() -> connection.setNetworkTimeout(Executors.newSingleThreadExecutor(), 1000)
78+
);
79+
assertEquals(securityException.getMessage(), "Permission setNetworkTimeout is not allowed");
80+
}
81+
82+
/**
83+
* Lists permissions supported by JDBC API.
84+
*
85+
* <ul>
86+
* <li>setLog</li>
87+
* <li>callAbort</li>
88+
* <li>setSyncFactory<</li>
89+
* <li>setNetworkTimeout</li>
90+
* <li>deregisterDriver</li>
91+
* </ul>
92+
*
93+
* @see java.sql.SQLPermission
94+
*/
95+
private enum JdbcPermission {
96+
SET_LOG("setLog"),
97+
CALL_ABORT("callAbort"),
98+
SET_SYNC_FACTORY("setSyncFactory"),
99+
SET_NETWORK_TIMEOUT("setNetworkTimeout"),
100+
DEREGISTER_DRIVER("deregisterDriver");
101+
102+
private final String permissionName;
103+
104+
JdbcPermission(String permissionName) {
105+
this.permissionName = permissionName;
106+
}
107+
108+
public String getPermissionName() {
109+
return permissionName;
110+
}
111+
112+
public static JdbcPermission fromName(String name) {
113+
for (JdbcPermission values : JdbcPermission.values()) {
114+
if (values.permissionName.equals(name)) {
115+
return values;
116+
}
117+
}
118+
return null;
119+
}
120+
}
121+
122+
private static class JdbcSecurityManager extends SecurityManager {
123+
private final boolean allowAll;
124+
private final EnumSet<JdbcPermission> exclusions;
125+
126+
/**
127+
* Configures a new {@link SecurityManager} that follows the custom rules.
128+
*
129+
* @param allowAll whether permissions are allowed by default or not
130+
* @param exclusions optional set of exclusions
131+
*/
132+
private JdbcSecurityManager(boolean allowAll, EnumSet<JdbcPermission> exclusions) {
133+
this.exclusions = exclusions;
134+
this.allowAll = allowAll;
135+
}
136+
137+
@Override
138+
public void checkPermission(Permission permission) {
139+
JdbcPermission jdbcPermission = JdbcPermission.fromName(permission.getName());
140+
if (jdbcPermission == null) {
141+
return;
142+
}
143+
boolean allowed = allowAll ^ exclusions.contains(jdbcPermission);
144+
if (!allowed) {
145+
throw new SecurityException("Permission " + jdbcPermission.getPermissionName() + " is not allowed");
146+
}
147+
}
148+
}
149+
}
150+

0 commit comments

Comments
 (0)