[java-opensaml] 01/02: Implement message channel security support for SOAP client case

Brent Putman putmanb at georgetown.edu
Wed Sep 26 23:33:02 EDT 2018


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch master
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=00a87cea5cd32d33cb465f83b58083fe5a2314d2

commit 00a87cea5cd32d33cb465f83b58083fe5a2314d2
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Sep 26 21:17:03 2018 -0400

    Implement message channel security support for SOAP client case
---
 .../NoConfidentialityMessageChannelPredicate.java  |  42 ++++++
 .../logic/NoIntegrityMessageChannelPredicate.java  |  41 +++++
 .../org/opensaml/messaging/logic/package-info.java |  19 +++
 .../impl/AbstractMessageChannelSecurity.java       |  95 ++++++++++++
 .../handler/impl/StaticMessageChannelSecurity.java |  81 ++++++++++
 .../impl/URLEvaluatingMessageChannelSecurity.java  | 147 ++++++++++++++++++
 .../URLEvaluatingMessageChannelSecurityTest.java   | 166 +++++++++++++++++++++
 ...opulateHttpClientSecurityParametersHandler.java |  13 +-
 .../opensaml/soap/client/SOAPClientContext.java    |  21 +++
 .../http/AbstractPipelineHttpSOAPClient.java       |   8 +-
 .../messaging/SOAPClientDestinationURILookup.java  |  54 +++++++
 .../soap/client/messaging/package-info.java        |  19 +++
 12 files changed, 701 insertions(+), 5 deletions(-)

