This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch 2_4_x-fixes
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
The following commit(s) were added to refs/heads/2_4_x-fixes by this push:
new f1af85fa2 Properly wire UsernameToken + RSA15 flags for the streaming
layer (#717)
f1af85fa2 is described below
commit f1af85fa2b5170d24bbb702c6df75f50db725fb5
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon Sep 21 09:29:11 2026 +0100
Properly wire UsernameToken + RSA15 flags for the streaming layer (#717)
---
src/site/asciidoc/streaming.adoc | 77 ----------
.../wss4j/policy/stax/enforcer/PolicyEnforcer.java | 124 ++++++++++++++---
.../policy/stax/enforcer/PolicyInputProcessor.java | 16 ++-
.../policy/stax/test/ScopedEngineDefaultsTest.java | 155 +++++++++++++++++++++
.../testdata/wsdl/mixedPasswordPolicies.wsdl | 98 +++++++++++++
5 files changed, 369 insertions(+), 101 deletions(-)
diff --git a/src/site/asciidoc/streaming.adoc b/src/site/asciidoc/streaming.adoc
deleted file mode 100644
index 3c4112cfc..000000000
--- a/src/site/asciidoc/streaming.adoc
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-// 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.
-//
-
-== Streaming (StAX) WS-Security support in Apache WSS4J™ 2.0.0
-
-=== Overview of new features
-
-WSS4J 2.0.0 introduces a streaming (StAX-based) WS-Security implementation to
-complement the existing DOM-based implementation. The DOM-based implementation
-is quite performant and flexible, but suffers from having to read the entire
-XML tree into memory. For large SOAP requests this can have a detrimental
-impact on performance. In addition, for web services stacks such as Apache CXF
-which are streaming-based, it carries an additional performance penalty of
-having to explicitly convert the request stream to a DOM Element.
-
-The new StAX-based WS-Security implementation does not read the request into
-memory, and hence uses far less memory for large requests. It is also more
-performant in certain circumstances. The StAX-based code offers largely the
-same functionality as that available as part of the DOM code, and is
-configured in mostly the same way (via configuration tags that are shared
-between both stacks). It does not offer the low-level API available in the DOM
-code to individually construct various WS-Security tokens, but instead must be
-used by specifying various actions to perform.
-
-As of the time of writing, Apache CXF is the only web services stack to
-integrate the new WS-Security streaming functionality. To switch to use the
-streaming code for the manual "Action" based approach, simply change the
-outbound and inbound interceptors as follows:
-
- * "org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor" to
-"org.apache.cxf.ws.security.wss4j.WSS4JStaxOutInterceptor".
- * "org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor" to
-"org.apache.cxf.ws.security.wss4j.WSS4JStaxInInterceptor".
-
-For the WS-SecurityPolicy based approach of configuring WS-Security, simply
-set the JAX-WS property SecurityConstants.ENABLE_STREAMING_SECURITY
-("ws-security.enable.streaming") to "true".
-
-=== Limitations of the streaming WS-Security implementation
-
-The new streaming implementation in WSS4J 2.0.0 meets the vast majority of the
-most common use-cases. However, it does not support everything that the DOM
-implementation supports. The limitations are:
-
- * XPath evaluation is not supported apart from certain simple expressions.
-XPath evaluations are used with WS-SecurityPolicy RequiredElements,
-SignedElements, (Content)EncryptedElements. XPath expressions that point
-directly to the element are supported, e.g. /soap:Envelope/soap:Header/wsa:To.
-See WSS-445.
- * WS-SecurityPolicy "Strict" Layout validation is not enforced. This includes
-enforcing whether a Timestamp is first or last. See WSS-444.
- * A SymmetricBinding policy with a ProtectTokens assertion is not supported.
-See WSS-456.
- * The combination of EncryptBeforeSigning + EncryptSignature policies are not
-supported. See WSS-464.
- * Deriving keys from Username Tokens (Endorsing Username Tokens) are not
-supported.
- * Endorsing tokens don't work with Symmetric + Asymmetric binding on the
-client side, unless the endorsing token is a SAML or IssuedToken.
- * Derived Endorsing Tokens are not supported on the client side.
-
diff --git
a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java
b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java
index 5e48f7281..c3280c74f 100644
---
a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java
+++
b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyEnforcer.java
@@ -104,8 +104,10 @@ import
org.apache.wss4j.policy.stax.assertionStates.X509TokenAssertionState;
import org.apache.wss4j.stax.ext.WSSConstants;
import org.apache.wss4j.stax.securityEvent.NoSecuritySecurityEvent;
import org.apache.wss4j.stax.securityEvent.OperationSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.UsernameTokenSecurityEvent;
import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.stax.securityEvent.AlgorithmSuiteSecurityEvent;
import org.apache.xml.security.stax.securityEvent.SecurityEvent;
import org.apache.xml.security.stax.securityEvent.SecurityEventConstants;
import org.apache.xml.security.stax.securityEvent.SecurityEventListener;
@@ -156,6 +158,8 @@ public class PolicyEnforcer implements
SecurityEventListener {
private boolean faultOccurred;
private final PolicyAsserter policyAsserter;
private boolean soap12;
+ private boolean usernameTokenNoPasswordRelaxedForAllOperations;
+ private boolean rsa15KeyTransportRelaxedForAllOperations;
public PolicyEnforcer(List<OperationPolicy> operationPolicies, String
soapAction, boolean initiator,
String actorOrRole, int attachmentCount,
PolicyAsserter policyAsserter, boolean soap12
@@ -892,6 +896,11 @@ public class PolicyEnforcer implements
SecurityEventListener {
new IllegalArgumentException(message));
}
+ //The operation is known now, so an engine default relaxed on the
strength of
+ //the whole policy set can be reimposed where this operation's
policy does not
+ //ask for it.
+ verifyRelaxedEngineDefaults();
+
try {
Iterator<SecurityEvent> securityEventIterator =
securityEventQueue.descendingIterator();
while (securityEventIterator.hasNext()) {
@@ -923,18 +932,25 @@ public class PolicyEnforcer implements
SecurityEventListener {
}
/**
- * Returns true if any configured operation policy contains a
UsernameToken assertion
- * that explicitly allows password-less tokens (sp:NoPassword). Used to
decide whether
- * the engine's hardened default (rejecting password-less UsernameTokens)
may be
- * relaxed in policy mode.
+ * Returns true if the policy that governs this message contains a
UsernameToken
+ * assertion that explicitly allows password-less tokens (sp:NoPassword).
Used to
+ * decide whether the engine's hardened default (rejecting password-less
+ * UsernameTokens) may be relaxed in policy mode.
+ *
+ * This is asked while the security header is being read, so the operation
is known
+ * only where SOAPAction already selected its policy. Failing that the
question can be
+ * answered across the configured operations and no more, which relaxes
the default for
+ * a message that may turn out to invoke an operation whose own policy
does not allow
+ * it. Record that, so that verifyRelaxedEngineDefaults() can reimpose the
default once
+ * the operation is known.
*/
public boolean isUsernameTokenNoPasswordAllowedByPolicy() {
+ if (effectivePolicy != null) {
+ return allowsUsernameTokenNoPassword(effectivePolicy);
+ }
for (OperationPolicy operationPolicy : operationPolicies) {
- Policy policy = operationPolicy.getPolicy();
- if (policy != null && policyContains(policy,
- assertion -> assertion instanceof UsernameToken
- && ((UsernameToken)assertion).getPasswordType()
- == UsernameToken.PasswordType.NoPassword)) {
+ if (allowsUsernameTokenNoPassword(operationPolicy)) {
+ usernameTokenNoPasswordRelaxedForAllOperations = true;
return true;
}
}
@@ -942,28 +958,92 @@ public class PolicyEnforcer implements
SecurityEventListener {
}
/**
- * Returns true if any configured operation policy contains an
AlgorithmSuite whose
- * asymmetric key wrap is RSA v1.5. Used to decide whether the engine's
hardened
- * default (rejecting rsa-1_5 key transport) may be relaxed in policy mode.
+ * Returns true if the policy that governs this message contains an
AlgorithmSuite whose
+ * asymmetric key wrap is RSA v1.5. Used to decide whether the engine's
hardened default
+ * (rejecting rsa-1_5 key transport) may be relaxed in policy mode. Scoped
to the
+ * operation, and recorded when it cannot be, exactly as above.
*/
public boolean isRSA15KeyTransportAllowedByPolicy() {
+ if (effectivePolicy != null) {
+ return allowsRSA15KeyTransport(effectivePolicy);
+ }
for (OperationPolicy operationPolicy : operationPolicies) {
- Policy policy = operationPolicy.getPolicy();
- if (policy != null && policyContains(policy, assertion -> {
- if (!(assertion instanceof AlgorithmSuite)) {
- return false;
- }
- AlgorithmSuite.AlgorithmSuiteType algorithmSuiteType =
- ((AlgorithmSuite)assertion).getAlgorithmSuiteType();
- return algorithmSuiteType != null
- &&
SPConstants.KW_RSA15.equals(algorithmSuiteType.getAsymmetricKeyWrap());
- })) {
+ if (allowsRSA15KeyTransport(operationPolicy)) {
+ rsa15KeyTransportRelaxedForAllOperations = true;
return true;
}
}
return false;
}
+ private static boolean allowsUsernameTokenNoPassword(OperationPolicy
operationPolicy) {
+ Policy policy = operationPolicy.getPolicy();
+ return policy != null && policyContains(policy,
+ assertion -> assertion instanceof UsernameToken
+ && ((UsernameToken)assertion).getPasswordType()
+ == UsernameToken.PasswordType.NoPassword);
+ }
+
+ private static boolean allowsRSA15KeyTransport(OperationPolicy
operationPolicy) {
+ Policy policy = operationPolicy.getPolicy();
+ return policy != null && policyContains(policy, assertion -> {
+ if (!(assertion instanceof AlgorithmSuite)) {
+ return false;
+ }
+ AlgorithmSuite.AlgorithmSuiteType algorithmSuiteType =
+ ((AlgorithmSuite)assertion).getAlgorithmSuiteType();
+ return algorithmSuiteType != null
+ &&
SPConstants.KW_RSA15.equals(algorithmSuiteType.getAsymmetricKeyWrap());
+ });
+ }
+
+ /**
+ * An engine default that was relaxed on the strength of the whole set of
operation
+ * policies - because the operation was not yet known when the security
header was read
+ * - must still hold for the operation the message turned out to invoke.
Otherwise one
+ * operation that asks for sp:NoPassword or for rsa-1_5 lowers the
engine's floor for
+ * every other operation of the endpoint, and whether that is caught
depends on whether
+ * the effective policy happens to carry an assertion that rejects what
was accepted: a
+ * password-less UsernameToken is not examined at all by a policy that
names no
+ * UsernameToken.
+ */
+ private void verifyRelaxedEngineDefaults() throws WSSecurityException {
+ if (usernameTokenNoPasswordRelaxedForAllOperations
+ && !allowsUsernameTokenNoPassword(effectivePolicy)) {
+ for (SecurityEvent securityEvent : securityEventQueue) {
+ if (securityEvent instanceof UsernameTokenSecurityEvent
+ &&
((UsernameTokenSecurityEvent)securityEvent).getSecurityToken() != null
+ && WSSConstants.UsernameTokenPasswordType.PASSWORD_NONE
+ ==
((UsernameTokenSecurityEvent)securityEvent).getUsernameTokenPasswordType()) {
+ rejectRelaxedEngineDefault("a UsernameToken with no
password");
+ }
+ }
+ }
+ if (rsa15KeyTransportRelaxedForAllOperations
+ && !allowsRSA15KeyTransport(effectivePolicy)) {
+ for (SecurityEvent securityEvent : securityEventQueue) {
+ if (securityEvent instanceof AlgorithmSuiteSecurityEvent) {
+ AlgorithmSuiteSecurityEvent algorithmSuiteSecurityEvent =
+ (AlgorithmSuiteSecurityEvent)securityEvent;
+ if
(WSSConstants.Asym_Key_Wrap.equals(algorithmSuiteSecurityEvent.getAlgorithmUsage())
+ &&
WSSConstants.NS_XENC_RSA15.equals(algorithmSuiteSecurityEvent.getAlgorithmURI()))
{
+ rejectRelaxedEngineDefault("rsa-1_5 key transport");
+ }
+ }
+ }
+ }
+ }
+
+ private void rejectRelaxedEngineDefault(String what) throws
WSSecurityException {
+ String message = "The message uses " + what + ", which the policy for
operation "
+ + effectivePolicy.getOperationName() + " does not allow";
+ LOG.warn("{}; rejecting the message", message);
+ securityEventQueue.clear();
+ throw new WSSecurityException(
+ WSSecurityException.ErrorCode.INVALID_SECURITY,
+ new IllegalArgumentException(message));
+ }
+
private static boolean policyContains(PolicyComponent policyComponent,
Predicate<Assertion> predicate) {
if (policyComponent instanceof PolicyOperator) {
for (PolicyComponent childComponent
diff --git
a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java
b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java
index 78a4d3ef2..90d7264fd 100644
---
a/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java
+++
b/ws-security-policy-stax/src/main/java/org/apache/wss4j/policy/stax/enforcer/PolicyInputProcessor.java
@@ -254,11 +254,23 @@ public class PolicyInputProcessor extends
AbstractInputProcessor {
// unconditionally, which silently reversed both defaults even
when the
// configured policy contained no assertion that re-imposes the
check. Only
// relax an engine default when the policy actually covers it.
- if (policyEnforcer.isRSA15KeyTransportAllowedByPolicy()) {
+ // Where the caller has already relaxed a default itself there is
nothing to
+ // grant, and asking would arm a re-check against a policy the
caller has
+ // deliberately overruled, so leave that case alone. The
properties are
+ // optional on this processor - a caller may construct it with
none - and then
+ // the caller has relaxed nothing.
+ XMLSecurityProperties properties = getSecurityProperties();
+ WSSSecurityProperties securityProperties =
+ properties instanceof WSSSecurityProperties ?
(WSSSecurityProperties) properties : null;
+ boolean callerAllowsRSA15 =
+ securityProperties != null &&
securityProperties.isAllowRSA15KeyTransportAlgorithm();
+ boolean callerAllowsNoPassword =
+ securityProperties != null &&
securityProperties.isAllowUsernameTokenNoPassword();
+ if (!callerAllowsRSA15 &&
policyEnforcer.isRSA15KeyTransportAllowedByPolicy()) {
inputProcessorChain.getSecurityContext().put(
WSSConstants.PROP_ALLOW_RSA15_KEYTRANSPORT_ALGORITHM,
Boolean.TRUE);
}
- if (policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy()) {
+ if (!callerAllowsNoPassword &&
policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy()) {
inputProcessorChain.getSecurityContext().put(
WSSConstants.PROP_ALLOW_USERNAMETOKEN_NOPASSWORD,
Boolean.TRUE.toString());
}
diff --git
a/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/ScopedEngineDefaultsTest.java
b/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/ScopedEngineDefaultsTest.java
new file mode 100644
index 000000000..b97605567
--- /dev/null
+++
b/ws-security-policy-stax/src/test/java/org/apache/wss4j/policy/stax/test/ScopedEngineDefaultsTest.java
@@ -0,0 +1,155 @@
+/**
+ * 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.wss4j.policy.stax.test;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+
+import javax.xml.namespace.QName;
+
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.util.DateUtil;
+import org.apache.wss4j.policy.stax.enforcer.PolicyEnforcer;
+import org.apache.wss4j.policy.stax.enforcer.PolicyEnforcerFactory;
+import org.apache.wss4j.stax.ext.WSSConstants;
+import org.apache.wss4j.stax.impl.securityToken.UsernameSecurityTokenImpl;
+import org.apache.wss4j.stax.securityEvent.OperationSecurityEvent;
+import org.apache.wss4j.stax.securityEvent.UsernameTokenSecurityEvent;
+import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.stax.impl.util.IDGenerator;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The engine's hardened defaults - rejecting password-less UsernameTokens and
rsa-1_5 key
+ * transport - are relaxed in policy mode so that the corresponding policy
assertions can
+ * take over. The relaxation belongs to the operation whose policy asks for
it, not to every
+ * operation of the endpoint.
+ *
+ * The WSDL behind these tests has two operations: one asks for sp:NoPassword,
the other
+ * names no UsernameToken at all, so nothing in its own policy would reject a
password-less
+ * token that the engine has already accepted.
+ */
+public class ScopedEngineDefaultsTest extends AbstractPolicyTestBase {
+
+ private static final String NAMESPACE =
"http://www.example.net/MixedPolicyService";
+ private static final QName NO_PASSWORD_OPERATION = new QName(NAMESPACE,
"noPasswordOperation");
+ private static final QName PASSWORD_OPERATION = new QName(NAMESPACE,
"passwordOperation");
+
+ /**
+ * Where SOAPAction has already selected the operation, only that
operation's policy
+ * decides. The operation that asks for no password gets the relaxation;
the one that
+ * does not, does not.
+ */
+ @Test
+ public void testRelaxationIsScopedToTheOperationSelectedBySOAPAction()
throws Exception {
+
assertTrue(newPolicyEnforcer("noPasswordOperationAction").isUsernameTokenNoPasswordAllowedByPolicy());
+
assertFalse(newPolicyEnforcer("passwordOperationAction").isUsernameTokenNoPasswordAllowedByPolicy());
+ }
+
+ /**
+ * Without a SOAPAction the operation is not known while the security
header is being
+ * read, so the question can only be answered across every configured
operation. The
+ * engine default is still relaxed, as before, or a deployment whose
operation cannot be
+ * determined that early would stop working.
+ */
+ @Test
+ public void testRelaxationStillGrantedWhenTheOperationIsNotYetKnown()
throws Exception {
+
assertTrue(newPolicyEnforcer("").isUsernameTokenNoPasswordAllowedByPolicy());
+ }
+
+ /**
+ * ...but once the operation turns out to be the one whose policy says
nothing about
+ * UsernameTokens, a password-less token accepted under that relaxation is
rejected.
+ */
+ @Test
+ public void
testPasswordlessTokenRejectedForAnOperationThatDoesNotAllowIt() throws
Exception {
+ PolicyEnforcer policyEnforcer = newPolicyEnforcer("");
+ assertTrue(policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy());
+ policyEnforcer.registerSecurityEvent(passwordlessUsernameTokenEvent());
+
+ OperationSecurityEvent operationSecurityEvent = new
OperationSecurityEvent();
+ operationSecurityEvent.setOperation(PASSWORD_OPERATION);
+
+ WSSecurityException ex = assertThrows(WSSecurityException.class,
+ () ->
policyEnforcer.registerSecurityEvent(operationSecurityEvent));
+
+ assertEquals("The message uses a UsernameToken with no password, which
the policy for "
+ + "operation " + PASSWORD_OPERATION + " does not
allow",
+ ex.getCause().getMessage());
+ assertEquals(WSSecurityException.INVALID_SECURITY, ex.getFaultCode());
+ }
+
+ /**
+ * The same message is accepted for the operation whose policy does ask for
+ * sp:NoPassword.
+ */
+ @Test
+ public void testPasswordlessTokenAcceptedForTheOperationThatAllowsIt()
throws Exception {
+ PolicyEnforcer policyEnforcer = newPolicyEnforcer("");
+ assertTrue(policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy());
+ policyEnforcer.registerSecurityEvent(passwordlessUsernameTokenEvent());
+
+ OperationSecurityEvent operationSecurityEvent = new
OperationSecurityEvent();
+ operationSecurityEvent.setOperation(NO_PASSWORD_OPERATION);
+
+ policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+ }
+
+ /**
+ * A message that carries no password-less token is unaffected, even
though the
+ * relaxation was granted across the endpoint.
+ */
+ @Test
+ public void testOperationWithoutAPasswordlessTokenIsUnaffected() throws
Exception {
+ PolicyEnforcer policyEnforcer = newPolicyEnforcer("");
+ assertTrue(policyEnforcer.isUsernameTokenNoPasswordAllowedByPolicy());
+
+ OperationSecurityEvent operationSecurityEvent = new
OperationSecurityEvent();
+ operationSecurityEvent.setOperation(PASSWORD_OPERATION);
+
+ policyEnforcer.registerSecurityEvent(operationSecurityEvent);
+ }
+
+ private PolicyEnforcer newPolicyEnforcer(String soapAction) throws
Exception {
+ PolicyEnforcerFactory policyEnforcerFactory =
PolicyEnforcerFactory.newInstance(
+
this.getClass().getClassLoader().getResource("testdata/wsdl/mixedPasswordPolicies.wsdl"));
+ return policyEnforcerFactory.newPolicyEnforcer(soapAction, false,
null, 0, false);
+ }
+
+ private UsernameTokenSecurityEvent passwordlessUsernameTokenEvent() throws
XMLSecurityException {
+ UsernameTokenSecurityEvent usernameTokenSecurityEvent = new
UsernameTokenSecurityEvent();
+
usernameTokenSecurityEvent.setUsernameTokenProfile(WSSConstants.NS_USERNAMETOKEN_PROFILE11);
+ ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
+ String created = DateUtil.getDateTimeFormatter(true).format(now);
+ UsernameSecurityTokenImpl usernameSecurityToken = new
UsernameSecurityTokenImpl(
+ WSSConstants.UsernameTokenPasswordType.PASSWORD_NONE,
+ "username", null, created, null, new byte[10], 10L,
+ null, IDGenerator.generateID(null),
+
WSSecurityTokenConstants.KEYIDENTIFIER_SECURITY_TOKEN_DIRECT_REFERENCE);
+
usernameSecurityToken.addTokenUsage(WSSecurityTokenConstants.TOKENUSAGE_SUPPORTING_TOKENS);
+ usernameTokenSecurityEvent.setSecurityToken(usernameSecurityToken);
+ return usernameTokenSecurityEvent;
+ }
+}
diff --git
a/ws-security-policy-stax/src/test/resources/testdata/wsdl/mixedPasswordPolicies.wsdl
b/ws-security-policy-stax/src/test/resources/testdata/wsdl/mixedPasswordPolicies.wsdl
new file mode 100644
index 000000000..015ceb6ac
--- /dev/null
+++
b/ws-security-policy-stax/src/test/resources/testdata/wsdl/mixedPasswordPolicies.wsdl
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<wsdl:definitions
+ name="MixedPolicyService"
+ targetNamespace="http://www.example.net/MixedPolicyService"
+ xmlns:tns="http://www.example.net/MixedPolicyService"
+ xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
+ xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
+
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ >
+
+ <!-- Two operations of one endpoint, only one of which asks for
password-less
+ UsernameTokens. The other names no UsernameToken at all, so nothing
in its own
+ policy would reject one that the engine has already accepted. -->
+
+ <wsdl:types>
+ <xs:schema targetNamespace="http://www.example.net/MixedPolicyService"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="noPasswordOperation" type="xs:string"/>
+ <xs:element name="passwordOperation" type="xs:string"/>
+ </xs:schema>
+ </wsdl:types>
+
+ <wsp:Policy wsu:Id="NoPasswordPolicy">
+ <wsp:ExactlyOne>
+ <wsp:All>
+ <sp:SupportingTokens>
+ <wsp:Policy>
+ <sp:UsernameToken>
+ <wsp:Policy>
+ <sp:NoPassword/>
+ </wsp:Policy>
+ </sp:UsernameToken>
+ </wsp:Policy>
+ </sp:SupportingTokens>
+ </wsp:All>
+ </wsp:ExactlyOne>
+ </wsp:Policy>
+
+ <wsdl:message name="noPasswordOperationRequest">
+ <wsdl:part name="parameters" element="tns:noPasswordOperation"/>
+ </wsdl:message>
+ <wsdl:message name="passwordOperationRequest">
+ <wsdl:part name="parameters" element="tns:passwordOperation"/>
+ </wsdl:message>
+
+ <wsdl:portType name="MixedPolicyPort">
+ <wsdl:operation name="noPasswordOperation">
+ <wsdl:input message="tns:noPasswordOperationRequest"/>
+ </wsdl:operation>
+ <wsdl:operation name="passwordOperation">
+ <wsdl:input message="tns:passwordOperationRequest"/>
+ </wsdl:operation>
+ </wsdl:portType>
+
+ <wsdl:binding name="MixedPolicySOAPBinding" type="tns:MixedPolicyPort">
+ <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
+ <wsdl:operation name="noPasswordOperation">
+ <wsp:PolicyReference URI="#NoPasswordPolicy"/>
+ <soap:operation soapAction="noPasswordOperationAction"
style="document"/>
+ <wsdl:input>
+ <soap:body use="literal"/>
+ </wsdl:input>
+ </wsdl:operation>
+ <wsdl:operation name="passwordOperation">
+ <soap:operation soapAction="passwordOperationAction"
style="document"/>
+ <wsdl:input>
+ <soap:body use="literal"/>
+ </wsdl:input>
+ </wsdl:operation>
+ </wsdl:binding>
+
+ <wsdl:service name="MixedPolicyService">
+ <wsdl:port name="MixedPolicy" binding="tns:MixedPolicySOAPBinding">
+ <soap:address location="http://localhost:8080/MixedPolicyService"/>
+ </wsdl:port>
+ </wsdl:service>
+</wsdl:definitions>