[java-plugin-shibd] branch main updated: JSHIBD-18 - Automate population of initiator and token consumer flows

Codeberg noreply at shibboleth.net
Wed Jan 14 18:00:34 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/421e3445ab30e527737c79643f6053110c35422e

The following commit(s) were added to refs/heads/main by this push:
     new 421e344  JSHIBD-18 - Automate population of initiator and token consumer flows
421e344 is described below

commit 421e3445ab30e527737c79643f6053110c35422e
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Jan 14 13:00:16 2026 -0500

    JSHIBD-18 - Automate population of initiator and token consumer flows
    
    https://shibboleth.atlassian.net/browse/JSHIBD-18
---
 .../idp/flows/sp/initiator/disco/disco-beans.xml   |   8 +-
 .../session-initiator/session-initiator-flow.xml   |  14 ++-
 .../shibboleth/idp/module/conf/sp/sp.properties    |   4 -
 .../net/shibboleth/sp/conf/agents-system.xml       |  16 +--
 .../net/shibboleth/sp/service/utility.xml          |   3 +
 .../main/java/net/shibboleth/sp/Application.java   |  26 +++--
 .../shibboleth/sp/BasicProtocolSupportService.java | 110 +++++++++++++++++++++
 .../net/shibboleth/sp/ProtocolSupportService.java  |  54 ++++++++++
 .../java/net/shibboleth/sp/impl/BasicAgent.java    |  12 ++-
 .../net/shibboleth/sp/impl/BasicApplication.java   |  84 ++++++++++++++--
 .../sp/impl/ProtocolSupportServiceManager.java     |  60 +++++++++++
 .../sp/profile/impl/IssueDiscoveryRequest.java     |  27 +----
 .../impl/InitializeAgentRequestContextTest.java    |   3 +-
 .../sp/profile/impl/BaseAgentRequestTest.java      |   3 +-
 .../sp/profile/impl/IssueDiscoveryRequestTest.java |   2 +-
 15 files changed, 362 insertions(+), 64 deletions(-)

diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml
index e50a266..0cf4bb6 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/disco/disco-beans.xml
@@ -10,21 +10,15 @@
     <util:constant id="shiibboleth.sp.ProfileId"
         static-field="net.shibboleth.saml.saml2.profile.config.BrowserSSOProfileConfiguration.PROFILE_ID" />
 
-    <import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/saml2-common-beans.xml" />
-
     <bean id="ValidateSessionInitiatorRequest"
         class="net.shibboleth.sp.profile.impl.ValidateSessionInitiatorRequest" scope="prototype"
         p:flowId="discovery"
         p:requireDiscoveryURL="true"
         p:requireRelyingPartyId="false" />
-        
-    <bean id="DefaultDiscoveryURLStrategy" parent="shibboleth.Functions.Constant"
-        c:_0="#{'%{sp.discoveryURL:}'.trim()}" />
     
     <bean id="IssueDiscoveryRequest"
         class="net.shibboleth.sp.profile.impl.IssueDiscoveryRequest" scope="prototype"
         p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier"
-        p:createOutputObjects="true"
-        p:discoveryURLLookupStrategy="#{getObject('%{sp.discoveryURLFunction:DefaultDiscoveryURLStrategy}'.trim())}" />
+        p:createOutputObjects="true" />
 
 </beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
index 9c8ea82..1cf23bb 100644
--- a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-initiator/session-initiator-flow.xml
@@ -24,7 +24,7 @@
     </action-state>
 
     <decision-state id="CheckIterator">
-        <if test="SessionInitiatorIterator.hasNext()" then="CallInitiatorFlow" else="NoPotentialFlow" />
+        <if test="SessionInitiatorIterator.hasNext()" then="CallInitiatorFlow" else="CheckForDiscovery" />
     </decision-state>
     
     <subflow-state id="CallInitiatorFlow" subflow="sp/initiator/#{SessionInitiatorIterator.next()}">