diff --git a/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/NoConfidentialityMessageChannelPredicate.java b/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/NoConfidentialityMessageChannelPredicate.java
new file mode 100644
index 0000000..a99e32d
--- /dev/null
+++ b/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/NoConfidentialityMessageChannelPredicate.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.messaging.logic;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageChannelSecurityContext;
+import org.opensaml.messaging.context.MessageContext;
+
+import com.google.common.base.Predicate;
+
+/**
+ * A predicate implementation that indicates whether the message channel does
+ * <strong>NOT</strong> support confidentiality end-to-end.
+ * 
+ * <p>Typically but not exclusively used as a predicate for whether to encrypt something.</p>
+ */
+public class NoConfidentialityMessageChannelPredicate implements Predicate<MessageContext> {
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean apply(@Nullable final MessageContext input) {
+        return input == null
+                || !input.getSubcontext(MessageChannelSecurityContext.class, true).isConfidentialityActive();
+    }
+    
+}
\ No newline at end of file
diff --git a/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/NoIntegrityMessageChannelPredicate.java b/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/NoIntegrityMessageChannelPredicate.java
new file mode 100644
index 0000000..7478a53
--- /dev/null
+++ b/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/NoIntegrityMessageChannelPredicate.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.messaging.logic;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageChannelSecurityContext;
+import org.opensaml.messaging.context.MessageContext;
+
+import com.google.common.base.Predicate;
+
+/**
+ * A predicate implementation that indicates whether the message channel does
+ * <strong>NOT</strong> support integrity end-to-end.
+ * 
+ * <p>Typically but not exclusively used as a predicate for whether to sign something.</p>
+ */
+public class NoIntegrityMessageChannelPredicate implements Predicate<MessageContext> {
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean apply(@Nullable final MessageContext input) {
+        return input == null || !input.getSubcontext(MessageChannelSecurityContext.class, true).isIntegrityActive();
+    }
+    
+}
\ No newline at end of file
diff --git a/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/package-info.java b/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/package-info.java
new file mode 100644
index 0000000..3e261d8
--- /dev/null
+++ b/opensaml-messaging-api/src/main/java/org/opensaml/messaging/logic/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.
+ */
+
+/** Interfaces and classes for messaging logic. */
+package org.opensaml.messaging.logic;
\ No newline at end of file
diff --git a/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/AbstractMessageChannelSecurity.java b/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/AbstractMessageChannelSecurity.java
new file mode 100644
index 0000000..b8a68d0
--- /dev/null
+++ b/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/AbstractMessageChannelSecurity.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+
+import com.google.common.base.Function;
+
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Abstract base class for message handlers which populate a
+ * {@link org.opensaml.messaging.context.MessageChannelSecurityContext} on a {@link BaseContext},
+ * where the latter is located using a lookup strategy.
+ * 
+ * @param <MessageType> the type of message being carried
+ */
+public abstract class AbstractMessageChannelSecurity<MessageType> extends AbstractMessageHandler<MessageType> {
+    
+    /**
+     * Strategy used to look up the parent {@link BaseContext} on which the
+     * {@link org.opensaml.messaging.context.MessageChannelSecurityContext} will be populated.
+     */
+    @Nonnull private Function<MessageContext, BaseContext> parentContextLookupStrategy;
+    
+    /** Parent for eventual context. */
+    @Nullable private BaseContext parentContext;
+    
+    /** Constructor. */
+    public AbstractMessageChannelSecurity() {
+        //TODO this just returns the input MC - need better default?
+        parentContextLookupStrategy = new Function<MessageContext, BaseContext>() {
+            @Nullable public BaseContext apply(@Nullable final MessageContext input) {
+                return input;
+            }
+        };
+    }
+    
+    /**
+     * Set the strategy used to look up the parent {@link BaseContext} on which the
+     * {@link org.opensaml.messaging.context.MessageChannelSecurityContext} will be populated.
+     * 
+     * @param strategy strategy used to look up the parent {@link BaseContext} on which to populate
+     *          the {@link org.opensaml.messaging.context.MessageChannelSecurityContext}
+     */
+    public void setParentContextLookupStrategy(@Nonnull final Function<MessageContext, BaseContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        parentContextLookupStrategy = Constraint.isNotNull(strategy, "Parent context lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        parentContext = parentContextLookupStrategy.apply(messageContext);
+        if (parentContext != null) {
+            return super.doPreInvoke(messageContext);
+        }
+        return false;
+    }
+    
+    /**
+     * Get the parent context on which the {@link org.opensaml.messaging.context.MessageChannelSecurityContext}
+     * will be populated.
+     * 
+     * @return the parent context
+     */
+    protected BaseContext getParentContext() {
+        return parentContext;
+    }
+
+}
\ No newline at end of file
diff --git a/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/StaticMessageChannelSecurity.java b/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/StaticMessageChannelSecurity.java
new file mode 100644
index 0000000..bf70a0c
--- /dev/null
+++ b/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/StaticMessageChannelSecurity.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.messaging.handler.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageChannelSecurityContext;
+import org.opensaml.messaging.context.MessageContext;
+
+/**
+ * Message handler which populates a {@link MessageChannelSecurityContext} based on static configuration flags.
+ */
+public class StaticMessageChannelSecurity extends AbstractMessageChannelSecurity {
+    
+    /** Message channel confidentiality flag. */
+    private boolean confidentialityActive;
+    
+    /** Message channel integrity flag. */
+    private boolean integrityActive;
+
+    /**
+     * Get whether message channel confidentiality is active.
+     * 
+     * @return Returns the confidentialityActive.
+     */
+    public boolean isConfidentialityActive() {
+        return confidentialityActive;
+    }
+
+    /**
+     * Set whether message channel confidentiality is active.
+     * 
+     * @param flag The confidentialityActive to set.
+     */
+    public void setConfidentialityActive(final boolean flag) {
+        confidentialityActive = flag;
+    }
+
+    /**
+     * Get whether message channel integrity is active.
+     * 
+     * @return Returns the integrityActive.
+     */
+    public boolean isIntegrityActive() {
+        return integrityActive;
+    }
+
+    /**
+     * Set whether message channel integrity is active.
+     * 
+     * @param flag The integrityActive to set.
+     */
+    public void setIntegrityActive(final boolean flag) {
+        integrityActive = flag;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) {
+        final MessageChannelSecurityContext channelContext =
+                getParentContext().getSubcontext(MessageChannelSecurityContext.class, true);
+        channelContext.setConfidentialityActive(isConfidentialityActive());
+        channelContext.setIntegrityActive(isIntegrityActive());
+    }
+
+}
\ No newline at end of file
diff --git a/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/URLEvaluatingMessageChannelSecurity.java b/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/URLEvaluatingMessageChannelSecurity.java
new file mode 100644
index 0000000..37f819a
--- /dev/null
+++ b/opensaml-messaging-impl/src/main/java/org/opensaml/messaging/handler/impl/URLEvaluatingMessageChannelSecurity.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.messaging.handler.impl;
+
+import java.net.MalformedURLException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageChannelSecurityContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.net.URLBuilder;
+
+/**
+ * Message handler which populates a {@link MessageChannelSecurityContext} based on evaluating a
+ * target URL resolved via a configured strategy function.
+ */
+public class URLEvaluatingMessageChannelSecurity extends AbstractMessageChannelSecurity {
+    
+    /** Logger. */
+    private Logger log = LoggerFactory.getLogger(URLEvaluatingMessageChannelSecurity.class);
+
+    /** Flag controlling whether traffic on the default TLS port is "secure". */
+    private boolean defaultPortInsecure;
+    
+    /** Function which looks up the URL to evaluate. */
+    @NonnullAfterInit private Function<MessageContext, String> urlLookup;
+    
+    /** The target resolved URL. */
+    @Nullable private String url;
+    
+    /** Target resolved and parsed URL. */
+    @Nullable private URLBuilder urlBuilder;
+    
+    /** Constructor. */
+    public URLEvaluatingMessageChannelSecurity() {
+        defaultPortInsecure = true;
+    }
+    
+    /**
+     * Set whether traffic on the default TLS port is "secure" for the purposes of this action.
+     * 
+     * <p>Defaults to "true"</p>
+     *
+     * <p>Ordinarily TLS is considered a "secure" channel, but traffic to a default port meant
+     * for browser access tends to rely on server certificates that are unsuited to secure messaging
+     * use cases. This flag allows software layers to recognize traffic on this port as "insecure" and
+     * needing additional security measures.</p>
+     * 
+     * @param flag flag to set
+     */
+    public void setDefaultPortInsecure(final boolean flag) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        defaultPortInsecure = flag;
+    }
+    
+    /**
+     * Set the function which looks up the destination URL to evaluate.
+     * 
+     * @param function the lookup function
+     */
+    public void setURLLookup(@Nullable final Function<MessageContext, String> function) {
+        urlLookup = function;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (urlLookup == null) {
+            throw new ComponentInitializationException("Destination URL lookup function is required");
+        }
+    }
+
+    /** {@inheritDoc} */
+    protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+        url = urlLookup.apply(messageContext);
+        if (url != null) {
+            try {
+                urlBuilder = new URLBuilder(url);
+                return super.doPreInvoke(messageContext);
+            } catch (final MalformedURLException e){
+                log.warn("Unable to parse resolved target URL: {}", url, e);
+                return false;
+            }
+        } else {
+            log.warn("No target URL resolved, skipping MessageChannelSecurityContext population");
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInvoke(final MessageContext messageContext) {
+        final MessageChannelSecurityContext channelContext =
+                getParentContext().getSubcontext(MessageChannelSecurityContext.class, true);
+        
+        final String scheme = urlBuilder.getScheme();
+        // Note that below we don't care about port if scheme != https,
+        // so only need to worry about default port for https, not all possible schemes.
+        final Integer port = urlBuilder.getPort() != null 
+                ? urlBuilder.getPort() 
+                        : "https".equalsIgnoreCase(scheme) ? 443 : null;
+                
+        log.debug("Evaluating message channel security for scheme '{}' and port '{}' for URL: {}",
+                scheme, port, url);
+        
+        if ("https".equalsIgnoreCase(scheme) && (!defaultPortInsecure || port != 443)) {
+            channelContext.setConfidentialityActive(true);
+            channelContext.setIntegrityActive(true);
+        } else {
+            channelContext.setConfidentialityActive(false);
+            channelContext.setIntegrityActive(false);
+        }
+        
+        log.debug("Set MessageChannelSecurityContext isIntegrityActive: {}", 
+                channelContext.isIntegrityActive());
+        log.debug("Set MessageChannelSecurityContext isConfidentialityActive: {}", 
+                channelContext.isConfidentialityActive());
+    }
+
+}
\ No newline at end of file
diff --git a/opensaml-messaging-impl/src/test/java/org/opensaml/messaging/handler/impl/URLEvaluatingMessageChannelSecurityTest.java b/opensaml-messaging-impl/src/test/java/org/opensaml/messaging/handler/impl/URLEvaluatingMessageChannelSecurityTest.java
new file mode 100644
index 0000000..c81eabb
--- /dev/null
+++ b/opensaml-messaging-impl/src/test/java/org/opensaml/messaging/handler/impl/URLEvaluatingMessageChannelSecurityTest.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.messaging.handler.impl;
+
+import org.opensaml.messaging.context.MessageChannelSecurityContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+public class URLEvaluatingMessageChannelSecurityTest {
+    
+    private URLEvaluatingMessageChannelSecurity handler;
+    
+    private MessageContext messageContext;
+    
+    @BeforeMethod
+    public void setUp() throws ComponentInitializationException {
+        handler = new URLEvaluatingMessageChannelSecurity();
+        messageContext = new MessageContext();
+    }
+    
+    @Test
+    public void testHTTPSNoPort() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("https://www.example.edu"));
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNotNull(channelSecurityContext);
+        Assert.assertFalse(channelSecurityContext.isIntegrityActive());
+        Assert.assertFalse(channelSecurityContext.isConfidentialityActive());
+    }
+
+    @Test
+    public void testHTTPSWithDefaultPort() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("https://www.example.edu:443"));
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNotNull(channelSecurityContext);
+        Assert.assertFalse(channelSecurityContext.isIntegrityActive());
+        Assert.assertFalse(channelSecurityContext.isConfidentialityActive());
+    }
+    
+    @Test
+    public void testHTTPSWithDefaultPortAsSecure() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("https://www.example.edu:443"));
+        handler.setDefaultPortInsecure(false);
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNotNull(channelSecurityContext);
+        Assert.assertTrue(channelSecurityContext.isIntegrityActive());
+        Assert.assertTrue(channelSecurityContext.isConfidentialityActive());
+    }
+    
+    @Test
+    public void testHTTPSNoPortAsSecure() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("https://www.example.edu"));
+        handler.setDefaultPortInsecure(false);
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNotNull(channelSecurityContext);
+        Assert.assertTrue(channelSecurityContext.isIntegrityActive());
+        Assert.assertTrue(channelSecurityContext.isConfidentialityActive());
+    }
+    
+    @Test
+    public void testHTTPSWithNonDefaultPort() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("https://www.example.edu:8443"));
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNotNull(channelSecurityContext);
+        Assert.assertTrue(channelSecurityContext.isIntegrityActive());
+        Assert.assertTrue(channelSecurityContext.isConfidentialityActive());
+    }
+    
+    @Test
+    public void testHTTPNoPort() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("http://www.example.edu"));
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNotNull(channelSecurityContext);
+        Assert.assertFalse(channelSecurityContext.isIntegrityActive());
+        Assert.assertFalse(channelSecurityContext.isConfidentialityActive());
+    }
+    
+    @Test
+    public void testHTTPWithPort() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("http://www.example.edu:80"));
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNotNull(channelSecurityContext);
+        Assert.assertFalse(channelSecurityContext.isIntegrityActive());
+        Assert.assertFalse(channelSecurityContext.isConfidentialityActive());
+    }
+    
+    @Test
+    public void testBadURL() throws ComponentInitializationException, MessageHandlerException {
+        handler.setURLLookup(new MockURLLookup("foobar"));
+        handler.initialize();
+        
+        handler.invoke(messageContext);
+        
+        MessageChannelSecurityContext channelSecurityContext = messageContext.getSubcontext(MessageChannelSecurityContext.class);
+        Assert.assertNull(channelSecurityContext);
+    }
+    
+    @Test(expectedExceptions=ComponentInitializationException.class)
+    public void testMissingURLLookup() throws ComponentInitializationException {
+        handler.initialize();
+    }
+    
+    private static class MockURLLookup implements Function<MessageContext, String> {
+        
+        private String url;
+
+        public MockURLLookup(String value) {
+            url = value;
+        }
+
+        /** {@inheritDoc} */
+        public String apply(MessageContext input) {
+            return url;
+        }
+        
+    }
+}
diff --git a/opensaml-security-impl/src/main/java/org/opensaml/security/messaging/impl/PopulateHttpClientSecurityParametersHandler.java b/opensaml-security-impl/src/main/java/org/opensaml/security/messaging/impl/PopulateHttpClientSecurityParametersHandler.java
index 06a7f37..900e40f 100644
--- a/opensaml-security-impl/src/main/java/org/opensaml/security/messaging/impl/PopulateHttpClientSecurityParametersHandler.java
+++ b/opensaml-security-impl/src/main/java/org/opensaml/security/messaging/impl/PopulateHttpClientSecurityParametersHandler.java
@@ -239,9 +239,16 @@ public class PopulateHttpClientSecurityParametersHandler extends AbstractMessage
     protected void postProcessParams(@Nonnull final MessageContext messageContext, 
             @Nonnull final HttpClientSecurityParameters params) {
         
-        if (clientTLSPredicate != null && ! clientTLSPredicate.apply(messageContext)) {
-            log.debug("Configured client TLS predicate indicates to exclude client TLS credential");
-            params.setClientTLSCredential(null);
+        if (clientTLSPredicate != null) { 
+            if (!clientTLSPredicate.apply(messageContext)) {
+                log.debug("Configured client TLS predicate indicates to exclude client TLS credential");
+                params.setClientTLSCredential(null);
+            } else {
+                if (params.getClientTLSCredential() == null) {
+                    log.warn("Configured client TLS predicate indicates to include client TLS credential, " +
+                            "but no client TLS credential was present in resolved parameters");
+                }
+            }
         }
     }
     
diff --git a/opensaml-soap-api/src/main/java/org/opensaml/soap/client/SOAPClientContext.java b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/SOAPClientContext.java
index c8fb849..acc19e6 100644
--- a/opensaml-soap-api/src/main/java/org/opensaml/soap/client/SOAPClientContext.java
+++ b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/SOAPClientContext.java
@@ -33,6 +33,9 @@ public class SOAPClientContext extends BaseContext {
     
     /** Name of the specific SOAP client pipeline to use, for example with {@link PipelineFactoryHttpSOAPClient}. */
     @Nullable private String pipelineName;
+    
+    /** The destination URI for the SOAP message being sent. */
+    @Nullable private String destinationURI;
 
     /**
      * Gets a set of binding/transport-specific request parameters.
@@ -71,5 +74,23 @@ public class SOAPClientContext extends BaseContext {
     public void setPipelineName(@Nullable final String name) {
         pipelineName = StringSupport.trimOrNull(name);
     }
+    
+    /**
+     * Get the the destination URI for the SOAP message being sent.
+     * 
+     * @return the destination URI, or null
+     */
+    @Nullable public String getDestinationURI() {
+        return destinationURI;
+    }
+
+    /**
+     * Set the destination URI for the SOAP message being sent.
+     * 
+     * @param uri the destination URI, or null
+     */
+    public void setDestinationURI(@Nullable final String uri) {
+        destinationURI = StringSupport.trimOrNull(uri);
+    }
 
 }
\ No newline at end of file
diff --git a/opensaml-soap-api/src/main/java/org/opensaml/soap/client/http/AbstractPipelineHttpSOAPClient.java b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/http/AbstractPipelineHttpSOAPClient.java
index 57636dd..2f32c92 100644
--- a/opensaml-soap-api/src/main/java/org/opensaml/soap/client/http/AbstractPipelineHttpSOAPClient.java
+++ b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/http/AbstractPipelineHttpSOAPClient.java
@@ -60,6 +60,7 @@ import org.opensaml.security.httpclient.HttpClientSecurityParameters;
 import org.opensaml.security.httpclient.HttpClientSecuritySupport;
 import org.opensaml.security.messaging.HttpClientSecurityContext;
 import org.opensaml.soap.client.SOAPClient;
+import org.opensaml.soap.client.SOAPClientContext;
 import org.opensaml.soap.client.SOAPFaultException;
 import org.opensaml.soap.common.SOAP11FaultDecodingException;
 import org.opensaml.soap.common.SOAPException;
@@ -185,7 +186,7 @@ public abstract class AbstractPipelineHttpSOAPClient<OutboundMessageType, Inboun
     }
     
     /** {@inheritDoc} */
