diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index e0131e6e99..2b75244715 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -26,7 +26,8 @@ import net.corda.finance.USD import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.driver @@ -52,9 +53,16 @@ class NodeMonitorModelTest : DriverBasedTest() { override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) { val cashUser = User("user1", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission(), - startFlowPermission()) + startFlow(), + startFlow(), + startFlow(), + invokeRpc(CordaRPCOps::notaryIdentities), + invokeRpc("vaultTrackBy"), + invokeRpc("vaultQueryBy"), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), + invokeRpc(CordaRPCOps::stateMachineRecordedTransactionMappingFeed), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::networkMapFeed)) ) val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser)) val notaryHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java index d46afb11b9..698aca77c9 100644 --- a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -26,7 +26,8 @@ import static java.util.Objects.requireNonNull; import static kotlin.test.AssertionsKt.assertEquals; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.contracts.GetBalances.getCashBalance; -import static net.corda.node.services.FlowPermissions.startFlowPermission; +import static net.corda.node.services.Permissions.invokeRpc; +import static net.corda.node.services.Permissions.startFlow; import static net.corda.testing.TestConstants.getALICE; public class CordaRPCJavaClientTest extends NodeBasedTest { @@ -34,7 +35,12 @@ public class CordaRPCJavaClientTest extends NodeBasedTest { super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName())); } - private List perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class)); + private List perms = Arrays.asList( + startFlow(CashPaymentFlow.class), + startFlow(CashIssueFlow.class), + invokeRpc("nodeInfo"), + invokeRpc("vaultQueryBy"), + invokeRpc("vaultQueryByCriteria")); private Set permSet = new HashSet<>(perms); private User rpcUser = new User("user1", "test", permSet); diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index e5b412b596..4d67be16d8 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -4,10 +4,7 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowInitiator import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.packageName -import net.corda.core.messaging.FlowProgressHandle -import net.corda.core.messaging.StateMachineUpdate -import net.corda.core.messaging.startFlow -import net.corda.core.messaging.startTrackedFlow +import net.corda.core.messaging.* import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS @@ -20,7 +17,8 @@ import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.schemas.CashSchemaV1 import net.corda.node.internal.Node import net.corda.node.internal.StartedNode -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE import net.corda.testing.chooseIdentity @@ -36,9 +34,12 @@ import kotlin.test.assertTrue class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) { private val rpcUser = User("user1", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission() - )) + startFlow(), + startFlow(), + invokeRpc("vaultQueryBy"), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc("vaultQueryByCriteria")) + ) private lateinit var node: StartedNode private lateinit var client: CordaRPCClient private var connection: CordaRPCConnection? = null diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt index 3502145ae0..0eaa7fbacc 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt @@ -6,7 +6,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.messaging.RPCOps import net.corda.core.utilities.getOrThrow -import net.corda.node.services.messaging.getRpcContext +import net.corda.node.services.messaging.rpcContext import net.corda.testing.RPCDriverExposedDSLInterface import net.corda.testing.rpcDriver import net.corda.testing.rpcTestUser @@ -65,7 +65,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() { override fun makeComplicatedObservable() = complicatedObservable override fun makeComplicatedListenableFuture() = complicatedListenableFuturee override fun addedLater(): Unit = throw IllegalStateException() - override fun captureUser(): String = getRpcContext().currentUser.username + override fun captureUser(): String = rpcContext().currentUser.username } @Test diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt index cee75881aa..00392eb37a 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt @@ -1,7 +1,9 @@ package net.corda.client.rpc +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps -import net.corda.node.services.messaging.getRpcContext +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.messaging.rpcContext import net.corda.node.services.messaging.requirePermission import net.corda.nodeapi.User import net.corda.testing.RPCDriverExposedDSLInterface @@ -9,6 +11,8 @@ import net.corda.testing.rpcDriver import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredMemberFunctions import kotlin.test.assertFailsWith @RunWith(Parameterized::class) @@ -28,7 +32,7 @@ class RPCPermissionsTests : AbstractRPCTest() { class TestOpsImpl : TestOps { override val protocolVersion = 1 - override fun validatePermission(str: String) = getRpcContext().requirePermission(str) + override fun validatePermission(str: String) { rpcContext().requirePermission(str) } } /** @@ -89,4 +93,19 @@ class RPCPermissionsTests : AbstractRPCTest() { } } + @Test + fun `fine grained permissions are enforced`() { + val allPermissions = CordaRPCOps::class.declaredMemberFunctions.filter { it.visibility == KVisibility.PUBLIC }.map { invokeRpc(it) } + allPermissions.forEach { permission -> + rpcDriver { + val user = userOf("Mark", setOf(permission)) + val proxy = testProxyFor(user) + + proxy.validatePermission(permission) + (allPermissions - permission).forEach { notOwnedPermission -> + assertFailsWith(PermissionException::class, { proxy.validatePermission(notOwnedPermission) }) + } + } + } + } } diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 3e87d4ba39..9556bb0a4f 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -100,21 +100,13 @@ interface CordaRPCOps : RPCOps { // Java Helpers // DOCSTART VaultQueryAPIHelpers - fun vaultQuery(contractStateType: Class): Vault.Page { - return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultQuery(contractStateType: Class): Vault.Page - fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page { - return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page - fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { - return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType) - } + fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page - fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { - return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType) - } + fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page // DOCEND VaultQueryAPIHelpers /** @@ -141,21 +133,13 @@ interface CordaRPCOps : RPCOps { // Java Helpers // DOCSTART VaultTrackAPIHelpers - fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> { - return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> - fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { - return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> - fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { - return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType) - } + fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> - fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { - return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType) - } + fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> // DOCEND VaultTrackAPIHelpers /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index ba7cb8de1f..be7cdf0d7b 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -108,7 +108,7 @@ abstract class TraversableTransaction(open val componentGroups: List, val groupHashes: List diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 19b93ba663..52ad1049c4 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -16,9 +16,9 @@ import net.corda.finance.USD import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow -import net.corda.node.internal.CordaRPCOpsImpl +import net.corda.node.internal.SecureCordaRPCOps import net.corda.node.internal.StartedNode -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -118,7 +118,7 @@ class ContractUpgradeFlowTest { return startRpcClient( rpcAddress = startRpcServer( rpcUser = user, - ops = CordaRPCOpsImpl(node.services, node.smm, node.database, node.services) + ops = SecureCordaRPCOps(node.services, node.smm, node.database, node.services) ).get().broker.hostAndPort!!, username = user.username, password = user.password @@ -134,10 +134,10 @@ class ContractUpgradeFlowTest { val stx = bobNode.services.addSignature(signedByA) val user = rpcTestUser.copy(permissions = setOf( - startFlowPermission(), - startFlowPermission>(), - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow>(), + startFlow(), + startFlow() )) val rpcA = startProxy(aliceNode, user) val rpcB = startProxy(bobNode, user) diff --git a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/CompatibleTransactionTests.kt similarity index 95% rename from core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/CompatibleTransactionTests.kt index 70995cd8cd..7464126983 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/CompatibleTransactionTests.kt @@ -1,9 +1,9 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.* import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* import net.corda.core.serialization.serialize -import net.corda.core.transactions.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -399,8 +399,7 @@ class CompatibleTransactionTests { @Test fun `FilteredTransaction signer manipulation tests`() { // Required to call the private constructor. - val ftxConstructor = FilteredTransaction::class.java.declaredConstructors[1] - ftxConstructor.isAccessible = true + val ftxConstructor = ::FilteredTransaction // 1st and 3rd commands require a signature from KEY_1. val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) @@ -479,12 +478,12 @@ class CompatibleTransactionTests { // A command with no corresponding signer detected // because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer. val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) - assertFails { ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } + assertFails { ftxConstructor.invoke(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } // Remove both last signer (KEY1) and related command. // Update partial Merkle tree for signers. val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup) - val ftxNoLastCommandAndSigners = ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction + val ftxNoLastCommandAndSigners = ftxConstructor.invoke(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction // verify() will pass as the transaction is well-formed. ftxNoLastCommandAndSigners.verify() // checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail. @@ -493,7 +492,7 @@ class CompatibleTransactionTests { // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. // Do not change partial Merkle tree for signers. // This time the object can be constructed as there is no pointer mismatch. - val ftxNoLastSigner = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction + val ftxNoLastSigner = ftxConstructor.invoke(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction // verify() will fail as we didn't change the partial Merkle tree. assertFailsWith { ftxNoLastSigner.verify() } // checkCommandVisibility() will not pass. @@ -501,7 +500,7 @@ class CompatibleTransactionTests { // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. // Update partial Merkle tree for signers. - val ftxNoLastSignerB = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction + val ftxNoLastSignerB = ftxConstructor.invoke(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction // verify() will pass, the transaction is well-formed. ftxNoLastSignerB.verify() // But, checkAllComponentsVisible() will not pass. @@ -526,20 +525,18 @@ class CompatibleTransactionTests { val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup) // Do not update groupHashes. - val ftxAlterSigner = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction + val ftxAlterSigner = ftxConstructor.invoke(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction // Visible components in signers group cannot be verified against their partial Merkle tree. assertFailsWith { ftxAlterSigner.verify() } // Also, checkAllComponentsVisible() will not pass (groupHash matching will fail). assertFailsWith { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) } // Update groupHashes. - val ftxAlterSignerB = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction + val ftxAlterSignerB = ftxConstructor.invoke(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction // Visible components in signers group cannot be verified against their partial Merkle tree. assertFailsWith { ftxAlterSignerB.verify() } // Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id). assertFailsWith { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) } - - ftxConstructor.isAccessible = false } } diff --git a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt similarity index 98% rename from core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt index 6e835d5209..28d7b6615c 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt @@ -1,8 +1,7 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.* import net.corda.core.identity.AbstractParty -import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.TransactionBuilder import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockServices diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt similarity index 96% rename from core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt index 8988d74a42..f96519dfec 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -1,7 +1,9 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.Contract +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.requireThat import net.corda.core.identity.AbstractParty -import net.corda.core.transactions.LedgerTransaction import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash @@ -12,7 +14,7 @@ import org.junit.Test import java.time.Instant import java.time.temporal.ChronoUnit -val TEST_TIMELOCK_ID = "net.corda.core.contracts.TransactionEncumbranceTests\$DummyTimeLock" +val TEST_TIMELOCK_ID = "net.corda.core.transactions.TransactionEncumbranceTests\$DummyTimeLock" class TransactionEncumbranceTests { val defaultIssuer = MEGA_CORP.ref(1) diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt similarity index 97% rename from core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt index 26878fea9c..17cc1bb769 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt @@ -1,11 +1,9 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey import net.corda.core.identity.Party -import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.WireTransaction import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY import net.corda.testing.* import net.corda.testing.contracts.DummyContract diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index f3f5e0be7c..184a572810 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,7 @@ from the previous milestone release. UNRELEASED ---------- +* ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows. * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. diff --git a/docs/source/clientrpc.rst b/docs/source/clientrpc.rst index 14695b7943..14d29ba9b9 100644 --- a/docs/source/clientrpc.rst +++ b/docs/source/clientrpc.rst @@ -41,7 +41,7 @@ The syntax for adding an RPC user is: ... ] -Currently, users need special permissions to start flows via RPC. These permissions are added as follows: +Users need permissions to invoke any RPC call. By default, nothing is allowed. These permissions are specified as follows: .. container:: codeset @@ -62,6 +62,15 @@ Currently, users need special permissions to start flows via RPC. These permissi .. note:: Currently, the node's web server has super-user access, meaning that it can run any RPC operation without logging in. This will be changed in a future release. +Permissions Syntax +^^^^^^^^^^^^^^^^^^ + +Fine grained permissions allow a user to invoke a specific RPC operation, or to start a specific flow. The syntax is: + +- to start a specific flow: ``StartFlow.`` e.g., ``StartFlow.net.corda.flows.ExampleFlow1``. +- to invoke a RPC operation: ``InvokeRpc.`` e.g., ``InvokeRpc.nodeInfo``. +.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows. + Observables ----------- The RPC system handles observables in a special way. When a method returns an observable, whether directly or diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index c665a8cb67..a5ee68bce7 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -1,6 +1,7 @@ package net.corda.docs import net.corda.core.internal.concurrent.transpose +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.messaging.vaultTrackBy import net.corda.core.node.services.Vault @@ -10,7 +11,8 @@ import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.driver @@ -24,11 +26,18 @@ class IntegrationTestingTutorial { driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) { val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow(), + invokeRpc(CordaRPCOps::waitUntilNetworkReady), + invokeRpc("vaultTrackBy"), + invokeRpc(CordaRPCOps::notaryIdentities), + invokeRpc(CordaRPCOps::networkMapFeed) )) val bobUser = User("bobUser", "testPassword2", permissions = setOf( - startFlowPermission() + startFlow(), + invokeRpc(CordaRPCOps::waitUntilNetworkReady), + invokeRpc("vaultTrackBy"), + invokeRpc(CordaRPCOps::networkMapFeed) )) val (alice, bob) = listOf( startNode(providedName = ALICE.name, rpcUsers = listOf(aliceUser)), diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt index 5b11d79549..1cfb712ec8 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt @@ -14,7 +14,8 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.nodeapi.User import net.corda.testing.ALICE import net.corda.testing.DUMMY_NOTARY @@ -42,10 +43,11 @@ fun main(args: Array) { val printOrVisualise = PrintOrVisualise.valueOf(args[0]) val baseDirectory = Paths.get("build/rpc-api-tutorial") - val user = User("user", "password", permissions = setOf(startFlowPermission(), - startFlowPermission(), - startFlowPermission())) - + val user = User("user", "password", permissions = setOf(startFlow(), + startFlow(), + startFlow(), + invokeRpc(CordaRPCOps::nodeInfo) + )) driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance")) { startNotaryNode(DUMMY_NOTARY.name) val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)).get() @@ -143,4 +145,4 @@ class ExampleRPCSerializationWhitelist : SerializationWhitelist { // Add classes like this. override val whitelist = listOf(ExampleRPCValue::class.java) } -// END 7 +// END 7 \ No newline at end of file diff --git a/docs/source/tutorial-clientrpc-api.rst b/docs/source/tutorial-clientrpc-api.rst index aa49889b4f..66c9846dae 100644 --- a/docs/source/tutorial-clientrpc-api.rst +++ b/docs/source/tutorial-clientrpc-api.rst @@ -122,7 +122,7 @@ In the instructions above the server node permissions are configured programmati .. code-block:: text driver(driverDirectory = baseDirectory) { - val user = User("user", "password", permissions = setOf(startFlowPermission())) + val user = User("user", "password", permissions = setOf(startFlow())) val node = startNode("CN=Alice Corp,O=Alice Corp,L=London,C=GB", rpcUsers = listOf(user)).get() When starting a standalone node using a configuration file we must supply the RPC credentials as follows: diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index f17465ac47..e4c4ff5198 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -7,7 +7,7 @@ import net.corda.core.internal.div import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.nodeapi.internal.ServiceInfo import net.corda.nodeapi.internal.ServiceType @@ -29,7 +29,7 @@ class BootTests { @Test fun `java deserialization is disabled`() { driver { - val user = User("u", "p", setOf(startFlowPermission())) + val user = User("u", "p", setOf(startFlow())) val future = startNode(rpcUsers = listOf(user)).getOrThrow().rpcClientToNode(). start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue assertThatThrownBy { future.getOrThrow() }.isInstanceOf(InvalidClassException::class.java).hasMessage("filter status: REJECTED") diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt index 50788582b5..8515b9b4ec 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt @@ -7,7 +7,7 @@ import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE import net.corda.testing.BOB @@ -19,7 +19,7 @@ import org.junit.Test class CordappScanningDriverTest { @Test fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() { - val user = User("u", "p", setOf(startFlowPermission())) + val user = User("u", "p", setOf(startFlow())) // The driver will automatically pick up the annotated flows below driver { val (alice, bob) = listOf( diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 6cddc95bed..eac55b5544 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -12,7 +12,7 @@ import net.corda.core.utilities.minutes import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity @@ -59,7 +59,7 @@ class NodePerformanceTests { @Test fun `empty flow per second`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow())))).get() a.rpcClientToNode().use("A", "A") { connection -> val timings = Collections.synchronizedList(ArrayList()) @@ -89,7 +89,7 @@ class NodePerformanceTests { @Test fun `empty flow rate`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow())))).get() a as NodeHandle.InProcess val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics) a.rpcClientToNode().use("A", "A") { connection -> @@ -105,7 +105,7 @@ class NodePerformanceTests { driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) { val a = startNotaryNode( DUMMY_NOTARY.name, - rpcUsers = listOf(User("A", "A", setOf(startFlowPermission(), startFlowPermission()))) + rpcUsers = listOf(User("A", "A", setOf(startFlow(), startFlow()))) ).getOrThrow() a as NodeHandle.InProcess val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 06ed089e16..6952390506 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -11,7 +11,8 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.POUNDS import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.nodeapi.User import net.corda.testing.* @@ -34,8 +35,10 @@ class DistributedServiceTests : DriverBasedTest() { // Start Alice and 3 notaries in a RAFT cluster val clusterSize = 3 val testUser = User("test", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission()) + startFlow(), + startFlow(), + invokeRpc(CordaRPCOps::nodeInfo), + invokeRpc(CordaRPCOps::stateMachinesFeed)) ) val aliceFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)) val notariesFuture = startNotaryCluster( @@ -137,4 +140,4 @@ class DistributedServiceTests : DriverBasedTest() { private fun paySelf(amount: Amount) { aliceProxy.startFlow(::CashPaymentFlow, amount, alice.nodeInfo.chooseIdentity()).returnValue.getOrThrow() } -} +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index ebc494c3c1..00edc6d782 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -17,7 +17,8 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.getOrThrow -import net.corda.node.services.FlowPermissions +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.nodeapi.User import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity @@ -39,7 +40,7 @@ class NodeStatePersistenceTests { // More investigation is needed to establish why. Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) - val user = User("mark", "dadada", setOf(FlowPermissions.startFlowPermission())) + val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"))) val message = Message("Hello world!") driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { val (nodeName, notaryNodeHandle) = { 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 e61471a8e1..23b08e1542 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -154,7 +154,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** The implementation of the [CordaRPCOps] interface used by this node. */ open fun makeRPCOps(flowStarter: FlowStarter): CordaRPCOps { - return CordaRPCOpsImpl(services, smm, database, flowStarter) + return SecureCordaRPCOps(services, smm, database, flowStarter) } private fun saveOwnNodeInfo() { diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 863835c25a..6871c234ba 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -20,11 +20,9 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.getOrThrow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.services.messaging.getRpcContext -import net.corda.node.services.messaging.requirePermission +import net.corda.node.services.messaging.rpcContext import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.CordaPersistence import rx.Observable @@ -36,7 +34,7 @@ import java.time.Instant * Server side implementations of RPCs available to MQ based client tools. Execution takes place on the server * thread (i.e. serially). Arguments are serialised and deserialised automatically. */ -class CordaRPCOpsImpl( +internal class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, private val database: CordaPersistence, @@ -149,9 +147,7 @@ class CordaRPCOpsImpl( private fun startFlow(logicType: Class>, args: Array): FlowStateMachine { require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" } - val rpcContext = getRpcContext() - rpcContext.requirePermission(startFlowPermission(logicType)) - val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username) + val currentUser = FlowInitiator.RPC(rpcContext().currentUser.username) // TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow. return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow() } @@ -221,6 +217,38 @@ class CordaRPCOpsImpl( } } + override fun vaultQuery(contractStateType: Class): Vault.Page { + return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page { + return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { + return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType) + } + + override fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { + return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType) + } + + override fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> { + return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { + return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { + return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType) + } + + override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { + return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType) + } + companion object { private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo { return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.flowInitiator, flowLogic.track()) @@ -233,6 +261,4 @@ class CordaRPCOpsImpl( } } } - -} - +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/DefaultCordaRpcPermissions.kt b/node/src/main/kotlin/net/corda/node/internal/DefaultCordaRpcPermissions.kt new file mode 100644 index 0000000000..dccec894e7 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/DefaultCordaRpcPermissions.kt @@ -0,0 +1,25 @@ +package net.corda.node.internal + +import net.corda.core.flows.FlowLogic +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.services.Permissions.Companion.all +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc +import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredMemberFunctions + +object DefaultCordaRpcPermissions { + + private val invokePermissions = CordaRPCOps::class.declaredMemberFunctions.filter { it.visibility == KVisibility.PUBLIC }.associate { it.name to setOf(invokeRpc(it), all()) } + private val startFlowPermissions = setOf("startFlow", "startFlowDynamic", "startTrackedFlow", "startTrackedFlowDynamic").associate { it to this::startFlowPermission } + + fun permissionsAllowing(methodName: String, args: List): Set { + + val invoke = invokePermissions[methodName] ?: emptySet() + val start = startFlowPermissions[methodName]?.invoke(args) + return if (start != null) invoke + start else invoke + } + + @Suppress("UNCHECKED_CAST") + private fun startFlowPermission(args: List): String = if (args[0] is Class<*>) startFlow(args[0] as Class>) else startFlow(args[0] as String) +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt new file mode 100644 index 0000000000..c99103b06d --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt @@ -0,0 +1,158 @@ +package net.corda.node.internal + +import net.corda.core.contracts.ContractState +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.DataFeed +import net.corda.core.node.NodeInfo +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.Vault +import net.corda.core.node.services.vault.PageSpecification +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.node.services.vault.Sort +import net.corda.node.services.messaging.RpcContext +import net.corda.node.services.messaging.requireEitherPermission +import java.io.InputStream +import java.security.PublicKey + +// TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 +class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcContext, private val permissionsAllowing: (methodName: String, args: List) -> Set) : CordaRPCOps { + + override fun stateMachinesSnapshot() = guard("stateMachinesSnapshot") { + implementation.stateMachinesSnapshot() + } + + override fun stateMachinesFeed() = guard("stateMachinesFeed") { + implementation.stateMachinesFeed() + } + + override fun vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class) = guard("vaultQueryBy") { + implementation.vaultQueryBy(criteria, paging, sorting, contractStateType) + } + + override fun vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class) = guard("vaultTrackBy") { + implementation.vaultTrackBy(criteria, paging, sorting, contractStateType) + } + + override fun internalVerifiedTransactionsSnapshot() = guard("internalVerifiedTransactionsSnapshot", implementation::internalVerifiedTransactionsSnapshot) + + override fun internalVerifiedTransactionsFeed() = guard("internalVerifiedTransactionsFeed", implementation::internalVerifiedTransactionsFeed) + + override fun stateMachineRecordedTransactionMappingSnapshot() = guard("stateMachineRecordedTransactionMappingSnapshot", implementation::stateMachineRecordedTransactionMappingSnapshot) + + override fun stateMachineRecordedTransactionMappingFeed() = guard("stateMachineRecordedTransactionMappingFeed", implementation::stateMachineRecordedTransactionMappingFeed) + + override fun networkMapSnapshot(): List = guard("networkMapSnapshot", implementation::networkMapSnapshot) + + override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> = guard("networkMapFeed", implementation::networkMapFeed) + + override fun startFlowDynamic(logicType: Class>, vararg args: Any?) = guard("startFlowDynamic", listOf(logicType)) { + implementation.startFlowDynamic(logicType, *args) + } + + override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?) = guard("startTrackedFlowDynamic", listOf(logicType)) { + implementation.startTrackedFlowDynamic(logicType, *args) + } + + override fun nodeInfo(): NodeInfo = guard("nodeInfo", implementation::nodeInfo) + + override fun notaryIdentities(): List = guard("notaryIdentities", implementation::notaryIdentities) + + override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") { + implementation.addVaultTransactionNote(txnId, txnNote) + } + + override fun getVaultTransactionNotes(txnId: SecureHash): Iterable = guard("getVaultTransactionNotes") { + implementation.getVaultTransactionNotes(txnId) + } + + override fun attachmentExists(id: SecureHash) = guard("attachmentExists") { + implementation.attachmentExists(id) + } + + override fun openAttachment(id: SecureHash) = guard("openAttachment") { + implementation.openAttachment(id) + } + + override fun uploadAttachment(jar: InputStream) = guard("uploadAttachment") { + implementation.uploadAttachment(jar) + } + + override fun currentNodeTime() = guard("currentNodeTime", implementation::currentNodeTime) + + override fun waitUntilNetworkReady() = guard("waitUntilNetworkReady", implementation::waitUntilNetworkReady) + + override fun wellKnownPartyFromAnonymous(party: AbstractParty) = guard("wellKnownPartyFromAnonymous") { + implementation.wellKnownPartyFromAnonymous(party) + } + + override fun partyFromKey(key: PublicKey) = guard("partyFromKey") { + implementation.partyFromKey(key) + } + + override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name) = guard("wellKnownPartyFromX500Name") { + implementation.wellKnownPartyFromX500Name(x500Name) + } + + override fun notaryPartyFromX500Name(x500Name: CordaX500Name) = guard("notaryPartyFromX500Name") { + implementation.notaryPartyFromX500Name(x500Name) + } + + override fun partiesFromName(query: String, exactMatch: Boolean) = guard("partiesFromName") { + implementation.partiesFromName(query, exactMatch) + } + + override fun registeredFlows() = guard("registeredFlows", implementation::registeredFlows) + + override fun nodeInfoFromParty(party: AbstractParty) = guard("nodeInfoFromParty") { + implementation.nodeInfoFromParty(party) + } + + override fun clearNetworkMapCache() = guard("clearNetworkMapCache", implementation::clearNetworkMapCache) + + override fun vaultQuery(contractStateType: Class): Vault.Page = guard("vaultQuery") { + implementation.vaultQuery(contractStateType) + } + + override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page = guard("vaultQueryByCriteria") { + implementation.vaultQueryByCriteria(criteria, contractStateType) + } + + override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page = guard("vaultQueryByWithPagingSpec") { + implementation.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) + } + + override fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page = guard("vaultQueryByWithSorting") { + implementation.vaultQueryByWithSorting(contractStateType, criteria, sorting) + } + + override fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> = guard("vaultTrack") { + implementation.vaultTrack(contractStateType) + } + + override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> = guard("vaultTrackByCriteria") { + implementation.vaultTrackByCriteria(contractStateType, criteria) + } + + override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> = guard("vaultTrackByWithPagingSpec") { + implementation.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) + } + + override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> = guard("vaultTrackByWithSorting") { + implementation.vaultTrackByWithSorting(contractStateType, criteria, sorting) + } + + // TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 + private inline fun guard(methodName: String, action: () -> RESULT) = guard(methodName, emptyList(), action) + + // TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 + private inline fun guard(methodName: String, args: List, action: () -> RESULT): RESULT { + + context.invoke().requireEitherPermission(permissionsAllowing.invoke(methodName, args)) + return action.invoke() + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/SecureCordaRPCOps.kt b/node/src/main/kotlin/net/corda/node/internal/SecureCordaRPCOps.kt new file mode 100644 index 0000000000..7083a434d2 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/SecureCordaRPCOps.kt @@ -0,0 +1,24 @@ +package net.corda.node.internal + +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.services.api.FlowStarter +import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.messaging.rpcContext +import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.utilities.CordaPersistence + +/** + * Implementation of [CordaRPCOps] that checks authorisation. + */ +class SecureCordaRPCOps(services: ServiceHubInternal, + smm: StateMachineManager, + database: CordaPersistence, + flowStarter: FlowStarter, + val unsafe: CordaRPCOps = CordaRPCOpsImpl(services, smm, database, flowStarter)) : CordaRPCOps by RpcAuthorisationProxy(unsafe, ::rpcContext, DefaultCordaRpcPermissions::permissionsAllowing) { + + /** + * Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed + * to be present. + */ + override val protocolVersion: Int get() = unsafe.nodeInfo().platformVersion +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/Permissions.kt b/node/src/main/kotlin/net/corda/node/services/Permissions.kt new file mode 100644 index 0000000000..f87060fba1 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/Permissions.kt @@ -0,0 +1,59 @@ +package net.corda.node.services + +import net.corda.core.flows.FlowLogic +import kotlin.reflect.KFunction + +/** + * Helper class for creating permissions. + */ +class Permissions { + + companion object { + + /** + * Global admin permissions. + */ + @JvmStatic + fun all() = "ALL" + + /** + * Creates the flow permission string of the format "StartFlow.{ClassName}". + * + * @param className a flow class name for which permission is created. + */ + @JvmStatic + fun startFlow(className: String) = "StartFlow.$className" + + /** + * An overload for the [startFlow] + * + * @param clazz a class for which permission is created. + */ + @JvmStatic + fun

> startFlow(clazz: Class

) = startFlow(clazz.name) + + /** + * An overload for the [startFlow]. + * + * @param P a class for which permission is created. + */ + @JvmStatic + inline fun > startFlow(): String = startFlow(P::class.java) + + /** + * Creates a permission string with format "InvokeRpc.{MethodName}". + * + * @param methodName a RPC method name for which permission is created. + */ + @JvmStatic + fun invokeRpc(methodName: String) = "InvokeRpc.$methodName" + + /** + * Creates a permission string with format "InvokeRpc.{method.name}". + * + * @param method a RPC [KFunction] for which permission is created. + */ + @JvmStatic + fun invokeRpc(method: KFunction<*>) = invokeRpc(method.name) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt index 58a50dceb7..371815ed94 100644 --- a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt +++ b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt @@ -1,6 +1,5 @@ package net.corda.node.services -import net.corda.core.flows.FlowLogic import net.corda.nodeapi.User /** @@ -26,36 +25,3 @@ class RPCUserServiceImpl(override val users: List) : RPCUserService { override fun getUser(username: String): User? = users.find { it.username == username } } - -/** - * Helper class for creating flow class permissions. - */ -class FlowPermissions { - companion object { - - /** - * Creates the flow permission string of the format "StartFlow.{ClassName}". - * - * @param className a flow class name for which permission is created. - */ - @JvmStatic - fun startFlowPermission(className: String) = "StartFlow.$className" - - /** - * An overload for the [startFlowPermission] - * - * @param clazz a class for which permission is created. - * - */ - @JvmStatic - fun

> startFlowPermission(clazz: Class

) = startFlowPermission(clazz.name) - - /** - * An overload for the [startFlowPermission]. - * - * @param P a class for which permission is created. - */ - @JvmStatic - inline fun > startFlowPermission(): String = startFlowPermission(P::class.java) - } -} diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index ae4f328378..d74ffa35c6 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -373,7 +373,7 @@ internal val CURRENT_RPC_CONTEXT: ThreadLocal = ThreadLocal() * throw. If you'd like to use the context outside of the call (e.g. in another thread) then pass the returned reference * around explicitly. */ -fun getRpcContext(): RpcContext = CURRENT_RPC_CONTEXT.get() +fun rpcContext(): RpcContext = CURRENT_RPC_CONTEXT.get() /** * @param currentUser This is available to RPC implementations to query the validated [User] that is calling it. Each diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt index e8e91d793f..6e68a81687 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt @@ -3,13 +3,18 @@ package net.corda.node.services.messaging import net.corda.client.rpc.PermissionException +import net.corda.node.services.Permissions.Companion.all import net.corda.nodeapi.ArtemisMessagingComponent /** Helper method which checks that the current RPC user is entitled for the given permission. Throws a [PermissionException] otherwise. */ -fun RpcContext.requirePermission(permission: String) { +fun RpcContext.requirePermission(permission: String): RpcContext = requireEitherPermission(setOf(permission)) + +/** Helper method which checks that the current RPC user is entitled with any of the given permissions. Throws a [PermissionException] otherwise. */ +fun RpcContext.requireEitherPermission(permissions: Set): RpcContext { // TODO remove the NODE_USER condition once webserver doesn't need it val currentUserPermissions = currentUser.permissions - if (currentUser.username != ArtemisMessagingComponent.NODE_USER && currentUserPermissions.intersect(listOf(permission, "ALL")).isEmpty()) { - throw PermissionException("User not permissioned for $permission, permissions are $currentUserPermissions") + if (currentUser.username != ArtemisMessagingComponent.NODE_USER && currentUserPermissions.intersect(permissions + all()).isEmpty()) { + throw PermissionException("User not permissioned with any of $permissions, permissions are $currentUserPermissions") } -} + return this +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index ae39004b31..cae74c44ba 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -23,9 +23,10 @@ import net.corda.finance.USD import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.internal.CordaRPCOpsImpl +import net.corda.node.internal.SecureCordaRPCOps import net.corda.node.internal.StartedNode -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.RpcContext import net.corda.nodeapi.User @@ -63,19 +64,20 @@ class CordaRPCOpsImplTest { lateinit var transactions: Observable lateinit var vaultTrackCash: Observable> + private val user = User("user", "pwd", permissions = emptySet()) + @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) aliceNode = mockNet.createNode() notaryNode = mockNet.createNotaryNode(validating = false) - rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( - startFlowPermission(), - startFlowPermission() - )))) + rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) + CURRENT_RPC_CONTEXT.set(RpcContext(user)) mockNet.runNetwork() - notary = rpc.notaryIdentities().first() + withPermissions(invokeRpc(CordaRPCOps::notaryIdentities)) { + notary = rpc.notaryIdentities().first() + } } @After @@ -85,170 +87,185 @@ class CordaRPCOpsImplTest { @Test fun `cash issue accepted`() { - aliceNode.database.transaction { - stateMachineUpdates = rpc.stateMachinesFeed().updates - vaultTrackCash = rpc.vaultTrackBy().updates - } - val quantity = 1000L - val ref = OpaqueBytes(ByteArray(1) { 1 }) + withPermissions(invokeRpc("vaultTrackBy"), invokeRpc("vaultQueryBy"), invokeRpc(CordaRPCOps::stateMachinesFeed), startFlow()) { - // Check the monitoring service wallet is empty - aliceNode.database.transaction { - assertFalse(aliceNode.services.vaultService.queryBy().totalStatesAvailable > 0) - } + aliceNode.database.transaction { + stateMachineUpdates = rpc.stateMachinesFeed().updates + vaultTrackCash = rpc.vaultTrackBy().updates + } - // Tell the monitoring service node to issue some cash - val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, notary) - mockNet.runNetwork() + val quantity = 1000L + val ref = OpaqueBytes(ByteArray(1) { 1 }) - var issueSmId: StateMachineRunId? = null - stateMachineUpdates.expectEvents { - sequence( - // ISSUE - expect { add: StateMachineUpdate.Added -> - issueSmId = add.id - }, - expect { remove: StateMachineUpdate.Removed -> - require(remove.id == issueSmId) - } - ) - } + // Check the monitoring service wallet is empty + aliceNode.database.transaction { + assertFalse(aliceNode.services.vaultService.queryBy().totalStatesAvailable > 0) + } - val anonymisedRecipient = result.returnValue.getOrThrow().recipient!! - val expectedState = Cash.State(Amount(quantity, - Issued(aliceNode.info.chooseIdentity().ref(ref), GBP)), - anonymisedRecipient) + // Tell the monitoring service node to issue some cash + val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, notary) + mockNet.runNetwork() - // Query vault via RPC - val cash = rpc.vaultQueryBy() - assertEquals(expectedState, cash.states.first().state.data) + var issueSmId: StateMachineRunId? = null + stateMachineUpdates.expectEvents { + sequence( + // ISSUE + expect { add: StateMachineUpdate.Added -> + issueSmId = add.id + }, + expect { remove: StateMachineUpdate.Removed -> + require(remove.id == issueSmId) + } + ) + } - vaultTrackCash.expectEvents { - expect { update -> - val actual = update.produced.single().state.data - assertEquals(expectedState, actual) + val anonymisedRecipient = result.returnValue.getOrThrow().recipient!! + val expectedState = Cash.State(Amount(quantity, + Issued(aliceNode.info.chooseIdentity().ref(ref), GBP)), + anonymisedRecipient) + + // Query vault via RPC + val cash = rpc.vaultQueryBy() + assertEquals(expectedState, cash.states.first().state.data) + + vaultTrackCash.expectEvents { + expect { update -> + val actual = update.produced.single().state.data + assertEquals(expectedState, actual) + } } } } @Test fun `issue and move`() { - aliceNode.database.transaction { - stateMachineUpdates = rpc.stateMachinesFeed().updates - transactions = rpc.internalVerifiedTransactionsFeed().updates - vaultTrackCash = rpc.vaultTrackBy().updates - } - val result = rpc.startFlow(::CashIssueFlow, - 100.DOLLARS, - OpaqueBytes(ByteArray(1, { 1 })), - notary - ) + withPermissions(invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), + invokeRpc("vaultTrackBy"), + startFlow(), + startFlow()) { + aliceNode.database.transaction { + stateMachineUpdates = rpc.stateMachinesFeed().updates + transactions = rpc.internalVerifiedTransactionsFeed().updates + vaultTrackCash = rpc.vaultTrackBy().updates + } - mockNet.runNetwork() - - rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.info.chooseIdentity()) - - mockNet.runNetwork() - - var issueSmId: StateMachineRunId? = null - var moveSmId: StateMachineRunId? = null - stateMachineUpdates.expectEvents { - sequence( - // ISSUE - expect { add: StateMachineUpdate.Added -> - issueSmId = add.id - }, - expect { remove: StateMachineUpdate.Removed -> - require(remove.id == issueSmId) - }, - // MOVE - expect { add: StateMachineUpdate.Added -> - moveSmId = add.id - }, - expect { remove: StateMachineUpdate.Removed -> - require(remove.id == moveSmId) - } + val result = rpc.startFlow(::CashIssueFlow, + 100.DOLLARS, + OpaqueBytes(ByteArray(1, { 1 })), + notary ) - } - result.returnValue.getOrThrow() - transactions.expectEvents { - sequence( - // ISSUE - expect { stx -> - require(stx.tx.inputs.isEmpty()) - require(stx.tx.outputs.size == 1) - val signaturePubKeys = stx.sigs.map { it.by }.toSet() - // Only Alice signed, as issuer - val aliceKey = aliceNode.info.chooseIdentity().owningKey - require(signaturePubKeys.size <= aliceKey.keys.size) - require(aliceKey.isFulfilledBy(signaturePubKeys)) - }, - // MOVE - expect { stx -> - require(stx.tx.inputs.size == 1) - require(stx.tx.outputs.size == 1) - val signaturePubKeys = stx.sigs.map { it.by }.toSet() - // Alice and Notary signed - require(aliceNode.services.keyManagementService.filterMyKeys(signaturePubKeys).toList().isNotEmpty()) - require(notary.owningKey.isFulfilledBy(signaturePubKeys)) - } - ) - } + mockNet.runNetwork() - vaultTrackCash.expectEvents { - sequence( - // ISSUE - expect { (consumed, produced) -> - require(consumed.isEmpty()) { consumed.size } - require(produced.size == 1) { produced.size } - }, - // MOVE - expect { (consumed, produced) -> - require(consumed.size == 1) { consumed.size } - require(produced.size == 1) { produced.size } - } - ) + rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.info.chooseIdentity()) + + mockNet.runNetwork() + + var issueSmId: StateMachineRunId? = null + var moveSmId: StateMachineRunId? = null + stateMachineUpdates.expectEvents { + sequence( + // ISSUE + expect { add: StateMachineUpdate.Added -> + issueSmId = add.id + }, + expect { remove: StateMachineUpdate.Removed -> + require(remove.id == issueSmId) + }, + // MOVE + expect { add: StateMachineUpdate.Added -> + moveSmId = add.id + }, + expect { remove: StateMachineUpdate.Removed -> + require(remove.id == moveSmId) + } + ) + } + + result.returnValue.getOrThrow() + transactions.expectEvents { + sequence( + // ISSUE + expect { stx -> + require(stx.tx.inputs.isEmpty()) + require(stx.tx.outputs.size == 1) + val signaturePubKeys = stx.sigs.map { it.by }.toSet() + // Only Alice signed, as issuer + val aliceKey = aliceNode.info.chooseIdentity().owningKey + require(signaturePubKeys.size <= aliceKey.keys.size) + require(aliceKey.isFulfilledBy(signaturePubKeys)) + }, + // MOVE + expect { stx -> + require(stx.tx.inputs.size == 1) + require(stx.tx.outputs.size == 1) + val signaturePubKeys = stx.sigs.map { it.by }.toSet() + // Alice and Notary signed + require(aliceNode.services.keyManagementService.filterMyKeys(signaturePubKeys).toList().isNotEmpty()) + require(notary.owningKey.isFulfilledBy(signaturePubKeys)) + } + ) + } + + vaultTrackCash.expectEvents { + sequence( + // ISSUE + expect { (consumed, produced) -> + require(consumed.isEmpty()) { consumed.size } + require(produced.size == 1) { produced.size } + }, + // MOVE + expect { (consumed, produced) -> + require(consumed.size == 1) { consumed.size } + require(produced.size == 1) { produced.size } + } + ) + } } } @Test fun `cash command by user not permissioned for cash`() { - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = emptySet()))) - assertThatExceptionOfType(PermissionException::class.java).isThrownBy { - rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), notary) + withoutAnyPermissions { + assertThatExceptionOfType(PermissionException::class.java).isThrownBy { + rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), notary) + } } } @Test fun `can upload an attachment`() { - val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) - val secureHash = rpc.uploadAttachment(inputJar) - assertTrue(rpc.attachmentExists(secureHash)) + withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) { + val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val secureHash = rpc.uploadAttachment(inputJar) + assertTrue(rpc.attachmentExists(secureHash)) + } } @Test fun `can download an uploaded attachment`() { - val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) - val secureHash = rpc.uploadAttachment(inputJar) - val bufferFile = ByteArrayOutputStream() - val bufferRpc = ByteArrayOutputStream() + withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::openAttachment)) { + val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val secureHash = rpc.uploadAttachment(inputJar) + val bufferFile = ByteArrayOutputStream() + val bufferRpc = ByteArrayOutputStream() - IOUtils.copy(Thread.currentThread().contextClassLoader.getResourceAsStream(testJar), bufferFile) - IOUtils.copy(rpc.openAttachment(secureHash), bufferRpc) + IOUtils.copy(Thread.currentThread().contextClassLoader.getResourceAsStream(testJar), bufferFile) + IOUtils.copy(rpc.openAttachment(secureHash), bufferRpc) - assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray()) + assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray()) + } } @Test fun `attempt to start non-RPC flow`() { - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( - startFlowPermission() - )))) - assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy { - rpc.startFlow(::NonRPCFlow) + withPermissions(startFlow()) { + assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy { + rpc.startFlow(::NonRPCFlow) + } } } @@ -259,12 +276,11 @@ class CordaRPCOpsImplTest { @Test fun `attempt to start RPC flow with void return`() { - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( - startFlowPermission() - )))) - val result = rpc.startFlow(::VoidRPCFlow) - mockNet.runNetwork() - assertNull(result.returnValue.getOrThrow()) + withPermissions(startFlow()) { + val result = rpc.startFlow(::VoidRPCFlow) + mockNet.runNetwork() + assertNull(result.returnValue.getOrThrow()) + } } @StartableByRPC @@ -272,4 +288,17 @@ class CordaRPCOpsImplTest { @Suspendable override fun call(): Void? = null } -} + + private fun withPermissions(vararg permissions: String, action: () -> Unit) { + + val previous = CURRENT_RPC_CONTEXT.get() + try { + CURRENT_RPC_CONTEXT.set(RpcContext(user.copy(permissions = permissions.toSet()))) + action.invoke() + } finally { + CURRENT_RPC_CONTEXT.set(previous) + } + } + + private fun withoutAnyPermissions(action: () -> Unit) = withPermissions(action = action) +} \ No newline at end of file diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index b1c0ace017..210b62c4fa 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -1,7 +1,9 @@ package net.corda.attachmentdemo +import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.getOrThrow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B @@ -17,7 +19,14 @@ class AttachmentDemoTest { fun `attachment demo using a 10MB zip file`() { val numOfExpectedBytes = 10_000_000 driver(isDebug = true, portAllocation = PortAllocation.Incremental(20000)) { - val demoUser = listOf(User("demo", "demo", setOf(startFlowPermission()))) + val demoUser = listOf(User("demo", "demo", setOf( + startFlow(), + invokeRpc(CordaRPCOps::attachmentExists), + invokeRpc(CordaRPCOps::uploadAttachment), + invokeRpc(CordaRPCOps::openAttachment), + invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed) + ))) val (nodeA, nodeB) = listOf( startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser, maximumHeapSize = "1g"), startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser, maximumHeapSize = "1g"), diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt index 147d6a65e0..83d3f0858f 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt @@ -1,5 +1,6 @@ package net.corda.bank +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.QueryCriteria @@ -7,7 +8,8 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueAndPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.driver @@ -16,10 +18,16 @@ import org.junit.Test class BankOfCordaRPCClientTest { @Test fun `issuer flow via RPC`() { + val commonPermissions = setOf( + invokeRpc("vaultTrackByCriteria"), + invokeRpc(CordaRPCOps::waitUntilNetworkReady), + invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), + invokeRpc(CordaRPCOps::notaryIdentities) + ) driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = { val bocManager = User("bocManager", "password1", permissions = setOf( - startFlowPermission())) - val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet()) + startFlow()) + commonPermissions) + val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet() + commonPermissions) val nodeBankOfCordaFuture = startNotaryNode(BOC.name, rpcUsers = listOf(bocManager), validating = false) val nodeBigCorporationFuture = startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO)) val (nodeBankOfCorda, nodeBigCorporation) = listOf(nodeBankOfCordaFuture, nodeBigCorporationFuture).map { it.getOrThrow() } @@ -80,4 +88,4 @@ class BankOfCordaRPCClientTest { } }, isDebug = true) } -} +} \ No newline at end of file diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index b526e580fe..d7ad085a9d 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -9,7 +9,7 @@ import net.corda.finance.flows.CashConfigDataFlow import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.nodeapi.User import net.corda.testing.BOC @@ -63,19 +63,19 @@ private class BankOfCordaDriver { BANK_USERNAME, "test", permissions = setOf( - startFlowPermission(), - startFlowPermission(), - startFlowPermission(), - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow(), + startFlow(), + startFlow(), + startFlow() )) val bankOfCorda = startNode( providedName = BOC.name, rpcUsers = listOf(bankUser)) val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf( - startFlowPermission(), - startFlowPermission())) + startFlow(), + startFlow())) startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser)) startWebserver(bankOfCorda.get()) waitForAllNodesToFinish() @@ -120,4 +120,3 @@ private class BankOfCordaDriver { } } - diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index 0fa498891f..98e7012ef0 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -37,14 +37,10 @@ class IRSDemoTest : IntegrationTestCategory { val log = loggerFor() } - val rpcUsers = listOf(User("user", "password", - setOf("StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", - "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", - "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow"))) - - val currentDate: LocalDate = LocalDate.now() - val futureDate: LocalDate = currentDate.plusMonths(6) - val maxWaitTime: Duration = 60.seconds + private val rpcUsers = listOf(User("user", "password", setOf("ALL"))) + private val currentDate: LocalDate = LocalDate.now() + private val futureDate: LocalDate = currentDate.plusMonths(6) + private val maxWaitTime: Duration = 60.seconds @Test fun `runs IRS demo`() { diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index ae34b399da..59f6d525ee 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -140,7 +140,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten node1.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java) node2.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java) - val notaryId = node1.rpcOps.notaryIdentities().first() + val notaryId = notary.info.legalIdentities[1] @InitiatingFlow class StartDealFlow(val otherParty: Party, diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index 8e349494b8..97168834c0 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -3,7 +3,7 @@ package net.corda.notarydemo import net.corda.cordform.CordformContext import net.corda.cordform.CordformDefinition import net.corda.core.internal.div -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.config.NotaryConfig import net.corda.nodeapi.User import net.corda.notarydemo.flows.DummyIssueAndMove @@ -19,7 +19,7 @@ import net.corda.testing.internal.demorun.runNodes fun main(args: Array) = SingleNotaryCordform().runNodes() -val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission(), startFlowPermission())) +val notaryDemoUser = User("demou", "demop", setOf(all())) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index 392ecd60ac..1a00d51bec 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -42,12 +42,7 @@ dependencies { } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [ - 'StartFlow.net.corda.finance.flows.CashIssueFlow', - 'StartFlow.net.corda.finance.flows.CashPaymentFlow', - 'StartFlow.net.corda.traderdemo.flow.CommercialPaperIssueFlow', - 'StartFlow.net.corda.traderdemo.flow.SellerFlow' - ]]] + ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["ALL"]]] directory "./build/nodes" // This name "Notary" is hard-coded into TraderDemoClientApi so if you change it here, change it there too. diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index 8e7e639967..df4c1ec6db 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -6,7 +6,8 @@ import net.corda.core.utilities.millis import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions +import net.corda.node.services.Permissions.Companion.all +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.NodeHandle @@ -22,11 +23,12 @@ import java.util.concurrent.Executors class TraderDemoTest { @Test fun `runs trader demo`() { - val demoUser = User("demo", "demo", setOf(FlowPermissions.startFlowPermission())) + val demoUser = User("demo", "demo", setOf(startFlow(), all())) val bankUser = User("user1", "test", permissions = setOf( - FlowPermissions.startFlowPermission(), - FlowPermissions.startFlowPermission(), - FlowPermissions.startFlowPermission())) + startFlow(), + startFlow(), + startFlow(), + all())) driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) { val (nodeA, nodeB, bankNode) = listOf( startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)), diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt index 4a032576f5..a349d4c210 100644 --- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt @@ -2,7 +2,8 @@ package net.corda.traderdemo import net.corda.core.internal.div import net.corda.finance.flows.CashIssueFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.all +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.BOC import net.corda.testing.DUMMY_BANK_A @@ -18,13 +19,14 @@ import net.corda.traderdemo.flow.SellerFlow */ fun main(args: Array) { val permissions = setOf( - startFlowPermission(), - startFlowPermission()) + startFlow(), + startFlow(), + all()) val demoUser = listOf(User("demo", "demo", permissions)) driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) { - val user = User("user1", "test", permissions = setOf(startFlowPermission(), - startFlowPermission(), - startFlowPermission())) + val user = User("user1", "test", permissions = setOf(startFlow(), + startFlow(), + startFlow())) startNotaryNode(DUMMY_NOTARY.name, validating = false) startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser) startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser) diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt index 8adba7f34d..68537ea0ab 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.list import net.corda.core.internal.read import net.corda.core.messaging.startFlow import net.corda.core.serialization.CordaSerializable -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.driver.driver import net.corda.testing.node.MockNetwork @@ -229,7 +229,7 @@ class FlowStackSnapshotTest { @Test fun `flowStackSnapshot contains full frames when methods with side effects are called`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val stackSnapshotFrames = connection.proxy.startFlow(::SideEffectFlow).returnValue.get() val iterator = stackSnapshotFrames.listIterator() @@ -244,7 +244,7 @@ class FlowStackSnapshotTest { @Test fun `flowStackSnapshot contains empty frames when methods with no side effects are called`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val stackSnapshotFrames = connection.proxy.startFlow(::NoSideEffectFlow).returnValue.get() val iterator = stackSnapshotFrames.listIterator() @@ -259,7 +259,7 @@ class FlowStackSnapshotTest { @Test fun `persistFlowStackSnapshot persists empty frames to a file when methods with no side effects are called`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val flowId = connection.proxy.startFlow(::PersistingNoSideEffectFlow).returnValue.get() @@ -276,7 +276,7 @@ class FlowStackSnapshotTest { @Test fun `persistFlowStackSnapshot persists multiple snapshots in different files`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val numberOfFlowSnapshots = 5 @@ -306,7 +306,7 @@ class FlowStackSnapshotTest { @Test fun `persistFlowStackSnapshot stack traces are aligned with stack objects`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val flowId = connection.proxy.startFlow(::PersistingSideEffectFlow).returnValue.get() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 6cdad1c883..c552f30f07 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -26,6 +26,7 @@ import net.corda.node.internal.Node import net.corda.node.internal.NodeStartup import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.config.* import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.ServiceIdentityGenerator @@ -76,6 +77,12 @@ private val DEFAULT_POLL_INTERVAL = 500.millis private const val DEFAULT_WARN_COUNT = 120 +private val DRIVER_REQUIRED_PERMISSIONS = setOf( + invokeRpc(CordaRPCOps::nodeInfo), + invokeRpc(CordaRPCOps::networkMapFeed), + invokeRpc(CordaRPCOps::networkMapSnapshot) +) + /** * This is the interface that's exposed to DSL users. */ @@ -721,6 +728,7 @@ class DriverDSL( val webAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") + val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, @@ -730,7 +738,7 @@ class DriverDSL( "rpcAddress" to rpcAddress.toString(), "webAddress" to webAddress.toString(), "useTestClock" to useTestClock, - "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers.map { it.toConfig().root().unwrapped() }, + "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, "verifierType" to verifierType.name ) + customOverrides ) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index d22e628596..d1913e2c01 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -20,7 +20,7 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.* import net.corda.finance.flows.CashExitFlow.ExitRequest import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE import net.corda.testing.BOB @@ -33,14 +33,14 @@ import java.util.* class ExplorerSimulation(val options: OptionSet) { private val user = User("user1", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow() )) private val manager = User("manager", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission(), - startFlowPermission(), - startFlowPermission()) + startFlow(), + startFlow(), + startFlow(), + startFlow()) ) private lateinit var notaryNode: NodeHandle diff --git a/tools/loadtest/src/main/resources/loadtest-reference.conf b/tools/loadtest/src/main/resources/loadtest-reference.conf index 8b03044650..67ea98c2ae 100644 --- a/tools/loadtest/src/main/resources/loadtest-reference.conf +++ b/tools/loadtest/src/main/resources/loadtest-reference.conf @@ -8,4 +8,4 @@ localTunnelStartingPort = 10000 remoteNodeDirectory = "/opt/corda" rpcPort = 10003 remoteSystemdServiceName = "corda" -rpcUser = {username = corda, password = not_blockchain, permissions = []} +rpcUser = {username = corda, password = not_blockchain, permissions = ["ALL"]}