@@ -33,6 +33,18 @@
         <transition on="ReselectFlow" to="CheckIterator" />
     </subflow-state>
     
+    <decision-state id="CheckForDiscovery">
+        <if test="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getApplication().getDiscoveryService(opensamlProfileRequestContext) != null"
+            then="CallDiscoveryFlow"
+            else="NoPotentialFlow" />
+    </decision-state>
+    
+    <subflow-state id="CallDiscoveryFlow" subflow="sp/initiator/disco">
+        <input name="calledAsSubflow" value="true" />
+        <transition on="proceed" to="EncodeAgentResponse" />
+        <transition on="ReselectFlow" to="NoPotentialFlow" />
+    </subflow-state>
+        
     <action-state id="NoPotentialFlow">
         <evaluate expression="'NoPotentialFlow'" />
     </action-state>
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index 700d23e..7e5f1ab 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -10,10 +10,6 @@ sp.issuer = https://sp.example.org
 #sp.service.agents.failFast = false
 sp.service.agents.checkInterval = PT5M
 
-# Default precedence/set of Session Initiator and Token Consumer flows to attempt
-#sp.application.sessionInitiators = 
-#sp.application.tokenConsumers =
-
 # General SP cookie properties
 #sp.cookie.secure = true
 #sp.cookie.httpOnly = true
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
index f44fcca..30c8b1b 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/sp/conf/agents-system.xml
@@ -18,12 +18,18 @@
     <!-- Exposes auto-wired map of Agents. -->
     <bean id="shibboleth.sp.AgentResolver" class="net.shibboleth.sp.impl.DefaultAgentResolver" />
 
+    <!-- Exposes auto-wired protocol support information from protocol plugins. -->
+    <bean id="shibboleth.sp.ProtocolSupportServiceManager" class="net.shibboleth.sp.impl.ProtocolSupportServiceManager" />
+
     <!-- Parent beans for Agents and Applications. -->
 
     <bean id="DefaultAllowedAddressRanges" parent="shibboleth.CommaDelimStringArray">
         <constructor-arg type="java.lang.String" value="%{sp.agent.authn.allowedAddressRanges:127.0.0.1/32,::1/128}" />
     </bean>
 
+    <bean id="DefaultDiscoveryServiceStrategy" parent="shibboleth.Functions.Constant"
+        c:_0="#{environment.containsProperty('sp.discoveryURL') ? '%{sp.discoveryURL:}'.trim() : null}" />
+
     <bean id="shibboleth.sp.Agent" parent="shibboleth.sp.Application.NoInheritance"
         class="net.shibboleth.sp.impl.BasicAgent" abstract="true"
         p:authenticationMethod="%{sp.agent.authn.method:}"
@@ -33,6 +39,7 @@
         p:postLimit="%{sp.postData.limit:1048576}" />
 
     <bean id="shibboleth.sp.Application.NoInheritance" class="net.shibboleth.sp.impl.BasicApplication" abstract="true"
+        c:_0-ref="shibboleth.sp.ProtocolSupportServiceManager"
         p:allowInheritance="false"
         p:metricName="net.shibboleth.sp.relyingparty.configurations"
         p:authenticatingAuthority="%{sp.defaultAuthority:}"
@@ -46,19 +53,12 @@
         p:profileConfigurations-ref="shibboleth.sp.DefaultProfileConfigurations"
         p:unverifiedConfiguration-ref="DefaultUnverifiedRelyingPartyConfiguration"
         p:relyingPartyConfigurations-ref="shibboleth.sp.RelyingPartyOverrides"
-        p:sessionInitiators-ref="shibboleth.sp.SessionInitiators"
-        p:tokenConsumers-ref="shibboleth.sp.TokenConsumers"
+        p:discoveryServiceLookupStrategy="#{getObject('%{sp.discoveryURLFunction:DefaultDiscoveryServiceStrategy}'.trim())}"
         p:stateTokenManager-ref="#{'%{sp.stateToken.Manager:shibboleth.sp.StorageStateTokenManager}'.trim()}" />
 
     <bean id="shibboleth.sp.Application" class="net.shibboleth.sp.impl.BasicApplication" abstract="true"
         p:metricName="net.shibboleth.sp.relyingparty.configurations" />
 