-    // Checkstyle: CyclomaticComplexity OFF
+    // Checkstyle: CyclomaticComplexity|MethodLength OFF
     public void send(@Nonnull @NotEmpty final String endpoint, @Nonnull final InOutOperationContext operationContext)
             throws SOAPException, SecurityException {
         Constraint.isNotNull(endpoint, "Endpoint cannot be null");
@@ -193,6 +194,9 @@ public abstract class AbstractPipelineHttpSOAPClient<OutboundMessageType, Inboun
         
         HttpClientMessagePipeline<InboundMessageType, OutboundMessageType> pipeline = null;
         try {
+            // Store the endpoint URI
+            operationContext.getSubcontext(SOAPClientContext.class, true).setDestinationURI(endpoint);
+            
             // Pipeline resolution
             pipeline = resolvePipeline(operationContext);
             
@@ -256,7 +260,7 @@ public abstract class AbstractPipelineHttpSOAPClient<OutboundMessageType, Inboun
             }
         }
     }
-    // Checkstyle: CyclomaticComplexity ON
+    // Checkstyle: CyclomaticComplexity|MethodLength ON
     
     /**
      * Resolve and return a new instance of the {@link HttpClientMessagePipeline} to be processed.
diff --git a/opensaml-soap-api/src/main/java/org/opensaml/soap/client/messaging/SOAPClientDestinationURILookup.java b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/messaging/SOAPClientDestinationURILookup.java
new file mode 100644
index 0000000..9e7fac0
--- /dev/null
+++ b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/messaging/SOAPClientDestinationURILookup.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.soap.client.messaging;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.soap.client.SOAPClientContext;
+
+import com.google.common.base.Function;
+
+/**
+ * Function which resolves and returns the intended SOAP client message destination URI
+ * via the {@link SOAPClientContext#getDestinationURI()} of the message context's
+ * parent {@link {@link InOutOperationContext}.
+ */
+public class SOAPClientDestinationURILookup implements Function<MessageContext, String> {
+
+    /** {@inheritDoc} */
+    public String apply(@Nullable final MessageContext messageContext) {
+        if (messageContext == null) {
+            return null;
+        }
+        
+        if (!(messageContext.getParent() instanceof InOutOperationContext)) {
+            return null;
+        }
+        
+        final InOutOperationContext opContext = (InOutOperationContext) messageContext.getParent();
+        
+        if (opContext.getSubcontext(SOAPClientContext.class) == null) {
+            return null;
+        } else {
+            return opContext.getSubcontext(SOAPClientContext.class).getDestinationURI();
+        }
+    }
+
+}
diff --git a/opensaml-soap-api/src/main/java/org/opensaml/soap/client/messaging/package-info.java b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/messaging/package-info.java
new file mode 100644
index 0000000..d76d6c6
--- /dev/null
+++ b/opensaml-soap-api/src/main/java/org/opensaml/soap/client/messaging/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.
+ */
+
+/** SOAP HTTP client messaging functionality. */
+package org.opensaml.soap.client.messaging;
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list