This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 056750c5c3198f10b3de1fd758f03d7233743b36
Author: Robert Lazarski <[email protected]>
AuthorDate: Wed Sep 2 18:26:59 2026 -1000

    Require opt-in to select a service from the message body
    
    The Dispatch phase runs after Security, and DispatchPhase installs only the
    phases that follow it, so a service bound from the SOAP body namespace is 
bound
    after Security has run against no service and Security is never revisited: a
    request naming no service in its URI could reach a service whose engaged
    security modules never ran for it. allowContentBasedServiceDispatch gates 
both
    body-namespace dispatchers and defaults to false; dispatch by request URI,
    SOAPAction and WS-Addressing binds before Security and is untouched.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../axis2/engine/ServiceDispatchingTest.java       |  4 ++
 modules/kernel/conf/axis2.xml                      |  9 +++
 .../org/apache/axis2/deployment/axis2_default.xml  |  9 +++
 .../dispatchers/ContentBasedDispatchPolicy.java    | 73 ++++++++++++++++++++++
 .../SOAPMessageBodyBasedDispatcher.java            | 10 +++
 .../SOAPMessageBodyBasedServiceDispatcher.java     | 10 +++
 .../SOAPMessageBodyBasedServiceDispatcherTest.java | 31 +++++++++
 modules/webapp/conf/axis2.xml                      |  9 +++
 8 files changed, 155 insertions(+)

diff --git 
a/modules/integration/test/org/apache/axis2/engine/ServiceDispatchingTest.java 
b/modules/integration/test/org/apache/axis2/engine/ServiceDispatchingTest.java
index 8d649922f0..c5eed6c1eb 100644
--- 
a/modules/integration/test/org/apache/axis2/engine/ServiceDispatchingTest.java
+++ 
b/modules/integration/test/org/apache/axis2/engine/ServiceDispatchingTest.java
@@ -23,6 +23,7 @@ import org.apache.axiom.om.OMElement;
 import org.apache.axis2.client.ServiceClient;
 import org.apache.axis2.dispatchers.RequestURIBasedDispatcher;
 import org.apache.axis2.dispatchers.RequestURIBasedOperationDispatcher;
+import org.apache.axis2.dispatchers.ContentBasedDispatchPolicy;
 import org.apache.axis2.dispatchers.SOAPMessageBodyBasedDispatcher;
 import org.apache.axis2.integration.LocalTestCase;
 import org.apache.axis2.integration.TestingUtils;
