[java-plugin-shibd-oidc] branch main updated: Session initiator flow up to metadata lookup

Phil Smart philip.smart at jisc.ac.uk
Fri Aug 15 13:32:03 UTC 2025


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-plugin-shibd-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd-oidc.git;a=commit;h=b513d9ef070375291322dad85af95f29047e8d06

The following commit(s) were added to refs/heads/main by this push:
     new b513d9e  Session initiator flow up to metadata lookup
b513d9e is described below

commit b513d9ef070375291322dad85af95f29047e8d06
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Aug 15 14:32:00 2025 +0100

    Session initiator flow up to metadata lookup
---
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  35 ++++
 .../idp/flows/sp/initiator/oidc/oidc-beans.xml     |  33 +++-
 .../idp/flows/sp/initiator/oidc/oidc-flow.xml      |  13 +-
 .../sp/oidc/flows/OIDCAuthenticationFlowTest.java  |   2 +-
 .../idp/module/conf/sp/oidc-test-agents.xml        |   9 +-
 .../shibboleth/idp/module/conf/sp/sp.properties    |   2 +-
 sp-oidc-impl/pom.xml                               |  41 ++++
 .../java/net/shibboleth/sp/oidc/impl/Example.java  |  22 ---
 .../impl/PrepareOIDCInboundMessageContext.java     | 156 ++++++++++++++++
 .../impl/OIDCProviderMetadataLookupHandler.java    | 208 +++++++++++++++++++++
 10 files changed, 482 insertions(+), 39 deletions(-)

diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index d2a3e26..ea55793 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -14,4 +14,39 @@
     default-init-method="initialize" default-destroy-method="destroy">
 
 
+    <!-- OpenID Provider information resolver service beans. -->
+    <bean id="shibboleth.authn.oidc.rp.ProviderMetadataResolver"
+        class="net.shibboleth.oidc.metadata.impl.ReloadingProviderMetadataProvider"
+        c:resolverService-ref="shibboleth.authn.oidc.rp.ProviderMetadataResolverService" />
+
+    <bean id="shibboleth.authn.oidc.rp.ProviderMetadataResolverService"
+        class="net.shibboleth.shared.spring.service.ReloadableSpringService"
+        p:serviceConfigurations-ref="ExtendedProviderMetadataResolverResources"
+        p:failFast="%{idp.service.providermetadata.failFast:%{idp.service.failFast:false}}"
+        p:reloadCheckDelay="%{idp.service.providermetadata.checkInterval:PT0S}"
+        p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
+        p:beanFactoryPostProcessors-ref="shibboleth.PropertySourcesPlaceholderConfigurer">
+        <constructor-arg name="claz" value="net.shibboleth.oidc.metadata.ProviderMetadataResolver" />
+        <constructor-arg name="strategy">
+            <bean
+                class="net.shibboleth.oidc.profile.spring.relyingparty.metadata.impl.ProviderMetadataResolverServiceStrategy" />
+        </constructor-arg>
+    </bean>
+
+    <util:list id="shibboleth.DefaultProviderMetadataResolverResources">
+        <value>conditional:%{idp.home}/conf/authn/oidc-rp-providermetadata-resolvers.xml</value>
+    </util:list>
+    <!-- Auto-append system config file to resource set. -->
+    <bean id="ExtendedProviderMetadataResolverResources"
+        class="net.shibboleth.shared.spring.factory.CombiningListFactoryBean"
+        p:firstList="#{getObject('%{idp.service.providermetadata.resources:shibboleth.ProviderMetadataResolverResources}'.trim()) ?:
+                getObject('shibboleth.DefaultProviderMetadataResolverResources')}">
+        <property name="secondList">
+            <util:list>
+                <value>classpath:/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
+                </value>
+            </util:list>
+        </property>
+    </bean>
+
 </beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
index fb5acb4..ec603e5 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
@@ -6,17 +6,40 @@
                            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="ValidateSessionInitiatorRequest"
