Skip to content

Commit ca16596

Browse files
authored
Merge pull request #6405 from corda/merge-release/os/5.2-release/os/5.3-2024-11-28-397
CORE-20926: Merging forward updates from release/os/5.2 to release/os/5.3 - 2024-11-28
2 parents fa70322 + 9c1aca9 commit ca16596

File tree

53 files changed

+971
-391
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+971
-391
lines changed

.snyk

+32
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,36 @@ ignore:
99
OSGi so for now we have to wait for the next one.
1010
expires: 2024-07-31T00:00:00.000Z
1111
created: 2024-04-11T15:11:31.735Z
12+
SNYK-JAVA-ORGJETBRAINSKOTLIN-2393744:
13+
- '*':
14+
reason: >-
15+
Corda5 Shippable artifacts do not make use of detekt-cli, which is
16+
where this dependency originates, this is used at compile / build time
17+
only for static code analysis and not shipped in any of our releasable artifacts.
18+
expires: 2025-11-20T14:30:31.735Z
19+
created: 2024-11-20T14:30:31.735Z
20+
SNYK-JAVA-ORGECLIPSEJETTY-8186141:
21+
- '*':
22+
reason: >-
23+
This project acknowledges the presence of CVE-2024-6763 in the version of Jetty currently used by Javalin.
24+
The vulnerability affects users of Jetty's HttpURI class, which our project does not directly utilize,
25+
nor is it exposed through Javalin in our application context.
26+
The Javalin team has indicated that they do not use HttpURI, and we have verified that our dependency tree presents no indirect
27+
exposure. We will monitor Javalin updates and adopt a release upgrading Jetty to a patched version (≥12.0.12) when feasible.
28+
Given the limited risk, no immediate action is required beyond ongoing dependency monitoring.
29+
Note: there are currently no versions of Javalin released without this issue.
30+
expires: 2025-11-21T14:30:31.735Z
31+
created: 2024-11-21T12:30:31.735Z
32+
SNYK-JAVA-ORGECLIPSEJETTY-8186158:
33+
- '*':
34+
reason: >-
35+
This project acknowledges the presence of CVE-2024-6763 in the version of Jetty currently used by Javalin.
36+
The vulnerability affects users of Jetty's HttpURI class, which our project does not directly utilize,
37+
nor is it exposed through Javalin in our application context.
38+
The Javalin team has indicated that they do not use HttpURI, and we have verified that our dependency tree presents no indirect
39+
exposure. We will monitor Javalin updates and adopt a release upgrading Jetty to a patched version (≥12.0.12) when feasible.
40+
Given the limited risk, no immediate action is required beyond ongoing dependency monitoring.
41+
Note: there are currently no versions of Javalin released without this issue.
42+
expires: 2025-11-21T14:30:31.735Z
43+
created: 2024-11-21T12:30:31.735Z
1244
patch: {}

components/flow/flow-rest-resource-service-impl/src/main/kotlin/net/corda/flow/rest/impl/FlowRestExceptionConstants.kt

+2
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ object FlowRestExceptionConstants {
1414
const val INVALID_ID = "Supplied clientRequestId %s is invalid, it must conform to the pattern %s."
1515
const val CPI_NOT_FOUND = "Failed to find a CPI for ID = %s."
1616
const val FLOW_STATUS_NOT_FOUND = "Failed to find the flow status for holdingId = %s and clientRequestId = %s."
17+
const val MAX_FLOW_START_ARGS_SIZE = "The flow start payload has exceeded the max allowed payload size. Note: max payload size is set" +
18+
" to half the value of maxAllowedMessageSize."
1719

1820
}

components/flow/flow-rest-resource-service-impl/src/main/kotlin/net/corda/flow/rest/impl/v1/FlowRestResourceImpl.kt

+16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.corda.flow.rest.impl.v1
22

