[java-identity-provider] branch main updated: IDP-1829 - Add option to disable outbound logout
Scott Cantor
cantor.2 at osu.edu
Fri Jun 11 18:03:59 UTC 2021
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=aac60b27814aae9830f741a7f96cadf38403dde8
The following commit(s) were added to refs/heads/main by this push:
new aac60b278 IDP-1829 - Add option to disable outbound logout
aac60b278 is described below
commit aac60b27814aae9830f741a7f96cadf38403dde8
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Jun 11 14:03:54 2021 -0400
IDP-1829 - Add option to disable outbound logout
https://issues.shibboleth.net/jira/browse/IDP-1829
Option added to treat lack of endpoints as async.
---
.../flows/saml/saml2/slo-front-abstract-beans.xml | 4 +
.../flows/saml/saml2/slo-front-abstract-flow.xml | 20 ++-
idp-conf/src/main/resources/conf/idp.properties | 3 +
idp-conf/src/test/resources/conf/idp.properties | 3 +
.../profile/impl/PreProcessLogoutMessage.java | 171 +++++++++++++++++++++
.../saml2/profile/impl/ProcessLogoutRequest.java | 6 -
.../profile/impl/PreProcessLogoutMessageTest.java | 104 +++++++++++++
7 files changed, 294 insertions(+), 17 deletions(-)
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-beans.xml
index 5909d9823..5ee862cd8 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-beans.xml
@@ -28,6 +28,10 @@
p:loggingId="%{idp.service.logging.saml2slo:Logout}"
p:browserProfile="true" />
+ <bean id="PreProcessLogoutMessage"
+ class="net.shibboleth.idp.saml.saml2.profile.impl.PreProcessLogoutMessage" scope="prototype"
+ p:assumeAsynchronousLogout="%{idp.logout.assumeAsync:false}" />
+
<bean id="InitializeMessageChannelSecurityContext"
class="org.opensaml.profile.action.impl.StaticMessageChannelSecurity" scope="prototype"
p:confidentialityActive="false" p:integrityActive="false" />
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-flow.xml
index 9a290019b..95813d4d6 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-flow.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/saml/saml2/slo-front-abstract-flow.xml
@@ -25,19 +25,18 @@
passes control back to the next parent state.
-->
<subflow-state id="DoInboundInterceptSubflow">
- <transition on="proceed" to="CheckLogoutResponse" />
+ <transition on="proceed" to="PreProcessLogoutMessage" />
</subflow-state>
- <decision-state id="CheckLogoutResponse">
- <if test="opensamlProfileRequestContext.getInboundMessageContext().getMessage() instanceof T(org.opensaml.saml.saml2.core.LogoutResponse)"
- then="LogoutResponseView" else="CheckAsync1"/>
- </decision-state>
+ <!-- Tri-decision state. -->
+ <action-state id="PreProcessLogoutMessage">
+ <evaluate expression="PreProcessLogoutMessage" />
+ <evaluate expression="'proceed'" />
- <decision-state id="CheckAsync1">
- <!-- If parent DoInboundInterceptSubflow transition changes on 'proceed', the else clause needs to also change. -->
- <if test="opensamlProfileRequestContext.getInboundMessageContext().getMessage() instanceof T(org.opensaml.saml.saml2.core.LogoutRequest) and opensamlProfileRequestContext.getInboundMessageContext().getMessage().getExtensions() != null and !opensamlProfileRequestContext.getInboundMessageContext().getMessage().getExtensions().getUnknownXMLObjects(T(org.opensaml.saml.ext.saml2aslo.Asynchronous).DEFAULT_ELEMENT_NAME).isEmpty()"
- then="AsyncDecryptionParameters" else="OutboundContextsAndSecurityParameters" />
- </decision-state>
+ <transition on="proceed" to="OutboundContextsAndSecurityParameters" />
+ <transition on="IsLogoutResponse" to="LogoutResponseView" />
+ <transition on="IsLogoutRequestAsync" to="AsyncDecryptionParameters" />
+ </action-state>
<!-- This is the one action inside "OutboundContextsAndSecurityParameters" that we can't skip. -->
<action-state id="AsyncDecryptionParameters">
@@ -46,7 +45,6 @@
<transition on="proceed" to="ExtractSubject" />
</action-state>
-
<!-- LogoutRequest handling starts by loading client storage if needed. -->
diff --git a/idp-conf/src/main/resources/conf/idp.properties b/idp-conf/src/main/resources/conf/idp.properties
index 7064c18d8..8b5d71a35 100644
--- a/idp-conf/src/main/resources/conf/idp.properties
+++ b/idp-conf/src/main/resources/conf/idp.properties
@@ -182,6 +182,9 @@ idp.bindings.inMetadataOrder = false
# Whether to require logout requests/responses be signed/authenticated.
#idp.logout.authenticated = true
+# Whether to handle logout lacking response endpoonts as asynchronous.
+#idp.logout.assumeAsync = false
+
# Whether to hide logout propagation status reporting.
#idp.logout.propagationHidden = false
diff --git a/idp-conf/src/test/resources/conf/idp.properties b/idp-conf/src/test/resources/conf/idp.properties
index c6dd2a22b..37c1297f2 100644
--- a/idp-conf/src/test/resources/conf/idp.properties
+++ b/idp-conf/src/test/resources/conf/idp.properties
@@ -163,6 +163,9 @@ idp.session.secondaryServiceIndex = true
# Whether to require logout requests/responses be signed/authenticated.
#idp.logout.authenticated = true
+# Whether to handle logout lacking response endpoonts as asynchronous.
+idp.logout.assumeAsync = true
+
# Whether to hide logout propagation status reporting.
#idp.logout.propagationHidden = false
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/PreProcessLogoutMessage.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/PreProcessLogoutMessage.java
new file mode 100644
index 000000000..27c16cb15
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/PreProcessLogoutMessage.java
@@ -0,0 +1,171 @@
+/*
+ * 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 java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.opensaml.saml.common.SAMLObject;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+import org.opensaml.saml.ext.saml2aslo.Asynchronous;
+import org.opensaml.saml.saml2.core.Extensions;
+import org.opensaml.saml.saml2.core.LogoutRequest;
+import org.opensaml.saml.saml2.core.LogoutResponse;
+import org.opensaml.saml.saml2.metadata.SSODescriptor;
+import org.opensaml.saml.saml2.metadata.SingleLogoutService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Profile action that performs initial analysis of a {@link LogoutRequest} or {@link LogoutResponse} to
+ * dispatch it for subsequent processing.
+ *
+ * <p>This action is essentially three decision states in one, using some custom events.</p>
+ *
+ * <p>If the inbound message is a {@link LogoutResponse} then the event "IsLogoutResponse" is signaled.</p>
+ *
+ * <p>If the inbound message is a {@link LogoutRequest}, then one of two events is signaled. If the request
+ * contains the {@link Asynchronous} extension, then the "IsLogoutRequestAsync" event is signaled. This also
+ * occurs, provided a particular option is enabled, if the request does not contain the extension but there
+ * is no SAML metadata available for the requester or the metadata contains no {@link SingleLogoutService}
+ * endpoints.</p>
+ *
+ * <p>Finally, {@link EventIds#PROCEED_EVENT_ID} is the result if neither of the above applies.</p>
+ *
+ * <p>Various standard events may occur if the message is missing or isn't an appropriate type.</p>
+ *
+ * @event {@link #IS_LOGOUT_RESPONSE}
+ * @event {@link #IS_LOGOUT_REQUEST_ASYNC}
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_MESSAGE}
+ *
+ * @since 4.2.0
+ */
+public class PreProcessLogoutMessage extends AbstractProfileAction {
+
+ /** Event to signal for a logout response. */
+ @Nonnull public static final String IS_LOGOUT_RESPONSE = "IsLogoutResponse";
+
+ /** Event to signal for a logout response. */
+ @Nonnull public static final String IS_LOGOUT_REQUEST_ASYNC = "IsLogoutRequestAsync";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PreProcessLogoutMessage.class);
+
+ /** Assume asynchronous in absence of metadata. */
+ private boolean assumeAsync;
+
+ /** Lookup strategy for metadata context. */
+ @Nonnull private Function<ProfileRequestContext,SAMLMetadataContext> metadataContextLookupStrategy;
+
+ /** Constructor. */
+ public PreProcessLogoutMessage() {
+ metadataContextLookupStrategy = new InboundMessageContextLookup().andThen(
+ new ChildContextLookup<>(SAMLPeerEntityContext.class).andThen(
+ new ChildContextLookup<>(SAMLMetadataContext.class)));
+ }
+
+ /**
+ * Sets whether to treat logout requests as asynchronous (not requiring a response) if no
+ * metadata is available or lacks endpoints.
+ *
+ * <p>Defaults to false.</p>
+ *
+ * @param flag
+ */
+ public void setAssumeAsynchronousLogout(final boolean flag) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ assumeAsync = flag;
+ }
+
+ /**
+ * Set the lookup strategy for the {@link SAMLMetadataContext}.
+ *
+ * @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");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ if (profileRequestContext.getInboundMessageContext() == null) {
+ log.warn("{} No inbound message context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ } else if (!(profileRequestContext.getInboundMessageContext().getMessage() instanceof SAMLObject)) {
+ log.warn("{} No inbound SAML message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final SAMLObject msg = (SAMLObject) profileRequestContext.getInboundMessageContext().getMessage();
+ if (msg instanceof LogoutResponse) {
+ ActionSupport.buildEvent(profileRequestContext, IS_LOGOUT_RESPONSE);
+ return;
+ } else if (!(msg instanceof LogoutRequest)) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ final Extensions exts = ((LogoutRequest) msg).getExtensions();
+ if (exts != null && !exts.getUnknownXMLObjects(Asynchronous.DEFAULT_ELEMENT_NAME).isEmpty()) {
+ log.debug("{} LogoutRequest contained Asynchronous extension", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IS_LOGOUT_REQUEST_ASYNC);
+ return;
+ }
+
+ if (assumeAsync) {
+ final SAMLMetadataContext mdCtx = metadataContextLookupStrategy.apply(profileRequestContext);
+ if (mdCtx == null || !(mdCtx.getRoleDescriptor() instanceof SSODescriptor) ||
+ ((SSODescriptor) mdCtx.getRoleDescriptor()).getSingleLogoutServices().isEmpty()) {
+ log.debug("{} LogoutRequest treated as Asynchronous due to lack of metadata", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IS_LOGOUT_REQUEST_ASYNC);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ProcessLogoutRequest.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ProcessLogoutRequest.java
index 55ccd97d5..1019cde32 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ProcessLogoutRequest.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ProcessLogoutRequest.java
@@ -53,7 +53,6 @@ import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.opensaml.saml.common.profile.SAMLEventIds;
-import org.opensaml.saml.ext.saml2aslo.Asynchronous;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.SessionIndex;
@@ -305,11 +304,6 @@ public class ProcessLogoutRequest extends AbstractProfileAction {
return false;
}
- if (log.isDebugEnabled() && logoutRequest.getExtensions() != null
- && !logoutRequest.getExtensions().getUnknownXMLObjects(Asynchronous.DEFAULT_ELEMENT_NAME).isEmpty()) {
- log.debug("{} LogoutRequest contained Asynchronous extension", getLogPrefix());
- }
-
qualifiedNameIDFormats = new HashSet<>(qualifiedNameIDFormatsLookupStrategy.apply(profileRequestContext));
return true;
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/PreProcessLogoutMessageTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/PreProcessLogoutMessageTest.java
new file mode 100644
index 000000000..fd9290b51
--- /dev/null
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/PreProcessLogoutMessageTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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 net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.idp.saml.saml2.profile.SAML2ActionTestingSupport;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.SAMLObjectBuilder;
+import org.opensaml.saml.ext.saml2aslo.Asynchronous;
+import org.opensaml.saml.saml2.core.Extensions;
+import org.opensaml.saml.saml2.core.LogoutRequest;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** {@link PreProcessLogoutMessage} unit test. */
+ at SuppressWarnings("javadoc")
+public class PreProcessLogoutMessageTest extends OpenSAMLInitBaseTestCase {
+
+ private RequestContext src;
+
+ private ProfileRequestContext prc;
+
+ private PreProcessLogoutMessage action;
+
+ @BeforeMethod public void setUpAction() throws ComponentInitializationException {
+ src = new RequestContextBuilder().buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+
+ action = new PreProcessLogoutMessage();
+ action.initialize();
+ }
+
+ @Test public void testNoMessage() {
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+ }
+
+ @Test public void testLogoutResponse() {
+ //final NameID nameId = SAML2ActionTestingSupport.buildNameID("jdoe");
+ prc.getInboundMessageContext().setMessage(SAML2ActionTestingSupport.buildLogoutResponse());
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, PreProcessLogoutMessage.IS_LOGOUT_RESPONSE);
+ }
+
+ @Test public void testLogoutRequest() {
+ prc.getInboundMessageContext().setMessage(SAML2ActionTestingSupport.buildLogoutRequest(null));
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ }
+
+ @Test public void testAssumeAsyncLogoutRequest() throws ComponentInitializationException {
+ prc.getInboundMessageContext().setMessage(SAML2ActionTestingSupport.buildLogoutRequest(null));
+
+ action = new PreProcessLogoutMessage();
+ action.setAssumeAsynchronousLogout(true);
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, PreProcessLogoutMessage.IS_LOGOUT_REQUEST_ASYNC);
+ }
+
+ @Test public void testAsyncLogoutRequest() {
+ prc.getInboundMessageContext().setMessage(SAML2ActionTestingSupport.buildLogoutRequest(null));
+
+ final SAMLObjectBuilder<Extensions> extsBuilder = (SAMLObjectBuilder<Extensions>)
+ XMLObjectProviderRegistrySupport.getBuilderFactory().<Extensions>getBuilderOrThrow(
+ Extensions.DEFAULT_ELEMENT_NAME);
+ final SAMLObjectBuilder<Asynchronous> asyncBuilder = (SAMLObjectBuilder<Asynchronous>)
+ XMLObjectProviderRegistrySupport.getBuilderFactory().<Asynchronous>getBuilderOrThrow(
+ Asynchronous.DEFAULT_ELEMENT_NAME);
+
+ ((LogoutRequest) prc.getInboundMessageContext().getMessage()).setExtensions(extsBuilder.buildObject());
+ ((LogoutRequest) prc.getInboundMessageContext().getMessage()).getExtensions().getUnknownXMLObjects().add(
+ asyncBuilder.buildObject());
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, PreProcessLogoutMessage.IS_LOGOUT_REQUEST_ASYNC);
+ }
+
+}
\ 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