From 2bcaa2ac80b241dc13198673f6cc965f65dd53c0 Mon Sep 17 00:00:00 2001 From: nikinagy <61757742+nikinagy@users.noreply.github.com> Date: Tue, 21 Apr 2020 14:39:41 +0100 Subject: [PATCH] CORDA-3569 - Add RestrictedConnection and more blocked methods to RestrictedEntityManager (#6129) * adding blocked functions ro RestrictedEntityManager and creating RestrictedConnection class * adding flow tests and fixing issues regarding the review * adding quasar util to gradle * updating flow tests * adding space before } at .isThrownBy() * adding spaces --- node-api-tests/build.gradle | 1 + .../RestrictedConnectionFlowTest.kt | 78 +++++++++++++ .../RestrictedEntityManagerFlowTest.kt | 84 ++++++++++++++ .../persistence/RestrictedConnection.kt | 80 ++++++++++++++ .../persistence/RestrictedEntityManager.kt | 28 ++++- .../persistence/RestrictedConnectionTest.kt | 104 ++++++++++++++++++ .../RestrtictedEntityManagerTest.kt | 52 +++++++++ .../net/corda/node/internal/AbstractNode.kt | 3 +- 8 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedConnectionFlowTest.kt create mode 100644 node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedEntityManagerFlowTest.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnection.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnectionTest.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrtictedEntityManagerTest.kt diff --git a/node-api-tests/build.gradle b/node-api-tests/build.gradle index 50cf05a864..8d4edc4aff 100644 --- a/node-api-tests/build.gradle +++ b/node-api-tests/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'kotlin' +apply plugin: 'net.corda.plugins.quasar-utils' description 'NodeAPI tests that require node etc' diff --git a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedConnectionFlowTest.kt b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedConnectionFlowTest.kt new file mode 100644 index 0000000000..f948c75f37 --- /dev/null +++ b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedConnectionFlowTest.kt @@ -0,0 +1,78 @@ +package net.corda.nodeapitests.internal.persistence + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.CordaX500Name +import net.corda.core.utilities.getOrThrow +import net.corda.nodeapi.internal.persistence.RestrictedConnection +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNetworkParameters +import net.corda.testing.node.StartedMockNode +import org.assertj.core.api.Assertions +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertTrue + +class RestrictedConnectionFlowTest { + private lateinit var aliceNode: StartedMockNode + private lateinit var mockNetwork: MockNetwork + + @InitiatingFlow + class TestIfItIsRestrictedConnection : FlowLogic() { + @Suspendable + override fun call() : Boolean { + val connection = serviceHub.jdbcSession() + return connection is RestrictedConnection + } + } + + @InitiatingFlow + class TestAutoCommitMethodIsBlocked : FlowLogic() { + @Suspendable + override fun call() { + val connection = serviceHub.jdbcSession() + connection.autoCommit = true + } + } + + @InitiatingFlow + class TestCloseMethodIsBlocked : FlowLogic() { + @Suspendable + override fun call() { + val connection = serviceHub.jdbcSession() + connection.close() + } + } + + @Before + fun init() { + mockNetwork = MockNetwork(MockNetworkParameters()) + aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB")) + } + + @After + fun done() { + mockNetwork.stopNodes() + } + + @Test(timeout=300_000) + fun testIfItIsRestrictedConnection() { + assertTrue { aliceNode.startFlow(TestIfItIsRestrictedConnection()).get() } + mockNetwork.runNetwork() + } + + @Test(timeout=300_000) + fun testMethodsAreBlocked() { + Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java) + .isThrownBy { aliceNode.startFlow(TestAutoCommitMethodIsBlocked()).getOrThrow() } + .withMessageContaining("This method cannot be called via ServiceHub.jdbcSession.") + + Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java) + .isThrownBy { aliceNode.startFlow(TestCloseMethodIsBlocked()).getOrThrow() } + .withMessageContaining("This method cannot be called via ServiceHub.jdbcSession.") + + mockNetwork.runNetwork() + } +} \ No newline at end of file diff --git a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedEntityManagerFlowTest.kt b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedEntityManagerFlowTest.kt new file mode 100644 index 0000000000..7da2ff26ff --- /dev/null +++ b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/persistence/RestrictedEntityManagerFlowTest.kt @@ -0,0 +1,84 @@ +package net.corda.nodeapitests.internal.persistence + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.CordaX500Name +import net.corda.core.utilities.getOrThrow +import net.corda.nodeapi.internal.persistence.RestrictedEntityManager +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNetworkParameters +import net.corda.testing.node.StartedMockNode +import org.assertj.core.api.Assertions +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertTrue + +class RestrictedEntityManagerFlowTest { + + private lateinit var aliceNode: StartedMockNode + private lateinit var mockNetwork: MockNetwork + + @InitiatingFlow + class TestIfItIsRestrictedEntityManager : FlowLogic() { + @Suspendable + override fun call() : Boolean { + var result = false + serviceHub.withEntityManager() { + result = this is RestrictedEntityManager + } + return result + } + } + + @InitiatingFlow + class TestCloseMethodIsBlocked : FlowLogic() { + @Suspendable + override fun call() { + serviceHub.withEntityManager() { + this.close() + } + } + } + + @InitiatingFlow + class TestJoinTransactionMethodIsBlocked : FlowLogic() { + @Suspendable + override fun call() { + serviceHub.withEntityManager() { + this.joinTransaction() + } + } + } + + @Before + fun init() { + mockNetwork = MockNetwork(MockNetworkParameters()) + aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB")) + } + + @After + fun done() { + mockNetwork.stopNodes() + } + + @Test(timeout=300_000) + fun testIfItIsRestrictedConnection() { + assertTrue { aliceNode.startFlow(TestIfItIsRestrictedEntityManager()).get() } + mockNetwork.runNetwork() + } + + @Test(timeout=300_000) + fun testMethodsAreBlocked() { + Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java) + .isThrownBy { aliceNode.startFlow(TestCloseMethodIsBlocked()).getOrThrow() } + .withMessageContaining("This method cannot be called via ServiceHub.withEntityManager.") + + Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java) + .isThrownBy { aliceNode.startFlow(TestJoinTransactionMethodIsBlocked()).getOrThrow() } + .withMessageContaining("This method cannot be called via ServiceHub.withEntityManager.") + + mockNetwork.runNetwork() + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnection.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnection.kt new file mode 100644 index 0000000000..997cdc3ebd --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnection.kt @@ -0,0 +1,80 @@ +package net.corda.nodeapi.internal.persistence + +import java.sql.Connection +import java.sql.Savepoint +import java.util.concurrent.Executor + +/** + * A delegate of [Connection] which disallows some operations. + */ +@Suppress("TooManyFunctions") +class RestrictedConnection(private val delegate : Connection) : Connection by delegate { + + override fun abort(executor: Executor?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun clearWarnings() { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun close() { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun commit() { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setSavepoint(): Savepoint? { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setSavepoint(name : String?): Savepoint? { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun releaseSavepoint(savepoint: Savepoint?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun rollback() { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun rollback(savepoint: Savepoint?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setCatalog(catalog : String?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setTransactionIsolation(level: Int) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setTypeMap(map: MutableMap>?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setHoldability(holdability: Int) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setSchema(schema: String?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setNetworkTimeout(executor: Executor?, milliseconds: Int) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setAutoCommit(autoCommit: Boolean) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } + + override fun setReadOnly(readOnly: Boolean) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.") + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedEntityManager.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedEntityManager.kt index 46df399b6c..8aebe2fb1f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedEntityManager.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/RestrictedEntityManager.kt @@ -1,6 +1,9 @@ package net.corda.nodeapi.internal.persistence import javax.persistence.EntityManager +import javax.persistence.EntityTransaction +import javax.persistence.LockModeType +import javax.persistence.metamodel.Metamodel /** * A delegate of [EntityManager] which disallows some operations. @@ -15,5 +18,28 @@ class RestrictedEntityManager(private val delegate: EntityManager) : EntityManag throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.") } - // TODO: Figure out which other methods on EntityManager need to be blocked? + override fun getMetamodel(): Metamodel? { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.") + } + + override fun getTransaction(): EntityTransaction? { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.") + } + + override fun joinTransaction() { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.") + } + + override fun lock(entity: Any?, lockMode: LockModeType?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.") + } + + override fun lock(entity: Any?, lockMode: LockModeType?, properties: MutableMap?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.") + } + + override fun setProperty(propertyName: String?, value: Any?) { + throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.") + } + } \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnectionTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnectionTest.kt new file mode 100644 index 0000000000..3708360bfc --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrictedConnectionTest.kt @@ -0,0 +1,104 @@ +package net.corda.nodeapi.internal.persistence + +import com.nhaarman.mockito_kotlin.mock +import org.junit.Test +import java.sql.Connection +import java.sql.Savepoint + +class RestrictedConnectionTest { + + private val connection : Connection = mock() + private val savePoint : Savepoint = mock() + private val restrictedConnection : RestrictedConnection = RestrictedConnection(connection) + + companion object { + private const val TEST_STRING : String = "test" + private const val TEST_INT : Int = 1 + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testAbort() { + restrictedConnection.abort { println("I'm just an executor for this test...") } + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testClearWarnings() { + restrictedConnection.clearWarnings() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testClose() { + restrictedConnection.close() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testCommit() { + restrictedConnection.commit() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetSavepoint() { + restrictedConnection.setSavepoint() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetSavepointWithName() { + restrictedConnection.setSavepoint(TEST_STRING) + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testReleaseSavepoint() { + restrictedConnection.releaseSavepoint(savePoint) + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testRollback() { + restrictedConnection.rollback() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testRollbackWithSavepoint() { + restrictedConnection.rollback(savePoint) + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetCatalog() { + restrictedConnection.catalog = TEST_STRING + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetTransactionIsolation() { + restrictedConnection.transactionIsolation = TEST_INT + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetTypeMap() { + val map: MutableMap> = mutableMapOf() + restrictedConnection.typeMap = map + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetHoldability() { + restrictedConnection.holdability = TEST_INT + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetSchema() { + restrictedConnection.schema = TEST_STRING + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetNetworkTimeout() { + restrictedConnection.setNetworkTimeout({ println("I'm just an executor for this test...") }, TEST_INT) + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetAutoCommit() { + restrictedConnection.autoCommit = true + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetReadOnly() { + restrictedConnection.isReadOnly = true + } +} \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrtictedEntityManagerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrtictedEntityManagerTest.kt new file mode 100644 index 0000000000..79dae1279c --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/RestrtictedEntityManagerTest.kt @@ -0,0 +1,52 @@ +package net.corda.nodeapi.internal.persistence + +import com.nhaarman.mockito_kotlin.mock +import org.junit.Test +import javax.persistence.EntityManager +import javax.persistence.LockModeType + +class RestrtictedEntityManagerTest { + private val entitymanager = mock() + private val restrictedEntityManager = RestrictedEntityManager(entitymanager) + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testClose() { + restrictedEntityManager.close() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testClear() { + restrictedEntityManager.clear() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testGetMetaModel() { + restrictedEntityManager.getMetamodel() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testGetTransaction() { + restrictedEntityManager.getTransaction() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testJoinTransaction() { + restrictedEntityManager.joinTransaction() + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testLockWithTwoParameters() { + restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC) + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testLockWithThreeParameters() { + val map: MutableMap = mutableMapOf() + restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC,map) + } + + @Test(expected = UnsupportedOperationException::class, timeout=300_000) + fun testSetProperty() { + restrictedEntityManager.setProperty("number", 12) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 985e598673..8db9cb47a9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -161,6 +161,7 @@ import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException import net.corda.nodeapi.internal.persistence.OutstandingDatabaseChangesException +import net.corda.nodeapi.internal.persistence.RestrictedConnection import net.corda.nodeapi.internal.persistence.SchemaMigration import net.corda.tools.shell.InteractiveShell import org.apache.activemq.artemis.utils.ReusableLatch @@ -1185,7 +1186,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return flowManager.getFlowFactoryForInitiatingFlow(initiatingFlowClass) } - override fun jdbcSession(): Connection = database.createSession() + override fun jdbcSession(): Connection = RestrictedConnection(database.createSession()) override fun withEntityManager(block: EntityManager.() -> T): T { return database.transaction {