This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
new 0724e4b0eb6 Disable decoupled destinations by default for
WS-Addressing (#3279)
0724e4b0eb6 is described below
commit 0724e4b0eb698d52672a723fe3ccfb35aa5c9963
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Jul 3 16:29:48 2026 +0100
Disable decoupled destinations by default for WS-Addressing (#3279)
(cherry picked from commit 24a45ef468910d314cc05ff61e13885c1eae537f)
---
.../org/apache/cxf/ws/addressing/ContextUtils.java | 80 ++++++++++++--
.../ws/addressing/impl/InternalContextUtils.java | 24 ++++-
.../cxf/ws/addressing/impl/MAPAggregatorImpl.java | 115 ++++++++++++++++++---
.../impl/DecoupledDestinationProtocolTest.java | 36 ++++++-
.../cxf/ws/addressing/impl/MAPAggregatorTest.java | 6 ++
.../org/apache/cxf/ws/rm/InternalContextUtils.java | 2 +-
.../java/org/apache/cxf/ws/rm/Messages.properties | 1 +
.../src/main/java/org/apache/cxf/ws/rm/Proxy.java | 5 +
.../java/org/apache/cxf/ws/rm/RMInInterceptor.java | 5 +
.../org/apache/cxf/ws/rm/RMOutInterceptor.java | 5 +
.../org/apache/cxf/ws/rm/RMInInterceptorTest.java | 8 +-
.../cxf/systest/ws/rm/DecoupledBareTest.java | 8 ++
.../systest/ws/rm/DecoupledClientServerTest.java | 8 ++
.../addr_feature/WSAFaultToClientServerTest.java | 6 ++
.../systest/ws/addressing/DecoupledJMSTest.java | 3 +-
.../apache/cxf/systest/ws/addressing/MAPTest.java | 10 +-
.../ws/policy/AddressingInlinePolicyTest.java | 7 ++
.../ws/policy/AddressingOptionalPolicyTest.java | 7 ++
.../ws/policy/AddressingPolicy0705Test.java | 8 ++
.../systest/ws/policy/AddressingPolicyTest.java | 8 ++
20 files changed, 319 insertions(+), 33 deletions(-)
diff --git a/core/src/main/java/org/apache/cxf/ws/addressing/ContextUtils.java
b/core/src/main/java/org/apache/cxf/ws/addressing/ContextUtils.java
index 1eed4d474c8..9648bcfb8c8 100644
--- a/core/src/main/java/org/apache/cxf/ws/addressing/ContextUtils.java
+++ b/core/src/main/java/org/apache/cxf/ws/addressing/ContextUtils.java
@@ -66,6 +66,30 @@ public final class ContextUtils {
public static final String ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY =
"org.apache.cxf.ws.addressing.decoupled.allowedSchemes";
+ /**
+ * System property that enables WS-Addressing decoupled destinations
(non-anonymous
+ * wsa:ReplyTo / wsa:FaultTo). Defaults to {@code false} — decoupled
destinations
+ * are <em>disabled</em> by default to prevent SSRF. Set to {@code true}
only when
+ * your deployment legitimately requires decoupled WS-Addressing callbacks.
+ */
+ public static final String WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY =
+ "org.apache.cxf.ws.addressing.decoupled.enabled";
+
+ /**
+ * Exchange property key that higher-level protocols (e.g. WS-RM) may set
to
+ * {@code Boolean.TRUE} to pre-approve decoupled wsa:ReplyTo / wsa:FaultTo
+ * destinations for a specific exchange, bypassing the global
+ * {@value #WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY} opt-in flag.
+ *
+ * <p>The URI-scheme allowlist ({@value
#ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY} /
+ * {@link #DEFAULT_ALLOWED_DECOUPLED_DEST_SCHEMES}) is still enforced even
when
+ * this flag is set, to prevent reaching dangerous URI types (e.g. {@code
file://}).
+ *
+ * <p>Only trusted CXF modules should set this property.
+ */
+ public static final String DECOUPLED_DESTINATION_APPROVED_PROPERTY =
+ "org.apache.cxf.ws.addressing.decoupled.approved";
+
/**
* Default set of URI scheme prefixes permitted in wsa:ReplyTo /
wsa:FaultTo
* decoupled-destination addresses. Schemes that can open arbitrary
filesystem or
@@ -590,13 +614,33 @@ public final class ContextUtils {
}
/**
- * Returns {@code true} if the scheme of {@code uri} is permitted as a
- * wsa:ReplyTo / wsa:FaultTo decoupled-destination address. The permitted
set is
- * read from the system property {@value
#ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY}
- * (comma-separated prefix list) and falls back to
- * {@link #DEFAULT_ALLOWED_DECOUPLED_DEST_SCHEMES} when the property is
absent.
+ * Returns {@code true} if {@code uri} is permitted as a wsa:ReplyTo /
wsa:FaultTo
+ * decoupled-destination address for the <em>WS-Addressing</em> path.
+ *
+ * <p>WS-Addressing decoupled destinations are <em>disabled by
default</em> to
+ * prevent SSRF: any attacker who can craft a SOAP request can otherwise
force
+ * the server to open an outbound connection to any URL. Enable with
+ * {@value #WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY}{@code =true}.
+ *
+ * <p>When enabled, the URI must start with a prefix from
+ * {@value #ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY} or
+ * {@link #DEFAULT_ALLOWED_DECOUPLED_DEST_SCHEMES}.
*/
public static boolean isDecoupledDestinationAllowed(String uri) {
+ if (uri == null) {
+ return false;
+ }
+ if (!Boolean.parseBoolean(
+ System.getProperty(WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"false"))) {
+ return false;
+ }
+ return isDecoupledDestinationSchemeAllowed(uri);
+ }
+
+ /**
+ * Validates the URI scheme for {@link #isDecoupledDestinationAllowed}.
+ */
+ public static boolean isDecoupledDestinationSchemeAllowed(String uri) {
if (uri == null) {
return false;
}
@@ -647,11 +691,29 @@ public final class ContextUtils {
return null;
}
final String destinationUri = reference.getAddress().getValue();
- if (!isDecoupledDestinationAllowed(destinationUri)) {
+ boolean approved = Boolean.TRUE.equals(
+
inMessage.getExchange().get(DECOUPLED_DESTINATION_APPROVED_PROPERTY));
+ if (approved) {
+ // Higher-level protocol (e.g. WS-RM) pre-approved decoupled
addressing
+ // for this exchange; still enforce the scheme allowlist.
+ if (!isDecoupledDestinationSchemeAllowed(destinationUri)) {
+ LOG.log(Level.WARNING,
+ "Rejected pre-approved decoupled destination with
disallowed scheme: {0}. "
+ + "Configure permitted URI schemes with system
property {1}",
+ new Object[] {destinationUri,
ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY});
+ return null;
+ }
+ } else if (!isDecoupledDestinationAllowed(destinationUri)) {
LOG.log(Level.WARNING,
- "Rejected wsa:ReplyTo/FaultTo address with disallowed
scheme: {0}. "
- + "Override permitted schemes with system property {1}",
- new Object[] {destinationUri,
ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY});
+ "Rejected wsa:ReplyTo/FaultTo decoupled destination: {0}. "
+ + "Decoupled WS-Addressing is disabled by default; "
+ + "enable with system property {1}=true, "
+ + "or configure permitted URI schemes with {2}",
+ new Object[] {
+ destinationUri,
+ WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
+ ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY
+ });
return null;
}
Bus bus = inMessage.getExchange().getBus();
diff --git
a/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/InternalContextUtils.java
b/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/InternalContextUtils.java
index 1caccd8bcf0..5b74976b914 100644
---
a/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/InternalContextUtils.java
+++
b/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/InternalContextUtils.java
@@ -95,11 +95,27 @@ final class InternalContextUtils {
return null;
}
final String destinationUri = reference.getAddress().getValue();
- if (!ContextUtils.isDecoupledDestinationAllowed(destinationUri)) {
+ boolean approved = Boolean.TRUE.equals(
+
inMessage.getExchange().get(ContextUtils.DECOUPLED_DESTINATION_APPROVED_PROPERTY));
+ if (approved) {
+ if
(!ContextUtils.isDecoupledDestinationSchemeAllowed(destinationUri)) {
+ LOG.log(Level.WARNING,
+ "Rejected pre-approved decoupled destination with
disallowed scheme: {0}. "
+ + "Configure permitted URI schemes with system
property {1}",
+ new Object[] {destinationUri,
ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY});
+ return null;
+ }
+ } else if
(!ContextUtils.isDecoupledDestinationAllowed(destinationUri)) {
LOG.log(Level.WARNING,
- "Rejected wsa:ReplyTo/FaultTo address with disallowed
scheme: {0}. "
- + "Override the permitted schemes with system property
{1}",
- new Object[] {destinationUri,
ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY});
+ "Rejected wsa:ReplyTo/FaultTo decoupled destination: {0}. "
+ + "Decoupled WS-Addressing is disabled by default; "
+ + "enable with system property {1}=true, "
+ + "and/or configure permitted URI schemes with {2}",
+ new Object[] {
+ destinationUri,
+ ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
+ ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY
+ });
return null;
}
Bus bus = inMessage.getExchange().getBus();
diff --git
a/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorImpl.java
b/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorImpl.java
index f33cfe43d97..321b78d7945 100644
---
a/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorImpl.java
+++
b/rt/ws/addr/src/main/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorImpl.java
@@ -470,6 +470,9 @@ public class MAPAggregatorImpl extends MAPAggregator {
if (isOneway
|| !ContextUtils.isGenericAddress(maps.getReplyTo())) {
+ // Fail fast before the 202 partial response is sent when
the
+ // non-anonymous ReplyTo is blocked by the
decoupled-destination guard.
+ assertDecoupledReplyToAllowed(message, maps);
InternalContextUtils.rebaseResponse(maps.getReplyTo(),
maps,
message);
@@ -885,18 +888,7 @@ public class MAPAggregatorImpl extends MAPAggregator {
if (isFault
&& !ContextUtils.isGenericAddress(inMAPs.getFaultTo())) {
-
- Message m = message.getExchange().getInFaultMessage();
- if (m == null) {
- m = message;
- }
- InternalContextUtils.rebaseResponse(inMAPs.getFaultTo(),
- inMAPs,
- m);
-
- Destination destination =
InternalContextUtils.createDecoupledDestination(m.getExchange(),
-
inMAPs.getFaultTo());
- m.getExchange().setDestination(destination);
+ rebaseOrFallbackFaultTo(message, maps, inMAPs);
}
}
}
@@ -1224,6 +1216,105 @@ public class MAPAggregatorImpl extends MAPAggregator {
}
}
+ /**
+ * Throws a {@code wsa:DestinationUnreachable} SOAP fault when a
non-anonymous
+ * {@code wsa:ReplyTo} is present but decoupled WS-Addressing is disabled.
+ * Must be called before {@link InternalContextUtils#rebaseResponse} so
the fault
+ * is returned on the synchronous connection, not silently dropped after
the 202.
+ */
+ private void assertDecoupledReplyToAllowed(Message message,
AddressingProperties maps) {
+ EndpointReferenceType replyTo = maps.getReplyTo();
+ if (ContextUtils.isGenericAddress(replyTo)) {
+ return;
+ }
+ final String uri = replyTo.getAddress().getValue();
+
+ boolean approved = Boolean.TRUE.equals(
+
message.getExchange().get(ContextUtils.DECOUPLED_DESTINATION_APPROVED_PROPERTY));
+ if (approved) {
+ if (ContextUtils.isDecoupledDestinationSchemeAllowed(uri)) {
+ return;
+ }
+ final String reason = "Decoupled WS-Addressing ReplyTo (" + uri
+ + ") is not permitted by this server: URI scheme is not
allowed. "
+ + "Configure permitted schemes with system property "
+ + ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY;
+ if (isSOAP12(message)) {
+ SoapFault f = new SoapFault(reason,
Soap12.getInstance().getSender());
+ f.setSubCode(Names.DESTINATION_UNREACHABLE_QNAME);
+ throw f;
+ }
+ throw new SoapFault(reason, Names.DESTINATION_UNREACHABLE_QNAME);
+ }
+
+ if (ContextUtils.isDecoupledDestinationAllowed(uri)) {
+ return;
+ }
+ final String reason = "Decoupled WS-Addressing ReplyTo (" + uri
+ + ") is not permitted by this server. Enable with system property "
+ + ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY + "=true";
+ if (isSOAP12(message)) {
+ SoapFault f = new SoapFault(reason,
Soap12.getInstance().getSender());
+ f.setSubCode(Names.DESTINATION_UNREACHABLE_QNAME);
+ throw f;
+ }
+ throw new SoapFault(reason, Names.DESTINATION_UNREACHABLE_QNAME);
+ }
+
+ /**
+ * Rebases the response to the non-anonymous {@code wsa:FaultTo} endpoint
when
+ * decoupled WS-Addressing is permitted, or falls back to the synchronous
+ * {@code wsa:ReplyTo} with a warning when it is not.
+ */
+ private void rebaseOrFallbackFaultTo(Message message,
+ AddressingProperties maps,
+ AddressingProperties inMAPs) {
+ final String faultToUri = inMAPs.getFaultTo().getAddress().getValue();
+ boolean approved = Boolean.TRUE.equals(
+
message.getExchange().get(ContextUtils.DECOUPLED_DESTINATION_APPROVED_PROPERTY));
+ if (approved) {
+ if (ContextUtils.isDecoupledDestinationSchemeAllowed(faultToUri)) {
+ Message m = message.getExchange().getInFaultMessage();
+ if (m == null) {
+ m = message;
+ }
+ InternalContextUtils.rebaseResponse(inMAPs.getFaultTo(),
inMAPs, m);
+ Destination destination =
+
InternalContextUtils.createDecoupledDestination(m.getExchange(),
inMAPs.getFaultTo());
+ m.getExchange().setDestination(destination);
+ return;
+ }
+ LOG.log(Level.WARNING,
+ "Decoupled pre-approved FaultTo ({0}) is not permitted: URI
scheme is not allowed. "
+ + "Fault will be delivered to ReplyTo instead. Configure
permitted schemes with {1}",
+ new Object[]{faultToUri,
ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY});
+ if (inMAPs.getReplyTo() != null) {
+ maps.setTo(inMAPs.getReplyTo());
+ }
+ return;
+ }
+ if (!ContextUtils.isDecoupledDestinationAllowed(faultToUri)) {
+ LOG.log(Level.WARNING,
+ "Decoupled WS-Addressing FaultTo ({0}) is not permitted; "
+ + "fault will be delivered to ReplyTo instead. "
+ + "Enable with system property {1}=true",
+ new Object[]{faultToUri,
+ ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY});
+ if (inMAPs.getReplyTo() != null) {
+ maps.setTo(inMAPs.getReplyTo());
+ }
+ return;
+ }
+ Message m = message.getExchange().getInFaultMessage();
+ if (m == null) {
+ m = message;
+ }
+ InternalContextUtils.rebaseResponse(inMAPs.getFaultTo(), inMAPs, m);
+ Destination destination =
+ InternalContextUtils.createDecoupledDestination(m.getExchange(),
inMAPs.getFaultTo());
+ m.getExchange().setDestination(destination);
+ }
+
private boolean isSOAP12(Message message) {
if (message.getExchange().getBinding() instanceof SoapBinding) {
SoapBinding binding =
(SoapBinding)message.getExchange().getBinding();
diff --git
a/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/DecoupledDestinationProtocolTest.java
b/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/DecoupledDestinationProtocolTest.java
index 98ce7592fe8..1c472c25809 100644
---
a/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/DecoupledDestinationProtocolTest.java
+++
b/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/DecoupledDestinationProtocolTest.java
@@ -35,6 +35,7 @@ import org.apache.cxf.ws.addressing.ContextUtils;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -61,9 +62,19 @@ import static org.mockito.Mockito.when;
*/
public class DecoupledDestinationProtocolTest {
+ @Before
+ public void clearSystemPropertiesBeforeTest() {
+ clearDecoupledDestinationSystemProperties();
+ }
+
@After
- public void clearSystemProperty() {
+ public void clearSystemPropertiesAfterTest() {
+ clearDecoupledDestinationSystemProperties();
+ }
+
+ private static void clearDecoupledDestinationSystemProperties() {
System.clearProperty(ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY);
+
System.clearProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY);
}
//
-------------------------------------------------------------------------
@@ -76,6 +87,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testFileSchemeReplyToIsRejected() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
final String attackerReplyTo = "file:///etc/passwd";
Bus bus = mock(Bus.class);
@@ -106,6 +118,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testFileSchemeViaFaultToIsRejected() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
final String attackerFaultTo = "file:///var/log/app.log";
Bus bus = mock(Bus.class);
@@ -139,6 +152,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testHttpSchemeReplyToIsAllowed() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
final String replyTo = "http://callback.example.com/reply";
Bus bus = mock(Bus.class);
@@ -181,6 +195,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testJmsSchemeReplyToIsAllowed() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
final String replyTo = "jms:queue:replies?timeToLive=1000";
Bus bus = mock(Bus.class);
@@ -222,6 +237,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testFileSchemeAllowedViaSystemProperty() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
System.setProperty(
ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY,
"file://,http://");
@@ -263,6 +279,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testSystemPropertyReplacesDefaults() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
System.setProperty(
ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY, "jms:");
@@ -296,6 +313,7 @@ public class DecoupledDestinationProtocolTest {
@Test
public void testIsReplyToSchemeAllowedDefaults() {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
// Allowed by default
for (String allowed :
ContextUtils.DEFAULT_ALLOWED_DECOUPLED_DEST_SCHEMES) {
assertEquals("Expected allowed: " + allowed,
@@ -316,6 +334,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testEmptyPropertyTokensDoNotBypassAllowlist() {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
// Leading comma — produces an empty first token
System.setProperty(ContextUtils.ALLOWED_DECOUPLED_DEST_SCHEMES_PROPERTY,
",http://");
@@ -356,6 +375,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testContextUtilsFaultToFileSchemeIsRejected() throws Exception
{
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
final String attackerFaultTo = "file:///etc/passwd";
Bus bus = mock(Bus.class);
@@ -386,6 +406,7 @@ public class DecoupledDestinationProtocolTest {
*/
@Test
public void testContextUtilsFaultToHttpSchemeIsAllowed() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
final String faultTo = "http://callback.example.com/fault";
Bus bus = mock(Bus.class);
@@ -417,6 +438,19 @@ public class DecoupledDestinationProtocolTest {
assertSame(mockConduit, backChannel);
}
+ /**
+ * With no system properties set, ALL decoupled destinations must be
rejected
+ * regardless of scheme. This is the new secure-by-default behaviour.
+ */
+ @Test
+ public void testDecoupledAddressingDisabledByDefault() {
+ assertEquals(false,
ContextUtils.isDecoupledDestinationAllowed("http://callback.example.com/"));
+ assertEquals(false,
ContextUtils.isDecoupledDestinationAllowed("https://callback.example.com/"));
+ assertEquals(false,
ContextUtils.isDecoupledDestinationAllowed("jms:queue:replies"));
+ assertEquals(false,
ContextUtils.isDecoupledDestinationAllowed("file:///etc/passwd"));
+ assertEquals(false, ContextUtils.isDecoupledDestinationAllowed(null));
+ }
+
//
-------------------------------------------------------------------------
// Helpers
//
-------------------------------------------------------------------------
diff --git
a/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorTest.java
b/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorTest.java
index 8118ed1c7ee..0fdd917d1d8 100644
---
a/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorTest.java
+++
b/rt/ws/addr/src/test/java/org/apache/cxf/ws/addressing/impl/MAPAggregatorTest.java
@@ -103,11 +103,17 @@ public class MAPAggregatorTest {
@Before
public void setUp() {
+ // Tests in this class exercise both anonymous and non-anonymous
(decoupled)
+ // WS-Addressing destinations. Enable decoupled so the legitimate
decoupled
+ // tests are not rejected by the SSRF guard.
+ System.setProperty(
+ ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY, "true");
aggregator = new MAPAggregatorImpl();
}
@After
public void tearDown() {
+
System.clearProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY);
expectedMAPs = null;
expectedTo = null;
expectedReplyTo = null;
diff --git
a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/InternalContextUtils.java
b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/InternalContextUtils.java
index e6d2220b657..889a5246e38 100644
--- a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/InternalContextUtils.java
+++ b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/InternalContextUtils.java
@@ -79,7 +79,7 @@ final class InternalContextUtils {
return null;
}
final String destinationUri = reference.getAddress().getValue();
- if (!ContextUtils.isDecoupledDestinationAllowed(destinationUri)) {
+ if
(!ContextUtils.isDecoupledDestinationSchemeAllowed(destinationUri)) {
return null;
}
Bus bus = inMessage.getExchange().getBus();
diff --git a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Messages.properties
b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Messages.properties
index 82661b57351..10b9acdd19f 100644
--- a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Messages.properties
+++ b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Messages.properties
@@ -24,6 +24,7 @@ RM_INVOCATION_FAILED = Invocation of RM protocol operation
failed.
SEQ_TERMINATION_FAILURE = Failed to terminate sequence {0}.
STANDALONE_ANON_ACKS_NOT_SUPPORTED = It is not possible to send out-of-band
acknowledgments to the anonymous address.\nAn acknowledgement will be
piggybacked on the next response.
+WSRM_ACKS_TO_SCHEME_NOT_PERMITTED = WS-RM standalone acknowledgement to "{0}"
was blocked: the URI scheme is not in the permitted list. Configure permitted
schemes with system property
org.apache.cxf.ws.addressing.decoupled.allowedSchemes.
STANDALONE_CLOSE_SEQUENCE_NO_TARGET_MSG = No target address to send
out-of-band close sequence to.
STANDALONE_CLOSE_SEQUENCE_ANON_TARGET_MSG = It is not possible to send an
out-of-band close sequence to the anonymous address.
STANDALONE_ANON_TERMINATE_SEQUENCE_MSG = It is not possible to send
out-of-band terminate sequence message to the anonymous address.\nIt will be
piggybacked on the next response.
diff --git a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Proxy.java
b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Proxy.java
index 44f19941048..3c6f54f4ae7 100644
--- a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Proxy.java
+++ b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/Proxy.java
@@ -49,6 +49,7 @@ import org.apache.cxf.service.model.OperationInfo;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.workqueue.SynchronousExecutor;
import org.apache.cxf.ws.addressing.AttributedURIType;
+import org.apache.cxf.ws.addressing.ContextUtils;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.apache.cxf.ws.addressing.MAPAggregator;
import org.apache.cxf.ws.addressing.RelatesToType;
@@ -92,6 +93,10 @@ public class Proxy {
LOG.log(Level.WARNING, "STANDALONE_ANON_ACKS_NOT_SUPPORTED");
return;
}
+ if (!ContextUtils.isDecoupledDestinationSchemeAllowed(address)) {
+ LOG.log(Level.WARNING, "WSRM_ACKS_TO_SCHEME_NOT_PERMITTED",
address);
+ return;
+ }
RMConstants constants = protocol.getConstants();
OperationInfo oi =
reliableEndpoint.getEndpoint(protocol).getEndpointInfo()
.getService().getInterface().getOperation(constants.getSequenceAckOperationName());
diff --git a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMInInterceptor.java
b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMInInterceptor.java
index 6438c473fd5..630962d87f5 100644
--- a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMInInterceptor.java
+++ b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMInInterceptor.java
@@ -161,6 +161,11 @@ public class RMInInterceptor extends
AbstractRMInterceptor<Message> {
return;
}
+ // Pre-approve decoupled wsa:ReplyTo/wsa:FaultTo destinations for this
exchange.
+ // MAPAggregator performs the early fail-fast check; this exchange
flag ensures
+ // the later decoupled backchannel creation path honors RM callbacks
as well.
+
message.getExchange().put(ContextUtils.DECOUPLED_DESTINATION_APPROVED_PROPERTY,
Boolean.TRUE);
+
Object originalRequestor =
message.get(RMMessageConstants.ORIGINAL_REQUESTOR_ROLE);
if (null != originalRequestor) {
LOG.fine("Restoring original requestor role to: " +
originalRequestor);
diff --git a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMOutInterceptor.java
b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMOutInterceptor.java
index 90f082016df..c576c7ac5e8 100644
--- a/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMOutInterceptor.java
+++ b/rt/ws/rm/src/main/java/org/apache/cxf/ws/rm/RMOutInterceptor.java
@@ -65,6 +65,11 @@ public class RMOutInterceptor extends
AbstractRMInterceptor<Message> {
return;
}
+ // Pre-approve decoupled wsa:ReplyTo/wsa:FaultTo destinations for this
exchange.
+ // WS-RM requires decoupled addressing for async acknowledgements and
responses;
+ // the ContextUtils guard is bypassed at exchange level rather than
globally.
+
msg.getExchange().put(ContextUtils.DECOUPLED_DESTINATION_APPROVED_PROPERTY,
Boolean.TRUE);
+
RMConfiguration config = getManager().getEffectiveConfiguration(msg);
String wsaNamespace = config.getAddressingNamespace();
String rmNamespace = config.getRMNamespace();
diff --git
a/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/RMInInterceptorTest.java
b/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/RMInInterceptorTest.java
index 00970c3f593..42c93f45d5c 100644
--- a/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/RMInInterceptorTest.java
+++ b/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/RMInInterceptorTest.java
@@ -100,7 +100,7 @@ public class RMInInterceptorTest {
interceptor.handle(message);
verify(rme, times(1)).receivedControlMessage();
- verify(message, times(2)).getExchange();
+ verify(message, times(3)).getExchange();
}
@Test
@@ -118,7 +118,7 @@ public class RMInInterceptorTest {
interceptor.handle(message);
verify(rme, times(1)).receivedControlMessage();
verify(proxy, times(1)).createSequenceResponse(csr,
ProtocolVariation.RM10WSA200408);
- verify(message, times(2)).getExchange();
+ verify(message, times(3)).getExchange();
}
@Test
@@ -140,7 +140,7 @@ public class RMInInterceptorTest {
interceptor.handle(message);
verify(rme, times(1)).receivedControlMessage();
verify(interceptor, times(1)).processAcknowledgments(rme, rmps,
ProtocolVariation.RM10WSA200408);
- verify(message, times(2)).getExchange();
+ verify(message, times(3)).getExchange();
}
@Test
@@ -159,7 +159,7 @@ public class RMInInterceptorTest {
when(message.get(AssertionInfoMap.class)).thenReturn(null);
interceptor.handle(message);
- verify(message, times(2)).getExchange();
+ verify(message, times(3)).getExchange();
verify(rme, times(1)).receivedControlMessage();
}
diff --git
a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledBareTest.java
b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledBareTest.java
index def4a8b5f7a..54e9101686a 100644
---
a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledBareTest.java
+++
b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledBareTest.java
@@ -33,11 +33,13 @@ import org.apache.cxf.message.Message;
import org.apache.cxf.systest.ws.util.ConnectionHelper;
import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+import org.apache.cxf.ws.addressing.ContextUtils;
import org.apache.hello_world_soap_http.DocLitBare;
import org.apache.hello_world_soap_http.DocLitBareGreeterImpl;
import org.apache.hello_world_soap_http.SOAPServiceAddressingDocLitBare;
import org.apache.hello_world_soap_http.types.BareDocumentResponse;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -81,9 +83,15 @@ public class DecoupledBareTest extends
AbstractBusClientServerTestBase {
@BeforeClass
public static void startServers() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
assertTrue("server did not launch correctly",
launchServer(DecoupledBareServer.class, true));
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+
System.clearProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY);
+ }
+
@Test
public void testDecoupled() throws Exception {
SpringBusFactory bf = new SpringBusFactory();
diff --git
a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledClientServerTest.java
b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledClientServerTest.java
index 631a8f663e5..2e8793fa727 100644
---
a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledClientServerTest.java
+++
b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/DecoupledClientServerTest.java
@@ -37,7 +37,9 @@ import org.apache.cxf.message.Message;
import org.apache.cxf.systest.ws.util.ConnectionHelper;
import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+import org.apache.cxf.ws.addressing.ContextUtils;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -89,10 +91,16 @@ public class DecoupledClientServerTest extends
AbstractBusClientServerTestBase {
@BeforeClass
public static void startServers() throws Exception {
+
System.setProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY,
"true");
assertTrue("server did not launch correctly",
launchServer(DecoupledServer.class, true));
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+
System.clearProperty(ContextUtils.WS_ADDRESSING_DECOUPLED_ENABLED_PROPERTY);
+ }
+
@Test
public void testDecoupled() throws Exception {
SpringBusFactory bf = new SpringBusFactory();
diff --git
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addr_feature/WSAFaultToClientServerTest.java
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addr_feature/WSAFaultToClientServerTest.java
index cb373c4a0d6..81822161ebd 100644
---
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addr_feature/WSAFaultToClientServerTest.java
+++
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addr_feature/WSAFaultToClientServerTest.java
@@ -38,6 +38,7 @@ import org.apache.cxf.ws.addressing.JAXWSAConstants;
import org.apache.hello_world_soap_http.Greeter;
import org.apache.hello_world_soap_http.SOAPService;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -55,9 +56,14 @@ public class WSAFaultToClientServerTest extends
AbstractWSATestBase {
@BeforeClass
public static void startServers() throws Exception {
+ System.setProperty("org.apache.cxf.ws.addressing.decoupled.enabled",
"true");
assertTrue("FaultTo server did not launch correctly",
launchServer(FaultToEndpointServer.class, true));
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+ System.clearProperty("org.apache.cxf.ws.addressing.decoupled.enabled");
+ }
@Test
public void testOneWayFaultTo() throws Exception {
diff --git
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/DecoupledJMSTest.java
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/DecoupledJMSTest.java
index c127db6db77..a45c1d9a8d0 100644
---
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/DecoupledJMSTest.java
+++
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/DecoupledJMSTest.java
@@ -77,7 +77,8 @@ public class DecoupledJMSTest extends MAPTestBase {
props,
null));
assertTrue("server did not launch correctly",
- launchServer(Server.class, null,
+ launchServer(Server.class,
+
Map.of("org.apache.cxf.ws.addressing.decoupled.enabled", "true"),
new String[] {ADDRESS,
GreeterImpl.class.getName()}, false));
}
diff --git
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/MAPTest.java
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/MAPTest.java
index 5843e092197..3c3711ae632 100644
---
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/MAPTest.java
+++
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/addressing/MAPTest.java
@@ -21,11 +21,12 @@ package org.apache.cxf.systest.ws.addressing;
import jakarta.jws.WebService;
-
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import static org.junit.Assert.assertTrue;
+
/**
* Tests the addition of WS-Addressing Message Addressing Properties.
*/
@@ -54,10 +55,17 @@ public class MAPTest extends MAPTestBase {
@BeforeClass
public static void startServers() throws Exception {
+ System.setProperty("org.apache.cxf.ws.addressing.decoupled.enabled",
"true");
assertTrue("server did not launch correctly",
launchServer(Server.class, null,
new String[] {ADDRESS,
GreeterImpl.class.getName()}, true));
}
+
+ @AfterClass
+ public static void cleanup() throws Exception {
+ System.clearProperty("org.apache.cxf.ws.addressing.decoupled.enabled");
+ }
+
@WebService(serviceName = "SOAPServiceAddressing",
portName = "SoapPort",
endpointInterface = "org.apache.hello_world_soap_http.Greeter",
diff --git
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingInlinePolicyTest.java
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingInlinePolicyTest.java
index 251195e9df4..2d6e8f4ad16 100644
---
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingInlinePolicyTest.java
+++
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingInlinePolicyTest.java
@@ -40,6 +40,7 @@ import org.apache.cxf.testutil.common.TestUtil;
import org.apache.cxf.ws.policy.PolicyInInterceptor;
import org.apache.cxf.ws.policy.PolicyOutInterceptor;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -92,9 +93,15 @@ public class AddressingInlinePolicyTest extends
AbstractBusClientServerTestBase
@BeforeClass
public static void startServers() throws Exception {
TestUtil.getNewPortNumber("decoupled");
+ System.setProperty("org.apache.cxf.ws.addressing.decoupled.enabled",
"true");
assertTrue("server did not launch correctly",
launchServer(AddressingInlinePolicyServer.class, true));
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+ System.clearProperty("org.apache.cxf.ws.addressing.decoupled.enabled");
+ }
+
@Test
public void testUsingAddressing() throws Exception {
diff --git
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingOptionalPolicyTest.java
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingOptionalPolicyTest.java
index 66e2963acc6..e6a97ea24e7 100644
---
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingOptionalPolicyTest.java
+++
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingOptionalPolicyTest.java
@@ -44,6 +44,7 @@ import org.apache.cxf.ws.policy.PolicyEngine;
import org.apache.cxf.ws.policy.selector.MinimalAlternativeSelector;
import org.apache.cxf.ws.rm.RMUtils;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -113,12 +114,18 @@ public class AddressingOptionalPolicyTest extends
AbstractBusClientServerTestBas
@BeforeClass
public static void startServers() throws Exception {
TestUtil.getNewPortNumber("decoupled");
+ System.setProperty("org.apache.cxf.ws.addressing.decoupled.enabled",
"true");
PolicyTestHelper.updatePolicyRef("addr-optional-external.xml",
":9020", ":" + PORT);
System.setProperty("temp.location", TEMPDIR);
assertTrue("server did not launch correctly",
launchServer(AddressingOptionalPolicyServer.class, null,
new
String[] {TEMPDIR}));
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+ System.clearProperty("temp.location");
+ System.clearProperty("org.apache.cxf.ws.addressing.decoupled.enabled");
+ }
@Test
public void testUsingAddressing() throws Exception {
diff --git
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicy0705Test.java
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicy0705Test.java
index 6ad442b6fe5..373ba19476d 100644
---
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicy0705Test.java
+++
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicy0705Test.java
@@ -38,6 +38,7 @@ import
org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
import org.apache.cxf.testutil.common.TestUtil;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -111,11 +112,18 @@ public class AddressingPolicy0705Test extends
AbstractBusClientServerTestBase {
TestUtil.getNewPortNumber("decoupled");
PolicyTestHelper.updatePolicyRef("addr-external0705.xml", ":9020", ":"
+ PORT);
System.setProperty("temp.location", TEMPDIR);
+ System.setProperty("org.apache.cxf.ws.addressing.decoupled.enabled",
"true");
assertTrue("server did not launch correctly",
launchServer(AddressingPolicy0705Server.class, null,
new
String[] {TEMPDIR}, true));
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+ System.clearProperty("temp.location");
+ System.clearProperty("org.apache.cxf.ws.addressing.decoupled.enabled");
+ }
+
@Test
public void testUsingAddressing() throws Exception {
SpringBusFactory bf = new SpringBusFactory();
diff --git
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicyTest.java
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicyTest.java
index 15fa2e48e53..1d6bd6241d2 100644
---
a/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicyTest.java
+++
b/systests/ws-specs/src/test/java/org/apache/cxf/systest/ws/policy/AddressingPolicyTest.java
@@ -38,6 +38,7 @@ import
org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
import org.apache.cxf.testutil.common.TestUtil;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -109,11 +110,18 @@ public class AddressingPolicyTest extends
AbstractBusClientServerTestBase {
TestUtil.getNewPortNumber("decoupled");
PolicyTestHelper.updatePolicyRef("addr-external.xml", ":9020", ":" +
PORT);
System.setProperty("temp.location", TEMPDIR);
+ System.setProperty("org.apache.cxf.ws.addressing.decoupled.enabled",
"true");
assertTrue("server did not launch correctly",
launchServer(AddressingPolicyServer.class, null,
new
String[] {TEMPDIR}, true));
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+ System.clearProperty("temp.location");
+ System.clearProperty("org.apache.cxf.ws.addressing.decoupled.enabled");
+ }
+
@Test
public void testUsingAddressing() throws Exception {
SpringBusFactory bf = new SpringBusFactory();