[java-plugin-shibd] branch main updated: Add logout consumer flow support to core objects.
Codeberg
noreply at shibboleth.net
Wed May 20 13:40:16 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd/commit/258bf7575777a352d2731fc21e99ac4617d2b4cc
The following commit(s) were added to refs/heads/main by this push:
new 258bf75 Add logout consumer flow support to core objects.
258bf75 is described below
commit 258bf7575777a352d2731fc21e99ac4617d2b4cc
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Wed May 20 09:38:21 2026 -0400
Add logout consumer flow support to core objects.
---
.../META-INF/net.shibboleth.idp/postconfig.xml | 6 +-
.../logout-consumer-beans.xml} | 10 +-
.../sp/logout-consumer/logout-consumer-flow.xml | 30 ++++++
.../sp/token-consumer/token-consumer-beans.xml | 2 +-
.../sp/flows/LogoutConsumerFlowText.java | 93 ++++++++++++++++
.../main/java/net/shibboleth/sp/Application.java | 9 ++
.../shibboleth/sp/BasicProtocolSupportService.java | 32 ++++--
.../net/shibboleth/sp/ProtocolSupportService.java | 10 ++
.../profile/BasicLogoutConsumerFlowDescriptor.java | 86 +++++++++++++++
.../sp/profile/LogoutConsumerFlowDescriptor.java | 45 ++++++++
.../net/shibboleth/sp/impl/BasicApplication.java | 57 ++++++++++
.../impl/LogoutConsumerFlowDescriptorManager.java | 41 +++++++
.../sp/profile/impl/SelectLogoutConsumerFlow.java | 120 +++++++++++++++++++++
13 files changed, 528 insertions(+), 13 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 1795b0e..061a026 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -39,12 +39,16 @@
<bean id="shibboleth.RemotedHttpServletRequestSupplier" class="net.shibboleth.sp.messaging.impl.RemotedHttpServletRequestSupplier" />
<bean id="shibboleth.RemotedHttpServletResponseSupplier" class="net.shibboleth.sp.messaging.impl.RemotedlHttpServletResponseSupplier" />
- <!-- Management bean to collect and expose TokenConsumerFlowDescriptors. -->
+ <!-- Management bean to collect and expose consumer flow descriptors. -->
<bean id="shibboleth.sp.TokenConsumerFlowDescriptorManager"
class="net.shibboleth.sp.profile.impl.TokenConsumerFlowDescriptorManager"
p:components="#{getObject('shibboleth.sp.AvailableTokenConsumerFlows')}" />
+ <bean id="shibboleth.sp.LogoutConsumerFlowDescriptorManager"
+ class="net.shibboleth.sp.profile.impl.LogoutConsumerFlowDescriptorManager"
+ p:components="#{getObject('shibboleth.sp.AvailableLogoutConsumerFlows')}" />
+
<bean id="shibboleth.sp.RemotedCookieManager" class="net.shibboleth.shared.net.CookieManager" lazy-init="true"
p:guardSetAttribute="false"
p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-consumer/logout-consumer-beans.xml
similarity index 72%
copy from sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
copy to sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-consumer/logout-consumer-beans.xml
index c374136..a89a953 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-consumer/logout-consumer-beans.xml
@@ -7,11 +7,11 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="initialize" default-destroy-method="destroy">
- <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/token-consumer" />
- <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.consumer:SPAgent.TokenConsumer}" />
+ <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/logout-consumer" />
+ <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.logout-consumer:SPAgent.LogoutConsumer}" />
- <bean id="SelectTokenConsumerFlow"
- class="net.shibboleth.sp.profile.impl.SelectTokenConsumerFlow" scope="prototype"
- p:availableFlows="#{getObject('shibboleth.sp.TokenConsumerFlowDescriptorManager').getComponents()}" />
+ <bean id="SelectLogoutConsumerFlow"
+ class="net.shibboleth.sp.profile.impl.SelectLogoutConsumerFlow" scope="prototype"
+ p:availableFlows="#{getObject('shibboleth.sp.LogoutConsumerFlowDescriptorManager').getComponents()}" />
</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-consumer/logout-consumer-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-consumer/logout-consumer-flow.xml
new file mode 100644
index 0000000..eeebca7
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-consumer/logout-consumer-flow.xml
@@ -0,0 +1,30 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="sp/application">
+
+ <action-state id="InitializeProfileRequestContext">
+ <evaluate expression="InitializeProfileRequestContext" />
+ <evaluate expression="'proceed'" />
+
+ <!-- Branch to parent flow for authentication. -->
+ <transition on="proceed" to="AuthenticationSetup" />
+ </action-state>
+
+ <!-- Resume flow operation after set up by parent. -->
+ <action-state id="DoApplicationOperation">
+ <evaluate expression="SelectLogoutConsumerFlow" />
+
+ <!-- Call a subflow with the same ID as the event. -->
+ <transition on="#{currentEvent.id.startsWith('sp/logout/consumer/')}" to="CallConsumerFlow" />
+ </action-state>
+
+ <subflow-state id="CallConsumerFlow" subflow="#{currentEvent.id}">
+ <input name="calledAsSubflow" value="true" />
+ <transition on="proceed" to="EncodeAgentResponse" />
+ </subflow-state>
+
+ <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+ <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/logout-consumer/logout-consumer-beans.xml" />
+
+</flow>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
index c374136..5c55e74 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/token-consumer/token-consumer-beans.xml
@@ -8,7 +8,7 @@
default-init-method="initialize" default-destroy-method="destroy">
<bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/token-consumer" />
- <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.consumer:SPAgent.TokenConsumer}" />
+ <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.token-consumer:SPAgent.TokenConsumer}" />
<bean id="SelectTokenConsumerFlow"
class="net.shibboleth.sp.profile.impl.SelectTokenConsumerFlow" scope="prototype"
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutConsumerFlowText.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutConsumerFlowText.java
new file mode 100644
index 0000000..7624ec7
--- /dev/null
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutConsumerFlowText.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed 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.sp.flows;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.profile.SPConstants;
+
+/**
+ * Unit test for the SP logout-consumer flow (basics only).
+ */
+public class LogoutConsumerFlowText extends AbstractSPFlowTest {
+
+ /** Flow ID. */
+ @Nonnull public static final String FLOW_ID = "sp/logout-consumer";
+
+ protected LogoutConsumerFlowText() {
+ super(FLOW_ID);
+ }
+
+ /**
+ * Test flow with no application specified.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testNoApplication() throws IOException {
+ setDefaultAuth();
+ setRequest("POST", new DDF(null));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+ }
+
+ /**
+ * Test flow with invalid application ID.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testBadApplication() throws IOException {
+ setDefaultAuth();
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(SPConstants.APPLICATION).string("foo");
+ setRequest("POST", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+ }
+
+ /**
+ * Test flow with no consumer subflows installed.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testNoFlows() throws IOException {
+ setDefaultAuth();
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(SPConstants.APPLICATION).string(APPLICATION_ID);
+ setRequest("POST", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
+ }
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
index e8e14d8..40c23c4 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
@@ -102,6 +102,15 @@ public interface Application extends RelyingPartyConfigurationResolver {
*/
@Nonnull List<String> getLogoutInitiators(@Nullable final ProfileRequestContext profileRequestContext);
+ /**
+ * Gets an ordered list of logout consumer flows to attempt for this application.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return ordered list of unprefixed flow IDs
+ */
+ @Nonnull List<String> getLogoutConsumers(@Nullable final ProfileRequestContext profileRequestContext);
+
/**
* Gets the {@link StateManager} to use for thie application.
*
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java b/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java
index 6757095..ce13d31 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java
@@ -42,11 +42,14 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
/** Session Initiator flow IDs. */
@Nonnull private List<String> sessionInitiators;
- /** Consumer flow IDs. */
- @Nonnull private List<String> consumers;
+ /** Token Consumer flow IDs. */
+ @Nonnull private List<String> tokenConsumers;
/** Logout Initiator flow IDs. */
@Nonnull private List<String> logoutInitiators;
+
+ /** Logout Consumer flow IDs. */
+ @Nonnull private List<String> logoutConsumers;
/** Non-metadata-driven verified configurations. */
@Nonnull private Collection<ProfileConfiguration> profileConfigurations;
@@ -63,8 +66,9 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
/** Constructor. */
public BasicProtocolSupportService() {
sessionInitiators = CollectionSupport.emptyList();
- consumers = CollectionSupport.emptyList();
+ tokenConsumers = CollectionSupport.emptyList();
logoutInitiators = CollectionSupport.emptyList();
+ logoutConsumers = CollectionSupport.emptyList();
profileConfigurations = CollectionSupport.emptySet();
metadataDrivenProfileConfigurations = CollectionSupport.emptySet();
unverifiedProfileConfigurations = CollectionSupport.emptySet();
@@ -105,18 +109,18 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
/** {@inheritDoc} */
@Nonnull @Unmodifiable @NotLive public List<String> getTokenConsumers() {
- return consumers;
+ return tokenConsumers;
}
/**
* Sets the ordered list of token consumer flow IDs to install.
*
- * @param flows ordered list of unprefixed consymer flow IDs
+ * @param flows ordered list of unprefixed token consumer flow IDs
*/
public void setTokenConsumers(@Nullable final List<String> flows) {
checkSetterPreconditions();
- consumers = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
+ tokenConsumers = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
}
/** {@inheritDoc} */
@@ -135,6 +139,22 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
logoutInitiators = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
}
+ /** {@inheritDoc} */
+ @Nonnull @Unmodifiable @NotLive public List<String> getLogoutConsumers() {
+ return logoutConsumers;
+ }
+
+ /**
+ * Sets the ordered list of logout consumer flow IDs to install.
+ *
+ * @param flows ordered list of unprefixed logout consumer flow IDs
+ */
+ public void setLogoutConsumers(@Nullable final List<String> flows) {
+ checkSetterPreconditions();
+
+ logoutConsumers = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
+ }
+
/** {@inheritDoc} */
@Nonnull @Unmodifiable @NotLive public Collection<ProfileConfiguration> getDefaultProfileConfigurations(
boolean metadataDriven) {
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java b/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java
index 4e08daa..7c428f6 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java
@@ -65,6 +65,16 @@ public interface ProtocolSupportService extends IdentifiedComponent, Initializab
* @return ordered list of unprefixed flow IDs
*/
@Nonnull @Unmodifiable @NotLive List<String> getLogoutInitiators();
+
+ /**
+ * Gets a default-ordered list of logout consumer flows supported by this protocol plugin.
+ *
+ * <p>The default order should reflect what "most" deployers would expect to use when adding
+ * support for this plugin to their system.</p>
+ *
+ * @return ordered list of unprefixed flow IDs
+ */
+ @Nonnull @Unmodifiable @NotLive List<String> getLogoutConsumers();
/**
* Gets the collection of default {@link ProfileConfiguration} objects supplied by this protocol plugin.
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/BasicLogoutConsumerFlowDescriptor.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/BasicLogoutConsumerFlowDescriptor.java
new file mode 100644
index 0000000..6344582
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/BasicLogoutConsumerFlowDescriptor.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed 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.sp.profile;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+
+/**
+ * Basic implementation of a descriptor for a logout consumer flow.
+ */
+public class BasicLogoutConsumerFlowDescriptor extends AbstractIdentifiableInitializableComponent
+ implements LogoutConsumerFlowDescriptor {
+
+ /** Whether this flow supports a particular request. */
+ @Nonnull private Predicate<ProfileRequestContext> activationCondition;
+
+ /** Constructor. */
+ public BasicLogoutConsumerFlowDescriptor() {
+ activationCondition = PredicateSupport.alwaysFalse();
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull public Predicate<ProfileRequestContext> getActivationCondition() {
+ return activationCondition;
+ }
+
+ /**
+ * Set activation condition to determine whether this flow supports a request.
+ *
+ * @param condition condition to use
+ */
+ public void setActivationCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return ensureId().hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof BasicLogoutConsumerFlowDescriptor) {
+ return ensureId().equals(((BasicLogoutConsumerFlowDescriptor) obj).getId());
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("flowId", getId())
+ .toString();
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/profile/LogoutConsumerFlowDescriptor.java b/sp-server-api/src/main/java/net/shibboleth/sp/profile/LogoutConsumerFlowDescriptor.java
new file mode 100644
index 0000000..0c02348
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/profile/LogoutConsumerFlowDescriptor.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed 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.sp.profile;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.profile.FlowDescriptor;
+
+/**
+ * A descriptor for a logout consumer subflow.
+ *
+ * <p>Logout consumers are the portion of the SP responsible for consuming logout protocol
+ * responses that attempt to implement single logout. In SAML, this would be the
+ * "single logout service".</p>
+ *
+ * <p>The primary need for this is to associate each subflow with a condition that can
+ * determine whether a given request is handled by a given subflow. For example, determining
+ * the protocol in use, the parametes included in the request, etc.</p>
+ */
+public interface LogoutConsumerFlowDescriptor extends FlowDescriptor {
+
+ /**
+ * Gets the activation condition to run to determine whether this flow can handle the request.
+ *
+ * @return a condition governing use of this flow
+ */
+ @Nonnull Predicate<ProfileRequestContext> getActivationCondition();
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
index 4c16a5c..4d007e4 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
@@ -95,6 +95,9 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
/** Logout initiator list lookup strategy. */
@NonnullAfterInit private Function<ProfileRequestContext,List<String>> logoutInitiatorLookupStrategy;
+
+ /** Logout consumer list lookup strategy. */
+ @NonnullAfterInit private Function<ProfileRequestContext,List<String>> logoutConsumerLookupStrategy;
/** State management. */
@Nullable private StateManager stateManager;
@@ -377,7 +380,49 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
logoutInitiatorLookupStrategy = Constraint.isNotNull(strategy,
"Logout initiators lookup strategy cannot be null");
}
+
+ /** {@inheritDoc} */
+ @Nonnull public List<String> getLogoutConsumers(@Nullable final ProfileRequestContext profileRequestContext) {
+ final List<String> ret = logoutConsumerLookupStrategy.apply(profileRequestContext);
+ if (ret != null && !ret.isEmpty()) {
+ return ret;
+ }
+
+ if (parent != null && allowInheritance) {
+ return parent.getLogoutConsumers(profileRequestContext);
+ }
+
+ return CollectionSupport.emptyList();
+ }
+
+ /**
+ * Sets the ordered collection of unprefixed logout consumer flow IDs to use for this application.
+ *
+ * @param consumers ordered collection of unprefixed logout consumer flow IDs
+ */
+ public void setLogoutConsumers(@Nullable final Collection<String> consumers) {
+ checkSetterPreconditions();
+ if (consumers != null) {
+ logoutConsumerLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.copyToList(StringSupport.normalizeStringCollection(consumers)));
+ } else {
+ logoutConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for the ordered collection of unprefixed logout consumer flow IDs
+ * to use for this application.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setLogoutConsumersLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ checkSetterPreconditions();
+
+ logoutConsumerLookupStrategy = Constraint.isNotNull(strategy, "Logout consumers lookup strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Nonnull public StateManager getStateManager() {
checkComponentActive();
@@ -608,6 +653,9 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
if (logoutInitiatorLookupStrategy == null) {
logoutInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
}
+ if (logoutConsumerLookupStrategy == null) {
+ logoutConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
return;
}
@@ -649,6 +697,15 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
}
logoutInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(initiators));
}
+
+ if (logoutConsumerLookupStrategy == null) {
+ final ArrayList<String> consumers = new ArrayList<>();
+ for (final ProtocolSupportService service : protocolServiceManager.getComponents()) {
+ assert service != null;
+ consumers.addAll(StringSupport.normalizeStringCollection(service.getLogoutConsumers()));
+ }
+ logoutConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(consumers));
+ }
}
/** {@inheritDoc} */
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/LogoutConsumerFlowDescriptorManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/LogoutConsumerFlowDescriptorManager.java
new file mode 100644
index 0000000..d831738
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/LogoutConsumerFlowDescriptorManager.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed 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.sp.profile.impl;
+
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import net.shibboleth.shared.spring.config.IdentifiedComponentManager;
+import net.shibboleth.sp.profile.LogoutConsumerFlowDescriptor;
+
+/**
+ * Manager of {@link LogoutConsumerFlowDescriptor} objects.
+ */
+public class LogoutConsumerFlowDescriptorManager extends IdentifiedComponentManager<LogoutConsumerFlowDescriptor> {
+
+ /**
+ * Constructor.
+ *
+ * @param freeObjects free-standing objects
+ */
+ @Autowired
+ public LogoutConsumerFlowDescriptorManager(@Nullable final Collection<LogoutConsumerFlowDescriptor> freeObjects) {
+ super(freeObjects);
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectLogoutConsumerFlow.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectLogoutConsumerFlow.java
new file mode 100644
index 0000000..af883b5
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/SelectLogoutConsumerFlow.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed 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.sp.profile.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.profile.LogoutConsumerFlowDescriptor;
+
+/**
+ * Action that checks all enabled logout consumer flows and returns the flow ID of the first
+ * enabled flow indicating it can handle the request.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_POTENTIAL_FLOW}
+ * @event selected logout consumer flow ID
+ */
+public class SelectLogoutConsumerFlow extends AbstractApplicationAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(SelectLogoutConsumerFlow.class);
+
+ /** Map of available consumer flows. */
+ @Nonnull private Map<String,LogoutConsumerFlowDescriptor> availableFlows;
+
+ /** Constructor. */
+ public SelectLogoutConsumerFlow() {
+ availableFlows = CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Sets the available token consumer flows to consider.
+ *
+ * @param flows available flows
+ */
+ public void setAvailableFlows(@Nonnull final Collection<LogoutConsumerFlowDescriptor> flows) {
+ checkSetterPreconditions();
+
+ availableFlows = flows.stream()
+ .collect(CollectionSupport.nonnullCollector(
+ Collectors.toUnmodifiableMap(LogoutConsumerFlowDescriptor::getId, Function.identity()))).get();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ if (ensureAgentRequestContext().getRemotedHttpServletRequest() == null) {
+ log.warn("No remoted HttpServletRequest available, malformed agent request?", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_POTENTIAL_FLOW);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ // We do the crazy stuff to accomodate flow activation conditions evaluating the servlet request.
+ try {
+ RemotedHttpServletRequestResponseContext.loadCurrent(ensureAgentRequestContext().getRemotedHttpServletRequest(),
+ ensureAgentRequestContext().getRemotedHttpServletResponse());
+
+ final List<String> flows = ensureApplication().getLogoutConsumers(profileRequestContext);
+ for (final String flowId : flows) {
+ assert flowId != null;
+ final LogoutConsumerFlowDescriptor flowDescriptor = availableFlows.get("sp/logout/consumer/" + flowId);
+ if (flowDescriptor != null) {
+ if (flowDescriptor.getActivationCondition().test(profileRequestContext)) {
+ log.debug("{} Logout consumer flow {} handling request", getLogPrefix(), flowId);
+ ActionSupport.buildEvent(profileRequestContext, "sp/logout/consumer/" + flowId);
+ return;
+ } else {
+ log.debug("{} Logout consumer flow {} cannot handle request", getLogPrefix(), flowId);
+ }
+ } else {
+ log.warn("{} Logout consumer flow {} is not available", getLogPrefix(), flowId);
+ }
+ }
+ } finally {
+ RemotedHttpServletRequestResponseContext.clearCurrent();
+ }
+
+ log.warn("{} No logout consumer flows were able to handle request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_POTENTIAL_FLOW);
+ }
+
+}
\ 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