diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index bf858c9290..787221859b 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -12,7 +12,6 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.createInstancesOfClassesImplementing -import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.messaging.ClientRpcSslOptions import net.corda.core.messaging.CordaRPCOps import net.corda.core.serialization.SerializationCustomSerializer @@ -494,7 +493,7 @@ class CordaRPCClient private constructor( } } - private fun getRpcClient(): RPCClient { + private fun getRpcClient(): RPCClient { return when { // Client->RPC broker haAddressPool.isEmpty() -> RPCClient( @@ -619,7 +618,7 @@ class CordaRPCClient private constructor( ) } else { CordaRPCConnection(getRpcClient().start( - InternalCordaRPCOps::class.java, + CordaRPCOps::class.java, username, password, externalTrace, diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index 8c36cc73ae..0a93440b5f 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -19,7 +19,6 @@ import net.corda.core.internal.LazyStickyPool import net.corda.core.internal.LifeCycle import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.ThreadBox -import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps @@ -363,10 +362,10 @@ internal class RPCClientProxyHandler( private fun produceMethodFullyQualifiedName(method: Method) : String { /* * Until version 4.3, rpc calls did not include class names. - * Up to this version, only CordaRPCOps and InternalCordaRPCOps were supported. + * Up to this version, only CordaRPCOps was supported. * So, for these classes only methods are sent across the wire to preserve backwards compatibility. */ - return if (CordaRPCOps::class.java == rpcOpsClass || InternalCordaRPCOps::class.java == rpcOpsClass) { + return if (CordaRPCOps::class.java == rpcOpsClass) { method.name } else { rpcOpsClass.name + CLASS_METHOD_DIVIDER + method.name diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/ReconnectingCordaRPCOps.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/ReconnectingCordaRPCOps.kt index 3c5085eb5a..e6d73b7b7e 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/ReconnectingCordaRPCOps.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/ReconnectingCordaRPCOps.kt @@ -19,7 +19,6 @@ import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConn import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConnection.CurrentState.UNCONNECTED import net.corda.client.rpc.reconnect.CouldNotStartFlowException import net.corda.core.flows.StateMachineRunId -import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.internal.min import net.corda.core.internal.times import net.corda.core.internal.uncheckedCast @@ -65,7 +64,7 @@ import java.util.concurrent.TimeUnit // ReconnectingObservables and other things can attach themselves as listeners for reconnect events. class ReconnectingCordaRPCOps private constructor( val reconnectingRPCConnection: ReconnectingRPCConnection -) : InternalCordaRPCOps by proxy(reconnectingRPCConnection) { +) : CordaRPCOps by proxy(reconnectingRPCConnection) { constructor( nodeHostAndPorts: List, username: String, @@ -86,11 +85,11 @@ class ReconnectingCordaRPCOps private constructor( observersPool)) private companion object { private val log = contextLogger() - private fun proxy(reconnectingRPCConnection: ReconnectingRPCConnection): InternalCordaRPCOps { + private fun proxy(reconnectingRPCConnection: ReconnectingRPCConnection): CordaRPCOps { return Proxy.newProxyInstance( this::class.java.classLoader, - arrayOf(InternalCordaRPCOps::class.java), - ErrorInterceptingHandler(reconnectingRPCConnection)) as InternalCordaRPCOps + arrayOf(CordaRPCOps::class.java), + ErrorInterceptingHandler(reconnectingRPCConnection)) as CordaRPCOps } } private val retryFlowsPool = Executors.newScheduledThreadPool(1) diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt index 74e30ac3ba..f7ee175318 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt @@ -1,6 +1,8 @@ package net.corda.client.rpc +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps +import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName import net.corda.node.services.rpc.rpcContext import net.corda.testing.node.User import net.corda.testing.node.internal.RPCDriverDSL @@ -25,16 +27,17 @@ class RPCPermissionsTests : AbstractRPCTest() { fun validatePermission(method: String, target: String? = null) } - class TestOpsImpl : TestOps { + private class TestOpsImpl : TestOps { override val protocolVersion = 1000 override fun validatePermission(method: String, target: String?) { + val methodFullName = methodFullName(CordaRPCOps::class.java, method) val authorized = if (target == null) { - rpcContext().isPermitted(method) + rpcContext().isPermitted(methodFullName) } else { - rpcContext().isPermitted(method, target) + rpcContext().isPermitted(methodFullName, target) } if (!authorized) { - throw PermissionException("RPC user not authorized") + throw PermissionException("RPC user not authorized for: $method") } } } diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt index fa7fa39c6f..01e9e81df7 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt @@ -86,7 +86,7 @@ class ContractUpgradeFlowRPCTest : WithContracts, WithFinality { return startRpcClient( rpcAddress = startRpcServer( rpcUser = user, - ops = node.rpcOps + ops = node.cordaRPCOps ).get().broker.hostAndPort!!, username = user.username, password = user.password diff --git a/core/src/main/kotlin/net/corda/core/internal/messaging/AttachmentTrustInfoRPCOps.kt b/core/src/main/kotlin/net/corda/core/internal/messaging/AttachmentTrustInfoRPCOps.kt new file mode 100644 index 0000000000..5edf63e76c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/messaging/AttachmentTrustInfoRPCOps.kt @@ -0,0 +1,11 @@ +package net.corda.core.internal.messaging + +import net.corda.core.internal.AttachmentTrustInfo +import net.corda.core.messaging.RPCOps + +interface AttachmentTrustInfoRPCOps : RPCOps { + /** + * Get all the attachment trust information + */ + val attachmentTrustInfos: List +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/messaging/FlowManagerRPCOps.kt b/core/src/main/kotlin/net/corda/core/internal/messaging/FlowManagerRPCOps.kt new file mode 100644 index 0000000000..45b6c3412e --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/messaging/FlowManagerRPCOps.kt @@ -0,0 +1,13 @@ +package net.corda.core.internal.messaging + +import net.corda.core.messaging.RPCOps + +/** + * RPC operations to perform operations related to flows including management of associated persistent states like checkpoints. + */ +interface FlowManagerRPCOps : RPCOps { + /** + * Dump all the current flow checkpoints as JSON into a zip file in the node's log directory. + */ + fun dumpCheckpoints() +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/messaging/InternalCordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/internal/messaging/InternalCordaRPCOps.kt deleted file mode 100644 index 8f92d54c32..0000000000 --- a/core/src/main/kotlin/net/corda/core/internal/messaging/InternalCordaRPCOps.kt +++ /dev/null @@ -1,16 +0,0 @@ -package net.corda.core.internal.messaging - -import net.corda.core.internal.AttachmentTrustInfo -import net.corda.core.messaging.CordaRPCOps - -/** - * Contains internal RPC functions that should not be publicly exposed in [CordaRPCOps] - */ -interface InternalCordaRPCOps : CordaRPCOps { - - /** Dump all the current flow checkpoints as JSON into a zip file in the node's log directory. */ - fun dumpCheckpoints() - - /** Get all attachment trust information */ - val attachmentTrustInfos: List -} \ No newline at end of file diff --git a/detekt-baseline.xml b/detekt-baseline.xml index cf8fc1beac..8d5171fa31 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -151,7 +151,7 @@ ComplexMethod:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$ private fun pollServerForCertificates(requestId: String): List<X509Certificate> ComplexMethod:NewTransaction.kt$NewTransaction$fun show(window: Window) ComplexMethod:NewTransaction.kt$NewTransaction$private fun newTransactionDialog(window: Window) - ComplexMethod:Node.kt$Node$override fun startMessagingService(rpcOps: RPCOps, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) + ComplexMethod:Node.kt$Node$override fun startMessagingService(rpcOps: List<RPCOps>, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) ComplexMethod:NodeNamedCache.kt$DefaultNamedCacheFactory$open protected fun <K, V> configuredForNamed(caffeine: Caffeine<K, V>, name: String): Caffeine<K, V> ComplexMethod:NodeVaultService.kt$NodeVaultService$@Throws(VaultQueryException::class) private fun <T : ContractState> _queryBy(criteria: QueryCriteria, paging_: PageSpecification, sorting: Sort, contractStateType: Class<out T>, skipPagingChecks: Boolean): Vault.Page<T> ComplexMethod:NodeVaultService.kt$NodeVaultService$private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord, previouslySeen: Boolean): List<Vault.Update<ContractState>> @@ -159,15 +159,12 @@ ComplexMethod:ObjectDiffer.kt$ObjectDiffer$fun diff(a: Any?, b: Any?): DiffTree? ComplexMethod:Obligation.kt$Obligation$override fun verify(tx: LedgerTransaction) ComplexMethod:QuasarInstrumentationHook.kt$QuasarInstrumentationHookAgent.Companion$@JvmStatic fun premain(argumentsString: String?, instrumentation: Instrumentation) - ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$ private fun close(notify: Boolean = true) ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$// The handler for Artemis messages. private fun artemisMessageHandler(message: ClientMessage) ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$// This is the general function that transforms a client side RPC to internal Artemis messages. override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$private fun attemptReconnect() ComplexMethod:RPCServer.kt$RPCServer$private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) ComplexMethod:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ReconnectingRPCConnection$ private tailrec fun establishConnectionWithRetry( retryInterval: Duration, roundRobinIndex: Int = 0, retries: Int = -1 ): CordaRPCConnection? ComplexMethod:RemoteTypeCarpenter.kt$SchemaBuildingRemoteTypeCarpenter$override fun carpent(typeInformation: RemoteTypeInformation): Type - ComplexMethod:SchemaMigration.kt$SchemaMigration$ private fun migrateOlderDatabaseToUseLiquibase(existingCheckpoints: Boolean): Boolean - ComplexMethod:SchemaMigration.kt$SchemaMigration$private fun doRunMigration( run: Boolean, check: Boolean, existingCheckpoints: Boolean? = null ) ComplexMethod:SendTransactionFlow.kt$DataVendingFlow$@Suspendable override fun call(): Void? ComplexMethod:ShellCmdLineOptions.kt$ShellCmdLineOptions$private fun toConfigFile(): Config ComplexMethod:StartedFlowTransition.kt$StartedFlowTransition$override fun transition(): TransitionResult @@ -176,7 +173,6 @@ ComplexMethod:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest$@Test(timeout=300_000) fun testClientServerTlsExchange() ComplexMethod:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$@Test(timeout=300_000) fun testClientServerTlsExchange() ComplexMethod:TransactionUtils.kt$ fun createComponentGroups(inputs: List<StateRef>, outputs: List<TransactionState<ContractState>>, commands: List<Command<*>>, attachments: List<SecureHash>, notary: Party?, timeWindow: TimeWindow?, references: List<StateRef>, networkParametersHash: SecureHash?): List<ComponentGroup> - ComplexMethod:TransitionExecutorImpl.kt$TransitionExecutorImpl$@Suppress("NestedBlockDepth", "ReturnCount") @Suspendable override fun executeTransition( fiber: FlowFiber, previousState: StateMachineState, event: Event, transition: TransitionResult, actionExecutor: ActionExecutor ): Pair<FlowContinuation, StateMachineState> ComplexMethod:TypeModellingFingerPrinter.kt$FingerPrintingState$// For a type we haven't seen before, determine the correct path depending on the type of type it is. private fun fingerprintNewType(type: LocalTypeInformation) ComplexMethod:UniversalContract.kt$UniversalContract$override fun verify(tx: LedgerTransaction) ComplexMethod:Util.kt$fun <T> debugCompare(perLeft: Perceivable<T>, perRight: Perceivable<T>) @@ -612,8 +608,6 @@ LongParameterList:AbstractCashSelection.kt$AbstractCashSelection$(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean) LongParameterList:AbstractCashSelection.kt$AbstractCashSelection$(services: ServiceHub, amount: Amount<Currency>, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, stateAndRefs: MutableList<StateAndRef<Cash.State>>) LongParameterList:AbstractCashSelection.kt$AbstractCashSelection$(services: ServiceHub, amount: Amount<Currency>, onlyFromIssuerParties: Set<AbstractParty> = emptySet(), notary: Party? = null, lockId: UUID, withIssuerRefs: Set<OpaqueBytes> = emptySet()) - LongParameterList:AbstractNode.kt$(databaseConfig: DatabaseConfig, wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, wellKnownPartyFromAnonymous: (AbstractParty) -> Party?, schemaService: SchemaService, hikariProperties: Properties, cacheFactory: NamedCacheFactory, customClassLoader: ClassLoader?) - LongParameterList:AbstractNode.kt$(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set<MappedSchema>, metricRegistry: MetricRegistry? = null, cordappLoader: CordappLoader? = null, currentDir: Path? = null, ourName: CordaX500Name) LongParameterList:ArtemisMessagingServer.kt$ArtemisMessagingServer$(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false, deleteDurableQueue: Boolean = false, createNonDurableQueue: Boolean = false, deleteNonDurableQueue: Boolean = false, manage: Boolean = false, browse: Boolean = false) LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, journalBufferTimeout: Int?, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean) LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, journalBufferTimeout: Int?, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean) @@ -655,13 +649,11 @@ LongParameterList:IdenticonRenderer.kt$IdenticonRenderer$(g: GraphicsContext, x: Double, y: Double, patchIndex: Int, turn: Int, patchSize: Double, _invert: Boolean, color: PatchColor) LongParameterList:Injectors.kt$( metricRegistry: MetricRegistry, parallelism: Int, overallDuration: Duration, injectionRate: Rate, queueSizeMetricName: String = "QueueSize", workDurationMetricName: String = "WorkDuration", work: () -> Unit ) LongParameterList:InteractiveShell.kt$InteractiveShell$(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer, inputObjectMapper: ObjectMapper = createYamlInputMapper(rpcOps)) - LongParameterList:InternalTestUtils.kt$(hikariProperties: Properties, databaseConfig: DatabaseConfig, wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, wellKnownPartyFromAnonymous: (AbstractParty) -> Party?, schemaService: SchemaService = NodeSchemaService(), internalSchemas: Set<MappedSchema> = NodeSchemaService().internalSchemas(), cacheFactory: NamedCacheFactory = TestingNamedCacheFactory(), ourName: CordaX500Name = TestIdentity(ALICE_NAME, 70).name) LongParameterList:InternalTestUtils.kt$(inputs: List<StateRef>, attachments: List<SecureHash>, outputs: List<TransactionState<*>>, commands: List<Command<*>>, notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt = PrivacySalt()) LongParameterList:JarSignatureTestUtils.kt$JarSignatureTestUtils$(alias: String = "Test", storePassword: String = "secret!", name: String = CODE_SIGNER.toString(), keyalg: String = "RSA", keyPassword: String = storePassword, storeName: String = "_teststore") LongParameterList:MockServices.kt$MockServices.Companion$( cordappLoader: CordappLoader, identityService: IdentityService, networkParameters: NetworkParameters, initialIdentity: TestIdentity, moreKeys: Set<KeyPair>, keyManagementService: KeyManagementService, schemaService: SchemaService, persistence: CordaPersistence ) LongParameterList:MockServices.kt$MockServices.Companion$( cordappPackages: List<String>, initialIdentity: TestIdentity, networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN), moreKeys: Set<KeyPair>, moreIdentities: Set<PartyAndCertificate>, cacheFactory: TestingNamedCacheFactory = TestingNamedCacheFactory() ) LongParameterList:NetworkBootstrapperTest.kt$NetworkBootstrapperTest$(copyCordapps: CopyCordapps = CopyCordapps.FirstRunOnly, packageOwnership: Map<String, PublicKey>? = emptyMap(), minimumPlatformVerison: Int? = PLATFORM_VERSION, maxMessageSize: Int? = DEFAULT_MAX_MESSAGE_SIZE, maxTransactionSize: Int? = DEFAULT_MAX_TRANSACTION_SIZE, eventHorizon: Duration? = 30.days) - LongParameterList:NetworkMapUpdater.kt$NetworkMapUpdater$(trustRoot: X509Certificate, currentParametersHash: SecureHash, ourNodeInfo: SignedNodeInfo, networkParameters: NetworkParameters, keyManagementService: KeyManagementService, networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings) LongParameterList:NetworkParameters.kt$NetworkParameters$(minimumPlatformVersion: Int = this.minimumPlatformVersion, notaries: List<NotaryInfo> = this.notaries, maxMessageSize: Int = this.maxMessageSize, maxTransactionSize: Int = this.maxTransactionSize, modifiedTime: Instant = this.modifiedTime, epoch: Int = this.epoch, whitelistedContractImplementations: Map<String, List<AttachmentId>> = this.whitelistedContractImplementations ) LongParameterList:NetworkParameters.kt$NetworkParameters$(minimumPlatformVersion: Int = this.minimumPlatformVersion, notaries: List<NotaryInfo> = this.notaries, maxMessageSize: Int = this.maxMessageSize, maxTransactionSize: Int = this.maxTransactionSize, modifiedTime: Instant = this.modifiedTime, epoch: Int = this.epoch, whitelistedContractImplementations: Map<String, List<AttachmentId>> = this.whitelistedContractImplementations, eventHorizon: Duration = this.eventHorizon ) LongParameterList:NodeParameters.kt$NodeParameters$( providedName: CordaX500Name?, rpcUsers: List<User>, verifierType: VerifierType, customOverrides: Map<String, Any?>, startInSameProcess: Boolean?, maximumHeapSize: String ) @@ -770,6 +762,7 @@ MagicNumber:CordaRPCClient.kt$CordaRPCClient$128 MagicNumber:CordaRPCClient.kt$CordaRPCClientConfiguration$3 MagicNumber:CordaRPCClient.kt$CordaRPCClientConfiguration$5 + MagicNumber:CordaSSHAuthInfo.kt$CordaSSHAuthInfo$10 MagicNumber:CordaSecurityProvider.kt$CordaSecurityProvider$0.1 MagicNumber:Crypto.kt$Crypto$2048 MagicNumber:Crypto.kt$Crypto$256 @@ -1080,6 +1073,7 @@ MagicNumber:RPCDriver.kt$RandomRpcUser.Companion$100 MagicNumber:RPCDriver.kt$RandomRpcUser.Companion$3 MagicNumber:RPCDriver.kt$RandomRpcUser.Companion$4 + MagicNumber:RPCPermissionResolver.kt$RPCPermissionResolver$20 MagicNumber:RPCServer.kt$RPCServer$5 MagicNumber:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps$4 MagicNumber:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ErrorInterceptingHandler$1000 @@ -1243,8 +1237,7 @@ SpreadOperator:AssertingTestDatabaseContext.kt$AssertingTestDatabaseContext$(*expectedScripts) SpreadOperator:AttachmentDemo.kt$(*args) SpreadOperator:AttachmentsClassLoaderStaticContractTests.kt$AttachmentsClassLoaderStaticContractTests$(*packages.toTypedArray()) - SpreadOperator:AuthenticatedRpcOpsProxy.kt$(methodName, *(args.map(Class<*>::getName).toTypedArray())) - SpreadOperator:AuthenticatedRpcOpsProxy.kt$AuthenticatedRpcOpsProxy$(logicType, *args) + SpreadOperator:AuthenticatedRpcOpsProxy.kt$AuthenticatedRpcOpsProxy.PermissionsEnforcingInvocationHandler$(methodFullName(method), *(args.map(Class<*>::getName).toTypedArray())) SpreadOperator:AzureInstantiator.kt$AzureInstantiator$(*portsToOpen.toIntArray()) SpreadOperator:ByteArrays.kt$OpaqueBytes.Companion$(*b) SpreadOperator:CertificateRevocationListNodeTests.kt$CertificateRevocationListNodeTests$(newNodeCert, INTERMEDIATE_CA.certificate, *nodeKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }.drop(2).toTypedArray()) @@ -1350,8 +1343,7 @@ SpreadOperator:PropertyValidationTest.kt$PropertyValidationTest$(*key.split(".").toTypedArray(), nestedKey) SpreadOperator:RPCClient.kt$RPCClient$(*haPoolTransportConfigurations.toTypedArray()) SpreadOperator:RPCDriver.kt$RandomRpcUser.Companion$(handle.proxy, *arguments.toTypedArray()) - SpreadOperator:RPCOpsWithContext.kt$(cordaRPCOps, *(args ?: arrayOf())) - SpreadOperator:RPCSecurityManagerTest.kt$RPCSecurityManagerTest$(request.first(), *args) + SpreadOperator:RPCSecurityManagerTest.kt$RPCSecurityManagerTest$(methodName, *args) SpreadOperator:RPCServer.kt$RPCServer$(invocationTarget.instance, *arguments.toTypedArray()) SpreadOperator:ReactiveArtemisConsumer.kt$ReactiveArtemisConsumer.Companion$(queueName, *queueNames) SpreadOperator:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ErrorInterceptingHandler$(reconnectingRPCConnection.proxy, *(args ?: emptyArray())) @@ -1379,7 +1371,6 @@ SpreadOperator:X509Utilities.kt$X509Utilities$(*certificates) ThrowsCount:AMQPTypeIdentifierParser.kt$AMQPTypeIdentifierParser$// Make sure our inputs aren't designed to blow things up. private fun validate(typeString: String) ThrowsCount:AbstractNode.kt$AbstractNode$private fun installCordaServices() - ThrowsCount:AbstractNode.kt$fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set<MappedSchema>, metricRegistry: MetricRegistry? = null, cordappLoader: CordappLoader? = null, currentDir: Path? = null, ourName: CordaX500Name) ThrowsCount:ArtemisMessagingServer.kt$ArtemisMessagingServer$// TODO: Maybe wrap [IOException] on a key store load error so that it's clearly splitting key store loading from // Artemis IO errors @Throws(IOException::class, AddressBindingException::class, KeyStoreException::class) private fun configureAndStartServer() ThrowsCount:BrokerJaasLoginModule.kt$BaseBrokerJaasLoginModule$@Suppress("DEPRECATION") // should use java.security.cert.X509Certificate protected fun getUsernamePasswordAndCerts(): Triple<String, String, Array<javax.security.cert.X509Certificate>?> ThrowsCount:CheckpointVerifier.kt$CheckpointVerifier$ fun verifyCheckpointsCompatible( checkpointStorage: CheckpointStorage, currentCordapps: List<Cordapp>, platformVersion: Int, serviceHub: ServiceHub, tokenizableServices: List<Any> ) @@ -1403,7 +1394,6 @@ ThrowsCount:PropertyDescriptor.kt$PropertyDescriptor$ fun validate() ThrowsCount:RPCApi.kt$RPCApi.ServerToClient.Companion$fun fromClientMessage(context: SerializationContext, message: ClientMessage): ServerToClient ThrowsCount:RPCServer.kt$RPCServer$private fun invokeRpc(context: RpcAuthContext, inMethodName: String, arguments: List<Any?>): Try<Any> - ThrowsCount:SchemaMigration.kt$SchemaMigration$private fun doRunMigration( run: Boolean, check: Boolean, existingCheckpoints: Boolean? = null ) ThrowsCount:ServicesForResolutionImpl.kt$ServicesForResolutionImpl$// We may need to recursively chase transactions if there are notary changes. fun inner(stateRef: StateRef, forContractClassName: String?): Attachment ThrowsCount:SignedNodeInfo.kt$SignedNodeInfo$// TODO Add root cert param (or TrustAnchor) to make sure all the identities belong to the same root fun verified(): NodeInfo ThrowsCount:SignedTransaction.kt$SignedTransaction$@DeleteForDJVM private fun resolveAndCheckNetworkParameters(services: ServiceHub) @@ -1466,7 +1456,6 @@ TooGenericExceptionCaught:Eventually.kt$e: Exception TooGenericExceptionCaught:Expect.kt$exception: Exception TooGenericExceptionCaught:Explorer.kt$Explorer$e: Exception - TooGenericExceptionCaught:FiberDeserializationCheckingInterceptor.kt$FiberDeserializationChecker$exception: Exception TooGenericExceptionCaught:FinanceJSONSupport.kt$CalendarDeserializer$e: Exception TooGenericExceptionCaught:FlowHandle.kt$FlowProgressHandleImpl$e: Exception TooGenericExceptionCaught:FlowMessaging.kt$FlowMessagingImpl$exception: Exception @@ -1541,6 +1530,7 @@ TooGenericExceptionCaught:RPCClient.kt$RPCClient$exception: Throwable TooGenericExceptionCaught:RPCClientProxyHandler.kt$RPCClientProxyHandler$e: Exception TooGenericExceptionCaught:RPCClientProxyHandler.kt$RPCClientProxyHandler$e: RuntimeException + TooGenericExceptionCaught:RPCPermissionResolver.kt$RPCPermissionResolver.InterfaceMethodMapCacheLoader$ex: Exception TooGenericExceptionCaught:RPCServer.kt$RPCServer$e: Exception TooGenericExceptionCaught:RPCServer.kt$RPCServer$exception: Throwable TooGenericExceptionCaught:RPCServer.kt$RPCServer$throwable: Throwable @@ -1630,7 +1620,7 @@ TooManyFunctions:ContractUpgradeTransactions.kt$ContractUpgradeLedgerTransaction : FullTransactionTransactionWithSignatures TooManyFunctions:CordaRPCOps.kt$CordaRPCOps : RPCOps TooManyFunctions:CordaRPCOps.kt$net.corda.core.messaging.CordaRPCOps.kt - TooManyFunctions:CordaRPCOpsImpl.kt$CordaRPCOpsImpl : InternalCordaRPCOpsAutoCloseable + TooManyFunctions:CordaRPCOpsImpl.kt$CordaRPCOpsImpl : CordaRPCOpsAutoCloseable TooManyFunctions:Crypto.kt$Crypto TooManyFunctions:CryptoUtils.kt$net.corda.core.crypto.CryptoUtils.kt TooManyFunctions:Currencies.kt$net.corda.finance.Currencies.kt @@ -1663,8 +1653,6 @@ TooManyFunctions:P2PMessagingClient.kt$P2PMessagingClient : SingletonSerializeAsTokenMessagingServiceAddressToArtemisQueueResolverServiceStateSupport TooManyFunctions:PathUtils.kt$net.corda.core.internal.PathUtils.kt TooManyFunctions:Perceivable.kt$net.corda.finance.contracts.universal.Perceivable.kt - TooManyFunctions:PersistentIdentityService.kt$PersistentIdentityService : SingletonSerializeAsTokenIdentityServiceInternal - TooManyFunctions:PersistentNetworkMapCache.kt$PersistentNetworkMapCache : NetworkMapCacheInternalSingletonSerializeAsToken TooManyFunctions:PortfolioApi.kt$PortfolioApi TooManyFunctions:PropertyDescriptor.kt$net.corda.serialization.internal.amqp.PropertyDescriptor.kt TooManyFunctions:QueryCriteria.kt$QueryCriteria$VaultQueryCriteria : CommonQueryCriteria @@ -1700,8 +1688,6 @@ UnusedImports:Amount.kt$import net.corda.core.crypto.CompositeKey UnusedImports:Amount.kt$import net.corda.core.identity.Party UnusedImports:DummyLinearStateSchemaV1.kt$import net.corda.core.contracts.ContractState - UnusedImports:InternalTestUtils.kt$import java.nio.file.Files - UnusedImports:InternalTestUtils.kt$import net.corda.nodeapi.internal.loadDevCaTrustStore UnusedImports:NetworkMap.kt$import net.corda.core.node.NodeInfo UnusedImports:NodeParameters.kt$import net.corda.core.identity.Party UnusedImports:SerializerFactory.kt$import java.io.NotSerializableException @@ -1960,7 +1946,6 @@ WildcardImport:CordaExceptionTest.kt$import org.junit.Assert.* WildcardImport:CordaFutureImplTest.kt$import com.nhaarman.mockito_kotlin.* WildcardImport:CordaInternal.kt$import kotlin.annotation.AnnotationTarget.* - WildcardImport:CordaMigration.kt$import net.corda.node.services.persistence.* WildcardImport:CordaModule.kt$import com.fasterxml.jackson.annotation.* WildcardImport:CordaModule.kt$import com.fasterxml.jackson.databind.* WildcardImport:CordaModule.kt$import net.corda.core.contracts.* @@ -1990,7 +1975,6 @@ WildcardImport:DBTransactionStorage.kt$import javax.persistence.* WildcardImport:DBTransactionStorage.kt$import net.corda.core.serialization.* WildcardImport:DBTransactionStorage.kt$import net.corda.nodeapi.internal.persistence.* - WildcardImport:DBTransactionStorageTests.kt$import net.corda.testing.core.* WildcardImport:DeleteForDJVM.kt$import kotlin.annotation.AnnotationTarget.* WildcardImport:DemoBench.kt$import tornadofx.* WildcardImport:DemoBenchNodeInfoFilesCopier.kt$import tornadofx.* @@ -2008,7 +1992,6 @@ WildcardImport:DigitalSignatureWithCert.kt$import java.security.cert.* WildcardImport:DistributedServiceTests.kt$import net.corda.testing.core.* WildcardImport:DockerInstantiator.kt$import com.github.dockerjava.api.model.* - WildcardImport:DriverDSLImpl.kt$import net.corda.testing.driver.* WildcardImport:DummyContract.kt$import net.corda.core.contracts.* WildcardImport:DummyContractV2.kt$import net.corda.core.contracts.* WildcardImport:DummyContractV3.kt$import net.corda.core.contracts.* @@ -2036,12 +2019,10 @@ WildcardImport:FlowCheckpointCordapp.kt$import net.corda.core.flows.* WildcardImport:FlowCheckpointVersionNodeStartupCheckTest.kt$import net.corda.core.flows.* WildcardImport:FlowCheckpointVersionNodeStartupCheckTest.kt$import net.corda.core.internal.* - WildcardImport:FlowFrameworkPersistenceTests.kt$import net.corda.testing.node.internal.* WildcardImport:FlowFrameworkTripartyTests.kt$import net.corda.testing.node.internal.* WildcardImport:FlowLogicRefFactoryImpl.kt$import net.corda.core.flows.* WildcardImport:FlowMatchers.kt$import net.corda.coretesting.internal.matchers.* WildcardImport:FlowOverrideTests.kt$import net.corda.core.flows.* - WildcardImport:FlowRetryTest.kt$import net.corda.core.flows.* WildcardImport:FlowStackSnapshotTest.kt$import net.corda.core.flows.* WildcardImport:FlowStateMachine.kt$import net.corda.core.flows.* WildcardImport:FlowsDrainingModeContentionTest.kt$import net.corda.core.flows.* @@ -2087,7 +2068,6 @@ WildcardImport:InternalMockNetwork.kt$import net.corda.core.internal.* WildcardImport:InternalMockNetwork.kt$import net.corda.node.services.config.* WildcardImport:InternalMockNetwork.kt$import net.corda.testing.node.* - WildcardImport:InternalTestUtils.kt$import net.corda.core.contracts.* WildcardImport:IssuerModel.kt$import tornadofx.* WildcardImport:JVMConfig.kt$import tornadofx.* WildcardImport:JacksonSupport.kt$import com.fasterxml.jackson.core.* @@ -2187,7 +2167,6 @@ WildcardImport:NodeAttachmentServiceTest.kt$import org.assertj.core.api.Assertions.* WildcardImport:NodeAttachmentTrustCalculator.kt$import net.corda.core.internal.* WildcardImport:NodeBasedTest.kt$import net.corda.node.services.config.* - WildcardImport:NodeController.kt$import net.corda.core.internal.* WildcardImport:NodeController.kt$import tornadofx.* WildcardImport:NodeControllerTest.kt$import kotlin.test.* WildcardImport:NodeData.kt$import tornadofx.* @@ -2197,7 +2176,6 @@ WildcardImport:NodeInterestRates.kt$import net.corda.core.flows.* WildcardImport:NodeInterestRatesTest.kt$import net.corda.testing.core.* WildcardImport:NodeInterestRatesTest.kt$import org.junit.Assert.* - WildcardImport:NodeProcess.kt$import net.corda.core.internal.* WildcardImport:NodeRegistrationTest.kt$import javax.ws.rs.* WildcardImport:NodeSchedulerService.kt$import net.corda.core.internal.* WildcardImport:NodeSchedulerServiceTest.kt$import com.nhaarman.mockito_kotlin.* diff --git a/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt index b3fd5de36f..4128a5eab8 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/rpc/ArtemisRpcTests.kt @@ -129,7 +129,7 @@ class ArtemisRpcTests { private fun InternalRPCMessagingClient.start(ops: OPS, securityManager: RPCSecurityManager, brokerControl: ActiveMQServerControl) { apply { - init(ops, securityManager, TestingNamedCacheFactory()) + init(listOf(ops), securityManager, TestingNamedCacheFactory()) start(brokerControl) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/rpc/DumpCheckpointsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/rpc/DumpCheckpointsTest.kt index 2e9e36e4cf..21ac40a96d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/rpc/DumpCheckpointsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/rpc/DumpCheckpointsTest.kt @@ -11,7 +11,6 @@ import net.corda.core.internal.div import net.corda.core.internal.inputStream import net.corda.core.internal.isRegularFile import net.corda.core.internal.list -import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.internal.readFully import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow @@ -21,6 +20,7 @@ import net.corda.node.services.statemachine.CountUpDownLatch import net.corda.testing.core.ALICE_NAME import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.checkpoint.CheckpointRpcHelper.checkpointsRpc import net.corda.testing.node.User import net.corda.testing.node.internal.enclosedCordapp import org.junit.Test @@ -44,15 +44,14 @@ class DumpCheckpointsTest { val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { - val proxy = it.proxy as InternalCordaRPCOps // 1 for GetNumberOfCheckpointsFlow itself - val checkPointCountFuture = proxy.startFlow(::GetNumberOfCheckpointsFlow).returnValue + val checkPointCountFuture = it.proxy.startFlow(::GetNumberOfCheckpointsFlow).returnValue val logDirPath = nodeAHandle.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME logDirPath.createDirectories() dumpCheckPointLatch.await() - proxy.dumpCheckpoints() + nodeAHandle.checkpointsRpc.use { checkpointRPCOps -> checkpointRPCOps.dumpCheckpoints() } flowProceedLatch.countDown() assertEquals(1, checkPointCountFuture.get()) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index f7813b2860..25b73c4b6a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -37,7 +37,8 @@ import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.div -import net.corda.core.internal.messaging.InternalCordaRPCOps +import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps +import net.corda.core.internal.messaging.FlowManagerRPCOps import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.rootMessage import net.corda.core.internal.uncheckedCast @@ -71,6 +72,8 @@ import net.corda.djvm.source.EmptyApi import net.corda.djvm.source.UserSource import net.corda.node.CordaClock import net.corda.node.VersionInfo +import net.corda.node.internal.attachments.AttachmentTrustInfoRPCOpsImpl +import net.corda.node.internal.checkpoints.FlowManagerRPCOpsImpl import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.cordapp.CordappConfigFileProvider import net.corda.node.internal.cordapp.CordappProviderImpl @@ -394,21 +397,28 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return this } - /** The implementation of the [CordaRPCOps] interface used by this node. */ - open fun makeRPCOps(cordappLoader: CordappLoader, checkpointDumper: CheckpointDumperImpl): CordaRPCOps { - val ops: InternalCordaRPCOps = CordaRPCOpsImpl( + /** The implementation of the [RPCOps] interfaces used by this node. */ + open fun makeRPCOps(cordappLoader: CordappLoader): List { + val cordaRPCOpsImpl = Pair(CordaRPCOps::class.java, CordaRPCOpsImpl( services, smm, - flowStarter, - checkpointDumper + flowStarter ) { shutdownExecutor.submit(::stop) - }.also { it.closeOnStop() } - val proxies = mutableListOf<(InternalCordaRPCOps) -> InternalCordaRPCOps>() - // Mind that order is relevant here. - proxies += ::AuthenticatedRpcOpsProxy - proxies += { ThreadContextAdjustingRpcOpsProxy(it, cordappLoader.appClassLoader) } - return proxies.fold(ops) { delegate, decorate -> decorate(delegate) } + }.also { it.closeOnStop() }) + + val checkpointRPCOpsImpl = Pair(FlowManagerRPCOps::class.java, FlowManagerRPCOpsImpl(checkpointDumper)) + + val attachmentTrustInfoRPCOps = Pair(AttachmentTrustInfoRPCOps::class.java, AttachmentTrustInfoRPCOpsImpl(services.attachmentTrustCalculator)) + + return listOf(cordaRPCOpsImpl, checkpointRPCOpsImpl, attachmentTrustInfoRPCOps).map { rpcOpsImplPair -> + // Mind that order of proxies is important + val targetInterface = rpcOpsImplPair.first + val stage1Proxy = AuthenticatedRpcOpsProxy.proxy(rpcOpsImplPair.second, targetInterface) + val stage2Proxy = ThreadContextAdjustingRpcOpsProxy.proxy(stage1Proxy, targetInterface, cordappLoader.appClassLoader) + + stage2Proxy + } } private fun quasarExcludePackages(nodeConfiguration: NodeConfiguration) { @@ -513,7 +523,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, installCoreFlows() registerCordappFlows() services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } - val rpcOps = makeRPCOps(cordappLoader, checkpointDumper) + val rpcOps = makeRPCOps(cordappLoader) startShell() networkMapClient?.start(trustRoot) @@ -623,7 +633,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } /** Subclasses must override this to create a "started" node of the desired type, using the provided machinery. */ - abstract fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): S + abstract fun createStartedNode(nodeInfo: NodeInfo, rpcOps: List, notaryService: NotaryService?): S private fun verifyCheckpointsCompatible(tokenizableServices: List) { try { @@ -1035,7 +1045,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected abstract fun makeMessagingService(): MessagingService - protected abstract fun startMessagingService(rpcOps: RPCOps, + protected abstract fun startMessagingService(rpcOps: List, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 1a758a4050..ab311ecaed 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -18,12 +18,11 @@ import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.AttachmentTrustInfo import net.corda.core.internal.FlowStateMachineHandle import net.corda.core.internal.RPC_UPLOADER import net.corda.core.internal.STRUCTURAL_STEP_PREFIX -import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.internal.sign +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandleImpl @@ -54,7 +53,6 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.services.rpc.CheckpointDumperImpl import net.corda.node.services.rpc.context import net.corda.node.services.statemachine.StateMachineManager import net.corda.nodeapi.exceptions.NonRpcFlowException @@ -75,9 +73,8 @@ internal class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, private val flowStarter: FlowStarter, - private val checkpointDumper: CheckpointDumperImpl, private val shutdownNode: () -> Unit -) : InternalCordaRPCOps, AutoCloseable { +) : CordaRPCOps, AutoCloseable { private companion object { private val logger = loggerFor() @@ -157,13 +154,6 @@ internal class CordaRPCOpsImpl( return services.validatedTransactions.track() } - override fun dumpCheckpoints() = checkpointDumper.dumpCheckpoints() - - override val attachmentTrustInfos: List - get() { - return services.attachmentTrustCalculator.calculateAllTrustInfo() - } - override fun stateMachinesSnapshot(): List { val (snapshot, updates) = stateMachinesFeed() updates.notUsed() diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 5ae7633110..504627dad2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -22,7 +22,6 @@ import net.corda.core.internal.errors.AddressBindingException import net.corda.core.internal.getJavaUpdateVersion import net.corda.core.internal.isRegularFile import net.corda.core.internal.notary.NotaryService -import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo @@ -140,7 +139,7 @@ open class Node(configuration: NodeConfiguration, allowHibernateToManageAppSchema = allowHibernateToManageAppSchema ) { - override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): NodeInfo = + override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: List, notaryService: NotaryService?): NodeInfo = nodeInfo companion object { @@ -340,7 +339,7 @@ open class Node(configuration: NodeConfiguration, ) } - override fun startMessagingService(rpcOps: RPCOps, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) { + override fun startMessagingService(rpcOps: List, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) { require(nodeInfo.legalIdentities.size in 1..2) { "Currently nodes must have a primary address and optionally one serviced address" } network as P2PMessagingClient diff --git a/node/src/main/kotlin/net/corda/node/internal/attachments/AttachmentTrustInfoRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/attachments/AttachmentTrustInfoRPCOpsImpl.kt new file mode 100644 index 0000000000..edc6ad4cf6 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/attachments/AttachmentTrustInfoRPCOpsImpl.kt @@ -0,0 +1,16 @@ +package net.corda.node.internal.attachments + +import net.corda.core.internal.AttachmentTrustCalculator +import net.corda.core.internal.AttachmentTrustInfo +import net.corda.core.internal.PLATFORM_VERSION +import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps + +class AttachmentTrustInfoRPCOpsImpl(private val attachmentTrustCalculator: AttachmentTrustCalculator) : AttachmentTrustInfoRPCOps { + + override val protocolVersion: Int = PLATFORM_VERSION + + override val attachmentTrustInfos: List + get() { + return attachmentTrustCalculator.calculateAllTrustInfo() + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/checkpoints/FlowManagerRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/checkpoints/FlowManagerRPCOpsImpl.kt new file mode 100644 index 0000000000..e265b1e2a4 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/checkpoints/FlowManagerRPCOpsImpl.kt @@ -0,0 +1,15 @@ +package net.corda.node.internal.checkpoints + +import net.corda.core.internal.PLATFORM_VERSION +import net.corda.core.internal.messaging.FlowManagerRPCOps +import net.corda.node.services.rpc.CheckpointDumperImpl + +/** + * Implementation of [FlowManagerRPCOps] + */ +internal class FlowManagerRPCOpsImpl(private val checkpointDumper: CheckpointDumperImpl) : FlowManagerRPCOps { + + override val protocolVersion: Int = PLATFORM_VERSION + + override fun dumpCheckpoints() = checkpointDumper.dumpCheckpoints() +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/AuthenticatedRpcOpsProxy.kt b/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/AuthenticatedRpcOpsProxy.kt index 1e0caa50a2..7e2925b458 100644 --- a/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/AuthenticatedRpcOpsProxy.kt +++ b/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/AuthenticatedRpcOpsProxy.kt @@ -1,55 +1,72 @@ package net.corda.node.internal.rpc.proxies import net.corda.client.rpc.PermissionException -import net.corda.core.flows.FlowLogic -import net.corda.core.internal.messaging.InternalCordaRPCOps -import net.corda.core.messaging.CordaRPCOps import net.corda.core.internal.utilities.InvocationHandlerTemplate +import net.corda.core.messaging.RPCOps +import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName import net.corda.node.services.rpc.RpcAuthContext import net.corda.node.services.rpc.rpcContext import java.lang.reflect.Method import java.lang.reflect.Proxy /** - * Implementation of [CordaRPCOps] that checks authorisation. + * Creates proxy that checks entitlements for every RPCOps interface call. */ -internal class AuthenticatedRpcOpsProxy(private val delegate: InternalCordaRPCOps) : InternalCordaRPCOps by proxy(delegate, ::rpcContext) { - /** - * Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed - * to be present. - * - * TODO: Why is this logic duplicated vs the actual implementation? - */ - override val protocolVersion: Int get() = delegate.nodeInfo().platformVersion +internal object AuthenticatedRpcOpsProxy { - // Need overriding to pass additional `listOf(logicType)` argument for polymorphic `startFlow` permissions. - override fun startFlowDynamic(logicType: Class>, vararg args: Any?) = guard("startFlowDynamic", listOf(logicType), ::rpcContext) { - delegate.startFlowDynamic(logicType, *args) + fun proxy(delegate: T, targetInterface: Class): T { + require(targetInterface.isInterface) { "Interface is expected instead of $targetInterface" } + val handler = PermissionsEnforcingInvocationHandler(delegate, targetInterface) + @Suppress("UNCHECKED_CAST") + return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(targetInterface), handler) as T } - // Need overriding to pass additional `listOf(logicType)` argument for polymorphic `startFlow` permissions. - override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?) = guard("startTrackedFlowDynamic", listOf(logicType), ::rpcContext) { - delegate.startTrackedFlowDynamic(logicType, *args) - } + private class PermissionsEnforcingInvocationHandler(override val delegate: Any, private val clazz: Class<*>) : InvocationHandlerTemplate { - private companion object { - private fun proxy(delegate: InternalCordaRPCOps, context: () -> RpcAuthContext): InternalCordaRPCOps { - val handler = PermissionsEnforcingInvocationHandler(delegate, context) - return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(InternalCordaRPCOps::class.java), handler) as InternalCordaRPCOps + private val exemptMethod = RPCOps::class.java.getMethod("getProtocolVersion") + + private val namedInterfaces = setOf( + net.corda.core.messaging.CordaRPCOps::class.java) + private val namedMethods = setOf("startFlowDynamic", "startTrackedFlowDynamic") + + override fun invoke(proxy: Any, method: Method, arguments: Array?): Any? { + + if (method == exemptMethod) { + // "getProtocolVersion" is an exempt from entitlements check as this is the very first *any* RPCClient calls upon login + return super.invoke(proxy, method, arguments) + } + + val importantArgs = if (clazz in namedInterfaces && method.name in namedMethods) { + // Normally list of arguments makes no difference when checking entitlements, however when starting flows + // first argument represents a class name of the flow to be started and special handling applies in this case with + // name of the class extracted and passed into `guard` method for entitlements check. + val nonNullArgs = requireNotNull(arguments) + require(nonNullArgs.isNotEmpty()) + val firstArgClass = requireNotNull(nonNullArgs[0] as? Class<*>) + listOf(firstArgClass) + } else emptyList() + + return guard(method, importantArgs, ::rpcContext) { super.invoke(proxy, method, arguments) } } - } - private class PermissionsEnforcingInvocationHandler(override val delegate: InternalCordaRPCOps, private val context: () -> RpcAuthContext) : InvocationHandlerTemplate { - override fun invoke(proxy: Any, method: Method, arguments: Array?) = guard(method.name, context) { super.invoke(proxy, method, arguments) } + private fun guard(method: Method, args: List>, context: () -> RpcAuthContext, action: () -> RESULT): RESULT { + if (!context().isPermitted(methodFullName(method), *(args.map(Class<*>::getName).toTypedArray()))) { + throw PermissionException("User not authorized to perform RPC call $method with target $args") + } else { + return action() + } + } } } -private fun guard(methodName: String, context: () -> RpcAuthContext, action: () -> RESULT) = guard(methodName, emptyList(), context, action) +object RpcAuthHelper { + const val INTERFACE_SEPARATOR = "#" -private fun guard(methodName: String, args: List>, context: () -> RpcAuthContext, action: () -> RESULT): RESULT { - if (!context().isPermitted(methodName, *(args.map(Class<*>::getName).toTypedArray()))) { - throw PermissionException("User not authorized to perform RPC call $methodName with target $args") - } else { - return action() + fun methodFullName(method: Method):String = methodFullName(method.declaringClass, method.name) + + fun methodFullName(clazz: Class<*>, methodName: String): String { + require(clazz.isInterface) { "Must be an interface: $clazz"} + require(RPCOps::class.java.isAssignableFrom(clazz)) { "Must be assignable from RPCOps: $clazz" } + return clazz.name + INTERFACE_SEPARATOR + methodName } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxy.kt b/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxy.kt index 21ebd23682..77c63ea233 100644 --- a/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxy.kt +++ b/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxy.kt @@ -1,30 +1,28 @@ package net.corda.node.internal.rpc.proxies import net.corda.core.internal.executeWithThreadContextClassLoader -import net.corda.core.internal.messaging.InternalCordaRPCOps -import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.RPCOps import net.corda.core.internal.utilities.InvocationHandlerTemplate import java.lang.reflect.Method import java.lang.reflect.Proxy /** - * A [CordaRPCOps] proxy that adjusts the thread context's class loader temporarily on every invocation with the provided classloader. + * A proxy that adjusts the thread context's class loader temporarily on every invocation of supplied interface with the provided classloader. * As an example, this can be used to work-around cases, where 3rd party libraries prioritise the thread context's class loader over the current one, * without sensible fallbacks to the classloader of the current instance. * If clients' CorDapps use one of these libraries, this temporary adjustment can ensure that any provided classes from these libraries will be available during RPC calls. */ -internal class ThreadContextAdjustingRpcOpsProxy(private val delegate: InternalCordaRPCOps, private val classLoader: ClassLoader) : InternalCordaRPCOps by proxy(delegate, classLoader) { - private companion object { - private fun proxy(delegate: InternalCordaRPCOps, classLoader: ClassLoader): InternalCordaRPCOps { - val handler = ThreadContextAdjustingRpcOpsProxy.ThreadContextAdjustingInvocationHandler(delegate, classLoader) - return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(InternalCordaRPCOps::class.java), handler) as InternalCordaRPCOps - } +internal object ThreadContextAdjustingRpcOpsProxy { + fun proxy(delegate: T, clazz: Class, classLoader: ClassLoader): T { + require(clazz.isInterface) { "Interface is expected instead of $clazz" } + val handler = ThreadContextAdjustingInvocationHandler(delegate, classLoader) + @Suppress("UNCHECKED_CAST") + return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(clazz), handler) as T } - private class ThreadContextAdjustingInvocationHandler(override val delegate: InternalCordaRPCOps, private val classLoader: ClassLoader) : InvocationHandlerTemplate { + internal class ThreadContextAdjustingInvocationHandler(override val delegate: Any, private val classLoader: ClassLoader) : InvocationHandlerTemplate { override fun invoke(proxy: Any, method: Method, arguments: Array?): Any? { return executeWithThreadContextClassLoader(this.classLoader) { super.invoke(proxy, method, arguments) } } } - } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/security/RPCPermissionResolver.kt b/node/src/main/kotlin/net/corda/node/internal/security/RPCPermissionResolver.kt new file mode 100644 index 0000000000..6d42383d73 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/security/RPCPermissionResolver.kt @@ -0,0 +1,175 @@ +package net.corda.node.internal.security + +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.LoadingCache +import net.corda.core.internal.toMultiMap +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.RPCOps +import net.corda.node.internal.rpc.proxies.RpcAuthHelper.INTERFACE_SEPARATOR +import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName +import org.apache.shiro.authz.Permission +import org.apache.shiro.authz.permission.PermissionResolver +import org.slf4j.LoggerFactory +import java.lang.reflect.Method +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KProperty +import kotlin.reflect.jvm.javaMethod + +/* + * A [org.apache.shiro.authz.permission.PermissionResolver] implementation for RPC permissions. + * Provides a method to construct an [RPCPermission] instance from its string representation + * in the form used by a Node admin. + * + * Currently valid permission strings have the forms: + * + * - `ALL`: allowing all type of RPC calls + * + * - `InvokeRpc.$RPCMethodName`: allowing to call a given RPC method without restrictions on its arguments. + * + * - `StartFlow.$FlowClassName`: allowing to call a `startFlow*` RPC method targeting a Flow instance + * of a given class + */ +internal object RPCPermissionResolver : PermissionResolver { + + private val logger = LoggerFactory.getLogger(RPCPermissionResolver::class.java) + + private const val SEPARATOR = '.' + private const val NEW_STYLE_SEP = ":" + private const val ACTION_START_FLOW = "startflow" + private const val ACTION_INVOKE_RPC = "invokerpc" + private const val ACTION_ALL = "all" + private val FLOW_RPC_CALLS = setOf( + "startFlowDynamic", + "startTrackedFlowDynamic", + "startFlow", + "startTrackedFlow") + + override fun resolvePermission(representation: String): Permission { + when (representation.substringBefore(SEPARATOR).toLowerCase()) { + ACTION_INVOKE_RPC -> { + val rpcCall = representation.substringAfter(SEPARATOR, "") + require(representation.count { it == SEPARATOR } == 1 && rpcCall.isNotEmpty()) { + "Malformed permission string" + } + val legacyPermitted = when (rpcCall) { + "startFlow" -> setOf("startFlowDynamic", rpcCall) + "startTrackedFlow" -> setOf("startTrackedFlowDynamic", rpcCall) + else -> setOf(rpcCall) + } + return RPCPermission(legacyPermitted.toFullyQualified()) + } + ACTION_START_FLOW -> { + val targetFlow = representation.substringAfter(SEPARATOR, "") + require(targetFlow.isNotEmpty()) { + "Missing target flow after StartFlow" + } + return RPCPermission(FLOW_RPC_CALLS.toFullyQualified(), targetFlow) + } + ACTION_ALL -> { + // Leaving empty set of targets and actions to match everything + return RPCPermission() + } + else -> return attemptNewStyleParsing(representation) + } + } + + private fun Set.toFullyQualified(): Set { + return map { methodFullName(CordaRPCOps::class.java, it) }.toSet() + } + + /** + * New style permissions representation: + * 1. Fully qualified form: InvokeRpc:com.fully.qualified.package.CustomClientRpcOps#firstMethod + * 2. All methods of the interface: InvokeRpc:com.fully.qualified.package.CustomClientRpcOps#ALL + * 3. Methods of specific group: InvokeRpc:com.fully.qualified.package.CustomClientRpcOps#READONLY + */ + private fun attemptNewStyleParsing(permAsString: String): Permission { + return when(permAsString.substringBefore(NEW_STYLE_SEP).toLowerCase()) { + ACTION_INVOKE_RPC -> { + val interfaceAndMethods = permAsString.substringAfter(NEW_STYLE_SEP, "") + val interfaceParts = interfaceAndMethods.split(INTERFACE_SEPARATOR) + require(interfaceParts.size == 2) { "Malformed to comply with new style of InvokeRpc: $interfaceAndMethods" } + val methodsMap = requireNotNull(cache.get(interfaceParts[0])) + { "Method map for ${interfaceParts[0]} must not be null in the cache. There must have been error processing interface. " + + "Please look at the error log lines above." } + val lookupKey = interfaceAndMethods.toLowerCase() + val methods = requireNotNull(methodsMap[lookupKey]) { "Cannot find record for " + + "'$lookupKey' for interface '${interfaceParts[0]}' in $methodsMap. " + + "Please check permissions configuration string '$permAsString' matching class representation." } + RPCPermission(methods) + } + else -> throw IllegalArgumentException("Unable to parse permission as string: $permAsString") + } + } + + private val cache: LoadingCache>> = Caffeine.newBuilder() + .maximumSize(java.lang.Long.getLong("net.corda.node.internal.security.rpc.interface.cacheSize", 20)) + .build(InterfaceMethodMapCacheLoader()) + + private class InterfaceMethodMapCacheLoader : CacheLoader>> { + override fun load(interfaceName: String): Map>? { + return try { + inspectInterface(interfaceName) + } catch (ex: Exception) { + logger.error("Unexpected error when populating cache for $interfaceName", ex) + null + } + } + } + + /** + * Returns a map where key is either: + * - fully qualified interface method name; + * or + * - Wildcard string representing the group of methods like: ALL, READ_ONLY, etc. + * Value is always a set of fully qualified method names. + */ + internal fun inspectInterface(interfaceName: String): Map> { + val interfaceClass = Class.forName(interfaceName).kotlin + require(interfaceClass.java.isInterface) { "Must be an interface: $interfaceClass"} + require(RPCOps::class.java.isAssignableFrom(interfaceClass.java)) { "Must be assignable from RPCOps: $interfaceClass" } + + val membersPairs = interfaceClass.members.flatMap { member -> + when(member) { + is KFunction -> { + val method = member.javaMethod + if (method != null) { + processMethod(method, interfaceClass) + } else { + logger.info("KFunction $member does not have Java representation - ignoring") + emptyList() + } + } + is KProperty -> { + val method = member.getter.javaMethod + if (method != null) { + processMethod(method, interfaceClass) + } else { + logger.info("KProperty $member does not have Java representation - ignoring") + emptyList() + } + } + else -> { + logger.info("$member is an unhandled type of KCallable - ignoring") + emptyList() + } + } + } + // Pack the pairs into desired resulting data structure + return membersPairs.toMultiMap().mapValues { it.value.toSet() } + } + + private fun processMethod(method: Method, interfaceClass: KClass): List> { + if(!RPCOps::class.java.isAssignableFrom(method.declaringClass)) { + // To prevent going too deep to Object level + return emptyList() + } + + val allKey = methodFullName(interfaceClass.java, ACTION_ALL).toLowerCase() + val methodFullName = methodFullName(method) + return listOf(allKey to methodFullName) + // ALL group + listOf(methodFullName.toLowerCase() to methodFullName) // Full method names individually + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt index 2f4246422a..605bf0bd71 100644 --- a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt @@ -16,10 +16,8 @@ import org.apache.shiro.authc.* import org.apache.shiro.authc.credential.PasswordMatcher import org.apache.shiro.authc.credential.SimpleCredentialsMatcher import org.apache.shiro.authz.AuthorizationInfo -import org.apache.shiro.authz.Permission import org.apache.shiro.authz.SimpleAuthorizationInfo import org.apache.shiro.authz.permission.DomainPermission -import org.apache.shiro.authz.permission.PermissionResolver import org.apache.shiro.cache.CacheManager import org.apache.shiro.mgt.DefaultSecurityManager import org.apache.shiro.realm.AuthorizingRealm @@ -106,7 +104,7 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig, cacheFactory: NamedCache * represented by instances of the [Permission] interface which offers a single method: [implies], to * test if the 'x implies y' binary predicate is satisfied. */ -private class RPCPermission : DomainPermission { +internal class RPCPermission : DomainPermission { /** * Helper constructor directly setting actions and target field @@ -123,63 +121,6 @@ private class RPCPermission : DomainPermission { constructor() : super() } -/* - * A [org.apache.shiro.authz.permission.PermissionResolver] implementation for RPC permissions. - * Provides a method to construct an [RPCPermission] instance from its string representation - * in the form used by a Node admin. - * - * Currently valid permission strings have the forms: - * - * - `ALL`: allowing all type of RPC calls - * - * - `InvokeRpc.$RPCMethodName`: allowing to call a given RPC method without restrictions on its arguments. - * - * - `StartFlow.$FlowClassName`: allowing to call a `startFlow*` RPC method targeting a Flow instance - * of a given class - */ -private object RPCPermissionResolver : PermissionResolver { - - private const val SEPARATOR = '.' - private const val ACTION_START_FLOW = "startflow" - private const val ACTION_INVOKE_RPC = "invokerpc" - private const val ACTION_ALL = "all" - private val FLOW_RPC_CALLS = setOf( - "startFlowDynamic", - "startTrackedFlowDynamic", - "startFlow", - "startTrackedFlow") - - override fun resolvePermission(representation: String): Permission { - val action = representation.substringBefore(SEPARATOR).toLowerCase() - when (action) { - ACTION_INVOKE_RPC -> { - val rpcCall = representation.substringAfter(SEPARATOR, "") - require(representation.count { it == SEPARATOR } == 1 && !rpcCall.isEmpty()) { - "Malformed permission string" - } - val permitted = when(rpcCall) { - "startFlow" -> setOf("startFlowDynamic", rpcCall) - "startTrackedFlow" -> setOf("startTrackedFlowDynamic", rpcCall) - else -> setOf(rpcCall) - } - return RPCPermission(permitted) - } - ACTION_START_FLOW -> { - val targetFlow = representation.substringAfter(SEPARATOR, "") - require(targetFlow.isNotEmpty()) { - "Missing target flow after StartFlow" - } - return RPCPermission(FLOW_RPC_CALLS, targetFlow) - } - ACTION_ALL -> { - // Leaving empty set of targets and actions to match everything - return RPCPermission() - } - else -> throw IllegalArgumentException("Unknown permission action specifier: $action") - } - } -} - class ShiroAuthorizingSubject( private val subjectId: PrincipalCollection, private val manager: DefaultSecurityManager) : AuthorizingSubject { diff --git a/node/src/main/kotlin/net/corda/node/services/rpc/InternalRPCMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/rpc/InternalRPCMessagingClient.kt index 8eda4f67db..8ed025549c 100644 --- a/node/src/main/kotlin/net/corda/node/services/rpc/InternalRPCMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/rpc/InternalRPCMessagingClient.kt @@ -21,7 +21,7 @@ class InternalRPCMessagingClient(val sslConfig: MutualSslConfiguration, val serv private var locator: ServerLocator? = null private var rpcServer: RPCServer? = null - fun init(rpcOps: RPCOps, securityManager: RPCSecurityManager, cacheFactory: NamedCacheFactory) = synchronized(this) { + fun init(rpcOps: List, securityManager: RPCSecurityManager, cacheFactory: NamedCacheFactory) = synchronized(this) { val tcpTransport = ArtemisTcpTransport.rpcInternalClientTcpTransport(serverAddress, sslConfig) locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { diff --git a/node/src/main/kotlin/net/corda/node/services/rpc/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/rpc/RPCServer.kt index 63503f8f28..fdda466c2d 100644 --- a/node/src/main/kotlin/net/corda/node/services/rpc/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/rpc/RPCServer.kt @@ -14,7 +14,6 @@ import net.corda.core.context.Trace.InvocationId import net.corda.core.identity.CordaX500Name import net.corda.core.internal.LifeCycle import net.corda.core.internal.NamedCacheFactory -import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext @@ -177,10 +176,10 @@ class RPCServer( val groupedMethods = with(interfaceClass) { /* * Until version 4.3, rpc calls did not include class names. - * Up to this version, only CordaRPCOps and InternalCordaRPCOps were supported. + * Up to this version, only CordaRPCOps was supported. * So, for these classes methods are registered without their class name as well to preserve backwards compatibility. */ - if(interfaceClass == CordaRPCOps::class.java || interfaceClass == InternalCordaRPCOps::class.java) { + if (interfaceClass == CordaRPCOps::class.java) { methods.groupBy { it.name } } else { methods.groupBy { interfaceClass.name + CLASS_METHOD_DIVIDER + it.name } diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index d7b45e42e6..029f4c5810 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -98,7 +98,7 @@ class CordaRPCOpsImplTest { fun setup() { mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) - rpc = aliceNode.rpcOps + rpc = aliceNode.cordaRPCOps CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), buildSubject("TEST_USER", emptySet()))) mockNet.runNetwork() diff --git a/node/src/test/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxyTest.kt b/node/src/test/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxyTest.kt index bf23068664..368bc45627 100644 --- a/node/src/test/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxyTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/rpc/proxies/ThreadContextAdjustingRpcOpsProxyTest.kt @@ -3,7 +3,7 @@ package net.corda.node.internal.rpc.proxies import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.mock import net.corda.core.flows.StateMachineRunId -import net.corda.core.internal.messaging.InternalCordaRPCOps +import net.corda.core.messaging.CordaRPCOps import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.mockito.Mockito.`when` @@ -12,9 +12,9 @@ class ThreadContextAdjustingRpcOpsProxyTest { private val coreOps = mock() private val mockClassloader = mock() - private val proxy = ThreadContextAdjustingRpcOpsProxy(coreOps, mockClassloader) + private val proxy = ThreadContextAdjustingRpcOpsProxy.proxy(coreOps, CordaRPCOps::class.java, mockClassloader) - private interface InstrumentedCordaRPCOps : InternalCordaRPCOps { + private interface InstrumentedCordaRPCOps : CordaRPCOps { fun getThreadContextClassLoader(): ClassLoader = Thread.currentThread().contextClassLoader } diff --git a/node/src/test/kotlin/net/corda/node/internal/security/RPCPermissionResolverTest.kt b/node/src/test/kotlin/net/corda/node/internal/security/RPCPermissionResolverTest.kt new file mode 100644 index 0000000000..0881e03028 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/internal/security/RPCPermissionResolverTest.kt @@ -0,0 +1,57 @@ +package net.corda.node.internal.security + +import net.corda.core.messaging.RPCOps +import net.corda.node.internal.rpc.proxies.RpcAuthHelper.methodFullName +import org.junit.Test + +import java.time.ZonedDateTime +import kotlin.test.assertEquals + +class RPCPermissionResolverTest { + + @Suppress("unused") + interface Alpha : RPCOps { + fun readAlpha() : String + } + + @Suppress("unused") + interface Beta : Alpha { + val betaValue : Int + + fun writeBeta(foo: Int) + + fun nothingSpecial() : Int + } + + @Suppress("unused") + interface Gamma : Beta { + fun readGamma() : ZonedDateTime + } + + private val readAlphaMethod = methodFullName(Alpha::class.java.getMethod("readAlpha")) + private val readAlphaMethodKey = readAlphaMethod.toLowerCase() + + @Test(timeout=300_000) + fun `test Alpha`() { + with(RPCPermissionResolver.inspectInterface(Alpha::class.java.name)) { + assertEquals(3, size, toString()) // protocolVersion, ALL, readAlpha + assertEquals(setOf(readAlphaMethod), this[readAlphaMethodKey], toString()) + } + } + + @Test(timeout=300_000) + fun `test Beta`() { + with(RPCPermissionResolver.inspectInterface(Beta::class.java.name)) { + assertEquals(6, size, toString()) // protocolVersion, ALL, readAlpha + // and 3 x Beta methods + } + } + + @Test(timeout=300_000) + fun `test Gamma`() { + with(RPCPermissionResolver.inspectInterface(Gamma::class.java.name)) { + assertEquals(7, size, toString()) // protocolVersion, ALL, readAlpha, + // 3 x Beta methods and 1 Gamma method + } + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt index a5278791fb..35aea3237e 100644 --- a/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/RPCSecurityManagerTest.kt @@ -3,6 +3,7 @@ package net.corda.node.services import net.corda.core.context.AuthServiceId import net.corda.core.flows.FlowLogic import net.corda.core.messaging.CordaRPCOps +import net.corda.node.internal.rpc.proxies.RpcAuthHelper import net.corda.node.internal.security.Password import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.tryAuthenticate @@ -152,14 +153,14 @@ class RPCSecurityManagerTest { userRealms.tryAuthenticate("user", Password("password"))!!, userRealms.buildSubject("user"))) { for (request in permitted) { - val call = request.first() + val methodName = RpcAuthHelper.methodFullName(CordaRPCOps::class.java, request.first()) val args = request.drop(1).toTypedArray() - require(subject.isPermitted(request.first(), *args)) { - "User ${subject.principal} should be permitted $call with target '${request.toList()}'" + require(subject.isPermitted(methodName, *args)) { + "User ${subject.principal} should be permitted $methodName with target '${request.toList()}'" } if (args.isEmpty()) { - require(subject.isPermitted(request.first(), "XXX")) { - "User ${subject.principal} should be permitted $call with any target" + require(subject.isPermitted(methodName, "XXX")) { + "User ${subject.principal} should be permitted $methodName with any target" } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/internal/checkpoint/CheckpointRpcHelper.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/internal/checkpoint/CheckpointRpcHelper.kt new file mode 100644 index 0000000000..867abe4794 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/internal/checkpoint/CheckpointRpcHelper.kt @@ -0,0 +1,24 @@ +package net.corda.testing.driver.internal.checkpoint + +import net.corda.core.internal.messaging.FlowManagerRPCOps +import net.corda.testing.driver.NodeHandle + +object CheckpointRpcHelper { + + interface CloseableFlowManagerRPCOps : FlowManagerRPCOps, AutoCloseable + + val NodeHandle.checkpointsRpc: CloseableFlowManagerRPCOps + get() { + val user = rpcUsers.first() + + val rpcConnection = net.corda.client.rpc.internal.RPCClient(rpcAddress) + .start(FlowManagerRPCOps::class.java, user.username, user.password) + val proxy = rpcConnection.proxy + + return object : CloseableFlowManagerRPCOps, FlowManagerRPCOps by proxy { + override fun close() { + rpcConnection.close() + } + } + } +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 1b6bef31f4..811b70144e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -109,7 +109,7 @@ interface TestStartedNode { val services: StartedNodeServices val smm: StateMachineManager val attachments: NodeAttachmentService - val rpcOps: CordaRPCOps + val rpcOpsList: List val network: MockNodeMessagingService val database: CordaPersistence val notaryService: NotaryService? @@ -135,6 +135,9 @@ interface TestStartedNode { fun > registerInitiatedFlow(initiatedFlowClass: Class, track: Boolean = false): Observable fun > registerInitiatedFlow(initiatingFlowClass: Class>, initiatedFlowClass: Class, track: Boolean = false): Observable + + val cordaRPCOps: CordaRPCOps + get() = rpcOpsList.mapNotNull { it as? CordaRPCOps }.single() } open class InternalMockNetwork(cordappPackages: List = emptyList(), @@ -306,7 +309,7 @@ open class InternalMockNetwork(cordappPackages: List = emptyList(), override val info: NodeInfo, override val smm: StateMachineManager, override val database: CordaPersistence, - override val rpcOps: CordaRPCOps, + override val rpcOpsList: List, override val notaryService: NotaryService?) : TestStartedNode { override fun dispose() = internals.stop() @@ -347,7 +350,7 @@ open class InternalMockNetwork(cordappPackages: List = emptyList(), override val started: TestStartedNode? get() = super.started - override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): TestStartedNode { + override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: List, notaryService: NotaryService?): TestStartedNode { return TestStartedNodeImpl( this, attachments, @@ -380,7 +383,7 @@ open class InternalMockNetwork(cordappPackages: List = emptyList(), return MockNodeMessagingService(configuration, serverThread).closeOnStop() } - override fun startMessagingService(rpcOps: RPCOps, + override fun startMessagingService(rpcOps: List, nodeInfo: NodeInfo, myNotaryIdentity: PartyAndCertificate?, networkParameters: NetworkParameters) { diff --git a/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt index d1a7df5eda..21852cb9ca 100644 --- a/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt +++ b/tools/shell/src/integration-test/kotlin/net/corda/tools/shell/InteractiveShellIntegrationTest.kt @@ -21,7 +21,6 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.inputStream import net.corda.core.internal.list -import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.messaging.ClientRpcSslOptions import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow @@ -49,6 +48,7 @@ import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.driver.internal.NodeHandleInternal +import net.corda.testing.driver.internal.checkpoint.CheckpointRpcHelper.checkpointsRpc import net.corda.testing.internal.useSslRpcOverrides import net.corda.testing.node.User import net.corda.testing.node.internal.enclosedCordapp @@ -299,7 +299,7 @@ class InteractiveShellIntegrationTest { (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).createDirectories() alice.rpc.startFlow(::ExternalOperationFlow) ExternalOperation.lock.acquire() - InteractiveShell.runDumpCheckpoints(alice.rpc as InternalCordaRPCOps) + alice.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) } ExternalOperation.lock2.release() val zipFile = (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() } @@ -322,7 +322,7 @@ class InteractiveShellIntegrationTest { (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).createDirectories() alice.rpc.startFlow(::ExternalAsyncOperationFlow) ExternalAsyncOperation.lock.acquire() - InteractiveShell.runDumpCheckpoints(alice.rpc as InternalCordaRPCOps) + alice.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) } ExternalAsyncOperation.future.complete(null) val zipFile = (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() } val json = ZipInputStream(zipFile.inputStream()).use { zip -> @@ -350,7 +350,7 @@ class InteractiveShellIntegrationTest { assertThrows { alice.rpc.startFlow(::WaitForStateConsumptionFlow, stateRefs).returnValue.getOrThrow(10.seconds) } - InteractiveShell.runDumpCheckpoints(alice.rpc as InternalCordaRPCOps) + alice.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) } val zipFile = (alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() } val json = ZipInputStream(zipFile.inputStream()).use { zip -> zip.nextEntry @@ -390,7 +390,7 @@ class InteractiveShellIntegrationTest { Thread.sleep(5000) mockRenderPrintWriter() - InteractiveShell.runDumpCheckpoints(aliceNode.rpc as InternalCordaRPCOps) + aliceNode.checkpointsRpc.use { InteractiveShell.runDumpCheckpoints(it) } val zipFile = (aliceNode.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list().first { "checkpoints_dump-" in it.toString() } val json = ZipInputStream(zipFile.inputStream()).use { zip -> diff --git a/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java index 30cd727585..c5f4a5f602 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/AttachmentShellCommand.java @@ -1,14 +1,22 @@ package net.corda.tools.shell; +import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps; import org.crsh.cli.Command; import org.crsh.cli.Man; import org.crsh.cli.Named; import org.crsh.cli.Usage; +import org.jetbrains.annotations.NotNull; import static net.corda.tools.shell.InteractiveShell.runAttachmentTrustInfoView; @Named("attachments") -public class AttachmentShellCommand extends InteractiveShellCommand { +public class AttachmentShellCommand extends InteractiveShellCommand { + + @NotNull + @Override + public Class getRpcOpsClass() { + return AttachmentTrustInfoRPCOps.class; + } @Command @Man("Displays the trusted CorDapp attachments that have been manually installed or received over the network") diff --git a/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java index f0a26e61d0..4842213b3c 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/CheckpointShellCommand.java @@ -1,14 +1,22 @@ package net.corda.tools.shell; +import net.corda.core.internal.messaging.FlowManagerRPCOps; import org.crsh.cli.Command; import org.crsh.cli.Man; import org.crsh.cli.Named; import org.crsh.cli.Usage; +import org.jetbrains.annotations.NotNull; import static net.corda.tools.shell.InteractiveShell.*; @Named("checkpoints") -public class CheckpointShellCommand extends InteractiveShellCommand { +public class CheckpointShellCommand extends InteractiveShellCommand { + + @NotNull + @Override + public Class getRpcOpsClass() { + return FlowManagerRPCOps.class; + } @Command @Man("Outputs the contents of all checkpoints as json to be manually reviewed") diff --git a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java index 3922b9a1cc..420cace42d 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/FlowShellCommand.java @@ -27,7 +27,7 @@ import static net.corda.tools.shell.InteractiveShell.runStateMachinesView; "flow constructors (the right one is picked automatically) are then specified using the same syntax as for the run command." ) @Named("flow") -public class FlowShellCommand extends InteractiveShellCommand { +public class FlowShellCommand extends CordaRpcOpsShellCommand { private static final Logger logger = LoggerFactory.getLogger(FlowShellCommand.class); diff --git a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java index 7d09802088..bcd10b09cb 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Optional; @Named("hashLookup") -public class HashLookupShellCommand extends InteractiveShellCommand { +public class HashLookupShellCommand extends CordaRpcOpsShellCommand { private static Logger logger = LoggerFactory.getLogger(HashLookupShellCommand.class); private static final String manualText ="Checks if a transaction matching a specified Id hash value is recorded on this node.\n\n" + "Both the transaction Id and the hashed value of a transaction Id (as returned by the Notary in case of a double-spend) is a valid input.\n" + diff --git a/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java index 5ec1937f3e..42fc3d8d77 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/OutputFormatCommand.java @@ -18,7 +18,7 @@ import java.util.Map; @Man("Allows you to see and update the format that's currently used for the commands' output.") @Usage("Allows you to see and update the format that's currently used for the commands' output.") @Named("output-format") -public class OutputFormatCommand extends InteractiveShellCommand { +public class OutputFormatCommand extends CordaRpcOpsShellCommand { public OutputFormatCommand() {} diff --git a/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java index 303d3dd4c3..123e2510db 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/RunShellCommand.java @@ -25,7 +25,7 @@ import static java.util.Comparator.comparing; // is the closest you can get in Kotlin to raw types. @Named("run") -public class RunShellCommand extends InteractiveShellCommand { +public class RunShellCommand extends CordaRpcOpsShellCommand { private static final Logger logger = LoggerFactory.getLogger(RunShellCommand.class); diff --git a/tools/shell/src/main/java/net/corda/tools/shell/SshAuthInfo.java b/tools/shell/src/main/java/net/corda/tools/shell/SshAuthInfo.java new file mode 100644 index 0000000000..7530ae7ba2 --- /dev/null +++ b/tools/shell/src/main/java/net/corda/tools/shell/SshAuthInfo.java @@ -0,0 +1,8 @@ +package net.corda.tools.shell; + +import net.corda.core.messaging.RPCOps; +import org.crsh.auth.AuthInfo; + +public interface SshAuthInfo extends AuthInfo { + T getOrCreateRpcOps(Class rpcOpsClass); +} \ No newline at end of file diff --git a/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java index a022e95af1..1245efdb02 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/StartShellCommand.java @@ -13,7 +13,7 @@ import java.util.*; import static java.util.stream.Collectors.joining; @Named("start") -public class StartShellCommand extends InteractiveShellCommand { +public class StartShellCommand extends CordaRpcOpsShellCommand { private static Logger logger = LoggerFactory.getLogger(StartShellCommand.class); diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaAuthenticationPlugin.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaAuthenticationPlugin.kt index 9ed0b743c8..84c616e6e1 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaAuthenticationPlugin.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaAuthenticationPlugin.kt @@ -1,14 +1,13 @@ package net.corda.tools.shell -import net.corda.client.rpc.CordaRPCConnection -import net.corda.core.internal.messaging.InternalCordaRPCOps +import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.loggerFor import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.crsh.auth.AuthInfo import org.crsh.auth.AuthenticationPlugin import org.crsh.plugin.CRaSHPlugin -class CordaAuthenticationPlugin(private val makeRPCConnection: (username: String, credential: String) -> CordaRPCConnection) : CRaSHPlugin>(), AuthenticationPlugin { +internal class CordaAuthenticationPlugin(private val rpcOpsProducer: RPCOpsProducer) : CRaSHPlugin>(), AuthenticationPlugin { companion object { private val logger = loggerFor() @@ -24,9 +23,10 @@ class CordaAuthenticationPlugin(private val makeRPCConnection: (username: String return AuthInfo.UNSUCCESSFUL } try { - val connection = makeRPCConnection(username, credential) - val ops = connection.proxy as InternalCordaRPCOps - return CordaSSHAuthInfo(true, ops, isSsh = true, rpcConn = connection) + val cordaSSHAuthInfo = CordaSSHAuthInfo(rpcOpsProducer, username, credential, isSsh = true) + // We cannot guarantee authentication happened successfully till `RCPClient` session been established, hence doing a dummy call + cordaSSHAuthInfo.getOrCreateRpcOps(CordaRPCOps::class.java).protocolVersion + return cordaSSHAuthInfo } catch (e: ActiveMQSecurityException) { logger.warn(e.message) } catch (e: Exception) { diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaDisconnectPlugin.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaDisconnectPlugin.kt index ba33dbbc8c..98e6d151cb 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaDisconnectPlugin.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaDisconnectPlugin.kt @@ -8,6 +8,6 @@ class CordaDisconnectPlugin : CRaSHPlugin(), DisconnectPlugin override fun getImplementation() = this override fun onDisconnect(userName: String?, authInfo: AuthInfo?) { - (authInfo as? CordaSSHAuthInfo)?.rpcConn?.forceClose() + (authInfo as? CordaSSHAuthInfo)?.cleanUp() } } diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaRpcOpsShellCommand.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaRpcOpsShellCommand.kt new file mode 100644 index 0000000000..430686a437 --- /dev/null +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaRpcOpsShellCommand.kt @@ -0,0 +1,22 @@ +package net.corda.tools.shell + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.type.TypeFactory +import net.corda.core.messaging.CordaRPCOps + +internal abstract class CordaRpcOpsShellCommand : InteractiveShellCommand() { + override val rpcOpsClass: Class = CordaRPCOps::class.java + + fun objectMapper(classLoader: ClassLoader?): ObjectMapper { + val om = createYamlInputMapper() + if (classLoader != null) { + om.typeFactory = TypeFactory.defaultInstance().withClassLoader(classLoader) + } + return om + } + + private fun createYamlInputMapper(): ObjectMapper { + val rpcOps = ops() + return InteractiveShell.createYamlInputMapper(rpcOps) + } +} \ No newline at end of file diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaSSHAuthInfo.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaSSHAuthInfo.kt index 33fdae2fb9..f8d91c75fd 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaSSHAuthInfo.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/CordaSSHAuthInfo.kt @@ -1,17 +1,53 @@ package net.corda.tools.shell -import com.fasterxml.jackson.databind.ObjectMapper -import net.corda.client.rpc.CordaRPCConnection -import net.corda.core.internal.messaging.InternalCordaRPCOps -import net.corda.tools.shell.InteractiveShell.createYamlInputMapper +import com.github.benmanes.caffeine.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.RemovalListener +import com.google.common.util.concurrent.MoreExecutors +import net.corda.client.rpc.RPCConnection +import net.corda.core.internal.utilities.InvocationHandlerTemplate +import net.corda.core.messaging.RPCOps import net.corda.tools.shell.utlities.ANSIProgressRenderer -import org.crsh.auth.AuthInfo +import java.lang.reflect.Proxy -class CordaSSHAuthInfo(val successful: Boolean, val rpcOps: InternalCordaRPCOps, val ansiProgressRenderer: ANSIProgressRenderer? = null, - val isSsh: Boolean = false, val rpcConn: CordaRPCConnection? = null) : AuthInfo { - override fun isSuccessful(): Boolean = successful +internal class CordaSSHAuthInfo(private val rpcOpsProducer: RPCOpsProducer, + private val username: String, private val credential: String, val ansiProgressRenderer: ANSIProgressRenderer? = null, + val isSsh: Boolean = false) : SshAuthInfo { + override fun isSuccessful(): Boolean = true - val yamlInputMapper: ObjectMapper by lazy { - createYamlInputMapper(rpcOps) + /** + * It is necessary to have a cache to prevent creation of too many proxies for the same class. Proxy ensures that RPC connections gracefully + * closed when cache entry is eliminated + */ + private val proxiesCache = Caffeine.newBuilder() + .maximumSize(10) + .removalListener(RemovalListener, Pair>> { _, value, _ -> value?.second?.close() }) + .executor(MoreExecutors.directExecutor()) + .build(CacheLoader, Pair>> { key -> createRpcOps(key) }) + + override fun getOrCreateRpcOps(rpcOpsClass: Class): T { + @Suppress("UNCHECKED_CAST") + return proxiesCache.get(rpcOpsClass)!!.first as T + } + + fun cleanUp() { + proxiesCache.asMap().forEach { + proxiesCache.invalidate(it.key) + it.value.second.forceClose() + } + } + + private fun createRpcOps(rpcOpsClass: Class): Pair> { + val producerResult = rpcOpsProducer(username, credential, rpcOpsClass) + val anotherProxy = proxyRPCOps(producerResult.proxy, rpcOpsClass) + return anotherProxy to producerResult + } + + private fun proxyRPCOps(instance: T, rpcOpsClass: Class): T { + require(rpcOpsClass.isInterface) { "$rpcOpsClass must be an interface" } + @Suppress("UNCHECKED_CAST") + return Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), object : InvocationHandlerTemplate { + override val delegate = instance + }) as T } } \ No newline at end of file diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt index fa7519b206..7521fe03b7 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt @@ -9,11 +9,8 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator import net.corda.client.jackson.JacksonSupport import net.corda.client.jackson.StringToMethodCallParser -import net.corda.client.rpc.CordaRPCClient -import net.corda.client.rpc.CordaRPCClientConfiguration -import net.corda.client.rpc.CordaRPCConnection -import net.corda.client.rpc.GracefulReconnect import net.corda.client.rpc.PermissionException +import net.corda.client.rpc.RPCConnection import net.corda.client.rpc.internal.RPCUtils.isShutdownMethodName import net.corda.client.rpc.notUsed import net.corda.core.CordaException @@ -27,7 +24,8 @@ import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.createDirectories import net.corda.core.internal.div -import net.corda.core.internal.messaging.InternalCordaRPCOps +import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps +import net.corda.core.internal.messaging.FlowManagerRPCOps import net.corda.core.internal.packageName_ import net.corda.core.internal.rootCause import net.corda.core.internal.uncheckedCast @@ -72,11 +70,10 @@ import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.UndeclaredThrowableException import java.nio.file.Path -import java.util.* +import java.util.Properties import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutionException import java.util.concurrent.Future -import kotlin.collections.ArrayList import kotlin.concurrent.thread // TODO: Add command history. @@ -95,9 +92,9 @@ const val STANDALONE_SHELL_PERMISSION = "ALL" @Suppress("MaxLineLength") object InteractiveShell { private val log = LoggerFactory.getLogger(javaClass) - private lateinit var makeRPCConnection: (username: String, password: String) -> CordaRPCConnection - private lateinit var ops: InternalCordaRPCOps - private lateinit var rpcConn: CordaRPCConnection + private lateinit var rpcOpsProducer: RPCOpsProducer + private lateinit var startupValidation: Lazy + private var rpcConn: RPCConnection? = null private var shell: Shell? = null private var classLoader: ClassLoader? = null private lateinit var shellConfiguration: ShellConfiguration @@ -113,26 +110,7 @@ object InteractiveShell { } fun startShell(configuration: ShellConfiguration, classLoader: ClassLoader? = null, standalone: Boolean = false) { - makeRPCConnection = { username: String, password: String -> - val connection = if (standalone) { - CordaRPCClient( - configuration.hostAndPort, - configuration.ssl, - classLoader - ).start(username, password, gracefulReconnect = GracefulReconnect()) - } else { - CordaRPCClient( - hostAndPort = configuration.hostAndPort, - configuration = CordaRPCClientConfiguration.DEFAULT.copy( - maxReconnectAttempts = 1 - ), - sslConfiguration = configuration.ssl, - classLoader = classLoader - ).start(username, password) - } - rpcConn = connection - connection - } + rpcOpsProducer = DefaultRPCOpsProducer(configuration, classLoader, standalone) launchShell(configuration, standalone, classLoader) } @@ -253,7 +231,7 @@ object InteractiveShell { // Don't use the Java language plugin (we may not have tools.jar available at runtime), this // will cause any commands using JIT Java compilation to be suppressed. In CRaSH upstream that // is only the 'jmx' command. - return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(makeRPCConnection) + + return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(rpcOpsProducer) + CordaDisconnectPlugin() } } @@ -262,15 +240,20 @@ object InteractiveShell { context.refresh() this.config = config start(context) - val rpcOps = { username: String, password: String -> makeRPCConnection(username, password).proxy as InternalCordaRPCOps } - ops = makeRPCOps(rpcOps, localUserName, localUserPassword) - return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, ops, - StdoutANSIProgressRenderer), shellSafety) + startupValidation = lazy { + rpcOpsProducer(localUserName, localUserPassword, CordaRPCOps::class.java).let { + rpcConn = it + it.proxy + } + } + // For local shell create an artificial authInfo with super user permissions + val authInfo = CordaSSHAuthInfo(rpcOpsProducer, localUserName, localUserPassword, StdoutANSIProgressRenderer) + return context.getPlugin(ShellFactory::class.java).create(null, authInfo, shellSafety) } } fun nodeInfo() = try { - ops.nodeInfo() + startupValidation.value.nodeInfo() } catch (e: UndeclaredThrowableException) { throw e.cause ?: e } @@ -572,13 +555,13 @@ object InteractiveShell { @JvmStatic fun runAttachmentTrustInfoView( out: RenderPrintWriter, - rpcOps: InternalCordaRPCOps + rpcOps: AttachmentTrustInfoRPCOps ): Any { return AttachmentTrustTable(out, rpcOps.attachmentTrustInfos) } @JvmStatic - fun runDumpCheckpoints(rpcOps: InternalCordaRPCOps) { + fun runDumpCheckpoints(rpcOps: FlowManagerRPCOps) { rpcOps.dumpCheckpoints() } @@ -682,7 +665,7 @@ object InteractiveShell { latch.await() // Unsubscribe or we hold up the shutdown subscription.unsubscribe() - rpcConn.forceClose() + rpcConn?.forceClose() onExit.invoke() } catch (e: InterruptedException) { // Cancelled whilst draining flows. So let's carry on from here @@ -690,7 +673,7 @@ object InteractiveShell { display { println("...cancelled clean shutdown.") } } } catch (e: Exception) { - display { println("RPC failed: ${e.rootCause}", Color.red) } + display { println("RPC failed: ${e.rootCause}", Decoration.bold, Color.red) } } finally { InputStreamSerializer.invokeContext = null InputStreamDeserializer.closeAll() diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShellCommand.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShellCommand.kt index 5a8aa5d443..fbf14d3134 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShellCommand.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShellCommand.kt @@ -1,23 +1,24 @@ package net.corda.tools.shell -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.type.TypeFactory +import net.corda.core.messaging.RPCOps import org.crsh.command.BaseCommand import org.crsh.shell.impl.command.CRaSHSession /** * Simply extends CRaSH BaseCommand to add easy access to the RPC ops class. */ -open class InteractiveShellCommand : BaseCommand() { - fun ops() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).rpcOps - fun ansiProgressRenderer() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).ansiProgressRenderer - fun objectMapper(classLoader: ClassLoader?): ObjectMapper { - val om = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).yamlInputMapper - if (classLoader != null) { - om.typeFactory = TypeFactory.defaultInstance().withClassLoader(classLoader) - } - return om +internal abstract class InteractiveShellCommand : BaseCommand() { + + abstract val rpcOpsClass: Class + + @Suppress("UNCHECKED_CAST") + fun ops(): T { + val cRaSHSession = context.session as CRaSHSession + val authInfo = cRaSHSession.authInfo as SshAuthInfo + return authInfo.getOrCreateRpcOps(rpcOpsClass) } + fun ansiProgressRenderer() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).ansiProgressRenderer + fun isSsh() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).isSsh } diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/RPCOpsProducer.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/RPCOpsProducer.kt new file mode 100644 index 0000000000..8b60087485 --- /dev/null +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/RPCOpsProducer.kt @@ -0,0 +1,49 @@ +package net.corda.tools.shell + +import net.corda.client.rpc.CordaRPCClient +import net.corda.client.rpc.CordaRPCClientConfiguration +import net.corda.client.rpc.GracefulReconnect +import net.corda.client.rpc.RPCConnection +import net.corda.client.rpc.internal.RPCClient +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.RPCOps + +internal interface RPCOpsProducer { + /** + * Returns [RPCConnection] of underlying proxy. Proxy can be obtained at any time by calling [RPCConnection.proxy] + */ + operator fun invoke(username: String?, credential: String?, rpcOpsClass: Class) : RPCConnection +} + +internal class DefaultRPCOpsProducer(private val configuration: ShellConfiguration, private val classLoader: ClassLoader? = null, private val standalone: Boolean) : RPCOpsProducer { + + override fun invoke(username: String?, credential: String?, rpcOpsClass: Class): RPCConnection { + + return if (rpcOpsClass == CordaRPCOps::class.java) { + // For CordaRPCOps we are using CordaRPCClient + val connection = if (standalone) { + CordaRPCClient( + configuration.hostAndPort, + configuration.ssl, + classLoader + ).start(username!!, credential!!, gracefulReconnect = GracefulReconnect()) + } else { + CordaRPCClient( + hostAndPort = configuration.hostAndPort, + configuration = CordaRPCClientConfiguration.DEFAULT.copy( + maxReconnectAttempts = 1 + ), + sslConfiguration = configuration.ssl, + classLoader = classLoader + ).start(username!!, credential!!) + } + @Suppress("UNCHECKED_CAST") + connection as RPCConnection + } else { + // For other types "plain" RPCClient is used + val rpcClient = RPCClient(configuration.hostAndPort, configuration.ssl) + val connection = rpcClient.start(rpcOpsClass, username!!, credential!!) + connection + } + } +} \ No newline at end of file diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/RPCOpsWithContext.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/RPCOpsWithContext.kt deleted file mode 100644 index e5373c4bd9..0000000000 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/RPCOpsWithContext.kt +++ /dev/null @@ -1,20 +0,0 @@ -package net.corda.tools.shell - -import net.corda.core.internal.messaging.InternalCordaRPCOps -import java.lang.reflect.InvocationTargetException -import java.lang.reflect.Proxy - -fun makeRPCOps(getCordaRPCOps: (username: String, credential: String) -> InternalCordaRPCOps, username: String, credential: String): InternalCordaRPCOps { - val cordaRPCOps: InternalCordaRPCOps by lazy { - getCordaRPCOps(username, credential) - } - - return Proxy.newProxyInstance(InternalCordaRPCOps::class.java.classLoader, arrayOf(InternalCordaRPCOps::class.java)) { _, method, args -> - try { - method.invoke(cordaRPCOps, *(args ?: arrayOf())) - } catch (e: InvocationTargetException) { - // Unpack exception. - throw e.targetException - } - } as InternalCordaRPCOps -} diff --git a/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt b/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt index 279c0c7df4..99089e0079 100644 --- a/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt +++ b/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt @@ -18,7 +18,7 @@ import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.messaging.InternalCordaRPCOps +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.FlowProgressHandleImpl import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort @@ -42,7 +42,7 @@ import kotlin.test.assertFailsWith class InteractiveShellTest { lateinit var inputObjectMapper: ObjectMapper - lateinit var cordaRpcOps: InternalCordaRPCOps + lateinit var cordaRpcOps: CordaRPCOps lateinit var invocationContext: InvocationContext> lateinit var printWriter: RenderPrintWriter