+        class="net.shibboleth.sp.profile.impl.ValidateSessionInitiatorRequest" scope="prototype"
+        p:flowId="oidc"
+        p:requireDiscoveryURL="false"
+        p:requireRelyingPartyId="true" />
+        
+        <!-- TODO Copied over -->
+    <bean id="PrepareInboundMessageContext" 
+        class="net.shibboleth.sp.oidc.impl.PrepareOIDCInboundMessageContext" scope="prototype"
+        p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+        p:identifierLookupStrategy-ref="shibboleth.RelyingPartyIdLookup.Simple">
+    </bean>
+    
+    <bean id="OIDCProviderMetadataLookup" parent="WebFlowMessageHandlerAdaptor" scope="prototype"
+        c:executionDirection="INBOUND">
+        <constructor-arg name="messageHandler"> <!-- TODO Copied over -->
+            <bean class="net.shibboleth.sp.oidc.metadata.impl.OIDCProviderMetadataLookupHandler"
+                scope="prototype">
+                <!-- <property name="providerMetadataResolver">
+                    <ref bean="shibboleth.authn.oidc.rp.ProviderMetadataResolver" />
+                </property> -->
+            </bean>
+        </constructor-arg>
+    </bean>
 
     <!-- <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="saml2"
-        p:requireDiscoveryURL="false"
-        p:requireRelyingPartyId="true" />
+    
 
     <bean id="PrepareInboundMessageContext"
             class="net.shibboleth.idp.saml.session.impl.PrepareInboundMessageContext" scope="prototype"
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
index 48e100e..466dd4b 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
@@ -5,10 +5,11 @@
 
     <!-- TODO. OIDC does not have the same session initiation mechanisms as SAML, so we will add our own -->
     
-    <!-- <action-state id="SAML2SessionInitiator">
+    <action-state id="OIDCSessionInitiator">
         <evaluate expression="ValidateSessionInitiatorRequest" />
-        <evaluate expression="PrepareInboundMessageContext" />
-        <evaluate expression="SAMLProtocolAndRole" />
+        <evaluate expression="PrepareInboundMessageContext" /> <!-- needs session and logout support -->
+        <evaluate expression="OIDCProviderMetadataLookup" />
+        <!-- <evaluate expression="SAMLProtocolAndRole" />
         <evaluate expression="SAMLMetadataLookup" />
         
         <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
@@ -27,13 +28,13 @@
 
         <evaluate expression="HandleOutboundMessage" />
         <evaluate expression="IssueCorrelationCookie" />
-        <evaluate expression="EncodeMessage" />
+        <evaluate expression="EncodeMessage" /> -->
         <evaluate expression="'proceed'" />
 
         <transition on="proceed" to="proceed" />
-        Remap any other events into a fall-through to the next flow.
+       <!--  Remap any other events into a fall-through to the next flow. -->
         <transition to="ReselectFlow" />
-    </action-state> -->
+    </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/initiator/oidc/oidc-beans.xml" />
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
index 4a23852..17f5bf3 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
@@ -76,7 +76,7 @@ public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
         input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
         input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
         input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
-        setApplicationRequest("no-metadata", input);
+        setApplicationRequest(APPLICATION_ID, input);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertFlowExecutionResult(result, FLOW_ID);
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
index d8656cb..b70c8b2 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
@@ -23,10 +23,11 @@
             <set>
                 <bean p:id="default" parent="shibboleth.Application"
                     p:issuer="https://testsp.example.org"
-                    p:authenticatingAuthority="https://idp.example.org"
-                    p:profileConfigurations-ref="test.ProfileConfigurations" />
+                    p:authenticatingAuthority="https://op.example.org"
+                    p:profileConfigurations-ref="test.ProfileConfigurations" 
+                    p:metadataResolver-ref="shibboleth.authn.oidc.rp.ProviderMetadataResolver"/>
 
-                <bean p:id="feature-blocking" parent="shibboleth.Application"
+<!--                 <bean p:id="feature-blocking" parent="shibboleth.Application"
                     p:issuer="https://testsp.example.org"
                     p:authenticatingAuthority="https://idp.example.org"
                     p:profileConfigurations-ref="test.featureBlockingProfileConfigurations" />
@@ -55,7 +56,7 @@
                     p:issuer="https://testsp.example.org"
                     p:authenticatingAuthority="https://idp.example.org"
                     p:profileConfigurations-ref="test.ProfileConfigurations"
