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>
