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-rampart.git


The following commit(s) were added to refs/heads/master by this push:
     new eb824bd7 RAMPART-460: allow a custom UsernameToken validator via 
RampartConfig
eb824bd7 is described below

commit eb824bd739254d488865f027062ae22e21a1549c
Author: Robert Lazarski <[email protected]>
AuthorDate: Tue Oct 13 02:24:29 2026 -1000

    RAMPART-460: allow a custom UsernameToken validator via RampartConfig
    
    Add an optional usernameTokenValidatorClass to RampartConfig, registered in
    place of RampartUsernameTokenValidator when set. This is for password stores
    that can only accept or reject a username/password pair, such as LDAP, and 
so
    cannot return the stored password to the callback. Without the setting,
    behaviour is unchanged.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/rampart/RampartMessageData.java     | 36 ++++++++++
 .../policy/builders/RampartConfigBuilder.java      |  6 ++
 .../apache/rampart/policy/model/RampartConfig.java | 26 +++++++
 .../resources/org/apache/rampart/errors.properties |  2 +
 .../org/apache/rampart/RampartMessageDataTest.java | 48 +++++++++++++
 .../policy/builders/RampartConfigBuilderTest.java  | 82 ++++++++++++++++++++++
 src/site/resources/rampart-config.xsd              |  1 +
 src/site/xdoc/rampartconfig-guide.xml              | 10 +++
 8 files changed, 211 insertions(+)

diff --git 
a/modules/rampart-core/src/main/java/org/apache/rampart/RampartMessageData.java 
b/modules/rampart-core/src/main/java/org/apache/rampart/RampartMessageData.java
index 97e6af78..a649d0d9 100644
--- 
a/modules/rampart-core/src/main/java/org/apache/rampart/RampartMessageData.java
+++ 
b/modules/rampart-core/src/main/java/org/apache/rampart/RampartMessageData.java
@@ -61,6 +61,7 @@ import org.apache.wss4j.dom.handler.WSHandlerResult;
 import org.apache.wss4j.dom.message.WSSecHeader;
 import org.apache.wss4j.dom.message.token.SecurityContextToken;
 import org.apache.wss4j.dom.util.WSSecurityUtil;
+import org.apache.wss4j.dom.validate.Validator;
 import org.apache.wss4j.dom.engine.WSSecurityEngine;
 import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
 
@@ -231,6 +232,34 @@ public class RampartMessageData {
         }
     }
 
+    /**
+     * Resolves the WSS4J Validator for inbound UsernameTokens: the
+     * <code>usernameTokenValidatorClass</code> from the RampartConfig when 
set, otherwise
+     * {@link RampartUsernameTokenValidator}. A custom class is for password 
stores that can
+     * only accept or reject a username/password pair and so cannot meet the 
password
+     * callback contract (RAMPART-460). A configured class that cannot be 
loaded fails the
+     * message rather than falling back, so a typo surfaces instead of being 
masked.
+     */
+    static Class<?> getUsernameTokenValidatorClass(RampartConfig rampartConfig,
+            ClassLoader classLoader) throws RampartException {
+        String className = rampartConfig == null ? null
+                : rampartConfig.getUsernameTokenValidatorClass();
+        if (className == null || className.isEmpty()) {
+            return RampartUsernameTokenValidator.class;
+        }
+        Class<?> validatorClass;
+        try {
+            validatorClass = Loader.loadClass(classLoader, className);
+        } catch (ClassNotFoundException e) {
+            throw new RampartException("cannotLoadUsernameTokenValidatorClass",
+                    new String[]{className}, e);
+        }
+        if (!Validator.class.isAssignableFrom(validatorClass)) {
+            throw new RampartException("notAUsernameTokenValidator", new 
String[]{className});
+        }
+        return validatorClass;
+    }
+
     public RampartMessageData(MessageContext msgCtx, boolean sender) throws 
