[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