[java-plugin-shibd] branch main updated: Add API and flow support for logout initiators.

Codeberg noreply at shibboleth.net
Tue Apr 7 15:17:55 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/c67a97a704c3cb6e3c90196860c84ca5d610c6e3

The following commit(s) were added to refs/heads/main by this push:
     new c67a97a  Add API and flow support for logout initiators.
c67a97a is described below

commit c67a97a704c3cb6e3c90196860c84ca5d610c6e3
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Apr 7 11:17:45 2026 -0400

    Add API and flow support for logout initiators.
---
 .../sp/logout-initiator/logout-initiator-beans.xml |  19 ++++
 .../sp/logout-initiator/logout-initiator-flow.xml  |  43 ++++++++
 .../flows/sp/logout/initiator/initiator-beans.xml  |  21 ++++
 .../flows/sp/logout/initiator/initiator-flow.xml   |  42 ++++++++
 .../sp/flows/LogoutInitiatorFlowTest.java          | 112 +++++++++++++++++++++
 .../main/java/net/shibboleth/sp/Application.java   |   9 ++
 .../shibboleth/sp/BasicProtocolSupportService.java |  32 ++++--
 .../net/shibboleth/sp/ProtocolSupportService.java  |  10 ++
 .../net/shibboleth/sp/impl/BasicApplication.java   |  59 +++++++++++
 .../sp/profile/impl/MapResourceToStateToken.java   |  30 +++++-
 10 files changed, 368 insertions(+), 9 deletions(-)

diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-beans.xml
new file mode 100644
index 0000000..f197e34
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-beans.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           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/session-initiator" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.initiator:SPAgent.SessionInitiator}" />
+
+    <bean id="MapResourceToStateToken" 
+        class="net.shibboleth.sp.profile.impl.MapResourceToStateToken" scope="prototype"
+        p:createOutputObjects="true"
+        p:targetRequired="false"
+        p:errorFatal="%{sp.stateToken.errorsFatal:false}" />
+        
+</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-flow.xml
new file mode 100644
index 0000000..b9da789
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-initiator/logout-initiator-flow.xml
@@ -0,0 +1,43 @@
+<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">
+        <on-entry>
+            <evaluate expression="new net.shibboleth.shared.collection.ReflectionSafeIterator(opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getApplication().getLogoutInitiators(opensamlProfileRequestContext).iterator())" result="flowScope.LogoutInitiatorIterator" />
+        </on-entry>
+        <evaluate expression="MapResourceToStateToken" />
+        <evaluate expression="'proceed'" />
+        
+        <!-- Branch to child flow for actual work. -->
+        <transition on="proceed" to="CheckIterator" />
+    </action-state>
+
+    <decision-state id="CheckIterator">
+        <if test="LogoutInitiatorIterator.hasNext()" then="CallInitiatorFlow" else="NoPotentialFlow" />
+    </decision-state>
+    
+    <subflow-state id="CallInitiatorFlow" subflow="sp/logout/initiator/#{LogoutInitiatorIterator.next()}">
+        <input name="calledAsSubflow" value="true" />
+        <transition on="proceed" to="EncodeAgentResponse" />
+        <transition on="ReselectFlow" to="CheckIterator" />
+    </subflow-state>
+    
+    <action-state id="NoPotentialFlow">
+        <evaluate expression="'NoPotentialFlow'" />
+    </action-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-initiator/logout-initiator-beans.xml" />
+
+</flow>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml
new file mode 100644
index 0000000..85815b8
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           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/session-initiator" />
+    <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.initiator:SPAgent.SessionInitiator}" />
+
+    <bean id="PreservePostData"
+        class="net.shibboleth.sp.profile.impl.PreservePostData" scope="prototype"
+        p:errorFatal="%{sp.postData.errorsFatal:false}"
+        p:lifetime="%{sp.postData.lifetime:PT15M}"
+        p:storageService-ref="#{'%{sp.postData.StorageService:shibboleth.StorageService}'.trim()}"
+        p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
+        p:cookiePrefix="#{'%{sp.postData.cookiePrefix:__Host-shibsp_post_}'.trim()}" />
+    
+</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-flow.xml
new file mode 100644
index 0000000..93b3882
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-flow.xml
@@ -0,0 +1,42 @@
+<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"
+      abstract="true">
+
+    <!-- Rudimentary impediment to direct execution of subflow. -->
+    <input name="calledAsSubflow" type="boolean" required="true" />
+
+    <!-- Defines the possible end-state events, and a global transition rule. -->
+
+    <!-- Successful completion of subflow. -->
+    <end-state id="proceed" />
+
+    <!-- Error events to reflect back from this subflow. -->
+    <end-state id="InvalidEvent" />
+    <end-state id="InvalidProfileContext" />
+    <end-state id="InvalidRelyingPartyContext" />
+    <end-state id="InvalidRelyingPartyConfiguration" />
+    <end-state id="ReselectFlow" />
+    <end-state id="RuntimeException" />
+
+    <action-state id="LogRuntimeException">
+        <on-entry>
+            <evaluate expression="LogRuntimeException.log('net.shibboleth.sp.profile.logoug.initiator', flowExecutionException)" />
+        </on-entry>
+        <evaluate expression="'RuntimeException'" />
+        
+        <transition to="RuntimeException" />
+    </action-state>
+
+    <global-transitions>
+        <transition on-exception="java.lang.RuntimeException" to="LogRuntimeException" />
+        <transition on="InvalidProfileContext" to="InvalidProfileContext" />
+        <transition on="InvalidRelyingPartyContext" to="InvalidRelyingPartyContext" />
+        <transition on="InvalidRelyingPartyConfiguration" to="InvalidRelyingPartyConfiguration" />
+        <transition on="ReselectFlow" to="ReselectFlow" />
+        <transition on="RuntimeException" to="RuntimeException" />
+    </global-transitions>
+
+    <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/logout/initiator/initiator-beans.xml" />
+
+</flow>
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutInitiatorFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutInitiatorFlowTest.java
new file mode 100644
index 0000000..dd78132
--- /dev/null
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutInitiatorFlowTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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 java.nio.charset.StandardCharsets;
+
+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-initiator flow (basics only).
+ */
+public class LogoutInitiatorFlowTest extends AbstractSPFlowTest {
+    
+    /** Flow ID. */
+    @Nonnull public static final String FLOW_ID = "sp/logout-initiator";
+
+    protected LogoutInitiatorFlowTest() {
+        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 initiator 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);
+        input.addmember(SPConstants.TARGET).unsafe_string("https://sp.example.org".getBytes(StandardCharsets.UTF_8));
+        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);
+    }
+
+    
+    /**
+     * Test flow with no initiator subflows installed.
+     * @throws IOException 
+     */
+    @Test
+    public void testNoFlowsNoTarget() 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 4cdfd61..bbe3915 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
@@ -93,6 +93,15 @@ public interface Application extends RelyingPartyConfigurationResolver {
      */
     @Nonnull List<String> getTokenConsumers(@Nullable final ProfileRequestContext profileRequestContext);
 
+    /**
+     * Gets an ordered list of logout initiator flows to attempt for this application.
+     * 
+     * @param profileRequestContext profile request context
+     *  
+     * @return ordered list of unprefixed flow IDs
+     */
+    @Nonnull List<String> getLogoutInitiators(@Nullable final ProfileRequestContext profileRequestContext);
+    
     /**
      * Gets the {@link StateTokenManager} 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 29e7e98..6757095 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
@@ -39,11 +39,14 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
     /** Spring auto-wiring order. */
     private int order;
     
-    /** Initiator flow IDs. */
-    @Nonnull private List<String> initiators;
+    /** Session Initiator flow IDs. */
+    @Nonnull private List<String> sessionInitiators;
     
     /** Consumer flow IDs. */
     @Nonnull private List<String> consumers;
+
+    /** Logout Initiator flow IDs. */
+    @Nonnull private List<String> logoutInitiators;
     
     /** Non-metadata-driven verified configurations. */
     @Nonnull private Collection<ProfileConfiguration> profileConfigurations;
@@ -59,8 +62,9 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
     
     /** Constructor. */
     public BasicProtocolSupportService() {
-        initiators = CollectionSupport.emptyList();
+        sessionInitiators = CollectionSupport.emptyList();
         consumers = CollectionSupport.emptyList();
+        logoutInitiators = CollectionSupport.emptyList();
         profileConfigurations = CollectionSupport.emptySet();
         metadataDrivenProfileConfigurations = CollectionSupport.emptySet();
         unverifiedProfileConfigurations = CollectionSupport.emptySet();
@@ -85,18 +89,18 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
     
     /** {@inheritDoc} */
     @Nonnull @Unmodifiable @NotLive public List<String> getSessionInitiators() {
-        return initiators;
+        return sessionInitiators;
     }
     
     /**
-     * Sets the ordered list of initiator flow IDs to install.
+     * Sets the ordered list of session initiator flow IDs to install.
      * 
      * @param flows ordered list of unprefixed initiator flow IDs
      */
     public void setSessionInitiators(@Nullable final List<String> flows) {
         checkSetterPreconditions();
         
-        initiators = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
+        sessionInitiators = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
     }
 
     /** {@inheritDoc} */
@@ -115,6 +119,22 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
         consumers = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
     }
 
+    /** {@inheritDoc} */
+    @Nonnull @Unmodifiable @NotLive public List<String> getLogoutInitiators() {
+        return logoutInitiators;
+    }
+    
+    /**
+     * Sets the ordered list of logout initiator flow IDs to install.
+     * 
+     * @param flows ordered list of unprefixed initiator flow IDs
+     */
+    public void setLogoutInitiators(@Nullable final List<String> flows) {
+        checkSetterPreconditions();
+        
+        logoutInitiators = 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 e91497c..4e08daa 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
@@ -56,6 +56,16 @@ public interface ProtocolSupportService extends IdentifiedComponent, Initializab
      */
     @Nonnull @Unmodifiable @NotLive List<String> getTokenConsumers();
 
+    /**
+     * Gets a default-ordered list of logout initiator 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> getLogoutInitiators();
+    
     /**
      * Gets the collection of default {@link ProfileConfiguration} objects supplied by this protocol plugin.
      * 
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 cb9b753..bb9c0d4 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
@@ -93,6 +93,9 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
     /** Token consumer list lookup strategy. */
     @NonnullAfterInit private Function<ProfileRequestContext,List<String>> tokenConsumerLookupStrategy;
 
+    /** Logout initiator list lookup strategy. */
+    @NonnullAfterInit private Function<ProfileRequestContext,List<String>> logoutInitiatorLookupStrategy;
+    
     /** State token management. */
     @Nullable private StateTokenManager stateTokenManager;
     
@@ -332,6 +335,50 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
         tokenConsumerLookupStrategy = Constraint.isNotNull(strategy, "Token consumers lookup strategy cannot be null");
     }
 
+    /** {@inheritDoc} */
+    @Nonnull public List<String> getLogoutInitiators(@Nullable final ProfileRequestContext profileRequestContext) {
+        final List<String> ret = logoutInitiatorLookupStrategy.apply(profileRequestContext);
+        if (ret != null && !ret.isEmpty()) {
+            return ret;
+        }
+        
+        if (parent != null && allowInheritance) {
+            return parent.getLogoutInitiators(profileRequestContext);
+        }
+        
+        return CollectionSupport.emptyList(); 
+    }
+    
+    /**
+     * Sets the ordered collection of unprefixed logout initiator flow IDs to use for this application.
+     * 
+     * @param initiators ordered collection of unprefixed logout initiator flow IDs
+     */
+    public void setLogoutInitiators(@Nullable final Collection<String> initiators) {
+        checkSetterPreconditions();
+        
+        if (initiators != null) {
+            logoutInitiatorLookupStrategy = FunctionSupport.constant(
+                    CollectionSupport.copyToList(StringSupport.normalizeStringCollection(initiators)));
+        } else {
+            logoutInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+        }
+    }
+    
+    /**
+     * Sets the lookup strategy for the ordered collection of unprefixed logout initiator flow IDs
+     * to use for this application.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setLogoutInitiatorsLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+        checkSetterPreconditions();
+        
+        logoutInitiatorLookupStrategy = Constraint.isNotNull(strategy,
+                "Logout initiators lookup strategy cannot be null");
+    }
+    
     /** {@inheritDoc} */
     @Nonnull public StateTokenManager getStateTokenManager() {
         checkComponentActive();
@@ -559,6 +606,9 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
             if (tokenConsumerLookupStrategy == null) {
                 tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
             }
+            if (logoutInitiatorLookupStrategy == null) {
+                logoutInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+            }
             return;
         }
         
@@ -591,6 +641,15 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
             }
             tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(consumers));
         }