RampartException {
         
         this.msgContext = msgCtx;
@@ -496,6 +525,13 @@ public class RampartMessageData {
             if (axisService != null) { 
                 this.customClassLoader = axisService.getClassLoader(); 
             } 
+
+            // Replaces the default registered above only once the policy (and 
any
+            // RampartConfigCallbackHandler update) and the service class 
loader are known.
+            if (this.policyData != null) {
+                this.config.setValidator(WSConstants.USERNAME_TOKEN, 
getUsernameTokenValidatorClass(
+                        this.policyData.getRampartConfig(), 
this.customClassLoader));
+            }
             
             if(this.sender && this.policyData != null) {
                 this.secHeader = new WSSecHeader(this.document);
diff --git 
a/modules/rampart-core/src/main/java/org/apache/rampart/policy/builders/RampartConfigBuilder.java
 
b/modules/rampart-core/src/main/java/org/apache/rampart/policy/builders/RampartConfigBuilder.java
index 6b6efdee..2a641ef6 100644
--- 
a/modules/rampart-core/src/main/java/org/apache/rampart/policy/builders/RampartConfigBuilder.java
+++ 
b/modules/rampart-core/src/main/java/org/apache/rampart/policy/builders/RampartConfigBuilder.java
@@ -77,6 +77,12 @@ public class RampartConfigBuilder implements 
AssertionBuilder<OMElement> {
         if (childElement != null) {
             
rampartConfig.setRampartConfigCbClass(childElement.getText().trim());
         }
+
+        childElement = element.getFirstChildWithName(new QName(
+                RampartConfig.NS, 
RampartConfig.USERNAME_TOKEN_VALIDATOR_CLASS_LN));
+        if (childElement != null) {
+            
rampartConfig.setUsernameTokenValidatorClass(childElement.getText().trim());
+        }
                       
         // handle ssl config   
                childElement = element.getFirstChildWithName(new QName(
diff --git 
a/modules/rampart-core/src/main/java/org/apache/rampart/policy/model/RampartConfig.java
 
b/modules/rampart-core/src/main/java/org/apache/rampart/policy/model/RampartConfig.java
index db97b23d..a9cbcdda 100644
--- 
a/modules/rampart-core/src/main/java/org/apache/rampart/policy/model/RampartConfig.java
+++ 
b/modules/rampart-core/src/main/java/org/apache/rampart/policy/model/RampartConfig.java
@@ -35,6 +35,7 @@ import javax.xml.stream.XMLStreamWriter;
  *  &lt;ramp:encryptionUser&gt;bob&lt;/ramp:encryptionUser&gt;
  *  
&lt;ramp:passwordCallbackClass&gt;org.apache.axis2.security.PWCallback&lt;/ramp:passwordCallbackClass&gt;
  *  
&lt;ramp:policyValidatorCbClass&gt;org.apache.axis2.security.ramp:PolicyValidatorCallbackHandler&lt;/ramp:policyValidatorCbClass&gt;
+ *  
&lt;ramp:usernameTokenValidatorClass&gt;org.example.MyUsernameTokenValidator&lt;/ramp:usernameTokenValidatorClass&gt;
  *  &lt;ramp:timestampPrecisionInMs&gt;true&lt;/timestampPrecisionInMs&gt;
  *  &lt;ramp:timestampTTL&gt;300&lt;/ramp:timestampTTL&gt;
  *  &lt;ramp:timestampMaxSkew&gt;0&lt;/ramp:timestampMaxSkew&gt;
@@ -91,6 +92,8 @@ public class RampartConfig implements Assertion {
     
     public final static String RAMPART_CONFIG_CB_CLASS_LN = 
"rampartConfigCallbackClass";
 
+    public final static String USERNAME_TOKEN_VALIDATOR_CLASS_LN = 
"usernameTokenValidatorClass";
+
     public final static String SIG_CRYPTO_LN = "signatureCrypto";
 
     public final static String ENCR_CRYPTO_LN = "encryptionCrypto";
@@ -152,6 +155,8 @@ public class RampartConfig implements Assertion {
     
     private String rampartConfigCbClass;
 
+    private String usernameTokenValidatorClass;
+
     private CryptoConfig sigCryptoConfig;
 
     private CryptoConfig encrCryptoConfig;
@@ -292,6 +297,21 @@ public class RampartConfig implements Assertion {
         this.policyValidatorCbClass = policyValidatorCbClass;
     }
 
+    public String getUsernameTokenValidatorClass() {
+        return usernameTokenValidatorClass;
+    }
+
+    /**
+     * @param usernameTokenValidatorClass a WSS4J
+     *            <code>org.apache.wss4j.dom.validate.Validator</code> 
registered for
+     *            inbound UsernameTokens in place of
+     *            <code>RampartUsernameTokenValidator</code>. It then owns the 
whole
+     *            password check.
+     */
+    public void setUsernameTokenValidatorClass(String 
usernameTokenValidatorClass) {
+        this.usernameTokenValidatorClass = usernameTokenValidatorClass;
+    }
+
     public String getRampartConfigCbClass() {
         return rampartConfigCbClass;
     }
@@ -394,6 +414,12 @@ public class RampartConfig implements Assertion {
             writer.writeEndElement();
         }
 
+        if (getUsernameTokenValidatorClass() != null && 
!getUsernameTokenValidatorClass().isEmpty()) {
+            writer.writeStartElement(NS, USERNAME_TOKEN_VALIDATOR_CLASS_LN);
+            writer.writeCharacters(getUsernameTokenValidatorClass());
+            writer.writeEndElement();
+        }
+
         writer.writeStartElement(NS, MUST_UNDERSTAND_SECURITY_HEADER_LN);
         
writer.writeCharacters(Boolean.toString(isMustUnderstandSecurityHeader()));
         writer.writeEndElement();
diff --git 
a/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties 
b/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties
index b4df6ed2..f1d39e14 100644
--- 
a/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties
+++ 
b/modules/rampart-core/src/main/resources/org/apache/rampart/errors.properties
@@ -28,6 +28,8 @@ cannotLoadPolicyValidatorCbClass = Cannot load custom policy 
validator callback
 cannotCreatePolicyValidatorCallbackInstance = Cannot create custom policy 
validator callback class instance : \"{0}\"
 cannotLoadRampartConfigCallbackClass = Cannot load rampart config callback 
class : \"{0}\"
 cannotCreateRampartConfigCallbackInstance = Cannot create rampart config 
callback class instance : \"{0}\"
+cannotLoadUsernameTokenValidatorClass = Cannot load UsernameToken validator 
class : \"{0}\"
+notAUsernameTokenValidator = UsernameToken validator class does not implement 
org.apache.wss4j.dom.validate.Validator : \"{0}\"
 missingEncryptionUser=Encryption user not specified (The context is created by 
the initiating party)
 rampartConfigMissing = Rampart policy configuration missing
 
diff --git 
a/modules/rampart-core/src/test/java/org/apache/rampart/RampartMessageDataTest.java
 
b/modules/rampart-core/src/test/java/org/apache/rampart/RampartMessageDataTest.java
index fcb493c4..f5aa6ffa 100644
--- 
a/modules/rampart-core/src/test/java/org/apache/rampart/RampartMessageDataTest.java
+++ 
b/modules/rampart-core/src/test/java/org/apache/rampart/RampartMessageDataTest.java
@@ -25,6 +25,9 @@ import java.util.concurrent.CountDownLatch;
 import junit.framework.TestCase;
 
 import org.apache.axis2.description.AxisService;
+import org.apache.rampart.handler.RampartUsernameTokenValidator;
+import org.apache.rampart.policy.model.RampartConfig;
+import org.apache.wss4j.dom.validate.UsernameTokenValidator;
 
 /**
  * RAMPART-427: adding the locked CLIENT_SIDE marker parameter to a service 
must be
@@ -80,4 +83,49 @@ public class RampartMessageDataTest extends TestCase {
         assertNotNull("CLIENT_SIDE parameter should be present",
                 service.getParameter(RampartMessageData.PARAM_CLIENT_SIDE));
     }
+
+    /** A stand-in for a validator that delegates to an external password 
store. */
+    public static class ExternalUsernameTokenValidator extends 
UsernameTokenValidator {
+    }
+
+    /** RAMPART-460: without the setting the default validator stays in place. 
*/
+    public void testDefaultUsernameTokenValidator() throws Exception {
+        assertEquals(RampartUsernameTokenValidator.class,
+                RampartMessageData.getUsernameTokenValidatorClass(null, null));
+        assertEquals(RampartUsernameTokenValidator.class,
+                RampartMessageData.getUsernameTokenValidatorClass(new 
RampartConfig(), null));
+    }
+
+    /** RAMPART-460: a configured validator replaces the default. */
+    public void testConfiguredUsernameTokenValidator() throws Exception {
+        RampartConfig config = new RampartConfig();
+        
config.setUsernameTokenValidatorClass(ExternalUsernameTokenValidator.class.getName());
+        assertEquals(ExternalUsernameTokenValidator.class,
+                RampartMessageData.getUsernameTokenValidatorClass(config, 
null));
+        assertEquals(ExternalUsernameTokenValidator.class,
+                RampartMessageData.getUsernameTokenValidatorClass(config,
+                        getClass().getClassLoader()));
+    }
+
+    /** A configured class that is missing must fail, not fall back to the 
default. */
+    public void testMissingUsernameTokenValidatorFails() {
+        RampartConfig config = new RampartConfig();
+        config.setUsernameTokenValidatorClass("org.example.NoSuchValidator");
+        try {
+            RampartMessageData.getUsernameTokenValidatorClass(config, null);
+            fail("a missing validator class must be rejected");
+        } catch (RampartException expected) {
+        }
+    }
+
+    /** A configured class that is not a WSS4J Validator must fail up front. */
+    public void testNonValidatorClassFails() {
+        RampartConfig config = new RampartConfig();
+        config.setUsernameTokenValidatorClass(String.class.getName());
+        try {
+            RampartMessageData.getUsernameTokenValidatorClass(config, null);
+            fail("a class that is not a Validator must be rejected");
+        } catch (RampartException expected) {
+        }
+    }
 }
diff --git 
a/modules/rampart-core/src/test/java/org/apache/rampart/policy/builders/RampartConfigBuilderTest.java
 
b/modules/rampart-core/src/test/java/org/apache/rampart/policy/builders/RampartConfigBuilderTest.java
new file mode 100644
index 00000000..ff5c0859
--- /dev/null
+++ 
b/modules/rampart-core/src/test/java/org/apache/rampart/policy/builders/RampartConfigBuilderTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.rampart.policy.builders;
+
+import java.io.StringWriter;
+
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamWriter;
+
+import junit.framework.TestCase;
+
+import org.apache.axiom.om.OMElement;
+import org.apache.axiom.om.util.AXIOMUtil;
+import org.apache.rampart.policy.model.RampartConfig;
+
+public class RampartConfigBuilderTest extends TestCase {
+
+    private static final String VALIDATOR = 
"org.example.LdapUsernameTokenValidator";
+
+    private static RampartConfig build(String body) throws Exception {
+        OMElement element = AXIOMUtil.stringToOM(
+                "<ramp:RampartConfig xmlns:ramp=\"" + RampartConfig.NS + "\">" 
+ body
+                        + "</ramp:RampartConfig>");
+        return (RampartConfig) new RampartConfigBuilder().build(element, null);
+    }
+
+    /** RAMPART-460: usernameTokenValidatorClass is parsed and written back 
out. */
+    public void testUsernameTokenValidatorClassRoundTrip() throws Exception {
+        RampartConfig config = build("<ramp:usernameTokenValidatorClass>\n    
" + VALIDATOR
+                + "\n</ramp:usernameTokenValidatorClass>");
+        assertEquals(VALIDATOR, config.getUsernameTokenValidatorClass());
+
+        StringWriter out = new StringWriter();
+        XMLStreamWriter writer = 
XMLOutputFactory.newInstance().createXMLStreamWriter(out);
+        config.serialize(writer);
+        writer.flush();
+
+        assertEquals(VALIDATOR, 
build(stripRoot(out.toString())).getUsernameTokenValidatorClass());
+    }
+
+    public void testUsernameTokenValidatorClassAbsentByDefault() throws 
Exception {
+        assertNull(build("").getUsernameTokenValidatorClass());
+    }
+
+    /** An empty element means the default validator and is not written back 
out. */
+    public void testEmptyUsernameTokenValidatorClassNotSerialized() throws 
Exception {
+        RampartConfig config = build("<ramp:usernameTokenValidatorClass/>");
+        assertEquals("", config.getUsernameTokenValidatorClass());
+
+        StringWriter out = new StringWriter();
+        XMLStreamWriter writer = 
XMLOutputFactory.newInstance().createXMLStreamWriter(out);
+        config.serialize(writer);
+        writer.flush();
+
+        
assertFalse(out.toString().contains(RampartConfig.USERNAME_TOKEN_VALIDATOR_CLASS_LN));
+    }
+
+    private static String stripRoot(String xml) throws Exception {
+        OMElement root = AXIOMUtil.stringToOM(xml);
+        StringBuilder sb = new StringBuilder();
+        for (java.util.Iterator<OMElement> it = root.getChildElements(); 
it.hasNext();) {
+            sb.append(it.next().toString());
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/site/resources/rampart-config.xsd 
b/src/site/resources/rampart-config.xsd
index b9e6632b..2b5fc57e 100644
--- a/src/site/resources/rampart-config.xsd
+++ b/src/site/resources/rampart-config.xsd
@@ -16,6 +16,7 @@
                                        <xs:element 
name="passwordCallbackClass" type="xs:string" minOccurs="0"/>
                                        <xs:element 
name="policyValidatorCbClass" type="xs:string" minOccurs="0"/>
                                        <xs:element 
name="rampartConfigCallbackClass" type="xs:string" minOccurs="0"/>
+                                       <xs:element 
name="usernameTokenValidatorClass" type="xs:string" minOccurs="0"/>
                                        <xs:element name="sslConfig" 
type="ramp:ssl" minOccurs="0"/>
                                        <xs:element name="kerberosConfig" 
type="ramp:ssl" minOccurs="0"/>
                                        <xs:element name="signatureCrypto" 
type="ramp:crypto" minOccurs="0"/>
diff --git a/src/site/xdoc/rampartconfig-guide.xml 
b/src/site/xdoc/rampartconfig-guide.xml
index 107d8efa..e6bcd93f 100644
--- a/src/site/xdoc/rampartconfig-guide.xml
+++ b/src/site/xdoc/rampartconfig-guide.xml
@@ -28,6 +28,16 @@
 &lt;policyValidatorCbClass&gt;
     org.apache.axis2.security.CustomPolicyValidater
 &lt;/policyValidatorCbClass&gt;
+</pre></td></tr>
+    <tr class="a"><td>usernameTokenValidatorClass</td><td>WSS4J Validator 
(org.apache.wss4j.dom.validate.Validator)
+        used for inbound UsernameTokens instead of the default 
RampartUsernameTokenValidator. Use it when the
+        password store can only accept or reject a username/password pair, for 
example LDAP, and cannot return
+        the stored password to the passwordCallbackClass. The class then 
performs the whole password check.
+        </td><td>
+<pre>
+&lt;usernameTokenValidatorClass&gt;
+    org.example.LdapUsernameTokenValidator
+&lt;/usernameTokenValidatorClass&gt;
 </pre></td></tr>
     <tr class="a"><td>signatureCrypto</td><td>properties to needed perform 
signature, such as crypto
         provider, keystore and its password</td><td>

Reply via email to