3+
import net.corda.avro.serialization.CordaAvroSerializationFactory
34
import net.corda.cpiinfo.read.CpiInfoReadService
45
import net.corda.data.flow.FlowKey
56
import net.corda.data.flow.output.FlowStates
@@ -41,6 +42,7 @@ import net.corda.rest.messagebus.MessageBusUtils.tryWithExceptionHandling
4142
import net.corda.rest.response.ResponseEntity
4243
import net.corda.rest.security.CURRENT_REST_CONTEXT
4344
import net.corda.schema.Schemas.Flow.FLOW_MAPPER_START
45+
import net.corda.schema.configuration.MessagingConfig
4446
import net.corda.tracing.TraceTag
4547
import net.corda.tracing.addTraceContextToRecord
4648
import net.corda.tracing.trace
@@ -71,6 +73,8 @@ class FlowRestResourceImpl @Activate constructor(
7173
private val permissionValidationService: PermissionValidationService,
7274
@Reference(service = PlatformInfoProvider::class)
7375
private val platformInfoProvider: PlatformInfoProvider,
76+
@Reference(service = CordaAvroSerializationFactory::class)
77+
private val cordaAvroSerializationFactory: CordaAvroSerializationFactory,
7478
) : FlowRestResource, PluggableRestResource<FlowRestResource>, Lifecycle {
7579

7680
private companion object {
@@ -82,12 +86,15 @@ class FlowRestResourceImpl @Activate constructor(
8286
override val targetInterface: Class<FlowRestResource> = FlowRestResource::class.java
8387
override val protocolVersion get() = platformInfoProvider.localWorkerPlatformVersion
8488

89+
private val serializer = cordaAvroSerializationFactory.createAvroSerializer<Any>()
8590
private var publisher: Publisher? = null
8691
private var fatalErrorOccurred = false
8792
private lateinit var onFatalError: () -> Unit
93+
private lateinit var messagingConfig: SmartConfig
8894

8995
override fun initialise(config: SmartConfig, onFatalError: () -> Unit) {
9096
this.onFatalError = onFatalError
97+
this.messagingConfig = config
9198
publisher?.close()
9299
publisher = publisherFactory.createPublisher(PublisherConfig("FlowRestResource"), config)
93100
}
@@ -196,6 +203,13 @@ class FlowRestResourceImpl @Activate constructor(
196203
startFlow.requestBody.escapedJson,
197204
flowContextPlatformProperties
198205
)
206+
val startEventSize = serializer.serialize(startEvent)?.size
207+
val maxAllowedMessageSize = getMaxAllowedMessageSize(messagingConfig)
208+
if (startEventSize != null && startEventSize > maxAllowedMessageSize) {
209+
log.warn(FlowRestExceptionConstants.MAX_FLOW_START_ARGS_SIZE, IllegalArgumentException("Flow start event of size " +
210+
"[$startEventSize] exceeds maxAllowedMessageSize [$maxAllowedMessageSize]"))
211+
throw InvalidInputDataException(FlowRestExceptionConstants.FATAL_ERROR)
212+
}
199213
val status = messageFactory.createStartFlowStatus(clientRequestId, vNode, flowClassName)
200214

201215
val records = listOf(
@@ -328,4 +342,6 @@ class FlowRestResourceImpl @Activate constructor(
328342
private fun getVirtualNode(holdingIdentityShortHash: String): VirtualNodeInfo {
329343
return virtualNodeInfoReadService.getByHoldingIdentityShortHashOrThrow(holdingIdentityShortHash).toAvro()
330344
}
345+
346+
private fun getMaxAllowedMessageSize(messagingConfig: SmartConfig) = messagingConfig.getLong(MessagingConfig.MAX_ALLOWED_MSG_SIZE)
331347
}

components/flow/flow-rest-resource-service-impl/src/test/kotlin/net/corda/flow/rest/impl/v1/FlowRestResourceImplTest.kt

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package net.corda.flow.rest.impl.v1
22

3+
import com.typesafe.config.ConfigValueFactory
4+
import net.corda.avro.serialization.CordaAvroSerializationFactory
5+
import net.corda.avro.serialization.CordaAvroSerializer
36
import net.corda.cpiinfo.read.CpiInfoReadService
47
import net.corda.crypto.core.SecureHashImpl
58
import net.corda.data.flow.FlowKey
@@ -10,6 +13,7 @@ import net.corda.flow.rest.factory.MessageFactory
1013
import net.corda.flow.rest.v1.FlowRestResource
1114
import net.corda.flow.rest.v1.types.request.StartFlowParameters
1215
import net.corda.flow.rest.v1.types.response.FlowStatusResponse
16+
import net.corda.libs.configuration.SmartConfig
1317
import net.corda.libs.configuration.SmartConfigImpl
1418
import net.corda.libs.packaging.core.CordappManifest
1519
import net.corda.libs.packaging.core.CpiIdentifier
@@ -34,6 +38,7 @@ import net.corda.rest.exception.ResourceNotFoundException
3438
import net.corda.rest.exception.ServiceUnavailableException
3539
import net.corda.rest.security.CURRENT_REST_CONTEXT
3640
import net.corda.rest.security.RestAuthContext
41+
import net.corda.schema.configuration.MessagingConfig
3742
import net.corda.test.util.identity.createTestHoldingIdentity
3843
import net.corda.utilities.MDC_CLIENT_ID
3944
import net.corda.virtualnode.OperationalStatus
@@ -46,6 +51,7 @@ import org.junit.jupiter.api.assertThrows
4651
import org.junit.jupiter.params.ParameterizedTest
4752
import org.junit.jupiter.params.provider.ValueSource
4853
import org.mockito.kotlin.any
54+
import org.mockito.kotlin.anyOrNull
4955
import org.mockito.kotlin.argumentCaptor
5056
import org.mockito.kotlin.atLeastOnce
5157
import org.mockito.kotlin.doThrow
@@ -64,6 +70,8 @@ class FlowRestResourceImplTest {
6470

6571
private lateinit var flowStatusLookupService: FlowStatusLookupService
6672
private lateinit var virtualNodeInfoReadService: VirtualNodeInfoReadService
73+
private lateinit var cordaAvroSerializationFactory: CordaAvroSerializationFactory
74+
private lateinit var serializer: CordaAvroSerializer<Any>
6775
private lateinit var publisherFactory: PublisherFactory
6876
private lateinit var messageFactory: MessageFactory
6977
private lateinit var cpiInfoReadService: CpiInfoReadService
@@ -131,6 +139,11 @@ class FlowRestResourceImplTest {
131139
permissionValidationService = mock()
132140
permissionValidator = mock()
133141
fatalErrorFunction = mock()
142+
cordaAvroSerializationFactory = mock()
143+
serializer = mock()
144+
145+
whenever(cordaAvroSerializationFactory.createAvroSerializer<Any>(anyOrNull())).thenReturn(serializer)
146+
whenever(serializer.serialize(anyOrNull())).thenReturn(byteArrayOf(1,2,3))
134147

135148
val cpiMetadata = getMockCPIMeta()
136149
whenever(cpiInfoReadService.get(any())).thenReturn(cpiMetadata)
@@ -169,16 +182,17 @@ class FlowRestResourceImplTest {
169182
).thenReturn(true)
170183
}
171184

172-
private fun createFlowRestResource(initialise: Boolean = true): FlowRestResource {
185+
private fun createFlowRestResource(initialise: Boolean = true, messagingConfigParam: SmartConfig = messagingConfig): FlowRestResource {
173186
return FlowRestResourceImpl(
174187
virtualNodeInfoReadService,
175188
flowStatusLookupService,
176189
publisherFactory,
177190
messageFactory,
178191
cpiInfoReadService,
179192
permissionValidationService,
180-
mock()
181-
).apply { if (initialise) (initialise(SmartConfigImpl.empty(), fatalErrorFunction)) }
193+
mock(),
194+
cordaAvroSerializationFactory
195+
).apply { if (initialise) (initialise(messagingConfigParam, fatalErrorFunction)) }
182196
}
183197

184198
@Test
@@ -350,6 +364,16 @@ class FlowRestResourceImplTest {
350364
}
351365
}
352366

367+
@Test
368+
fun `start flow fails with InvalidInputDataException when payload is too large`() {
369+
val flowRestResource = createFlowRestResource(true, SmartConfigImpl.empty().withValue(MessagingConfig.MAX_ALLOWED_MSG_SIZE,
370+
ConfigValueFactory.fromAnyRef(1)))
371+
372+
assertThrows<InvalidInputDataException> {
373+
flowRestResource.startFlow(VALID_SHORT_HASH, StartFlowParameters(clientRequestId, FLOW1, TestJsonObject()))
374+
}
375+
}
376+
353377
@Test
354378
fun `start flow event fails when not initialized`() {
355379
val flowRestResource = createFlowRestResource(false)
@@ -587,4 +611,7 @@ class FlowRestResourceImplTest {
587611
flowRestResource.startFlow(VALID_SHORT_HASH, StartFlowParameters("", FLOW1, TestJsonObject()))
588612
}
589613
}
614+
615+
private val messagingConfig = SmartConfigImpl.empty().withValue(MessagingConfig.MAX_ALLOWED_MSG_SIZE, ConfigValueFactory.fromAnyRef
616+
(10000000))
590617
}

components/flow/flow-service/src/main/kotlin/net/corda/flow/application/messaging/ExternalMessagingImpl.kt

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package net.corda.flow.application.messaging
22

3+
import net.corda.avro.serialization.CordaAvroSerializationFactory
34
import net.corda.flow.fiber.FlowFiberService
45
import net.corda.flow.fiber.FlowIORequest
56
import net.corda.sandbox.type.UsedByFlow
67
import net.corda.v5.application.messaging.ExternalMessaging
78
import net.corda.v5.base.annotations.Suspendable
9+
import net.corda.v5.base.exceptions.CordaRuntimeException
810
import net.corda.v5.serialization.SingletonSerializeAsToken
911
import org.osgi.service.component.annotations.Activate
1012
import org.osgi.service.component.annotations.Component
@@ -16,25 +18,46 @@ import java.util.UUID
1618
@Component(service = [ExternalMessaging::class, UsedByFlow::class], scope = ServiceScope.PROTOTYPE)
1719
class ExternalMessagingImpl(
1820
private val flowFiberService: FlowFiberService,
19-
private val idFactoryFunc: () -> String
21+
private val idFactoryFunc: () -> String,
22+
cordaAvroSerializationFactory: CordaAvroSerializationFactory
2023
) : ExternalMessaging, UsedByFlow, SingletonSerializeAsToken {
2124

25+
private val serializer = cordaAvroSerializationFactory.createAvroSerializer<Any>()
26+
2227
@Activate
2328
constructor(
2429
@Reference(service = FlowFiberService::class)
25-
flowFiberService: FlowFiberService
26-
) : this(flowFiberService, { UUID.randomUUID().toString() })
30+
flowFiberService: FlowFiberService,
31+
@Reference(service = CordaAvroSerializationFactory::class)
32+
cordaAvroSerializationFactory: CordaAvroSerializationFactory
33+
) : this(flowFiberService, { UUID.randomUUID().toString() }, cordaAvroSerializationFactory)
2734

2835
@Suspendable
2936
override fun send(channelName: String, message: String) {
37+
validateSize(message)
3038
send(channelName, idFactoryFunc(), message)
3139
}
3240

41+
private fun validateSize(message: String) {
42+
val bytesSize = serializer.serialize(message)?.size
43+
val maxAllowedMessageSize = maxMessageSize()
44+
if (bytesSize != null && maxAllowedMessageSize < bytesSize) {
45+
throw CordaRuntimeException(
46+
"Cannot send external messaging content as " +
47+
"it exceeds the max message size allowed. Message Size: [$bytesSize], Max Size: [$maxAllowedMessageSize}]"
48+
)
49+
}
50+
}
51+
3352
@Suspendable
3453
override fun send(channelName: String, messageId: String, message: String) {
54+
validateSize(message)
55+
3556
flowFiberService
3657
.getExecutingFiber()
3758
.suspend(FlowIORequest.SendExternalMessage(channelName, messageId, message))
3859
}
60+
61+
private fun maxMessageSize() = flowFiberService.getExecutingFiber().getExecutionContext().flowCheckpoint.maxMessageSize
3962
}
4063

components/flow/flow-service/src/main/kotlin/net/corda/flow/external/events/impl/ExternalEventManager.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package net.corda.flow.external.events.impl
22

3-
import java.time.Instant
43
import net.corda.data.flow.event.external.ExternalEvent
54
import net.corda.data.flow.event.external.ExternalEventResponse
65
import net.corda.data.flow.state.external.ExternalEventState
76
import net.corda.flow.external.events.factory.ExternalEventFactory
87
import net.corda.flow.external.events.factory.ExternalEventRecord
98
import net.corda.messaging.api.records.Record
109
import java.time.Duration
10+
import java.time.Instant
1111

1212
/**
1313
* [ExternalEventManager] encapsulates external event behaviour by creating and modifying [ExternalEventState]s.
@@ -86,4 +86,16 @@ interface ExternalEventManager {
8686
instant: Instant,
8787
retryWindow: Duration
8888
): Pair<ExternalEventState, Record<*, *>?>
89+
90+
/**
91+
* Get the external event to send for the transient error retry scenario.
92+
* Returns the event as is from the state. No additional checks required.
93+
* @param externalEventState The [ExternalEventState] to get the event from.
94+
* @param instant The current time. Used to set timestamp.
95+
* @return The external event request to resend
96+
* */
97+
fun getRetryEvent(
98+
externalEventState: ExternalEventState,
99+
instant: Instant,
100+
): Record<*, *>
89101
}

components/flow/flow-service/src/main/kotlin/net/corda/flow/external/events/impl/ExternalEventManagerImpl.kt

+7
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,13 @@ class ExternalEventManagerImpl(
174174
return externalEventState to record
175175
}
176176

177+
override fun getRetryEvent(
178+
externalEventState: ExternalEventState,
179+
instant: Instant,
180+
): Record<*, *> {
181+
return generateRecord(externalEventState, instant)
182+
}
183+
177184
private fun checkRetry(externalEventState: ExternalEventState, instant: Instant, retryWindow: Duration) {
178185
when {
179186
(externalEventState.sendTimestamp + retryWindow) >= instant -> {

0 commit comments

Comments
 (0)