This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/rsa15 in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
commit 0d1fca09f41e6b962da532f27ffcf2eaed62e466 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Mon Sep 21 09:23:52 2026 +0100 Properly wire UsernameToken + RSA15 flags for the streaming layer --- THREAT-MODEL.md | 27 ++++ src/site/asciidoc/streaming.adoc | 8 ++ .../wss4j/policy/stax/enforcer/PolicyEnforcer.java | 124 ++++++++++++++--- .../policy/stax/enforcer/PolicyInputProcessor.java | 16 ++- .../policy/stax/test/ScopedEngineDefaultsTest.java | 155 +++++++++++++++++++++ .../testdata/wsdl/mixedPasswordPolicies.wsdl | 98 +++++++++++++ 6 files changed, 404 insertions(+), 24 deletions(-) diff --git a/THREAT-MODEL.md b/THREAT-MODEL.md index 5852447bf..0ea98b153 100644 --- a/THREAT-MODEL.md +++ b/THREAT-MODEL.md @@ -573,6 +573,33 @@ on each is captured in §14 Q10–Q11. - *(documented: `ws-security-policy-stax/src/test/java/.../VulnerabliltyVectorsTest.java`)* +### P12 — An engine default relaxed by policy is scoped to the operation the message invokes (StAX policy mode) + +- **Condition**: streaming engine in policy mode (`PolicyInputProcessor`), + where the caller has not itself set `AllowUsernameTokenNoPassword` or + `AllowRSA15KeyTransportAlgorithm`. Those are explicit overrides by the + caller and are left alone. +- **Violation symptom**: one operation of an endpoint asks for + `sp:NoPassword`, or for an AlgorithmSuite whose asymmetric key wrap is + rsa-1_5, and the engine's corresponding hardened default is thereby + lowered for every other operation of that endpoint — so a + password-less UsernameToken is accepted for an operation whose policy + names no UsernameToken at all, and is examined by no assertion. +- **Mechanism**: the relaxation is decided while the security header is + read, before the Body names the operation. It is therefore scoped to + the operation's own policy where SOAPAction already selected it, and + otherwise granted across the policy set and reimposed once the + operation is known. +- **Residual**: an rsa-1_5 unwrap granted across the policy set is + performed before the operation is known. The message is rejected, but + the unwrap has happened, so the oracle surface of §8 P4 is reachable + for an operation whose own policy forbids rsa-1_5. Only a + SOAPAction-selected operation avoids that. +- **Severity**: **security-critical** for the UsernameToken case; + `VALID-HARDENING` for the rsa-1_5 case. +- *(documented: + `ws-security-policy-stax/src/test/java/.../ScopedEngineDefaultsTest.java`)* + ## §9 Security properties the project does *not* provide State each plainly so a triager can route an inbound report to the diff --git a/src/site/asciidoc/streaming.adoc b/src/site/asciidoc/streaming.adoc index 3d5484c85..705fc26e1 100644 --- a/src/site/asciidoc/streaming.adoc +++ b/src/site/asciidoc/streaming.adoc @@ -87,6 +87,14 @@ nothing on its own. Check the effective policy if you rely on a nested assertion being enforced. * Where a compact policy offers several alternatives within a nested policy, only the first is read. + * A hardened engine default that WS-SecurityPolicy relaxes - accepting a +password-less UsernameToken for an sp:NoPassword policy, or rsa-1_5 key transport +for an AlgorithmSuite that asks for it - has to be decided while the security +header is read, before the Body says which operation the message invokes. Where +SOAPAction has already selected the operation only that operation's policy is +consulted. Otherwise the relaxation is granted across the endpoint's operations +and reimposed once the operation is known, which rejects the message but, for +rsa-1_5, only after the key has been unwrapped. * The REQUIRE_SIGNED_ENCRYPTED_DATA_ELEMENTS ("requireSignedEncryptedDataElements") configuration tag is not read by the streaming code and is not enforced by it. The tag is shared between both stacks, and the streaming ConfigurationConverter 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 db1322d96..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) { - org.apache.neethi.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) { - org.apache.neethi.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>