+        
+        if (logoutInitiatorLookupStrategy == null) {
+            final ArrayList<String> initiators = new ArrayList<>();
+            for (final ProtocolSupportService service : protocolServiceManager.getComponents()) {
+                assert service != null;
+                initiators.addAll(StringSupport.normalizeStringCollection(service.getLogoutInitiators()));
+            }
+            logoutInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(initiators));
+        }
     }
     
     /** {@inheritDoc} */
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapResourceToStateToken.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapResourceToStateToken.java
index f8ef3a4..d09afca 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapResourceToStateToken.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/MapResourceToStateToken.java
@@ -36,7 +36,7 @@ import net.shibboleth.sp.profile.SPConstants;
  * 
  * <p>The existence of a {@link SPConstants#STATE} member will cause the action
  * to be skipped, while the absence of {@link SPConstants#TARGET} will result in
- * failure.</p>
+ * conditional failure.</p>
  * 
  * <p>Errors may be ignored or result in an {@link EventIds#IO_ERROR} event.</p>
  * 
@@ -52,12 +52,20 @@ public class MapResourceToStateToken extends AbstractApplicationAction {
     /** Whether an error constructing a state token is fatal. */
     private boolean errorFatal;
     
+    /** Whether to require target parameter in the absence of a state token. */
+    private boolean targetRequired;
+    
     /** Agent input. */
     @NonnullBeforeExec private DDF input;
 
     /** Target resource to operate on. */
     @NonnullBeforeExec private byte[] target;
     
+    /** Constructor. */
+    public MapResourceToStateToken() {
+        targetRequired = true;
+    }
+    
     /**
      * Sets whether an error computing a state token should result in a fatal event.
      * 
@@ -70,6 +78,20 @@ public class MapResourceToStateToken extends AbstractApplicationAction {
         
         errorFatal = flag;
     }
+
+    /**
+     * Sets whether the {@link SPConstants#TARGET} parameter is required if no state token
+     * exists already.
+     * 
+     * <p>Defaults to true.</p>
+     * 
+     * @param flag flag to set
+     */
+    public void setTargetRequired(final boolean flag) {
+        checkSetterPreconditions();
+        
+        targetRequired = flag;
+    }
     
     /** {@inheritDoc} */
     @Override
@@ -91,8 +113,10 @@ public class MapResourceToStateToken extends AbstractApplicationAction {
         
         target = input.getmember(SPConstants.TARGET).unsafe_string();
         if (target == null) {
-            log.warn("{} Input message did not contain {} member", getLogPrefix(), SPConstants.TARGET);
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+            if (targetRequired) {
+                log.warn("{} Input message did not contain {} member", getLogPrefix(), SPConstants.TARGET);
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+            }
             return false;
         }
         

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


More information about the commits mailing list