This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new 3e5c0cf1097d [backport camel-4.22.x] CAMEL-26340: camel-lra - validate
coordinator URL returned when starting a new saga against allowed coordinators
(#28599)
3e5c0cf1097d is described below
commit 3e5c0cf1097dca36e6dfe2484fc807087e638e54
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Oct 28 09:13:38 2026 +0100
[backport camel-4.22.x] CAMEL-26340: camel-lra - validate coordinator URL
returned when starting a new saga against allowed coordinators (#28599)
Backport of #28560.
Validate the LRA URL returned by the coordinator when starting a new saga
against the allowed coordinators, as getSaga() already does.
Co-Authored-By: Claude <[email protected]>
---
.../apache/camel/service/lra/LRASagaService.java | 23 ++++++-
.../camel/service/lra/LRASagaServiceTest.java | 75 ++++++++++++++++++++++
2 files changed, 97 insertions(+), 1 deletion(-)
diff --git
a/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaService.java
b/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaService.java
index a07081000ad5..9b0e5ac2a8da 100644
---
a/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaService.java
+++
b/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaService.java
@@ -18,6 +18,7 @@ package org.apache.camel.service.lra;
import java.net.MalformedURLException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
@@ -90,7 +91,19 @@ public class LRASagaService extends ServiceSupport
implements StaticService, Cam
@Override
public CompletableFuture<CamelSagaCoordinator> newSaga(Exchange exchange) {
return client.newLRA(exchange)
- .thenApply(url -> new LRASagaCoordinator(LRASagaService.this,
url));
+ .thenApply(url -> {
+ URI lra = toUri(url);
+ if (lra == null || !isHostedByAllowedCoordinator(lra)) {
+ // The LRA created at the coordinator is deliberately
not cancelled through the returned URL,
+ // as doing so would connect to the unapproved
destination.
+ throw new IllegalArgumentException(
+ "The Long-Running-Action " + url + " returned
by the LRA coordinator is not hosted by"
+ + " the LRA
coordinator configured on the LRA saga service."
+ + " If it is a
trusted coordinator, add its URL to"
+ + "
allowedCoordinatorUrls.");
+ }
+ return new LRASagaCoordinator(LRASagaService.this, url);
+ });
}
/**
@@ -199,6 +212,14 @@ public class LRASagaService extends ServiceSupport
implements StaticService, Cam
}
}
+ private static URI toUri(URL url) {
+ try {
+ return url != null ? url.toURI() : null;
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
private static int port(URL url) {
return url.getPort() != -1 ? url.getPort() : url.getDefaultPort();
}
diff --git
a/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaServiceTest.java
b/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaServiceTest.java
index c65387a522b3..0b9f92be7337 100644
---
a/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaServiceTest.java
+++
b/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaServiceTest.java
@@ -16,10 +16,13 @@
*/
package org.apache.camel.service.lra;
+import java.net.URI;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
import org.apache.camel.Exchange;
+import org.apache.camel.saga.CamelSagaCoordinator;
import org.apache.camel.test.junit6.CamelTestSupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
@@ -75,6 +78,78 @@ public class LRASagaServiceTest extends CamelTestSupport {
Mockito.verify(client).newLRA(exchange);
}
+ @DisplayName("Tests whether newSaga(Exchange) accepts an LRA hosted by the
configured coordinator")
+ @Test
+ void testNewSagaAcceptsAllowedCoordinatorUrl() throws Exception {
+ LRAClient client = Mockito.mock(LRAClient.class);
+ LRASagaService sagaService = startedService(client, null);
+
+ Exchange exchange = Mockito.mock(Exchange.class);
+ URL validUrl =
URI.create("http://lra-coordinator:8080/lra-coordinator/0_1").toURL();
+
Mockito.when(client.newLRA(exchange)).thenReturn(CompletableFuture.completedFuture(validUrl));
+
+ CamelSagaCoordinator coordinator =
sagaService.newSaga(exchange).join();
+ Assertions.assertNotNull(coordinator);
+ Assertions.assertEquals(validUrl.toString(), coordinator.getId());
+ }
+
+ @DisplayName("Tests whether newSaga(Exchange) rejects an LRA hosted by an
unapproved host")
+ @Test
+ void testNewSagaRejectsUnapprovedHost() throws Exception {
+ LRAClient client = Mockito.mock(LRAClient.class);
+ LRASagaService sagaService = startedService(client, null);
+
+ Exchange exchange = Mockito.mock(Exchange.class);
+ URL unapprovedUrl =
URI.create("http://elsewhere:8080/lra-coordinator/0_1").toURL();
+
Mockito.when(client.newLRA(exchange)).thenReturn(CompletableFuture.completedFuture(unapprovedUrl));
+
+ CompletionException ex =
Assertions.assertThrows(CompletionException.class, () ->
sagaService.newSaga(exchange).join());
+ Assertions.assertInstanceOf(IllegalArgumentException.class,
ex.getCause());
+ Assertions.assertTrue(ex.getCause().getMessage().contains("is not
hosted by the LRA coordinator configured"));
+ }
+
+ @DisplayName("Tests whether newSaga(Exchange) rejects an LRA path outside
the coordinator context path")
+ @Test
+ void testNewSagaRejectsPathOutsideCoordinatorContextPath() throws
Exception {
+ LRAClient client = Mockito.mock(LRAClient.class);
+ LRASagaService sagaService = startedService(client, null);
+
+ Exchange exchange = Mockito.mock(Exchange.class);
+ URL unapprovedPathUrl =
URI.create("http://lra-coordinator:8080/other/0_1").toURL();
+
Mockito.when(client.newLRA(exchange)).thenReturn(CompletableFuture.completedFuture(unapprovedPathUrl));
+
+ CompletionException ex =
Assertions.assertThrows(CompletionException.class, () ->
sagaService.newSaga(exchange).join());
+ Assertions.assertInstanceOf(IllegalArgumentException.class,
ex.getCause());
+ }
+
+ @DisplayName("Tests whether newSaga(Exchange) accepts an LRA matching
allowedCoordinatorUrls")
+ @Test
+ void testNewSagaAcceptsAlternativeAllowedCoordinatorUrl() throws Exception
{
+ LRAClient client = Mockito.mock(LRAClient.class);
+ LRASagaService sagaService = startedService(client,
"http://proxy-coordinator:8080/lra-coordinator");
+
+ Exchange exchange = Mockito.mock(Exchange.class);
+ URL proxyUrl =
URI.create("http://proxy-coordinator:8080/lra-coordinator/0_1").toURL();
+
Mockito.when(client.newLRA(exchange)).thenReturn(CompletableFuture.completedFuture(proxyUrl));
+
+ CamelSagaCoordinator coordinator =
sagaService.newSaga(exchange).join();
+ Assertions.assertNotNull(coordinator);
+ Assertions.assertEquals(proxyUrl.toString(), coordinator.getId());
+ }
+
+ private LRASagaService startedService(LRAClient client, String
allowedCoordinatorUrls) throws Exception {
+ LRASagaService sagaService = new ClientMockingLRASagaService(client);
+ sagaService.setCoordinatorUrl("http://lra-coordinator:8080");
+ sagaService.setCoordinatorContextPath("/lra-coordinator");
+ sagaService.setLocalParticipantUrl("http://participant:8080");
+ if (allowedCoordinatorUrls != null) {
+ sagaService.setAllowedCoordinatorUrls(allowedCoordinatorUrls);
+ }
+ sagaService.setCamelContext(this.context());
+ sagaService.doStart();
+ return sagaService;
+ }
+
@DisplayName("Tests whether doStart() fails when the callback secret is
set but blank")
@Test
void testBlankCallbackSecretFailsToStart() {