mirror of
https://github.com/corda/corda.git
synced 2025-02-10 04:41:35 +00:00
[CORDA-758]: Permissions are now checked for each RPC method. (#1985)
* Permissions are now checked for each RPC method. * Fixed NodeMonitorModelTest * Fixed IRSDemoTest
This commit is contained in:
parent
a21d361df8
commit
d882f8871e
@ -26,7 +26,8 @@ import net.corda.finance.USD
|
|||||||
import net.corda.finance.flows.CashExitFlow
|
import net.corda.finance.flows.CashExitFlow
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.nodeapi.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
@ -52,9 +53,16 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
|||||||
|
|
||||||
override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
||||||
val cashUser = User("user1", "test", permissions = setOf(
|
val cashUser = User("user1", "test", permissions = setOf(
|
||||||
startFlowPermission<CashIssueFlow>(),
|
startFlow<CashIssueFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlow<CashPaymentFlow>(),
|
||||||
startFlowPermission<CashExitFlow>())
|
startFlow<CashExitFlow>(),
|
||||||
|
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 aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
|
||||||
val notaryHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow()
|
val notaryHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow()
|
||||||
|
@ -26,7 +26,8 @@ import static java.util.Objects.requireNonNull;
|
|||||||
import static kotlin.test.AssertionsKt.assertEquals;
|
import static kotlin.test.AssertionsKt.assertEquals;
|
||||||
import static net.corda.finance.Currencies.DOLLARS;
|
import static net.corda.finance.Currencies.DOLLARS;
|
||||||
import static net.corda.finance.contracts.GetBalances.getCashBalance;
|
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;
|
import static net.corda.testing.TestConstants.getALICE;
|
||||||
|
|
||||||
public class CordaRPCJavaClientTest extends NodeBasedTest {
|
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()));
|
super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class));
|
private List<String> perms = Arrays.asList(
|
||||||
|
startFlow(CashPaymentFlow.class),
|
||||||
|
startFlow(CashIssueFlow.class),
|
||||||
|
invokeRpc("nodeInfo"),
|
||||||
|
invokeRpc("vaultQueryBy"),
|
||||||
|
invokeRpc("vaultQueryByCriteria"));
|
||||||
private Set<String> permSet = new HashSet<>(perms);
|
private Set<String> permSet = new HashSet<>(perms);
|
||||||
private User rpcUser = new User("user1", "test", permSet);
|
private User rpcUser = new User("user1", "test", permSet);
|
||||||
|
|
||||||
|
@ -4,10 +4,7 @@ import net.corda.core.crypto.random63BitValue
|
|||||||
import net.corda.core.flows.FlowInitiator
|
import net.corda.core.flows.FlowInitiator
|
||||||
import net.corda.core.internal.concurrent.flatMap
|
import net.corda.core.internal.concurrent.flatMap
|
||||||
import net.corda.core.internal.packageName
|
import net.corda.core.internal.packageName
|
||||||
import net.corda.core.messaging.FlowProgressHandle
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
|
||||||
import net.corda.core.messaging.startFlow
|
|
||||||
import net.corda.core.messaging.startTrackedFlow
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
@ -20,7 +17,8 @@ import net.corda.finance.flows.CashPaymentFlow
|
|||||||
import net.corda.finance.schemas.CashSchemaV1
|
import net.corda.finance.schemas.CashSchemaV1
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.StartedNode
|
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.nodeapi.User
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
@ -36,9 +34,12 @@ import kotlin.test.assertTrue
|
|||||||
|
|
||||||
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
|
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
|
||||||
private val rpcUser = User("user1", "test", permissions = setOf(
|
private val rpcUser = User("user1", "test", permissions = setOf(
|
||||||
startFlowPermission<CashIssueFlow>(),
|
startFlow<CashIssueFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>()
|
startFlow<CashPaymentFlow>(),
|
||||||
))
|
invokeRpc("vaultQueryBy"),
|
||||||
|
invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||||
|
invokeRpc("vaultQueryByCriteria"))
|
||||||
|
)
|
||||||
private lateinit var node: StartedNode<Node>
|
private lateinit var node: StartedNode<Node>
|
||||||
private lateinit var client: CordaRPCClient
|
private lateinit var client: CordaRPCClient
|
||||||
private var connection: CordaRPCConnection? = null
|
private var connection: CordaRPCConnection? = null
|
||||||
|
@ -6,7 +6,7 @@ import net.corda.core.internal.concurrent.openFuture
|
|||||||
import net.corda.core.internal.concurrent.thenMatch
|
import net.corda.core.internal.concurrent.thenMatch
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.RPCDriverExposedDSLInterface
|
||||||
import net.corda.testing.rpcDriver
|
import net.corda.testing.rpcDriver
|
||||||
import net.corda.testing.rpcTestUser
|
import net.corda.testing.rpcTestUser
|
||||||
@ -65,7 +65,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() {
|
|||||||
override fun makeComplicatedObservable() = complicatedObservable
|
override fun makeComplicatedObservable() = complicatedObservable
|
||||||
override fun makeComplicatedListenableFuture() = complicatedListenableFuturee
|
override fun makeComplicatedListenableFuture() = complicatedListenableFuturee
|
||||||
override fun addedLater(): Unit = throw IllegalStateException()
|
override fun addedLater(): Unit = throw IllegalStateException()
|
||||||
override fun captureUser(): String = getRpcContext().currentUser.username
|
override fun captureUser(): String = rpcContext().currentUser.username
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package net.corda.client.rpc
|
package net.corda.client.rpc
|
||||||
|
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.RPCOps
|
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.node.services.messaging.requirePermission
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.RPCDriverExposedDSLInterface
|
import net.corda.testing.RPCDriverExposedDSLInterface
|
||||||
@ -9,6 +11,8 @@ import net.corda.testing.rpcDriver
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
|
import kotlin.reflect.KVisibility
|
||||||
|
import kotlin.reflect.full.declaredMemberFunctions
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
@RunWith(Parameterized::class)
|
@RunWith(Parameterized::class)
|
||||||
@ -28,7 +32,7 @@ class RPCPermissionsTests : AbstractRPCTest() {
|
|||||||
|
|
||||||
class TestOpsImpl : TestOps {
|
class TestOpsImpl : TestOps {
|
||||||
override val protocolVersion = 1
|
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) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,21 +100,13 @@ interface CordaRPCOps : RPCOps {
|
|||||||
// Java Helpers
|
// Java Helpers
|
||||||
|
|
||||||
// DOCSTART VaultQueryAPIHelpers
|
// DOCSTART VaultQueryAPIHelpers
|
||||||
fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
|
fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T>
|
||||||
return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> {
|
fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T>
|
||||||
return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
|
fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T>
|
||||||
return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
|
fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T>
|
||||||
return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType)
|
|
||||||
}
|
|
||||||
// DOCEND VaultQueryAPIHelpers
|
// DOCEND VaultQueryAPIHelpers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,21 +133,13 @@ interface CordaRPCOps : RPCOps {
|
|||||||
// Java Helpers
|
// Java Helpers
|
||||||
|
|
||||||
// DOCSTART VaultTrackAPIHelpers
|
// DOCSTART VaultTrackAPIHelpers
|
||||||
fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>
|
||||||
return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>>
|
||||||
return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>>
|
||||||
return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>>
|
||||||
return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType)
|
|
||||||
}
|
|
||||||
// DOCEND VaultTrackAPIHelpers
|
// DOCEND VaultTrackAPIHelpers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,7 +108,7 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
|||||||
* @param groupHashes the roots of the transaction component groups.
|
* @param groupHashes the roots of the transaction component groups.
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class FilteredTransaction private constructor(
|
class FilteredTransaction internal constructor(
|
||||||
override val id: SecureHash,
|
override val id: SecureHash,
|
||||||
val filteredComponentGroups: List<FilteredComponentGroup>,
|
val filteredComponentGroups: List<FilteredComponentGroup>,
|
||||||
val groupHashes: List<SecureHash>
|
val groupHashes: List<SecureHash>
|
||||||
|
@ -16,9 +16,9 @@ import net.corda.finance.USD
|
|||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
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.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.nodeapi.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
@ -118,7 +118,7 @@ class ContractUpgradeFlowTest {
|
|||||||
return startRpcClient<CordaRPCOps>(
|
return startRpcClient<CordaRPCOps>(
|
||||||
rpcAddress = startRpcServer(
|
rpcAddress = startRpcServer(
|
||||||
rpcUser = user,
|
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!!,
|
).get().broker.hostAndPort!!,
|
||||||
username = user.username,
|
username = user.username,
|
||||||
password = user.password
|
password = user.password
|
||||||
@ -134,10 +134,10 @@ class ContractUpgradeFlowTest {
|
|||||||
val stx = bobNode.services.addSignature(signedByA)
|
val stx = bobNode.services.addSignature(signedByA)
|
||||||
|
|
||||||
val user = rpcTestUser.copy(permissions = setOf(
|
val user = rpcTestUser.copy(permissions = setOf(
|
||||||
startFlowPermission<FinalityInvoker>(),
|
startFlow<FinalityInvoker>(),
|
||||||
startFlowPermission<ContractUpgradeFlow.Initiate<*, *>>(),
|
startFlow<ContractUpgradeFlow.Initiate<*, *>>(),
|
||||||
startFlowPermission<ContractUpgradeFlow.Authorise>(),
|
startFlow<ContractUpgradeFlow.Authorise>(),
|
||||||
startFlowPermission<ContractUpgradeFlow.Deauthorise>()
|
startFlow<ContractUpgradeFlow.Deauthorise>()
|
||||||
))
|
))
|
||||||
val rpcA = startProxy(aliceNode, user)
|
val rpcA = startProxy(aliceNode, user)
|
||||||
val rpcB = startProxy(bobNode, user)
|
val rpcB = startProxy(bobNode, user)
|
||||||
|
@ -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.contracts.ComponentGroupEnum.*
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.*
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
@ -399,8 +399,7 @@ class CompatibleTransactionTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `FilteredTransaction signer manipulation tests`() {
|
fun `FilteredTransaction signer manipulation tests`() {
|
||||||
// Required to call the private constructor.
|
// Required to call the private constructor.
|
||||||
val ftxConstructor = FilteredTransaction::class.java.declaredConstructors[1]
|
val ftxConstructor = ::FilteredTransaction
|
||||||
ftxConstructor.isAccessible = true
|
|
||||||
|
|
||||||
// 1st and 3rd commands require a signature from KEY_1.
|
// 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))
|
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
|
// A command with no corresponding signer detected
|
||||||
// because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer.
|
// because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer.
|
||||||
val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree)
|
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.
|
// Remove both last signer (KEY1) and related command.
|
||||||
// Update partial Merkle tree for signers.
|
// Update partial Merkle tree for signers.
|
||||||
val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup)
|
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.
|
// verify() will pass as the transaction is well-formed.
|
||||||
ftxNoLastCommandAndSigners.verify()
|
ftxNoLastCommandAndSigners.verify()
|
||||||
// checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail.
|
// 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.
|
// 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.
|
// Do not change partial Merkle tree for signers.
|
||||||
// This time the object can be constructed as there is no pointer mismatch.
|
// 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.
|
// verify() will fail as we didn't change the partial Merkle tree.
|
||||||
assertFailsWith<FilteredTransactionVerificationException> { ftxNoLastSigner.verify() }
|
assertFailsWith<FilteredTransactionVerificationException> { ftxNoLastSigner.verify() }
|
||||||
// checkCommandVisibility() will not pass.
|
// 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.
|
// 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.
|
// 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.
|
// verify() will pass, the transaction is well-formed.
|
||||||
ftxNoLastSignerB.verify()
|
ftxNoLastSignerB.verify()
|
||||||
// But, checkAllComponentsVisible() will not pass.
|
// But, checkAllComponentsVisible() will not pass.
|
||||||
@ -526,20 +525,18 @@ class CompatibleTransactionTests {
|
|||||||
val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup)
|
val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup)
|
||||||
|
|
||||||
// Do not update groupHashes.
|
// 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.
|
// Visible components in signers group cannot be verified against their partial Merkle tree.
|
||||||
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSigner.verify() }
|
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSigner.verify() }
|
||||||
// Also, checkAllComponentsVisible() will not pass (groupHash matching will fail).
|
// Also, checkAllComponentsVisible() will not pass (groupHash matching will fail).
|
||||||
assertFailsWith<ComponentVisibilityException> { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) }
|
assertFailsWith<ComponentVisibilityException> { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) }
|
||||||
|
|
||||||
// Update groupHashes.
|
// 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.
|
// Visible components in signers group cannot be verified against their partial Merkle tree.
|
||||||
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSignerB.verify() }
|
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSignerB.verify() }
|
||||||
// Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id).
|
// Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id).
|
||||||
assertFailsWith<ComponentVisibilityException> { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) }
|
assertFailsWith<ComponentVisibilityException> { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) }
|
||||||
|
|
||||||
ftxConstructor.isAccessible = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.identity.AbstractParty
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
@ -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.identity.AbstractParty
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
@ -12,7 +14,7 @@ import org.junit.Test
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.temporal.ChronoUnit
|
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 {
|
class TransactionEncumbranceTests {
|
||||||
val defaultIssuer = MEGA_CORP.ref(1)
|
val defaultIssuer = MEGA_CORP.ref(1)
|
@ -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.*
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.identity.Party
|
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.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
@ -6,6 +6,7 @@ from the previous milestone release.
|
|||||||
|
|
||||||
UNRELEASED
|
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``.
|
* ``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.
|
This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable.
|
||||||
|
@ -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
|
.. 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
|
.. 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.
|
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.<fully qualified flow name>`` e.g., ``StartFlow.net.corda.flows.ExampleFlow1``.
|
||||||
|
- to invoke a RPC operation: ``InvokeRpc.<rpc method name>`` e.g., ``InvokeRpc.nodeInfo``.
|
||||||
|
.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows.
|
||||||
|
|
||||||
Observables
|
Observables
|
||||||
-----------
|
-----------
|
||||||
The RPC system handles observables in a special way. When a method returns an observable, whether directly or
|
The RPC system handles observables in a special way. When a method returns an observable, whether directly or
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.docs
|
package net.corda.docs
|
||||||
|
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.messaging.vaultTrackBy
|
import net.corda.core.messaging.vaultTrackBy
|
||||||
import net.corda.core.node.services.Vault
|
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.contracts.asset.Cash
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.nodeapi.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
@ -24,11 +26,18 @@ class IntegrationTestingTutorial {
|
|||||||
driver(startNodesInProcess = true,
|
driver(startNodesInProcess = true,
|
||||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
|
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
|
||||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
||||||
startFlowPermission<CashIssueFlow>(),
|
startFlow<CashIssueFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>()
|
startFlow<CashPaymentFlow>(),
|
||||||
|
invokeRpc(CordaRPCOps::waitUntilNetworkReady),
|
||||||
|
invokeRpc("vaultTrackBy"),
|
||||||
|
invokeRpc(CordaRPCOps::notaryIdentities),
|
||||||
|
invokeRpc(CordaRPCOps::networkMapFeed)
|
||||||
))
|
))
|
||||||
val bobUser = User("bobUser", "testPassword2", permissions = setOf(
|
val bobUser = User("bobUser", "testPassword2", permissions = setOf(
|
||||||
startFlowPermission<CashPaymentFlow>()
|
startFlow<CashPaymentFlow>(),
|
||||||
|
invokeRpc(CordaRPCOps::waitUntilNetworkReady),
|
||||||
|
invokeRpc("vaultTrackBy"),
|
||||||
|
invokeRpc(CordaRPCOps::networkMapFeed)
|
||||||
))
|
))
|
||||||
val (alice, bob) = listOf(
|
val (alice, bob) = listOf(
|
||||||
startNode(providedName = ALICE.name, rpcUsers = listOf(aliceUser)),
|
startNode(providedName = ALICE.name, rpcUsers = listOf(aliceUser)),
|
||||||
|
@ -14,7 +14,8 @@ import net.corda.finance.contracts.asset.Cash
|
|||||||
import net.corda.finance.flows.CashExitFlow
|
import net.corda.finance.flows.CashExitFlow
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.nodeapi.User
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
@ -42,10 +43,11 @@ fun main(args: Array<String>) {
|
|||||||
val printOrVisualise = PrintOrVisualise.valueOf(args[0])
|
val printOrVisualise = PrintOrVisualise.valueOf(args[0])
|
||||||
|
|
||||||
val baseDirectory = Paths.get("build/rpc-api-tutorial")
|
val baseDirectory = Paths.get("build/rpc-api-tutorial")
|
||||||
val user = User("user", "password", permissions = setOf(startFlowPermission<CashIssueFlow>(),
|
val user = User("user", "password", permissions = setOf(startFlow<CashIssueFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlow<CashPaymentFlow>(),
|
||||||
startFlowPermission<CashExitFlow>()))
|
startFlow<CashExitFlow>(),
|
||||||
|
invokeRpc(CordaRPCOps::nodeInfo)
|
||||||
|
))
|
||||||
driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
||||||
startNotaryNode(DUMMY_NOTARY.name)
|
startNotaryNode(DUMMY_NOTARY.name)
|
||||||
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)).get()
|
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)).get()
|
||||||
|
@ -122,7 +122,7 @@ In the instructions above the server node permissions are configured programmati
|
|||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
driver(driverDirectory = baseDirectory) {
|
driver(driverDirectory = baseDirectory) {
|
||||||
val user = User("user", "password", permissions = setOf(startFlowPermission<CashFlow>()))
|
val user = User("user", "password", permissions = setOf(startFlow<CashFlow>()))
|
||||||
val node = startNode("CN=Alice Corp,O=Alice Corp,L=London,C=GB", rpcUsers = listOf(user)).get()
|
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:
|
When starting a standalone node using a configuration file we must supply the RPC credentials as follows:
|
||||||
|
@ -7,7 +7,7 @@ import net.corda.core.internal.div
|
|||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.internal.NodeStartup
|
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.User
|
||||||
import net.corda.nodeapi.internal.ServiceInfo
|
import net.corda.nodeapi.internal.ServiceInfo
|
||||||
import net.corda.nodeapi.internal.ServiceType
|
import net.corda.nodeapi.internal.ServiceType
|
||||||
@ -29,7 +29,7 @@ class BootTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `java deserialization is disabled`() {
|
fun `java deserialization is disabled`() {
|
||||||
driver {
|
driver {
|
||||||
val user = User("u", "p", setOf(startFlowPermission<ObjectInputStreamFlow>()))
|
val user = User("u", "p", setOf(startFlow<ObjectInputStreamFlow>()))
|
||||||
val future = startNode(rpcUsers = listOf(user)).getOrThrow().rpcClientToNode().
|
val future = startNode(rpcUsers = listOf(user)).getOrThrow().rpcClientToNode().
|
||||||
start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue
|
start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue
|
||||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(InvalidClassException::class.java).hasMessage("filter status: REJECTED")
|
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(InvalidClassException::class.java).hasMessage("filter status: REJECTED")
|
||||||
|
@ -7,7 +7,7 @@ import net.corda.core.internal.concurrent.transpose
|
|||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.unwrap
|
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.nodeapi.User
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.BOB
|
import net.corda.testing.BOB
|
||||||
@ -19,7 +19,7 @@ import org.junit.Test
|
|||||||
class CordappScanningDriverTest {
|
class CordappScanningDriverTest {
|
||||||
@Test
|
@Test
|
||||||
fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() {
|
fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() {
|
||||||
val user = User("u", "p", setOf(startFlowPermission<ReceiveFlow>()))
|
val user = User("u", "p", setOf(startFlow<ReceiveFlow>()))
|
||||||
// The driver will automatically pick up the annotated flows below
|
// The driver will automatically pick up the annotated flows below
|
||||||
driver {
|
driver {
|
||||||
val (alice, bob) = listOf(
|
val (alice, bob) = listOf(
|
||||||
|
@ -12,7 +12,7 @@ import net.corda.core.utilities.minutes
|
|||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.nodeapi.User
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
@ -59,7 +59,7 @@ class NodePerformanceTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `empty flow per second`() {
|
fun `empty flow per second`() {
|
||||||
driver(startNodesInProcess = true) {
|
driver(startNodesInProcess = true) {
|
||||||
val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlowPermission<EmptyFlow>())))).get()
|
val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow<EmptyFlow>())))).get()
|
||||||
|
|
||||||
a.rpcClientToNode().use("A", "A") { connection ->
|
a.rpcClientToNode().use("A", "A") { connection ->
|
||||||
val timings = Collections.synchronizedList(ArrayList<Long>())
|
val timings = Collections.synchronizedList(ArrayList<Long>())
|
||||||
@ -89,7 +89,7 @@ class NodePerformanceTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `empty flow rate`() {
|
fun `empty flow rate`() {
|
||||||
driver(startNodesInProcess = true) {
|
driver(startNodesInProcess = true) {
|
||||||
val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlowPermission<EmptyFlow>())))).get()
|
val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow<EmptyFlow>())))).get()
|
||||||
a as NodeHandle.InProcess
|
a as NodeHandle.InProcess
|
||||||
val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics)
|
val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics)
|
||||||
a.rpcClientToNode().use("A", "A") { connection ->
|
a.rpcClientToNode().use("A", "A") { connection ->
|
||||||
@ -105,7 +105,7 @@ class NodePerformanceTests {
|
|||||||
driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
||||||
val a = startNotaryNode(
|
val a = startNotaryNode(
|
||||||
DUMMY_NOTARY.name,
|
DUMMY_NOTARY.name,
|
||||||
rpcUsers = listOf(User("A", "A", setOf(startFlowPermission<CashIssueFlow>(), startFlowPermission<CashPaymentFlow>())))
|
rpcUsers = listOf(User("A", "A", setOf(startFlow<CashIssueFlow>(), startFlow<CashPaymentFlow>())))
|
||||||
).getOrThrow()
|
).getOrThrow()
|
||||||
a as NodeHandle.InProcess
|
a as NodeHandle.InProcess
|
||||||
val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics)
|
val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics)
|
||||||
|
@ -11,7 +11,8 @@ import net.corda.core.utilities.getOrThrow
|
|||||||
import net.corda.finance.POUNDS
|
import net.corda.finance.POUNDS
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.node.services.transactions.RaftValidatingNotaryService
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
@ -34,8 +35,10 @@ class DistributedServiceTests : DriverBasedTest() {
|
|||||||
// Start Alice and 3 notaries in a RAFT cluster
|
// Start Alice and 3 notaries in a RAFT cluster
|
||||||
val clusterSize = 3
|
val clusterSize = 3
|
||||||
val testUser = User("test", "test", permissions = setOf(
|
val testUser = User("test", "test", permissions = setOf(
|
||||||
startFlowPermission<CashIssueFlow>(),
|
startFlow<CashIssueFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>())
|
startFlow<CashPaymentFlow>(),
|
||||||
|
invokeRpc(CordaRPCOps::nodeInfo),
|
||||||
|
invokeRpc(CordaRPCOps::stateMachinesFeed))
|
||||||
)
|
)
|
||||||
val aliceFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser))
|
val aliceFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser))
|
||||||
val notariesFuture = startNotaryCluster(
|
val notariesFuture = startNotaryCluster(
|
||||||
|
@ -17,7 +17,8 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.nodeapi.User
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
@ -39,7 +40,7 @@ class NodeStatePersistenceTests {
|
|||||||
// More investigation is needed to establish why.
|
// More investigation is needed to establish why.
|
||||||
Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
|
Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
|
||||||
|
|
||||||
val user = User("mark", "dadada", setOf(FlowPermissions.startFlowPermission<SendMessageFlow>()))
|
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
|
||||||
val message = Message("Hello world!")
|
val message = Message("Hello world!")
|
||||||
driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
|
driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
|
||||||
val (nodeName, notaryNodeHandle) = {
|
val (nodeName, notaryNodeHandle) = {
|
||||||
|
@ -154,7 +154,7 @@ abstract class AbstractNode(config: NodeConfiguration,
|
|||||||
|
|
||||||
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
||||||
open fun makeRPCOps(flowStarter: FlowStarter): CordaRPCOps {
|
open fun makeRPCOps(flowStarter: FlowStarter): CordaRPCOps {
|
||||||
return CordaRPCOpsImpl(services, smm, database, flowStarter)
|
return SecureCordaRPCOps(services, smm, database, flowStarter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveOwnNodeInfo() {
|
private fun saveOwnNodeInfo() {
|
||||||
|
@ -20,11 +20,9 @@ import net.corda.core.node.services.vault.QueryCriteria
|
|||||||
import net.corda.core.node.services.vault.Sort
|
import net.corda.core.node.services.vault.Sort
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.FlowStarter
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.messaging.getRpcContext
|
import net.corda.node.services.messaging.rpcContext
|
||||||
import net.corda.node.services.messaging.requirePermission
|
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.node.utilities.CordaPersistence
|
||||||
import rx.Observable
|
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
|
* 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.
|
* thread (i.e. serially). Arguments are serialised and deserialised automatically.
|
||||||
*/
|
*/
|
||||||
class CordaRPCOpsImpl(
|
internal class CordaRPCOpsImpl(
|
||||||
private val services: ServiceHubInternal,
|
private val services: ServiceHubInternal,
|
||||||
private val smm: StateMachineManager,
|
private val smm: StateMachineManager,
|
||||||
private val database: CordaPersistence,
|
private val database: CordaPersistence,
|
||||||
@ -149,9 +147,7 @@ class CordaRPCOpsImpl(
|
|||||||
|
|
||||||
private fun <T> startFlow(logicType: Class<out FlowLogic<T>>, args: Array<out Any?>): FlowStateMachine<T> {
|
private fun <T> startFlow(logicType: Class<out FlowLogic<T>>, args: Array<out Any?>): FlowStateMachine<T> {
|
||||||
require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" }
|
require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" }
|
||||||
val rpcContext = getRpcContext()
|
val currentUser = FlowInitiator.RPC(rpcContext().currentUser.username)
|
||||||
rpcContext.requirePermission(startFlowPermission(logicType))
|
|
||||||
val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username)
|
|
||||||
// TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow.
|
// TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow.
|
||||||
return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow()
|
return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow()
|
||||||
}
|
}
|
||||||
@ -221,6 +217,38 @@ class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
|
||||||
|
return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> {
|
||||||
|
return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
|
||||||
|
return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
|
||||||
|
return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||||
|
return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||||
|
return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||||
|
return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||||
|
return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo {
|
private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo {
|
||||||
return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.flowInitiator, flowLogic.track())
|
return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.flowInitiator, flowLogic.track())
|
||||||
@ -233,6 +261,4 @@ class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<Any?>): Set<String> {
|
||||||
|
|
||||||
|
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<Any?>): String = if (args[0] is Class<*>) startFlow(args[0] as Class<FlowLogic<*>>) else startFlow(args[0] as String)
|
||||||
|
}
|
@ -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<Any?>) -> Set<String>) : CordaRPCOps {
|
||||||
|
|
||||||
|
override fun stateMachinesSnapshot() = guard("stateMachinesSnapshot") {
|
||||||
|
implementation.stateMachinesSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stateMachinesFeed() = guard("stateMachinesFeed") {
|
||||||
|
implementation.stateMachinesFeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>) = guard("vaultQueryBy") {
|
||||||
|
implementation.vaultQueryBy(criteria, paging, sorting, contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>) = 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<NodeInfo> = guard("networkMapSnapshot", implementation::networkMapSnapshot)
|
||||||
|
|
||||||
|
override fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange> = guard("networkMapFeed", implementation::networkMapFeed)
|
||||||
|
|
||||||
|
override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?) = guard("startFlowDynamic", listOf(logicType)) {
|
||||||
|
implementation.startFlowDynamic(logicType, *args)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?) = guard("startTrackedFlowDynamic", listOf(logicType)) {
|
||||||
|
implementation.startTrackedFlowDynamic(logicType, *args)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nodeInfo(): NodeInfo = guard("nodeInfo", implementation::nodeInfo)
|
||||||
|
|
||||||
|
override fun notaryIdentities(): List<Party> = guard("notaryIdentities", implementation::notaryIdentities)
|
||||||
|
|
||||||
|
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") {
|
||||||
|
implementation.addVaultTransactionNote(txnId, txnNote)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVaultTransactionNotes(txnId: SecureHash): Iterable<String> = 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 <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> = guard("vaultQuery") {
|
||||||
|
implementation.vaultQuery(contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> = guard("vaultQueryByCriteria") {
|
||||||
|
implementation.vaultQueryByCriteria(criteria, contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> = guard("vaultQueryByWithPagingSpec") {
|
||||||
|
implementation.vaultQueryByWithPagingSpec(contractStateType, criteria, paging)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> = guard("vaultQueryByWithSorting") {
|
||||||
|
implementation.vaultQueryByWithSorting(contractStateType, criteria, sorting)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> = guard("vaultTrack") {
|
||||||
|
implementation.vaultTrack(contractStateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> = guard("vaultTrackByCriteria") {
|
||||||
|
implementation.vaultTrackByCriteria(contractStateType, criteria)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> = guard("vaultTrackByWithPagingSpec") {
|
||||||
|
implementation.vaultTrackByWithPagingSpec(contractStateType, criteria, paging)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> = 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 <RESULT> 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 <RESULT> guard(methodName: String, args: List<Any?>, action: () -> RESULT): RESULT {
|
||||||
|
|
||||||
|
context.invoke().requireEitherPermission(permissionsAllowing.invoke(methodName, args))
|
||||||
|
return action.invoke()
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
59
node/src/main/kotlin/net/corda/node/services/Permissions.kt
Normal file
59
node/src/main/kotlin/net/corda/node/services/Permissions.kt
Normal file
@ -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 <P : FlowLogic<*>> startFlow(clazz: Class<P>) = startFlow(clazz.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An overload for the [startFlow].
|
||||||
|
*
|
||||||
|
* @param P a class for which permission is created.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
inline fun <reified P : FlowLogic<*>> 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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node.services
|
package net.corda.node.services
|
||||||
|
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,36 +25,3 @@ class RPCUserServiceImpl(override val users: List<User>) : RPCUserService {
|
|||||||
|
|
||||||
override fun getUser(username: String): User? = users.find { it.username == username }
|
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 <P : FlowLogic<*>> startFlowPermission(clazz: Class<P>) = startFlowPermission(clazz.name)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An overload for the [startFlowPermission].
|
|
||||||
*
|
|
||||||
* @param P a class for which permission is created.
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
inline fun <reified P : FlowLogic<*>> startFlowPermission(): String = startFlowPermission(P::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -373,7 +373,7 @@ internal val CURRENT_RPC_CONTEXT: ThreadLocal<RpcContext> = ThreadLocal()
|
|||||||
* throw. If you'd like to use the context outside of the call (e.g. in another thread) then pass the returned reference
|
* 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.
|
* 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
|
* @param currentUser This is available to RPC implementations to query the validated [User] that is calling it. Each
|
||||||
|
@ -3,13 +3,18 @@
|
|||||||
package net.corda.node.services.messaging
|
package net.corda.node.services.messaging
|
||||||
|
|
||||||
import net.corda.client.rpc.PermissionException
|
import net.corda.client.rpc.PermissionException
|
||||||
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent
|
import net.corda.nodeapi.ArtemisMessagingComponent
|
||||||
|
|
||||||
/** Helper method which checks that the current RPC user is entitled for the given permission. Throws a [PermissionException] otherwise. */
|
/** 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<String>): RpcContext {
|
||||||
// TODO remove the NODE_USER condition once webserver doesn't need it
|
// TODO remove the NODE_USER condition once webserver doesn't need it
|
||||||
val currentUserPermissions = currentUser.permissions
|
val currentUserPermissions = currentUser.permissions
|
||||||
if (currentUser.username != ArtemisMessagingComponent.NODE_USER && currentUserPermissions.intersect(listOf(permission, "ALL")).isEmpty()) {
|
if (currentUser.username != ArtemisMessagingComponent.NODE_USER && currentUserPermissions.intersect(permissions + all()).isEmpty()) {
|
||||||
throw PermissionException("User not permissioned for $permission, permissions are $currentUserPermissions")
|
throw PermissionException("User not permissioned with any of $permissions, permissions are $currentUserPermissions")
|
||||||
}
|
}
|
||||||
|
return this
|
||||||
}
|
}
|
@ -23,9 +23,10 @@ import net.corda.finance.USD
|
|||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.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.CURRENT_RPC_CONTEXT
|
||||||
import net.corda.node.services.messaging.RpcContext
|
import net.corda.node.services.messaging.RpcContext
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
@ -63,20 +64,21 @@ class CordaRPCOpsImplTest {
|
|||||||
lateinit var transactions: Observable<SignedTransaction>
|
lateinit var transactions: Observable<SignedTransaction>
|
||||||
lateinit var vaultTrackCash: Observable<Vault.Update<Cash.State>>
|
lateinit var vaultTrackCash: Observable<Vault.Update<Cash.State>>
|
||||||
|
|
||||||
|
private val user = User("user", "pwd", permissions = emptySet())
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
||||||
aliceNode = mockNet.createNode()
|
aliceNode = mockNet.createNode()
|
||||||
notaryNode = mockNet.createNotaryNode(validating = false)
|
notaryNode = mockNet.createNotaryNode(validating = false)
|
||||||
rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services)
|
rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services)
|
||||||
CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf(
|
CURRENT_RPC_CONTEXT.set(RpcContext(user))
|
||||||
startFlowPermission<CashIssueFlow>(),
|
|
||||||
startFlowPermission<CashPaymentFlow>()
|
|
||||||
))))
|
|
||||||
|
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
withPermissions(invokeRpc(CordaRPCOps::notaryIdentities)) {
|
||||||
notary = rpc.notaryIdentities().first()
|
notary = rpc.notaryIdentities().first()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun cleanUp() {
|
fun cleanUp() {
|
||||||
@ -85,6 +87,9 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cash issue accepted`() {
|
fun `cash issue accepted`() {
|
||||||
|
|
||||||
|
withPermissions(invokeRpc("vaultTrackBy"), invokeRpc("vaultQueryBy"), invokeRpc(CordaRPCOps::stateMachinesFeed), startFlow<CashIssueFlow>()) {
|
||||||
|
|
||||||
aliceNode.database.transaction {
|
aliceNode.database.transaction {
|
||||||
stateMachineUpdates = rpc.stateMachinesFeed().updates
|
stateMachineUpdates = rpc.stateMachinesFeed().updates
|
||||||
vaultTrackCash = rpc.vaultTrackBy<Cash.State>().updates
|
vaultTrackCash = rpc.vaultTrackBy<Cash.State>().updates
|
||||||
@ -131,9 +136,16 @@ class CordaRPCOpsImplTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `issue and move`() {
|
fun `issue and move`() {
|
||||||
|
|
||||||
|
withPermissions(invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||||
|
invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed),
|
||||||
|
invokeRpc("vaultTrackBy"),
|
||||||
|
startFlow<CashIssueFlow>(),
|
||||||
|
startFlow<CashPaymentFlow>()) {
|
||||||
aliceNode.database.transaction {
|
aliceNode.database.transaction {
|
||||||
stateMachineUpdates = rpc.stateMachinesFeed().updates
|
stateMachineUpdates = rpc.stateMachinesFeed().updates
|
||||||
transactions = rpc.internalVerifiedTransactionsFeed().updates
|
transactions = rpc.internalVerifiedTransactionsFeed().updates
|
||||||
@ -213,24 +225,29 @@ class CordaRPCOpsImplTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cash command by user not permissioned for cash`() {
|
fun `cash command by user not permissioned for cash`() {
|
||||||
CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = emptySet())))
|
withoutAnyPermissions {
|
||||||
assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
|
assertThatExceptionOfType(PermissionException::class.java).isThrownBy {
|
||||||
rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), notary)
|
rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), notary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can upload an attachment`() {
|
fun `can upload an attachment`() {
|
||||||
|
withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) {
|
||||||
val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
||||||
val secureHash = rpc.uploadAttachment(inputJar)
|
val secureHash = rpc.uploadAttachment(inputJar)
|
||||||
assertTrue(rpc.attachmentExists(secureHash))
|
assertTrue(rpc.attachmentExists(secureHash))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can download an uploaded attachment`() {
|
fun `can download an uploaded attachment`() {
|
||||||
|
withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::openAttachment)) {
|
||||||
val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
||||||
val secureHash = rpc.uploadAttachment(inputJar)
|
val secureHash = rpc.uploadAttachment(inputJar)
|
||||||
val bufferFile = ByteArrayOutputStream()
|
val bufferFile = ByteArrayOutputStream()
|
||||||
@ -241,16 +258,16 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray())
|
assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `attempt to start non-RPC flow`() {
|
fun `attempt to start non-RPC flow`() {
|
||||||
CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf(
|
withPermissions(startFlow<NonRPCFlow>()) {
|
||||||
startFlowPermission<NonRPCFlow>()
|
|
||||||
))))
|
|
||||||
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
|
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
|
||||||
rpc.startFlow(::NonRPCFlow)
|
rpc.startFlow(::NonRPCFlow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NonRPCFlow : FlowLogic<Unit>() {
|
class NonRPCFlow : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -259,17 +276,29 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `attempt to start RPC flow with void return`() {
|
fun `attempt to start RPC flow with void return`() {
|
||||||
CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf(
|
withPermissions(startFlow<VoidRPCFlow>()) {
|
||||||
startFlowPermission<VoidRPCFlow>()
|
|
||||||
))))
|
|
||||||
val result = rpc.startFlow(::VoidRPCFlow)
|
val result = rpc.startFlow(::VoidRPCFlow)
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
assertNull(result.returnValue.getOrThrow())
|
assertNull(result.returnValue.getOrThrow())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class VoidRPCFlow : FlowLogic<Void?>() {
|
class VoidRPCFlow : FlowLogic<Void?>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Void? = null
|
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)
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package net.corda.attachmentdemo
|
package net.corda.attachmentdemo
|
||||||
|
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.nodeapi.User
|
||||||
import net.corda.testing.DUMMY_BANK_A
|
import net.corda.testing.DUMMY_BANK_A
|
||||||
import net.corda.testing.DUMMY_BANK_B
|
import net.corda.testing.DUMMY_BANK_B
|
||||||
@ -17,7 +19,14 @@ class AttachmentDemoTest {
|
|||||||
fun `attachment demo using a 10MB zip file`() {
|
fun `attachment demo using a 10MB zip file`() {
|
||||||
val numOfExpectedBytes = 10_000_000
|
val numOfExpectedBytes = 10_000_000
|
||||||
driver(isDebug = true, portAllocation = PortAllocation.Incremental(20000)) {
|
driver(isDebug = true, portAllocation = PortAllocation.Incremental(20000)) {
|
||||||
val demoUser = listOf(User("demo", "demo", setOf(startFlowPermission<AttachmentDemoFlow>())))
|
val demoUser = listOf(User("demo", "demo", setOf(
|
||||||
|
startFlow<AttachmentDemoFlow>(),
|
||||||
|
invokeRpc(CordaRPCOps::attachmentExists),
|
||||||
|
invokeRpc(CordaRPCOps::uploadAttachment),
|
||||||
|
invokeRpc(CordaRPCOps::openAttachment),
|
||||||
|
invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name),
|
||||||
|
invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed)
|
||||||
|
)))
|
||||||
val (nodeA, nodeB) = listOf(
|
val (nodeA, nodeB) = listOf(
|
||||||
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser, maximumHeapSize = "1g"),
|
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser, maximumHeapSize = "1g"),
|
||||||
startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser, maximumHeapSize = "1g"),
|
startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser, maximumHeapSize = "1g"),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.bank
|
package net.corda.bank
|
||||||
|
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.node.services.vault.QueryCriteria
|
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.DOLLARS
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
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.nodeapi.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
@ -16,10 +18,16 @@ import org.junit.Test
|
|||||||
class BankOfCordaRPCClientTest {
|
class BankOfCordaRPCClientTest {
|
||||||
@Test
|
@Test
|
||||||
fun `issuer flow via RPC`() {
|
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 = {
|
driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = {
|
||||||
val bocManager = User("bocManager", "password1", permissions = setOf(
|
val bocManager = User("bocManager", "password1", permissions = setOf(
|
||||||
startFlowPermission<CashIssueAndPaymentFlow>()))
|
startFlow<CashIssueAndPaymentFlow>()) + commonPermissions)
|
||||||
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet())
|
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet<String>() + commonPermissions)
|
||||||
val nodeBankOfCordaFuture = startNotaryNode(BOC.name, rpcUsers = listOf(bocManager), validating = false)
|
val nodeBankOfCordaFuture = startNotaryNode(BOC.name, rpcUsers = listOf(bocManager), validating = false)
|
||||||
val nodeBigCorporationFuture = startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO))
|
val nodeBigCorporationFuture = startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO))
|
||||||
val (nodeBankOfCorda, nodeBigCorporation) = listOf(nodeBankOfCordaFuture, nodeBigCorporationFuture).map { it.getOrThrow() }
|
val (nodeBankOfCorda, nodeBigCorporation) = listOf(nodeBankOfCordaFuture, nodeBigCorporationFuture).map { it.getOrThrow() }
|
||||||
|
@ -9,7 +9,7 @@ import net.corda.finance.flows.CashConfigDataFlow
|
|||||||
import net.corda.finance.flows.CashExitFlow
|
import net.corda.finance.flows.CashExitFlow
|
||||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.node.services.transactions.ValidatingNotaryService
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.BOC
|
import net.corda.testing.BOC
|
||||||
@ -63,19 +63,19 @@ private class BankOfCordaDriver {
|
|||||||
BANK_USERNAME,
|
BANK_USERNAME,
|
||||||
"test",
|
"test",
|
||||||
permissions = setOf(
|
permissions = setOf(
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlow<CashPaymentFlow>(),
|
||||||
startFlowPermission<CashConfigDataFlow>(),
|
startFlow<CashConfigDataFlow>(),
|
||||||
startFlowPermission<CashExitFlow>(),
|
startFlow<CashExitFlow>(),
|
||||||
startFlowPermission<CashIssueAndPaymentFlow>(),
|
startFlow<CashIssueAndPaymentFlow>(),
|
||||||
startFlowPermission<CashConfigDataFlow>()
|
startFlow<CashConfigDataFlow>()
|
||||||
))
|
))
|
||||||
val bankOfCorda = startNode(
|
val bankOfCorda = startNode(
|
||||||
providedName = BOC.name,
|
providedName = BOC.name,
|
||||||
rpcUsers = listOf(bankUser))
|
rpcUsers = listOf(bankUser))
|
||||||
val bigCorpUser = User(BIGCORP_USERNAME, "test",
|
val bigCorpUser = User(BIGCORP_USERNAME, "test",
|
||||||
permissions = setOf(
|
permissions = setOf(
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlow<CashPaymentFlow>(),
|
||||||
startFlowPermission<CashConfigDataFlow>()))
|
startFlow<CashConfigDataFlow>()))
|
||||||
startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser))
|
startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser))
|
||||||
startWebserver(bankOfCorda.get())
|
startWebserver(bankOfCorda.get())
|
||||||
waitForAllNodesToFinish()
|
waitForAllNodesToFinish()
|
||||||
@ -120,4 +120,3 @@ private class BankOfCordaDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,14 +37,10 @@ class IRSDemoTest : IntegrationTestCategory {
|
|||||||
val log = loggerFor<IRSDemoTest>()
|
val log = loggerFor<IRSDemoTest>()
|
||||||
}
|
}
|
||||||
|
|
||||||
val rpcUsers = listOf(User("user", "password",
|
private val rpcUsers = listOf(User("user", "password", setOf("ALL")))
|
||||||
setOf("StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester",
|
private val currentDate: LocalDate = LocalDate.now()
|
||||||
"StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast",
|
private val futureDate: LocalDate = currentDate.plusMonths(6)
|
||||||
"StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow")))
|
private val maxWaitTime: Duration = 60.seconds
|
||||||
|
|
||||||
val currentDate: LocalDate = LocalDate.now()
|
|
||||||
val futureDate: LocalDate = currentDate.plusMonths(6)
|
|
||||||
val maxWaitTime: Duration = 60.seconds
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `runs IRS demo`() {
|
fun `runs IRS demo`() {
|
||||||
|
@ -140,7 +140,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
|||||||
node1.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java)
|
node1.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java)
|
||||||
node2.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
|
@InitiatingFlow
|
||||||
class StartDealFlow(val otherParty: Party,
|
class StartDealFlow(val otherParty: Party,
|
||||||
|
@ -3,7 +3,7 @@ package net.corda.notarydemo
|
|||||||
import net.corda.cordform.CordformContext
|
import net.corda.cordform.CordformContext
|
||||||
import net.corda.cordform.CordformDefinition
|
import net.corda.cordform.CordformDefinition
|
||||||
import net.corda.core.internal.div
|
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.node.services.config.NotaryConfig
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.notarydemo.flows.DummyIssueAndMove
|
import net.corda.notarydemo.flows.DummyIssueAndMove
|
||||||
@ -19,7 +19,7 @@ import net.corda.testing.internal.demorun.runNodes
|
|||||||
|
|
||||||
fun main(args: Array<String>) = SingleNotaryCordform().runNodes()
|
fun main(args: Array<String>) = SingleNotaryCordform().runNodes()
|
||||||
|
|
||||||
val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>()))
|
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
|
// 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.
|
// NOT use this as a design to copy.
|
||||||
|
@ -42,12 +42,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
|
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["ALL"]]]
|
||||||
'StartFlow.net.corda.finance.flows.CashIssueFlow',
|
|
||||||
'StartFlow.net.corda.finance.flows.CashPaymentFlow',
|
|
||||||
'StartFlow.net.corda.traderdemo.flow.CommercialPaperIssueFlow',
|
|
||||||
'StartFlow.net.corda.traderdemo.flow.SellerFlow'
|
|
||||||
]]]
|
|
||||||
|
|
||||||
directory "./build/nodes"
|
directory "./build/nodes"
|
||||||
// This name "Notary" is hard-coded into TraderDemoClientApi so if you change it here, change it there too.
|
// This name "Notary" is hard-coded into TraderDemoClientApi so if you change it here, change it there too.
|
||||||
|
@ -6,7 +6,8 @@ import net.corda.core.utilities.millis
|
|||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
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.nodeapi.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.driver.NodeHandle
|
import net.corda.testing.driver.NodeHandle
|
||||||
@ -22,11 +23,12 @@ import java.util.concurrent.Executors
|
|||||||
class TraderDemoTest {
|
class TraderDemoTest {
|
||||||
@Test
|
@Test
|
||||||
fun `runs trader demo`() {
|
fun `runs trader demo`() {
|
||||||
val demoUser = User("demo", "demo", setOf(FlowPermissions.startFlowPermission<SellerFlow>()))
|
val demoUser = User("demo", "demo", setOf(startFlow<SellerFlow>(), all()))
|
||||||
val bankUser = User("user1", "test", permissions = setOf(
|
val bankUser = User("user1", "test", permissions = setOf(
|
||||||
FlowPermissions.startFlowPermission<CashIssueFlow>(),
|
startFlow<CashIssueFlow>(),
|
||||||
FlowPermissions.startFlowPermission<CashPaymentFlow>(),
|
startFlow<CashPaymentFlow>(),
|
||||||
FlowPermissions.startFlowPermission<CommercialPaperIssueFlow>()))
|
startFlow<CommercialPaperIssueFlow>(),
|
||||||
|
all()))
|
||||||
driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
||||||
val (nodeA, nodeB, bankNode) = listOf(
|
val (nodeA, nodeB, bankNode) = listOf(
|
||||||
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),
|
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),
|
||||||
|
@ -2,7 +2,8 @@ package net.corda.traderdemo
|
|||||||
|
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
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.nodeapi.User
|
||||||
import net.corda.testing.BOC
|
import net.corda.testing.BOC
|
||||||
import net.corda.testing.DUMMY_BANK_A
|
import net.corda.testing.DUMMY_BANK_A
|
||||||
@ -18,13 +19,14 @@ import net.corda.traderdemo.flow.SellerFlow
|
|||||||
*/
|
*/
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val permissions = setOf(
|
val permissions = setOf(
|
||||||
startFlowPermission<CashIssueFlow>(),
|
startFlow<CashIssueFlow>(),
|
||||||
startFlowPermission<SellerFlow>())
|
startFlow<SellerFlow>(),
|
||||||
|
all())
|
||||||
val demoUser = listOf(User("demo", "demo", permissions))
|
val demoUser = listOf(User("demo", "demo", permissions))
|
||||||
driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) {
|
driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) {
|
||||||
val user = User("user1", "test", permissions = setOf(startFlowPermission<CashIssueFlow>(),
|
val user = User("user1", "test", permissions = setOf(startFlow<CashIssueFlow>(),
|
||||||
startFlowPermission<CommercialPaperIssueFlow>(),
|
startFlow<CommercialPaperIssueFlow>(),
|
||||||
startFlowPermission<SellerFlow>()))
|
startFlow<SellerFlow>()))
|
||||||
startNotaryNode(DUMMY_NOTARY.name, validating = false)
|
startNotaryNode(DUMMY_NOTARY.name, validating = false)
|
||||||
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser)
|
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser)
|
||||||
startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser)
|
startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser)
|
||||||
|
@ -8,7 +8,7 @@ import net.corda.core.internal.list
|
|||||||
import net.corda.core.internal.read
|
import net.corda.core.internal.read
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.serialization.CordaSerializable
|
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.nodeapi.User
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -229,7 +229,7 @@ class FlowStackSnapshotTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `flowStackSnapshot contains full frames when methods with side effects are called`() {
|
fun `flowStackSnapshot contains full frames when methods with side effects are called`() {
|
||||||
driver(startNodesInProcess = true) {
|
driver(startNodesInProcess = true) {
|
||||||
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<SideEffectFlow>())))).get()
|
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow<SideEffectFlow>())))).get()
|
||||||
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
||||||
val stackSnapshotFrames = connection.proxy.startFlow(::SideEffectFlow).returnValue.get()
|
val stackSnapshotFrames = connection.proxy.startFlow(::SideEffectFlow).returnValue.get()
|
||||||
val iterator = stackSnapshotFrames.listIterator()
|
val iterator = stackSnapshotFrames.listIterator()
|
||||||
@ -244,7 +244,7 @@ class FlowStackSnapshotTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `flowStackSnapshot contains empty frames when methods with no side effects are called`() {
|
fun `flowStackSnapshot contains empty frames when methods with no side effects are called`() {
|
||||||
driver(startNodesInProcess = true) {
|
driver(startNodesInProcess = true) {
|
||||||
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<NoSideEffectFlow>())))).get()
|
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow<NoSideEffectFlow>())))).get()
|
||||||
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
||||||
val stackSnapshotFrames = connection.proxy.startFlow(::NoSideEffectFlow).returnValue.get()
|
val stackSnapshotFrames = connection.proxy.startFlow(::NoSideEffectFlow).returnValue.get()
|
||||||
val iterator = stackSnapshotFrames.listIterator()
|
val iterator = stackSnapshotFrames.listIterator()
|
||||||
@ -259,7 +259,7 @@ class FlowStackSnapshotTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `persistFlowStackSnapshot persists empty frames to a file when methods with no side effects are called`() {
|
fun `persistFlowStackSnapshot persists empty frames to a file when methods with no side effects are called`() {
|
||||||
driver(startNodesInProcess = true) {
|
driver(startNodesInProcess = true) {
|
||||||
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<PersistingNoSideEffectFlow>())))).get()
|
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow<PersistingNoSideEffectFlow>())))).get()
|
||||||
|
|
||||||
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
||||||
val flowId = connection.proxy.startFlow(::PersistingNoSideEffectFlow).returnValue.get()
|
val flowId = connection.proxy.startFlow(::PersistingNoSideEffectFlow).returnValue.get()
|
||||||
@ -276,7 +276,7 @@ class FlowStackSnapshotTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `persistFlowStackSnapshot persists multiple snapshots in different files`() {
|
fun `persistFlowStackSnapshot persists multiple snapshots in different files`() {
|
||||||
driver(startNodesInProcess = true) {
|
driver(startNodesInProcess = true) {
|
||||||
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<MultiplePersistingSideEffectFlow>())))).get()
|
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow<MultiplePersistingSideEffectFlow>())))).get()
|
||||||
|
|
||||||
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
||||||
val numberOfFlowSnapshots = 5
|
val numberOfFlowSnapshots = 5
|
||||||
@ -306,7 +306,7 @@ class FlowStackSnapshotTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `persistFlowStackSnapshot stack traces are aligned with stack objects`() {
|
fun `persistFlowStackSnapshot stack traces are aligned with stack objects`() {
|
||||||
driver(startNodesInProcess = true) {
|
driver(startNodesInProcess = true) {
|
||||||
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<PersistingSideEffectFlow>())))).get()
|
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow<PersistingSideEffectFlow>())))).get()
|
||||||
|
|
||||||
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection ->
|
||||||
val flowId = connection.proxy.startFlow(::PersistingSideEffectFlow).returnValue.get()
|
val flowId = connection.proxy.startFlow(::PersistingSideEffectFlow).returnValue.get()
|
||||||
|
@ -26,6 +26,7 @@ import net.corda.node.internal.Node
|
|||||||
import net.corda.node.internal.NodeStartup
|
import net.corda.node.internal.NodeStartup
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
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.config.*
|
||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
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 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.
|
* This is the interface that's exposed to DSL users.
|
||||||
*/
|
*/
|
||||||
@ -721,6 +728,7 @@ class DriverDSL(
|
|||||||
val webAddress = portAllocation.nextHostAndPort()
|
val webAddress = portAllocation.nextHostAndPort()
|
||||||
// TODO: Derive name from the full picked name, don't just wrap the common name
|
// 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 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(
|
val config = ConfigHelper.loadConfig(
|
||||||
baseDirectory = baseDirectory(name),
|
baseDirectory = baseDirectory(name),
|
||||||
allowMissingConfig = true,
|
allowMissingConfig = true,
|
||||||
@ -730,7 +738,7 @@ class DriverDSL(
|
|||||||
"rpcAddress" to rpcAddress.toString(),
|
"rpcAddress" to rpcAddress.toString(),
|
||||||
"webAddress" to webAddress.toString(),
|
"webAddress" to webAddress.toString(),
|
||||||
"useTestClock" to useTestClock,
|
"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
|
"verifierType" to verifierType.name
|
||||||
) + customOverrides
|
) + customOverrides
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,7 @@ import net.corda.finance.contracts.asset.Cash
|
|||||||
import net.corda.finance.flows.*
|
import net.corda.finance.flows.*
|
||||||
import net.corda.finance.flows.CashExitFlow.ExitRequest
|
import net.corda.finance.flows.CashExitFlow.ExitRequest
|
||||||
import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
|
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.nodeapi.User
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.BOB
|
import net.corda.testing.BOB
|
||||||
@ -33,14 +33,14 @@ import java.util.*
|
|||||||
|
|
||||||
class ExplorerSimulation(val options: OptionSet) {
|
class ExplorerSimulation(val options: OptionSet) {
|
||||||
private val user = User("user1", "test", permissions = setOf(
|
private val user = User("user1", "test", permissions = setOf(
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlow<CashPaymentFlow>(),
|
||||||
startFlowPermission<CashConfigDataFlow>()
|
startFlow<CashConfigDataFlow>()
|
||||||
))
|
))
|
||||||
private val manager = User("manager", "test", permissions = setOf(
|
private val manager = User("manager", "test", permissions = setOf(
|
||||||
startFlowPermission<CashIssueAndPaymentFlow>(),
|
startFlow<CashIssueAndPaymentFlow>(),
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
startFlow<CashPaymentFlow>(),
|
||||||
startFlowPermission<CashExitFlow>(),
|
startFlow<CashExitFlow>(),
|
||||||
startFlowPermission<CashConfigDataFlow>())
|
startFlow<CashConfigDataFlow>())
|
||||||
)
|
)
|
||||||
|
|
||||||
private lateinit var notaryNode: NodeHandle
|
private lateinit var notaryNode: NodeHandle
|
||||||
|
@ -8,4 +8,4 @@ localTunnelStartingPort = 10000
|
|||||||
remoteNodeDirectory = "/opt/corda"
|
remoteNodeDirectory = "/opt/corda"
|
||||||
rpcPort = 10003
|
rpcPort = 10003
|
||||||
remoteSystemdServiceName = "corda"
|
remoteSystemdServiceName = "corda"
|
||||||
rpcUser = {username = corda, password = not_blockchain, permissions = []}
|
rpcUser = {username = corda, password = not_blockchain, permissions = ["ALL"]}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user