[java-identity-provider] branch master updated: IDP-964 - Support SAML logout propagation via SOAP
Scott Cantor
cantor.2 at osu.edu
Fri Nov 16 11:58:30 EST 2018
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=aa717134dcaf9072d700637e19012f6481c2d9ca
The following commit(s) were added to refs/heads/master by this push:
new aa71713 IDP-964 - Support SAML logout propagation via SOAP
aa71713 is described below
commit aa717134dcaf9072d700637e19012f6481c2d9ca
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Nov 16 11:52:42 2018 -0500
IDP-964 - Support SAML logout propagation via SOAP
https://issues.shibboleth.net/jira/browse/IDP-964
Add SOAP support in front-channel propagation.
---
.../system/conf/relying-party-mddriven.xml | 24 ++
.../resources/system/conf/saml-binding-config.xml | 8 +
.../src/main/resources/system/conf/soap-client.xml | 15 +-
.../flows/logout/logout-propagation-flow.xml | 1 +
.../flows/logout/propagation/saml2-beans.xml | 8 +
.../system/flows/logout/propagation/saml2-flow.xml | 39 ++-
.../flows/saml/saml2/slo-front-abstract-beans.xml | 2 +-
.../ClientTLSSOAPLogoutRequestsPredicate.java | 47 +++
.../messaging/SignSOAPLogoutRequestsPredicate.java | 47 +++
.../config/SingleLogoutProfileConfiguration.java | 62 +++-
.../saml/saml2/profile/impl/SOAPLogoutRequest.java | 328 +++++++++++++++++++++
11 files changed, 572 insertions(+), 9 deletions(-)
diff --git a/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml b/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
index 18bca21..beacd93 100644
--- a/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
+++ b/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
@@ -421,6 +421,30 @@
</constructor-arg>
</bean>
</property>
+ <property name="signSOAPRequests">
+ <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
+ <constructor-arg>
+ <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="signSOAPRequests" />
+ </constructor-arg>
+ <constructor-arg>
+ <bean class="org.opensaml.messaging.logic.NoIntegrityMessageChannelPredicate" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="clientTLSSOAPRequests">
+ <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
+ <constructor-arg>
+ <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="clientTLSSOAPRequests" />
+ </constructor-arg>
+ <constructor-arg>
+ <bean parent="shibboleth.Conditions.NOT">
+ <constructor-arg>
+ <bean class="org.opensaml.messaging.logic.NoIntegrityMessageChannelPredicate" />
+ </constructor-arg>
+ </bean>
+ </constructor-arg>
+ </bean>
+ </property>
<property name="signRequests">
<bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
<constructor-arg>
diff --git a/idp-conf/src/main/resources/system/conf/saml-binding-config.xml b/idp-conf/src/main/resources/system/conf/saml-binding-config.xml
index b8407f7..6ee5272 100644
--- a/idp-conf/src/main/resources/system/conf/saml-binding-config.xml
+++ b/idp-conf/src/main/resources/system/conf/saml-binding-config.xml
@@ -95,11 +95,19 @@
<ref bean="shibboleth.Binding.SAML2Artifact" />
</util:list>
+ <util:list id="shibboleth.OutgoingSAML2SLOFrontBindings">
+ <ref bean="shibboleth.Binding.SAML2Redirect" />
+ <ref bean="shibboleth.Binding.SAML2POST" />
+ <ref bean="shibboleth.Binding.SAML2POSTSimpleSign" />
+ <ref bean="shibboleth.Binding.SAML2Artifact" />
+ </util:list>
+
<util:list id="shibboleth.OutgoingSAML2SLOBindings">
<ref bean="shibboleth.Binding.SAML2Redirect" />
<ref bean="shibboleth.Binding.SAML2POST" />
<ref bean="shibboleth.Binding.SAML2POSTSimpleSign" />
<ref bean="shibboleth.Binding.SAML2Artifact" />
+ <ref bean="shibboleth.Binding.SAML2SOAP" />
</util:list>
<util:list id="shibboleth.OutgoingSOAPBindings">
diff --git a/idp-conf/src/main/resources/system/conf/soap-client.xml b/idp-conf/src/main/resources/system/conf/soap-client.xml
index 7a2c987..7db69f2 100644
--- a/idp-conf/src/main/resources/system/conf/soap-client.xml
+++ b/idp-conf/src/main/resources/system/conf/soap-client.xml
@@ -30,6 +30,7 @@
<property name="serviceMappings">
<props>
<prop key="SAML2.ArtifactDecoder">SOAPClient.SAML2.Pipeline</prop>
+ <prop key="SAML2.Logout">SOAPClient.SAML2.Pipeline</prop>
</props>
</property>
</bean>
@@ -41,10 +42,12 @@
IMPORTANT: actual instances of this bean and the encoder/decoder below MUST be prototypes, as they are per-request instance. -->
<bean id="SOAPClient.SAML2.Pipeline.Abstract" abstract="true" class="org.opensaml.messaging.pipeline.httpclient.BasicHttpClientMessagePipeline">
<constructor-arg>
- <bean scope="prototype" init-method="" class="org.opensaml.saml.saml2.binding.encoding.impl.HttpClientRequestSOAP11Encoder" />
+ <bean scope="prototype" init-method=""
+ class="org.opensaml.saml.saml2.binding.encoding.impl.HttpClientRequestSOAP11Encoder" />
</constructor-arg>
<constructor-arg>
- <bean scope="prototype" init-method="" class="org.opensaml.saml.saml2.binding.decoding.impl.HttpClientResponseSOAP11Decoder" />
+ <bean scope="prototype" init-method=""
+ class="org.opensaml.saml.saml2.binding.decoding.impl.HttpClientResponseSOAP11Decoder" />
</constructor-arg>
</bean>
@@ -142,8 +145,6 @@
</bean>
-
-
<!-- Supporting beans. -->
<bean id="SOAPClient.OperationContextLookup"
@@ -187,6 +188,9 @@
<entry key="SAML2.ArtifactDecoder">
<bean class="net.shibboleth.idp.saml.profile.config.logic.messaging.ClientTLSArtifactRequestsPredicate" />
</entry>
+ <entry key="SAML2.Logout">
+ <bean class="net.shibboleth.idp.saml.profile.config.logic.messaging.ClientTLSSOAPLogoutRequestsPredicate" />
+ </entry>
</map>
</constructor-arg>
</bean>
@@ -198,6 +202,9 @@
<entry key="SAML2.ArtifactDecoder">
<bean class="net.shibboleth.idp.saml.profile.config.logic.messaging.SignArtifactRequestsPredicate" />
</entry>
+ <entry key="SAML2.Logout">
+ <bean class="net.shibboleth.idp.saml.profile.config.logic.messaging.SignSOAPLogoutRequestsPredicate" />
+ </entry>
</map>
</constructor-arg>
</bean>
diff --git a/idp-conf/src/main/resources/system/flows/logout/logout-propagation-flow.xml b/idp-conf/src/main/resources/system/flows/logout/logout-propagation-flow.xml
index a38385b..8255e7a 100644
--- a/idp-conf/src/main/resources/system/flows/logout/logout-propagation-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/logout/logout-propagation-flow.xml
@@ -34,6 +34,7 @@
<end-state id="RenderResult" view="logout/result">
<on-entry>
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.session.context.LogoutPropagationContext))" result="requestScope.logoutPropCtx" />
<evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.UserAgentContext))" result="requestScope.userAgentContext" />
<evaluate expression="userAgentContext.isInstance(T(eu.bitwalker.useragentutils.Browser).IE)
? externalContext.nativeResponse.setContentType('text/plain')
diff --git a/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-beans.xml b/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-beans.xml
index 2fd3b18..bc8bcc6 100644
--- a/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-beans.xml
@@ -66,6 +66,9 @@
<bean id="PostResponsePopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
p:fieldExtractors="#{getObject('shibboleth.PostResponseAuditExtractors') ?: getObject('shibboleth.DefaultPostResponseAuditExtractors')}" />
+
+ <bean id="PostDecodePopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
+ p:fieldExtractors="#{getObject('shibboleth.PostDecodeAuditExtractors') ?: getObject('shibboleth.DefaultPostDecodeAuditExtractors')}" />
<bean id="shibboleth.PreEncodeMessageHandler"
class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain" scope="prototype">
@@ -86,5 +89,10 @@
</list>
</property>
</bean>
+
+ <bean id="SOAPLogoutRequest"
+ class="net.shibboleth.idp.saml.saml2.profile.impl.SOAPLogoutRequest" scope="prototype"
+ p:SOAPClient-ref="shibboleth.SOAPClient.SAML"
+ p:SOAPPipelineName="SAML2.Logout" />
</beans>
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml b/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml
index d4c441b..1a2294a 100644
--- a/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml
@@ -22,7 +22,26 @@
<evaluate expression="InitializeOutboundMessageContext" />
<evaluate expression="InitializeMessageChannelSecurityContext" />
<evaluate expression="PopulateBindingAndEndpointContexts" />
+
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="CheckForSynchronousBinding1" />
+ </action-state>
+
+ <decision-state id="CheckForSynchronousBinding1">
+ <if test="opensamlProfileRequestContext.getOutboundMessageContext().getSubcontext('org.opensaml.saml.common.messaging.context.SAMLBindingContext').getBindingDescriptor().isSynchronous()"
+ then="PrepareRequest"
+ else="PopulateNonSOAPRequestSignatureSigningParameters"/>
+ </decision-state>
+
+ <action-state id="PopulateNonSOAPRequestSignatureSigningParameters">
<evaluate expression="PopulateRequestSignatureSigningParameters" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="PrepareRequest" />
+ </action-state>
+
+ <action-state id="PrepareRequest">
<evaluate expression="PopulateEncryptionParameters" />
<evaluate expression="AddLogoutRequest" />
@@ -35,7 +54,21 @@
<evaluate expression="'proceed'" />
- <transition on="proceed" to="HandleOutboundMessage" />
+ <transition on="proceed" to="CheckForSynchronousBinding2" />
+ </action-state>
+
+ <decision-state id="CheckForSynchronousBinding2">
+ <if test="opensamlProfileRequestContext.getOutboundMessageContext().getSubcontext('org.opensaml.saml.common.messaging.context.SAMLBindingContext').getBindingDescriptor().isSynchronous()"
+ then="HandleBackChannelMessage"
+ else="HandleOutboundMessage"/>
+ </decision-state>
+
+ <action-state id="HandleBackChannelMessage">
+ <evaluate expression="SOAPLogoutRequest" />
+ <evaluate expression="PostDecodePopulateAuditContext" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="proceed" />
</action-state>
<action-state id="HandleOutboundMessage">
@@ -68,8 +101,8 @@
</end-state>
<!--
- Unsuccessful terminal state. This is "proceed" because the parent flow will respond to that
- by emitting the outcome in the LogoutPropagationContext, which is always left at Failure.
+ Terminal state. This is "proceed" because the parent flow will respond to that
+ by emitting the outcome in the LogoutPropagationContext, which is set appropriately.
-->
<end-state id="proceed"/>
diff --git a/idp-conf/src/main/resources/system/flows/saml/saml2/slo-front-abstract-beans.xml b/idp-conf/src/main/resources/system/flows/saml/saml2/slo-front-abstract-beans.xml
index 952eb89..e789a2d 100644
--- a/idp-conf/src/main/resources/system/flows/saml/saml2/slo-front-abstract-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/saml/saml2/slo-front-abstract-beans.xml
@@ -30,7 +30,7 @@
class="org.opensaml.profile.action.impl.StaticMessageChannelSecurity" scope="prototype"
p:confidentialityActive="false" p:integrityActive="false" />
- <alias name="shibboleth.OutgoingSAML2SLOBindings" alias="shibboleth.OutgoingBindings"/>
+ <alias name="shibboleth.OutgoingSAML2SLOFrontBindings" alias="shibboleth.OutgoingBindings"/>
<bean id="ProcessLogoutRequest"
class="net.shibboleth.idp.saml.saml2.profile.impl.ProcessLogoutRequest" scope="prototype"
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/logic/messaging/ClientTLSSOAPLogoutRequestsPredicate.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/logic/messaging/ClientTLSSOAPLogoutRequestsPredicate.java
new file mode 100644
index 0000000..6c6a5be
--- /dev/null
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/logic/messaging/ClientTLSSOAPLogoutRequestsPredicate.java
@@ -0,0 +1,47 @@
+/*
+ * 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 net.shibboleth.idp.saml.profile.config.logic.messaging;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.profile.logic.messaging.AbstractRelyingPartyPredicate;
+import net.shibboleth.idp.saml.saml2.profile.config.SingleLogoutProfileConfiguration;
+
+/** A predicate implementation that forwards to
+ * {@link SingleLogoutProfileConfiguration#getClientTLSSOAPRequests()}. */
+public class ClientTLSSOAPLogoutRequestsPredicate extends AbstractRelyingPartyPredicate {
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean apply(@Nullable final MessageContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc != null && pc instanceof SingleLogoutProfileConfiguration) {
+ return ((SingleLogoutProfileConfiguration) pc).getClientTLSSOAPRequests().apply(input);
+ }
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/logic/messaging/SignSOAPLogoutRequestsPredicate.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/logic/messaging/SignSOAPLogoutRequestsPredicate.java
new file mode 100644
index 0000000..888a006
--- /dev/null
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/logic/messaging/SignSOAPLogoutRequestsPredicate.java
@@ -0,0 +1,47 @@
+/*
+ * 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 net.shibboleth.idp.saml.profile.config.logic.messaging;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.profile.logic.messaging.AbstractRelyingPartyPredicate;
+import net.shibboleth.idp.saml.saml2.profile.config.SingleLogoutProfileConfiguration;
+
+/** A predicate implementation that forwards to
+ * {@link SingleLogoutProfileConfiguration#getSignSOAPRequests()}. */
+public class SignSOAPLogoutRequestsPredicate extends AbstractRelyingPartyPredicate {
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean apply(@Nullable final MessageContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc != null && pc instanceof SingleLogoutProfileConfiguration) {
+ return ((SingleLogoutProfileConfiguration) pc).getSignSOAPRequests().apply(input);
+ }
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/SingleLogoutProfileConfiguration.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/SingleLogoutProfileConfiguration.java
index fe515f8..fe4c7f5 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/SingleLogoutProfileConfiguration.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/SingleLogoutProfileConfiguration.java
@@ -23,15 +23,19 @@ import java.util.Collections;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.messaging.context.MessageContext;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.logic.NoConfidentialityMessageChannelPredicate;
import org.opensaml.profile.logic.NoIntegrityMessageChannelPredicate;
import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
/** Configuration support for SAML 2 Single Logout. */
@@ -40,10 +44,16 @@ public class SingleLogoutProfileConfiguration extends AbstractSAML2ArtifactAware
/** ID for this profile configuration. */
public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/saml2/logout";
+ /** Predicate used to determine if SOAP-based requests should be signed. */
+ @Nonnull private Predicate<MessageContext> signSOAPRequestsPredicate;
+
+ /** Predicate used to determine if SOAP-based requests should use client TLS. */
+ @Nonnull private Predicate<MessageContext> clientTLSSOAPRequestsPredicate;
+
/** Lookup function to supply {@link #qualifiedNameIDFormats} property. */
@Nullable private Function<ProfileRequestContext,Collection<String>> qualifiedNameIDFormatsLookupStrategy;
- /** NameID formats whose matching rules accomodate defaulted qualifiers. */
+ /** NameID formats whose matching rules accommodate defaulted qualifiers. */
@Nonnull @NonnullElements private Collection<String> qualifiedNameIDFormats;
/** Constructor. */
@@ -62,10 +72,60 @@ public class SingleLogoutProfileConfiguration extends AbstractSAML2ArtifactAware
setSignResponses(new NoIntegrityMessageChannelPredicate());
setEncryptNameIDs(new NoConfidentialityMessageChannelPredicate());
+ signSOAPRequestsPredicate = new org.opensaml.messaging.logic.NoIntegrityMessageChannelPredicate();
+ clientTLSSOAPRequestsPredicate =
+ Predicates.not(new org.opensaml.messaging.logic.NoIntegrityMessageChannelPredicate());
+
qualifiedNameIDFormats = Collections.emptyList();
}
/**
+ * Get the predicate used to determine if SOAP-based requests should be signed.
+ *
+ * @return predicate used to determine if SOAP-based requests should be signed
+ *
+ * @since 4.0.0
+ */
+ @Nonnull public Predicate<MessageContext> getSignSOAPRequests() {
+ return signSOAPRequestsPredicate;
+ }
+
+ /**
+ * Set the predicate used to determine if SOAP-based requests should be signed.
+ *
+ * @param predicate the predicate
+ *
+ * @since 4.0.0
+ */
+ public void setSignSOAPRequests(@Nonnull final Predicate<MessageContext> predicate) {
+ signSOAPRequestsPredicate = Constraint.isNotNull(predicate,
+ "Predicate used to determine SOAP-based signing cannot be null");
+ }
+
+ /**
+ * Get the predicate used to determine if SOAP-based requests should use client TLS.
+ *
+ * @return predicate used to determine if SOAP-based requests should use client TLS
+ *
+ * @since 4.0.0
+ */
+ @Nonnull public Predicate<MessageContext> getClientTLSSOAPRequests() {
+ return clientTLSSOAPRequestsPredicate;
+ }
+
+ /**
+ * Set the predicate used to determine if SOAP-based requests should use client TLS.
+ *
+ * @param predicate the predicate
+ *
+ * @since 4.0.0
+ */
+ public void setClientTLSSOAPRequests(@Nonnull final Predicate<MessageContext> predicate) {
+ clientTLSSOAPRequestsPredicate = Constraint.isNotNull(predicate,
+ "Predicate used to determine SOAP-based client TLS use cannot be null");
+ }
+
+ /**
* Get a collection of {@link org.opensaml.saml.saml2.core.NameID} Format values for which the use of
* the NameQualifier and SPNameQualifier attributes is defined to allow default/implicit values
* derived from the asserting and relying parties.
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SOAPLogoutRequest.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SOAPLogoutRequest.java
new file mode 100644
index 0000000..8f1defc
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SOAPLogoutRequest.java
@@ -0,0 +1,328 @@
+/*
+ * 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 net.shibboleth.idp.saml.saml2.profile.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.session.context.LogoutPropagationContext;
+import net.shibboleth.idp.session.context.LogoutPropagationContext.Result;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.opensaml.messaging.MessageException;
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.MessageLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.opensaml.saml.common.messaging.context.SAMLBindingContext;
+import org.opensaml.saml.common.messaging.context.SAMLEndpointContext;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+import org.opensaml.saml.common.messaging.soap.SAMLSOAPClientContextBuilder;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.LogoutRequest;
+import org.opensaml.saml.saml2.core.LogoutResponse;
+import org.opensaml.saml.saml2.core.Status;
+import org.opensaml.saml.saml2.core.StatusCode;
+import org.opensaml.security.SecurityException;
+import org.opensaml.soap.client.SOAPClient;
+import org.opensaml.soap.client.http.PipelineFactoryHttpSOAPClient;
+import org.opensaml.soap.common.SOAPException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+/**
+ * Profile action that propagates a prepared {@link LogoutRequest} message to an SP via the SOAP
+ * binding, encapsulating SOAP pipeline construction and execution.
+ *
+ * <p>The outbound message is pulled from the {@link ProfileRequestContext} to allow the surrounding
+ * flow to remain largely SOAP-unaware.</p>
+ *
+ * <p>Success or failure is reflected in a {@link LogoutPropagationContext} accessed via a lookup
+ * strategy.</p>
+ *
+ * <p>The response message is also stored off in the inbound message context.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_MESSAGE}
+ * @event {@link EventIds#IO_ERROR}
+ *
+ * @post {@link LogoutPropagationContext#getResult()} reflects the status of the logout attempt.
+ * @post profileRequestContext.getInboundMessageContext().getMessage() is populated if a response is obtained.
+ *
+ * @since 4.0.0
+ */
+public class SOAPLogoutRequest extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SOAPLogoutRequest.class);
+
+ /** Lookup strategy for {@link LogoutRequest} to process. */
+ @Nonnull private Function<ProfileRequestContext,LogoutRequest> logoutRequestLookupStrategy;
+
+ /** Lookup strategy for context in which to report result. */
+ @Nonnull private Function<ProfileRequestContext,LogoutPropagationContext> propagationContextLookupStrategy;
+
+ /** Strategy function for access to {@link SAMLMetadataContext} for input to SOAP client. */
+ @Nonnull private Function<ProfileRequestContext,SAMLMetadataContext> metadataContextLookupStrategy;
+
+ /** Strategy function for access to {@link SAMLEndpointContext} to populate. */
+ @Nonnull private Function<ProfileRequestContext,SAMLEndpointContext> endpointContextLookupStrategy;
+
+ /** SOAP client. */
+ @NonnullAfterInit private SOAPClient soapClient;
+
+ /** The SOAP client message pipeline name. */
+ @Nullable @NotEmpty private String soapPipelineName;
+
+ /** LogoutRequest to process. */
+ @Nullable private LogoutRequest logoutRequest;
+
+ /** LogoutPropagationContext. */
+ @Nullable private LogoutPropagationContext propagationContext;
+
+ /** Optional metadata for use in SOAP client. */
+ @Nullable private SAMLMetadataContext mdContext;
+
+ /** Endpoint context to determine destination address. */
+ @Nullable private SAMLEndpointContext epContext;
+
+ /** Constructor. */
+ public SOAPLogoutRequest() {
+
+ logoutRequestLookupStrategy = Functions.compose(new MessageLookup<>(LogoutRequest.class),
+ new OutboundMessageContextLookup());
+
+ propagationContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
+
+ // Default: outbound msg context -> SAMLPeerEntityContext -> SAMLMetadataContext
+ metadataContextLookupStrategy = Functions.compose(
+ new ChildContextLookup<>(SAMLMetadataContext.class),
+ Functions.compose(new ChildContextLookup<>(SAMLPeerEntityContext.class),
+ new OutboundMessageContextLookup()));
+
+ // Default: outbound msg context -> SAMLPeerEntityContext -> SAMLEndpointContext
+ endpointContextLookupStrategy = Functions.compose(
+ new ChildContextLookup<>(SAMLEndpointContext.class, true),
+ Functions.compose(new ChildContextLookup<>(SAMLPeerEntityContext.class, true),
+ new OutboundMessageContextLookup()));
+ }
+
+ /**
+ * Set the lookup strategy for the {@link LogoutRequest} to send.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setLogoutRequestLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,LogoutRequest> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ logoutRequestLookupStrategy = Constraint.isNotNull(strategy, "LogoutRequest lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy for the {@link LogoutPropagationContext} to update.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setPropagationContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,LogoutPropagationContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ propagationContextLookupStrategy =
+ Constraint.isNotNull(strategy, "LogoutPropagationContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set lookup strategy for {@link SAMLMetadataContext} for input to SOAP client.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setMetadataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,SAMLMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ metadataContextLookupStrategy = Constraint.isNotNull(strategy,
+ "SAMLMetadataContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set lookup strategy for {@link SAMLEndpointContext} to read from.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEndpointContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,SAMLEndpointContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ endpointContextLookupStrategy = Constraint.isNotNull(strategy,
+ "SAMLEndpointContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the SOAP client instance.
+ *
+ * @param client the SOAP client
+ */
+ public void setSOAPClient(@Nonnull final SOAPClient client) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ soapClient = Constraint.isNotNull(client, "SOAPClient cannot be null");
+ }
+
+ /**
+ * Set the name of the specific SOAP client message pipeline to use,
+ * for example with {@link PipelineFactoryHttpSOAPClient}.
+ *
+ * @param name the pipeline name, or null
+ */
+ public void setSOAPPipelineName(@Nullable @NotEmpty final String name) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ soapPipelineName = StringSupport.trimOrNull(name);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (soapClient == null) {
+ throw new ComponentInitializationException("SOAPClient cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ propagationContext = propagationContextLookupStrategy.apply(profileRequestContext);
+ if (propagationContext == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ logoutRequest = logoutRequestLookupStrategy.apply(profileRequestContext);
+ if (logoutRequest == null) {
+ log.warn("{} No LogoutRequest found to process", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ epContext = endpointContextLookupStrategy.apply(profileRequestContext);
+ if (epContext == null || epContext.getEndpoint() == null || epContext.getEndpoint().getLocation() == null) {
+ log.warn("{} No destination endpoint found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ mdContext = metadataContextLookupStrategy.apply(profileRequestContext);
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ try {
+ final InOutOperationContext<LogoutResponse,LogoutRequest> opContext = new SAMLSOAPClientContextBuilder()
+ .setOutboundMessage(logoutRequest)
+ .setProtocol(SAMLConstants.SAML20P_NS)
+ .setPipelineName(soapPipelineName)
+ .setSecurityConfigurationProfileId(profileRequestContext.getProfileId())
+ .setPeerRoleDescriptor(mdContext != null ? mdContext.getRoleDescriptor() : null)
+ .build();
+
+ logoutRequest.setDestination(epContext.getEndpoint().getLocation());
+
+ log.debug("{} Executing LogoutRequest over SOAP 1.1 binding to endpoint: {}", getLogPrefix(),
+ logoutRequest.getDestination());
+
+ soapClient.send(logoutRequest.getDestination(), opContext);
+ final LogoutResponse response = opContext.getInboundMessageContext().getMessage();
+
+ if (response == null) {
+ throw new MessageException("No response message received");
+ }
+
+ // Store off message so audit extraction works.
+ // Also mock/copy SAMLBindingContext for the same reason (it's SOAP in both directions).
+ profileRequestContext.getInboundMessageContext().setMessage(response);
+ final SAMLBindingContext bctx =
+ profileRequestContext.getInboundMessageContext().getSubcontext(SAMLBindingContext.class, true);
+ bctx.setBindingDescriptor(
+ profileRequestContext.getOutboundMessageContext().getSubcontext(
+ SAMLBindingContext.class).getBindingDescriptor());
+
+ log.debug("{} Processing LogoutResponse received via SOAP 1.1 binding from endpoint: {}", getLogPrefix(),
+ logoutRequest.getDestination());
+ handleResponse(profileRequestContext, response);
+ } catch (final ClassCastException e) {
+ log.warn("{} SOAP message payload was not an instance of LogoutResponse", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ } catch (final MessageException | SOAPException | SecurityException e) {
+ log.warn("{} SOAP logout request failed", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ }
+ }
+
+ /**
+ * Turn status from response into an appropriate result.
+ *
+ * @param profileRequestContext current profile request context
+ * @param response message to examine
+ */
+ private void handleResponse(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final LogoutResponse response) {
+ final Status status = response.getStatus();
+ if (status != null) {
+ final StatusCode code = status.getStatusCode();
+ if (code != null) {
+ if (StatusCode.SUCCESS.equals(code.getValue())) {
+ log.debug("{} LogoutResponse was successful", getLogPrefix());
+ propagationContext.setResult(Result.Success);
+ return;
+ }
+ log.warn("{} LogoutResponse received with status code '{}'", getLogPrefix(), code.getValue());
+ } else {
+ log.warn("{} LogoutResponse received with no status code", getLogPrefix());
+ }
+ } else {
+ log.warn("{} LogoutResponse received with no status", getLogPrefix());
+ }
+ }
+
+}
\ 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