@@ -61,6 +62,9 @@ public class ServiceDispatchingTest extends LocalTestCase {
        DispatchPhase dp = new DispatchPhase();
        dp.addHandler(new SOAPMessageBodyBasedDispatcher());
        serverConfig.getInFlowPhases().set(1, dp);
+       // Selecting the service from the body is opt-in; see 
ContentBasedDispatchPolicy.
+       serverConfig.addParameter(
+                       
ContentBasedDispatchPolicy.ALLOW_CONTENT_BASED_DISPATCH, "true");
        
        ServiceClient sender = getClient(Echo.SERVICE_NAME, 
Echo.ECHO_OM_ELEMENT_OP_NAME);
        OMElement payload = 
TestingUtils.createDummyOMElement(sender.getOptions().getTo().getAddress());
diff --git a/modules/kernel/conf/axis2.xml b/modules/kernel/conf/axis2.xml
index 68614835b0..2266400d00 100644
--- a/modules/kernel/conf/axis2.xml
+++ b/modules/kernel/conf/axis2.xml
@@ -130,6 +130,15 @@
     <!--is set, then Axis2 tries to get the first exception and set its 
message as the faultreason/Reason.-->
     <parameter name="DrillDownToRootCauseForFaultReason">false</parameter>
 
+    <!--Whether a service may be selected from the namespace of the SOAP body's
+        first element. FALSE by default: the Dispatch phase runs after the 
Security
+        phase, so a service bound from message content is bound after the 
handlers
+        that would have authenticated the request for it have already run 
against no
+        service. Dispatch by request URI, SOAPAction and WS-Addressing is 
unaffected.
+        Turn this on only if services are genuinely addressed by body 
namespace, and
+        not together with per-service security modules.-->
+    <parameter name="allowContentBasedServiceDispatch">false</parameter>
+
     <parameter name="userName"></parameter>
     <parameter name="password"></parameter>
 
diff --git a/modules/kernel/src/org/apache/axis2/deployment/axis2_default.xml 
b/modules/kernel/src/org/apache/axis2/deployment/axis2_default.xml
index 1c9ff9d744..1baf5e8a42 100644
--- a/modules/kernel/src/org/apache/axis2/deployment/axis2_default.xml
+++ b/modules/kernel/src/org/apache/axis2/deployment/axis2_default.xml
@@ -44,6 +44,15 @@
         every configurator uses when no axis2.xml is found, so any value here 
is
         a published default credential. Leaving these blank disables the 
console
         until an operator sets both.-->
+    <!--Whether a service may be selected from the namespace of the SOAP body's
+        first element. FALSE by default: the Dispatch phase runs after the 
Security
+        phase, so a service bound from message content is bound after the 
handlers
+        that would have authenticated the request for it have already run 
against no
+        service. Dispatch by request URI, SOAPAction and WS-Addressing is 
unaffected.
+        Turn this on only if services are genuinely addressed by body 
namespace, and
+        not together with per-service security modules.-->
+    <parameter name="allowContentBasedServiceDispatch">false</parameter>
+
     <parameter name="userName"></parameter>
     <parameter name="password"></parameter>
 
diff --git 
a/modules/kernel/src/org/apache/axis2/dispatchers/ContentBasedDispatchPolicy.java
 
b/modules/kernel/src/org/apache/axis2/dispatchers/ContentBasedDispatchPolicy.java
new file mode 100644
index 0000000000..5778d20d9c
--- /dev/null
+++ 
b/modules/kernel/src/org/apache/axis2/dispatchers/ContentBasedDispatchPolicy.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axis2.dispatchers;
+
+import org.apache.axis2.context.MessageContext;
+import org.apache.axis2.description.Parameter;
+import org.apache.axis2.util.JavaUtils;
+
+/**
+ * Whether a service may be selected from the content of the message body.
+ * <p>
+ * The default inflow phase order is Transport, Addressing, <b>Security</b>,
+ * PreDispatch, <b>Dispatch</b>. A service bound in the Dispatch phase is 
therefore
+ * bound after the Security phase has already run, and
+ * {@link org.apache.axis2.engine.DispatchPhase} installs only the phases that 
follow
+ * Dispatch, so Security is never revisited. Per-service module handlers -- the
+ * WS-Security ones among them -- live in that global Security phase.
+ * <p>
+ * The body-namespace dispatchers select a service from a string the caller 
supplies,
+ * so with a request URI that names no service they let the caller pick the 
service
+ * only after the phase that would have authenticated the request for it has 
run
+ * against no service at all. That is why this is off unless an operator asks 
for it:
+ * {@code allowContentBasedServiceDispatch}, set on the AxisConfiguration in
+ * axis2.xml, defaults to {@code false}.
+ * <p>
+ * Dispatch by request URI, SOAPAction and WS-Addressing is unaffected; those 
bind
+ * the service before the Security phase runs. Only deployments that genuinely
+ * address services by body namespace need to turn this on, and they should 
not rely
+ * on per-service security modules while it is on.
+ */
+public final class ContentBasedDispatchPolicy {
+
+    /**
+     * Name of the AxisConfiguration parameter, default {@code false}.
+     */
+    public static final String ALLOW_CONTENT_BASED_DISPATCH = 
"allowContentBasedServiceDispatch";
+
+    private ContentBasedDispatchPolicy() {
+    }
+
+    /**
+     * @param messageContext the message being dispatched; may be {@code null}
+     * @return whether a service may be selected from the message body
+     */
+    public static boolean isAllowed(MessageContext messageContext) {
+        if (messageContext == null) {
+            return false;
+        }
+        // The service is null on this path by definition, so this resolves 
against
+        // the AxisConfiguration -- see MessageContext.getParameter.
+        Parameter parameter = 
messageContext.getParameter(ALLOW_CONTENT_BASED_DISPATCH);
+        if (parameter == null || parameter.getValue() == null) {
+            return false;
+        }
+        return JavaUtils.isTrueExplicitly(parameter.getValue());
+    }
+}
diff --git 
a/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedDispatcher.java
 
b/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedDispatcher.java
index ae3cc021c1..44262edde5 100644
--- 
a/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedDispatcher.java
+++ 
b/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedDispatcher.java
@@ -80,6 +80,16 @@ public class SOAPMessageBodyBasedDispatcher extends 
AbstractDispatcher {
      * @see 
org.apache.axis2.engine.AbstractDispatcher#findService(org.apache.axis2.context.MessageContext)
      */
     public AxisService findService(MessageContext messageContext) throws 
AxisFault {
+        if (!ContentBasedDispatchPolicy.isAllowed(messageContext)) {
+            if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) {
+                log.debug(messageContext.getLogIDString()
+                        + " Not dispatching on the SOAP body: "
+                        + 
ContentBasedDispatchPolicy.ALLOW_CONTENT_BASED_DISPATCH
+                        + " is false");
+            }
+            return null;
+        }
+
         String serviceName;
 
         String localPart = 
messageContext.getEnvelope().getSOAPBodyFirstElementLocalName();
diff --git 
a/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcher.java
 
b/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcher.java
index 7770954ba7..17b2b4de72 100644
--- 
a/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcher.java
+++ 
b/modules/kernel/src/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcher.java
@@ -36,6 +36,16 @@ public class SOAPMessageBodyBasedServiceDispatcher extends 
AbstractServiceDispat
     private static final Log log = 
LogFactory.getLog(SOAPMessageBodyBasedServiceDispatcher.class);
 
     public AxisService findService(MessageContext messageContext) throws 
AxisFault {
+        if (!ContentBasedDispatchPolicy.isAllowed(messageContext)) {
+            if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) {
+                log.debug(messageContext.getLogIDString()
+                        + " Not dispatching on the SOAP body: "
+                        + 
ContentBasedDispatchPolicy.ALLOW_CONTENT_BASED_DISPATCH
+                        + " is false");
+            }
+            return null;
+        }
+
         String serviceName = null;
         String localPart = 
messageContext.getEnvelope().getSOAPBodyFirstElementLocalName();
 
diff --git 
a/modules/kernel/test/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcherTest.java
 
b/modules/kernel/test/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcherTest.java
index e41a14c263..62c2e9623b 100644
--- 
a/modules/kernel/test/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcherTest.java
+++ 
b/modules/kernel/test/org/apache/axis2/dispatchers/SOAPMessageBodyBasedServiceDispatcherTest.java
@@ -32,6 +32,21 @@ import org.apache.axis2.engine.AxisConfiguration;
 
 public class SOAPMessageBodyBasedServiceDispatcherTest extends TestCase {
 
+    /**
+     * Body-namespace dispatch is off unless asked for: the Dispatch phase 
runs after
+     * the Security phase, so a service chosen from message content is chosen 
after
+     * the handlers that would have authenticated the request for it have run 
against
+     * no service. See {@link 
org.apache.axis2.dispatchers.ContentBasedDispatchPolicy}.
+     */
+    public void testFindServiceDeniedByDefault() throws AxisFault {
+        MessageContext messageContext = messageContextNaming("Service2");
+
+        new SOAPMessageBodyBasedServiceDispatcher().invoke(messageContext);
+
+        assertNull("a service must not be selected from the body by default",
+                messageContext.getAxisService());
+    }
+
     public void testFindService() throws AxisFault {
         MessageContext messageContext;
         AxisService as1 = new AxisService("Service1");
@@ -49,6 +64,7 @@ public class SOAPMessageBodyBasedServiceDispatcherTest 
extends TestCase {
                                                                          
"pfx"));
         messageContext.setEnvelope(se);
 
+        
ac.addParameter(ContentBasedDispatchPolicy.ALLOW_CONTENT_BASED_DISPATCH, 
"true");
 
         SOAPMessageBodyBasedServiceDispatcher ruisd = new 
SOAPMessageBodyBasedServiceDispatcher();
         ruisd.invoke(messageContext);
@@ -56,4 +72,19 @@ public class SOAPMessageBodyBasedServiceDispatcherTest 
extends TestCase {
         assertEquals(as2, messageContext.getAxisService());
     }
 
+    /** A message whose body first element namespace addresses the named 
service. */
+    private MessageContext messageContextNaming(String serviceName) throws 
AxisFault {
+        ConfigurationContext cc = 
ConfigurationContextFactory.createEmptyConfigurationContext();
+        AxisConfiguration ac = cc.getAxisConfiguration();
+        ac.addService(new AxisService(serviceName));
+        MessageContext messageContext = cc.createMessageContext();
+
+        SOAPEnvelope se = 
OMAbstractFactory.getSOAP11Factory().createSOAPEnvelope();
+        SOAPBody sb = OMAbstractFactory.getSOAP11Factory().createSOAPBody(se);
+        
sb.addChild(OMAbstractFactory.getSOAP11Factory().createOMElement("operation2",
+                "http://127.0.0.1:8080/axis2/services/"; + serviceName, "pfx"));
+        messageContext.setEnvelope(se);
+        return messageContext;
+    }
+
 }
diff --git a/modules/webapp/conf/axis2.xml b/modules/webapp/conf/axis2.xml
index 23929d6223..167acc086b 100644
--- a/modules/webapp/conf/axis2.xml
+++ b/modules/webapp/conf/axis2.xml
@@ -137,6 +137,15 @@
          operator sets both. The admin-console integration tests inject their
          own values into the unpacked WAR (see systests/webapp-tests/pom.xml);
          do not restore literals here to make a test pass. -->
+    <!--Whether a service may be selected from the namespace of the SOAP body's
+        first element. FALSE by default: the Dispatch phase runs after the 
Security
+        phase, so a service bound from message content is bound after the 
handlers
+        that would have authenticated the request for it have already run 
against no
+        service. Dispatch by request URI, SOAPAction and WS-Addressing is 
unaffected.
+        Turn this on only if services are genuinely addressed by body 
namespace, and
+        not together with per-service security modules.-->
+    <parameter name="allowContentBasedServiceDispatch">false</parameter>
+
     <parameter name="userName"></parameter>
     <parameter name="password"></parameter>
 

Reply via email to