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() {

Reply via email to