-    <bean id="shibboleth.sp.SessionInitiators" parent="shibboleth.CommaDelimStringArray"
-        c:_0="%{sp.application.sessionInitiators:}" />
-
-    <bean id="shibboleth.sp.TokenConsumers" parent="shibboleth.CommaDelimStringArray"
-        c:_0="%{sp.application.tokenConsumers:}" />
-
     <bean id="shibboleth.sp.PassthroughStateTokenManager" class="net.shibboleth.sp.impl.PassthroughStateTokenManager" lazy-init="true" />
 
     <bean id="shibboleth.sp.StorageStateTokenManager" class="net.shibboleth.sp.impl.StorageServiceStateTokenManager" lazy-init="true"
diff --git a/sp-conf-impl/src/test/resources/net/shibboleth/sp/service/utility.xml b/sp-conf-impl/src/test/resources/net/shibboleth/sp/service/utility.xml
index f5312f2..c3b0aef 100644
--- a/sp-conf-impl/src/test/resources/net/shibboleth/sp/service/utility.xml
+++ b/sp-conf-impl/src/test/resources/net/shibboleth/sp/service/utility.xml
@@ -17,6 +17,9 @@
     <bean id="shibboleth.IdentifiableBeanPostProcessor"
         class="net.shibboleth.shared.spring.config.IdentifiableBeanPostProcessor" />
     