-                    p:sessionInitiators="#{{}}" />
+                    p:sessionInitiators="#{{}}" /> -->
             </set>
         </property>
     </bean>
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index e006d59..ebc1e04 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -16,7 +16,7 @@ sp.service.agents.checkInterval = PT5M
 #sp.dataSealer = shibboleth.DataSealer
 
 # Default precedence/set of Session Initiator and Token Consumer flows to attempt
-sp.application.sessionInitiators = saml2
+sp.application.sessionInitiators = oidc
 sp.application.tokenConsumers = saml2/artifact, saml2/post, saml2/post-simplesign
 
 
diff --git a/sp-oidc-impl/pom.xml b/sp-oidc-impl/pom.xml
index 9abca38..0c57a4b 100644
--- a/sp-oidc-impl/pom.xml
+++ b/sp-oidc-impl/pom.xml
@@ -44,6 +44,47 @@
             <artifactId>idp-profile-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        
+        <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-crypto-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-crypto-api</artifactId>
+            <scope>provided</scope>
+        </dependency>         
+         <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-metadata-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-metadata-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-attribute-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-attribute-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-profile-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${oidc-common.groupId}</groupId>
+            <artifactId>oidc-common-profile-impl</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>${shib-profile.groupId}</groupId>
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/impl/Example.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/impl/Example.java
deleted file mode 100644
index 5dc692e..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/impl/Example.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.oidc.impl;
-
-/**
- *
- */
-public class Example {
-
-}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/impl/PrepareOIDCInboundMessageContext.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/impl/PrepareOIDCInboundMessageContext.java
new file mode 100644
index 0000000..c835dce
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/impl/PrepareOIDCInboundMessageContext.java
@@ -0,0 +1,156 @@
+/*
+ * 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.oidc.impl;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+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.profile.AbstractProfileAction;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Action that adds an inbound {@link MessageContext} and a {@link OIDCPeerEntityContext} to the
+ * {@link ProfileRequestContext} based on the identity of a relying party.
+ * 
+ * <p>If addToExistingInboundMessageContextPredicate is true, the {@link OIDCPeerEntityContext} is
+ * added as a subcontext to any existing inbound message context.</p>  
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @post Add an {@link OIDCPeerEntityContext} to the inbound message context.
+ */
+public class PrepareOIDCInboundMessageContext extends AbstractProfileAction {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareOIDCInboundMessageContext.class);
+    
+    /** Strategy to lookup the identifier of the OP/RP to base the inbound context on. */
+    @NonnullAfterInit private Function<ProfileRequestContext,String> identifierLookupStrategy;
+    
+    /** The identifier of the OP/RP to base the inbound context on. */
+    @Nullable private String identifier;
+    
+    /** 
+     * Should the peer entity context be added to an existing inbound message context or not.
+     * If not, a new inbound message context is created before the peer entity context is added.
+     * Defaults to false.
+     */
+    @Nonnull private Predicate<ProfileRequestContext> addToExistingInboundMessageContextPredicate;
+    
+    /** Constructor.*/
+    public PrepareOIDCInboundMessageContext() {
+        addToExistingInboundMessageContextPredicate = PredicateSupport.alwaysFalse();
+    }
+    
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (identifierLookupStrategy == null) {
+            throw new ComponentInitializationException("IdentifierLookupStrategy cannot be null");
+        }
+    }
+    
+    /**
+     * Set whether to append any defined subcontexts to the existing inbound message context, or to a new one. 
+     *  
+     * @param flag the flag to set.
+     */
+    public void setAddToExistingInboundMessageContext(final boolean flag) {
+    	checkSetterPreconditions();
+        
+        addToExistingInboundMessageContextPredicate = 
+                flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
+    }
+    
+    /**
+     * Set a predicate to determine whether to append any defined subcontexts to the existing inbound 
+     * message context, or to a new one. 
+     *  
+     * @param predicate the predicate to set.
+     */
+    public void setAddToExistingInboundMessageContextPredicate(
+            @Nonnull final Predicate<ProfileRequestContext> predicate) {
+    	checkSetterPreconditions();
+        
+        addToExistingInboundMessageContextPredicate = Constraint.isNotNull(predicate, "The predicate can not be null");
+    }
+    
+    /**
+     * Set the lookup strategy to identify the OP/RP.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setIdentifierLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+    	checkSetterPreconditions();
+        
+        identifierLookupStrategy = Constraint.isNotNull(strategy, "Identifier lookup strategy can not be null");
+    }
+
+    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }        
+        
+        identifier = identifierLookupStrategy.apply(profileRequestContext);
+        if (identifier == null) {       
+            log.warn("{} No identifier returned from lookup function", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        //TODO logout and session e.g. see saml PrepareInboundMessageContext
+        
+        return true;
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        //TODO not sure we need the existing context stuff for the SP-RP
+        MessageContext msgCtx = null;
+        if (addToExistingInboundMessageContextPredicate.test(profileRequestContext)) {
+            msgCtx = profileRequestContext.getInboundMessageContext();
+            if (msgCtx == null) {
+                log.warn("{} Profile request context did not contain an inbound message context", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+                return;
+            }
+        } else {
+            msgCtx = new MessageContext();
+            profileRequestContext.setInboundMessageContext(msgCtx);
+        }        
+
+        final OIDCPeerEntityContext peerContext = msgCtx.ensureSubcontext(OIDCPeerEntityContext.class);
+        peerContext.setIdentifier(identifier);
+
+        log.debug("{} Initialized inbound context for OIDC authentication message to {}", getLogPrefix(), identifier);
+    }
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/OIDCProviderMetadataLookupHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/OIDCProviderMetadataLookupHandler.java
new file mode 100644
index 0000000..ed40bf3
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/OIDCProviderMetadataLookupHandler.java
@@ -0,0 +1,208 @@
+/*
+ * 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.oidc.metadata.impl;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.handler.AbstractMessageHandler;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
+import net.shibboleth.oidc.profile.messaging.context.AbstractOIDCEntityContext;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+
+
+/**
+ * Handler for inbound OIDC protocol messages that attempts to locate OIDC metadata for a OP (issuer), 
+ * and attaches it with a {@link OIDCMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
+ * 
+ * <p>
+ * If the optional copy strategy is configured via {@link #setCopyContextStrategy(Function)},
+ * and if that lookup finds an existing metadata context with compatible data (matching the IssuerID),
+ * then its data will be re-used.
+ * </p>
+ * 
+ * 
+ * TODO, this comes from the agent service?
+ */
+public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
+    
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCProviderMetadataLookupHandler.class);
+    
+    /** Resolver used to look up OIDC provider information. */
+    @NonnullAfterInit private ProviderMetadataResolver providerResolver;
+
+    /** Strategy to resolve the context class to add the resolved metadata too.*/
+    @Nonnull private Function<MessageContext,? extends AbstractOIDCEntityContext> contextClassLookupStrategy;
+    
+    /** Optional strategy for resolving an existing metadata context from which to copy data. */
+    @Nullable private Function<MessageContext, OIDCProviderMetadataContext> copyContextStrategy;
+    
+    /** Constructor.*/
+    public OIDCProviderMetadataLookupHandler() {
+        contextClassLookupStrategy = new ChildContextLookup<>(OIDCPeerEntityContext.class);
+    }
+    
+    /** Set the context class lookup strategy.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setContextClassLookupStrategy(
+            @Nonnull final Function<MessageContext, ? extends AbstractOIDCEntityContext> strategy) {
+    	checkSetterPreconditions();
+        contextClassLookupStrategy = 
+                Constraint.isNotNull(strategy, "Context class lookup strategy can not be null");
+    }
+    
+    /**
+     * Set the optional strategy for resolving an existing metadata context from which to copy data.
+     *
+     * @param strategy the strategy function
+     */
+    public void setCopyContextStrategy(@Nullable final Function<MessageContext, OIDCProviderMetadataContext> strategy) {
+    	checkSetterPreconditions();
+
+        copyContextStrategy = strategy;
+    }
+    
+    
+    /**
+    * Set the {@link ProviderMetadataResolver} to use.
+    * 
+    * @param resolver The resolver to use.
+    */
+   public void setProviderMetadataResolver(@Nonnull final ProviderMetadataResolver resolver) {
+	   checkSetterPreconditions();
+
+       providerResolver = Constraint.isNotNull(resolver, "ProviderMetadataResolver cannot be null");
+   }
+   
+   /** {@inheritDoc} */
+   @Override
+   protected void doInitialize() throws ComponentInitializationException {
+       super.doInitialize();
+
+       if (providerResolver == null) {
+           throw new ComponentInitializationException("ProviderMetadataResolver cannot be null");
+       }
+
+   }
+
+    @Override
+    protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
+        ifNotInitializedThrowUninitializedComponentException();
+        
+        final AbstractOIDCEntityContext entityCtx = contextClassLookupStrategy.apply(messageContext);
+        final String id = entityCtx != null ? entityCtx.getIdentifier() : null;
+        if (entityCtx == null || id == null) {
+            log.debug("{} OIDC entity context class '{}' missing or did not contain an issuer identifier", 
+                    getLogPrefix(), AbstractOIDCEntityContext.class);
+            return;
+        }
+        
+        final OIDCProviderMetadataContext existingMetadataCtx = resolveExisting(messageContext,id);
+        if (existingMetadataCtx != null) {
+            log.debug("{} Resolved existing provider metadata context, removing existing "
+                    + "and re-using it", getLogPrefix());
+            entityCtx.removeSubcontext(OIDCProviderMetadataContext.class);
+            entityCtx.addSubcontext(existingMetadataCtx);
+            return;
+        }
+        
+        final IssuerIDCriterion issuerCriterion = new IssuerIDCriterion(new Issuer(entityCtx.getIdentifier()));
+        final CriteriaSet criteria = new CriteriaSet(issuerCriterion);
+        try {
+            final OIDCProviderMetadata issuerMetadata = providerResolver.resolveSingle(criteria);
+            if (issuerMetadata == null) {
+                log.debug("{} No provider metadata returned for {}",getLogPrefix(), entityCtx.getIdentifier());
+                return;
+            }
+            log.debug("{} Found provider metadata for '{}'", getLogPrefix(), entityCtx.getIdentifier());
+            final OIDCProviderMetadataContext context = new OIDCProviderMetadataContext();
+            context.setProviderInformation(issuerMetadata);            
+            entityCtx.addSubcontext(context);
+        } catch (final ResolverException e) {
+            log.error("{} ResolverException thrown during provider metadata lookup: {}", 
+                    getLogPrefix(), e.getMessage());
+            throw new MessageHandlerException(e);
+        }
+    }
+    
+    /**
+     * Attempt to resolve an existing {@link OIDCProviderMetadataContext} from which to copy.
+     *
+     * <p>
+     * The returned context will always be a fresh parent-less instance, suitable for the caller to
+     * directly store in the current message context.
+     * </p>
+     *
+     * @param messageContext the current message context
+     * @param issuer the identifier of the issuer against which to match
+     *
+     * @return a new instance of {@link SAMLMetadataContext}, or null if one can not be resolved
+     */
+    @Nullable protected OIDCProviderMetadataContext resolveExisting(@Nonnull final MessageContext messageContext,
+            @Nonnull final String issuer) {
+
+        if (copyContextStrategy == null) {
+            return null;
+        }
+        assert copyContextStrategy != null;
+        final OIDCProviderMetadataContext existing = copyContextStrategy.apply(messageContext);
+        if (existing != null) {
+            final OIDCProviderMetadata providerInformation = existing.getProviderInformation();
+            if (providerInformation != null) {
+                // Validate that existing data has the same issuer
+                if (Objects.equals(providerInformation.getIssuer().getValue(), issuer)) {
+                    log.debug("{} Found an existing and suitable OIDCProviderMetadataContext from which to copy ",
+                            getLogPrefix());
+                    final OIDCProviderMetadataContext copy = new OIDCProviderMetadataContext();
+                    copy.setProviderInformation(existing.getProviderInformation());
+                    return copy;
+                }
+                log.debug("{} Existing OIDCProviderMetadataContext was resolved, but the issuer "
+                        + "did not match the entity context data", getLogPrefix());
+            } else {
+                log.debug("{} Existing OIDCProviderMetadataContext was resolved but is missing ProviderInformation "
+                    + "data", getLogPrefix());
+            }
+        } else {
+            log.debug("{} No existing OIDCProviderMetadataContext was resolved", getLogPrefix());
+        }
+        return null;
+    }
+
+}

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


More information about the commits mailing list