+    <bean id="shibboleth.Functions.Constant"
+        class="net.shibboleth.shared.logic.FunctionSupport" factory-method="constant" abstract="true" />
+    
     <bean id="shibboleth.CommaDelimStringArray"
         class="org.springframework.util.StringUtils" factory-method="commaDelimitedListToStringArray" abstract="true" />
 
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 8bc03d1..0c75df6 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
@@ -65,7 +65,7 @@ public interface Application extends RelyingPartyConfigurationResolver {
     @Nullable @NotEmpty String getAuthenticatingAuthority(@Nullable final ProfileRequestContext profileRequestContext);
     
     /**
-     * Get an ordered list of session initiator flows to attempt for this application.
+     * Gets an ordered list of session initiator flows to attempt for this application.
      * 
      * @param profileRequestContext profile request context
      *  
@@ -74,7 +74,19 @@ public interface Application extends RelyingPartyConfigurationResolver {
     @Nonnull List<String> getSessionInitiators(@Nullable final ProfileRequestContext profileRequestContext);
 
     /**
-     * Get an ordered list of token consumer flows to attempt for this application.
+     * Gets the discovery service URL to use in the event a session initiator cannot be executed.
+     * 
+     * <p>In the absence of a value, the Hub will not attempt IdP discovery and session initiator requests that
+     * cannot be associated with a recognized authority to use will fail.</p>
+     * 
+     * @param profileRequestContext profile request context
+     * 
+     * @return discovery service location, or null
+     */
+    @Nullable String getDiscoveryService(@Nullable final ProfileRequestContext profileRequestContext);
+    
+    /**
+     * Gets an ordered list of token consumer flows to attempt for this application.
      * 
      * @param profileRequestContext profile request context
      *  
@@ -83,35 +95,35 @@ public interface Application extends RelyingPartyConfigurationResolver {
     @Nonnull List<String> getTokenConsumers(@Nullable final ProfileRequestContext profileRequestContext);
 
     /**
-     * Get the {@link StateTokenManager} to use for thie application.
+     * Gets the {@link StateTokenManager} to use for thie application.
      * 
      * @return the manager to use
      */
     @Nonnull StateTokenManager getStateTokenManager();
     
     /**
-     * Get {@link MetadataResolver} for this {@link Application}.
+     * Gets {@link MetadataResolver} for this {@link Application}.
      * 
      * @return the metadata source to use
      */
     @Nonnull ReloadableService<MetadataResolver> getMetadataResolver();
 
     /**
-     * Get {@link AttributeTranscoderRegistry} for this {@link Application}.
+     * Gets {@link AttributeTranscoderRegistry} for this {@link Application}.
      * 
      * @return the attribute transcoder registry to use
      */
     @Nonnull ReloadableService<AttributeTranscoderRegistry> getAttributeTranscoderRegistry();
 
     /**
-     * Get {@link AttributeResolver} for this {@link Application}.
+     * Gets {@link AttributeResolver} for this {@link Application}.
      * 
      * @return the attribute resolver to use
      */
     @Nonnull ReloadableService<AttributeResolver> getAttributeResolver();
 
     /**
-     * Get {@link AttributeFilter} for this {@link Application}.
+     * Gets {@link AttributeFilter} for this {@link Application}.
      * 
      * @return the attribute filter to use
      */
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
new file mode 100644
index 0000000..8077a36
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.springframework.core.Ordered;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * This is a simple implementation of the {@link ProtocolSupportService} interface for use
+ * by plugins that need to implement it.
+ */
+public class BasicProtocolSupportService extends AbstractIdentifiableInitializableComponent
+        implements ProtocolSupportService, Ordered {
+
+    /** Spring auto-wiring order. */
+    private int order;
+    
+    /** Initiator flow IDs. */
+    @Nonnull private List<String> initiators;
+    
+    /** Consumer flow IDs. */
+    @Nonnull private List<String> consumers;
+    
+    /** Constructor. */
+    public BasicProtocolSupportService() {
+        initiators = CollectionSupport.emptyList();
+        consumers = CollectionSupport.emptyList();
+    }
+    
+    /** {@inheritDoc} */
+    public int getOrder() {
+        return order;
+    }
+    
+    /**
+     * Set the order/priority value for the bean.
+     * 
+     * @param priority priority value
+     */
+    public void setOrder(final int priority) {
+        order = priority;
+    }
+    
+    /**
+     * Get a default-ordered list of session 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 public List<String> getSessionInitiators() {
+        return initiators;
+    }
+    
+    /**
+     * Sets the ordered list of 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));
+    }
+
+    /**
+     * Get a default-ordered list of token 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 public List<String> getTokenConsumers() {
+        return consumers;
+    }
+    
+    /**
+     * Sets the ordered list of token consumer flow IDs to install.
+     * 
+     * @param flows ordered list of unprefixed consymer flow IDs
+     */
+    public void setTokenConsumers(@Nullable final List<String> flows) {
+        checkSetterPreconditions();
+        
+        consumers = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
+    }
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..729eb15
--- /dev/null
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.component.IdentifiedComponent;
+
+/**
+ * This is an interface to a plugin-provided infrastructure bean that will be auto-wired
+ * into the Hub's supporting code in order to automatically install support for various
+ * aspects of a protocol's features.
+ * 
+ * <p>Plugins that implement particular SSO protocols for the Hub to operate on behalf of
+ * Agents will implement an instance of this interface to expose their specifics for use
+ * by the protocol-independent classes in this plugin.</p>
+ */
+public interface ProtocolSupportService extends IdentifiedComponent {
+
+    /**
+     * Get a default-ordered list of session 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 List<String> getSessionInitiators();
+
+    /**
+     * Get a default-ordered list of token 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 List<String> getTokenConsumers();
+    
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java
index 4141f8d..409e165 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicAgent.java
@@ -32,6 +32,7 @@ import javax.annotation.Nullable;
 import com.google.common.base.Functions;
 import com.google.common.base.MoreObjects;
 
+import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NonNegative;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
@@ -73,8 +74,15 @@ public class BasicAgent extends BasicApplication implements Agent {
     /** Application map. */
     @Nonnull private Map<String,Application> applicationMap;
     
-    /** Constructor. */
-    public BasicAgent() {
+    /**
+     * Constructor.
+     * 
+     * @param protocolManager  injected plugin capability objects
+     */
+    public BasicAgent(
+            @ParameterName(name="protocolManager") @Nonnull final ProtocolSupportServiceManager protocolManager) {
+        super(protocolManager);
+        
         supportsCachedAuthentication = true;
         allowedAddressRanges = CollectionSupport.emptySet();
         authenticationMethod  = null;
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 14955b3..6305d1b 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
@@ -15,6 +15,7 @@
 
 package net.shibboleth.sp.impl;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -42,6 +43,7 @@ import net.shibboleth.profile.relyingparty.BasicRelyingPartyConfiguration;
 import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
 import net.shibboleth.profile.relyingparty.VerifiedProfileCriterion;
 import net.shibboleth.profile.relyingparty.impl.DefaultRelyingPartyConfigurationResolver;
+import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -55,6 +57,7 @@ import net.shibboleth.shared.resolver.ResolverException;
 import net.shibboleth.shared.service.ReloadableService;
 import net.shibboleth.sp.Agent;
 import net.shibboleth.sp.Application;
+import net.shibboleth.sp.ProtocolSupportService;
 import net.shibboleth.sp.profile.StateTokenManager;
 import net.shibboleth.spring.security.CredentialHolder;
 
@@ -72,14 +75,20 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
     /** Our self-managed "defaults". */
     @NonnullAfterInit private ApplicationAwareRelyingPartyResolver relyingPartyResolver;
     
+    /** Manager of protocol auto-registration options. */
+    @Nonnull final ProtocolSupportServiceManager protocolServiceManager;
+    
     /** Lookup strategy for authenticating authority. */
     @Nonnull private Function<ProfileRequestContext,String> authenticatingAuthorityLookupStrategy;
     
     /** Session initiator list lookup strategy. */
-    @Nonnull private Function<ProfileRequestContext,List<String>> sessionInitiatorLookupStrategy;
+    @NonnullAfterInit private Function<ProfileRequestContext,List<String>> sessionInitiatorLookupStrategy;
 
+    /** Discovery service lookup strategy. */
+    @Nonnull private Function<ProfileRequestContext,String> discoveryServiceLookupStrategy;
+    
     /** Token consumer list lookup strategy. */
-    @Nonnull private Function<ProfileRequestContext,List<String>> tokenConsumerLookupStrategy;
+    @NonnullAfterInit private Function<ProfileRequestContext,List<String>> tokenConsumerLookupStrategy;
 
     /** State token management. */
     @Nullable private StateTokenManager stateTokenManager;
@@ -96,16 +105,22 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
     /** Attribute filter. */
     @Nullable private ReloadableService<AttributeFilter> attributeFilter;
     
-    /** Constructor. */
-    public BasicApplication() {
+    /**
+     * Constructor.
+     * 
+     * @param protocolManager injected plugin capability objects
+     */
+    public BasicApplication(
+            @ParameterName(name="protocolManager") @Nonnull final ProtocolSupportServiceManager protocolManager) {
+        protocolServiceManager = Constraint.isNotNull(protocolManager, "ProtocolSupportServiceManager cannot be null");
+        
         allowInheritance = true;
         
         relyingPartyResolver = new ApplicationAwareRelyingPartyResolver();
         relyingPartyResolver.setDefaultConfiguration(this);
         
         authenticatingAuthorityLookupStrategy = FunctionSupport.constant(null);
-        sessionInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
-        tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+        discoveryServiceLookupStrategy = FunctionSupport.constant(null);
     }
         
     /** {@inheritDoc} */
@@ -240,6 +255,33 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
                 "Session initiators lookup strategy cannot be null");
     }
 
+    /** [{@inheritDoc} */
+    @Nullable public String getDiscoveryService(@Nullable final ProfileRequestContext profileRequestContext) {
+        return discoveryServiceLookupStrategy.apply(profileRequestContext);
+    }
+    
+    /**
+     * Sets the discovery service URL to use in the event a session initiator cannot be executed.
+     * 
+     * @param url discovery service URL
+     */
+    public void setDiscoveryService(@Nullable final String url) {
+        checkSetterPreconditions();
+        
+        discoveryServiceLookupStrategy = FunctionSupport.constant(url);
+    }
+    
+    /**
+     * Sets the lookup strategy for the discovery service URL to use in the event a session initiator cannot be executed.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setDiscoveryServiceLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        checkSetterPreconditions();
+        
+        discoveryServiceLookupStrategy =
+                Constraint.isNotNull(strategy, "Discovery service lookup strategy cannot be null");
+    }
     
     /** {@inheritDoc} */
     @Nonnull public List<String> getTokenConsumers(@Nullable final ProfileRequestContext profileRequestContext) {
@@ -488,6 +530,16 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
         
         if (allowInheritance) {
             // Bypass component checking on the assumption the parent will handle it.
+            // This includes the flow properties that default in from the protocol support layer.
+            // But if those properties are still null here, set them to an empty list since
+            // they should devolve to the parent. Any non-null strategies had to be set by
+            // the deployer so they should stay put.
+            if (sessionInitiatorLookupStrategy == null) {
+                sessionInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+            }
+            if (tokenConsumerLookupStrategy == null) {
+                tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+            }
             return;
         }
         
@@ -502,6 +554,26 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
         } else if (attributeFilter == null) {
             throw new ComponentInitializationException("AttributeFilter cannot be null");
         }
+        
+        // Default in protocol support-directed flows.
+        
+        if (sessionInitiatorLookupStrategy == null) {
+            final ArrayList<String> initiators = new ArrayList<>();
+            for (final ProtocolSupportService service : protocolServiceManager.getComponents()) {
+                assert service != null;
+                initiators.addAll(StringSupport.normalizeStringCollection(service.getSessionInitiators()));
+            }
+            sessionInitiatorLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(initiators));
+        }
+        
+        if (tokenConsumerLookupStrategy == null) {
+            final ArrayList<String> consumers = new ArrayList<>();
+            for (final ProtocolSupportService service : protocolServiceManager.getComponents()) {
+                assert service != null;
+                consumers.addAll(StringSupport.normalizeStringCollection(service.getTokenConsumers()));
+            }
+            tokenConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(consumers));
+        }
     }
     
     /** {@inheritDoc} */
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/ProtocolSupportServiceManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/ProtocolSupportServiceManager.java
new file mode 100644
index 0000000..a36fa97
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/ProtocolSupportServiceManager.java
@@ -0,0 +1,60 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.spring.config.IdentifiedComponentManager;
+import net.shibboleth.sp.ProtocolSupportService;
+
+/**
+ * Manager of {@link ProtocolSupportService} objects.
+ */
+public class ProtocolSupportServiceManager extends IdentifiedComponentManager<ProtocolSupportService> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ProtocolSupportServiceManager.class);
+    
+    /**
+     * Constructor.
+     *
+     * @param freeObjects  free-standing objects
+     */
+    @Autowired
+    public ProtocolSupportServiceManager(@Nullable final List<ProtocolSupportService> freeObjects) {
+        super(freeObjects);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        for (final ProtocolSupportService service : getComponents()) {
+            assert service != null;
+            log.info("Installed SP Hub support for protocol {}: initiators ({}), consumers ({})", service.getId(),
+                    service.getSessionInitiators(), service.getTokenConsumers());
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java
index d5f152b..52d9ddc 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequest.java
@@ -30,9 +30,7 @@ import com.google.common.escape.Escaper;
 import com.google.common.net.UrlEscapers;
 
 import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.resolver.CriteriaSet;
@@ -63,9 +61,6 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(IssueDiscoveryRequest.class);
 
-    /** Strategy used to obtain the discovery URL. */
-    @NonnullAfterInit private Function<ProfileRequestContext,String> discoveryURLLookupStrategy;
-
     /** Strategy used to obtain the request issuer value. */
     @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
     
@@ -93,16 +88,6 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
         passiveCondition = new DefaultPassiveCondition();
     }
     
-    /**
-     * Set the strategy used to obtain the discovery URL.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setDiscoveryURLLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
-        checkSetterPreconditions();
-        discoveryURLLookupStrategy = Constraint.isNotNull(strategy, "Discovery URL lookup strategy cannot be null");
-    }
-    
     /**
      * Set the strategy used to locate the issuer value to use.
      * 
@@ -112,16 +97,6 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
         checkSetterPreconditions();
         issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
     }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (discoveryURLLookupStrategy == null) {
-            throw new ComponentInitializationException("Discovery URL lookup strategy cannot be null");
-        }
-    }
 
     /** {@inheritDoc} */
     @Override
@@ -131,7 +106,7 @@ public class IssueDiscoveryRequest extends AbstractApplicationAction {
             return false;
         }
         
-        discoveryURL = discoveryURLLookupStrategy.apply(profileRequestContext);
+        discoveryURL = ensureApplication().getDiscoveryService(profileRequestContext);
         if (discoveryURL == null) {
             log.error("{} Unable to obtain discovery URL for request", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java
index fd96267..e0bd62b 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/authn/impl/InitializeAgentRequestContextTest.java
@@ -32,6 +32,7 @@ import net.shibboleth.shared.testing.MockReloadableService;
 import net.shibboleth.sp.context.AgentRequestContext;
 import net.shibboleth.sp.impl.BasicAgent;
 import net.shibboleth.sp.impl.PassthroughStateTokenManager;
+import net.shibboleth.sp.impl.ProtocolSupportServiceManager;
 import net.shibboleth.sp.testing.MockAgentResolver;
 
 /**
@@ -51,7 +52,7 @@ public class InitializeAgentRequestContextTest {
         prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
         prc.ensureSubcontext(AuthenticationContext.class);
         
-        agent = new BasicAgent();
+        agent = new BasicAgent(new ProtocolSupportServiceManager(null));
         agent.setId("foo");
         agent.setMetadataResolver(new MockReloadableService<>(null));
         agent.setAttributeResolver(new MockReloadableService<>(null));
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java
index 004f70a..18bf605 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/BaseAgentRequestTest.java
@@ -25,6 +25,7 @@ import net.shibboleth.shared.testing.MockReloadableService;
 import net.shibboleth.sp.context.AgentRequestContext;
 import net.shibboleth.sp.impl.BasicAgent;
 import net.shibboleth.sp.impl.PassthroughStateTokenManager;
+import net.shibboleth.sp.impl.ProtocolSupportServiceManager;
 
 /**
  * Base class for unit tests that rely on set up of an agent request.
@@ -46,7 +47,7 @@ public abstract class BaseAgentRequestTest extends OpenSAMLInitBaseTestCase {
         prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
         arc = prc.ensureSubcontext(AgentRequestContext.class);
         
-        agent = new BasicAgent();
+        agent = new BasicAgent(new ProtocolSupportServiceManager(null));
         agent.setId("test");
         agent.setMetadataResolver(new MockReloadableService<>(null));
         agent.setAttributeResolver(new MockReloadableService<>(null));
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java
index 0e3fe4f..b20b388 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/profile/impl/IssueDiscoveryRequestTest.java
@@ -75,11 +75,11 @@ public class IssueDiscoveryRequestTest extends BaseAgplicationActionTest {
         rpc.initialize();
         
         application.setUnverifiedConfiguration(rpc);
+        application.setDiscoveryServiceLookupStrategy(input -> discoveryURL);
         application.initialize();
         
         action = new IssueDiscoveryRequest();
         action.setCreateOutputObjects(true);
-        action.setDiscoveryURLLookupStrategy(input -> discoveryURL);
         action.setHttpServletResponseSupplier(new RemotedlHttpServletResponseSupplier());
         action.initialize();
         

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


More information about the commits mailing list