[java-idp-oidc] 02/02: JOIDC-252 - Mechanism for custom metadata lookup flows
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Sep 5 09:52:05 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=ee83f90b6667671d3ed4c914ca37308ddb0cb789
commit ee83f90b6667671d3ed4c914ca37308ddb0cb789
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Sep 5 12:51:41 2025 +0300
JOIDC-252 - Mechanism for custom metadata lookup flows
https://shibboleth.atlassian.net/browse/JOIDC-252
Initial implementation extending the oidc/metadata-lookup flow
- If no metadata was found via OIDC/SAML metadata resolution, then the lookup extension flows are called
- Any global beans extending the abstract descriptor 'shibboleth.oidc.MetadataLookupExtensionFlow' bean are auto-wired
- the flows need to be prefixed with 'oidc/metadata-lookup-ext/'
- MetadataLookupExtensionContext in available for the flows, containing easy access to clientId and profileId values
- each extension flow is attempted until one populates OIDCMetadataContext with OIDCClientInformation
- Similarly to OIDC/SAML resolution, the OIDCMetadataContext is expected to be populated under inbound message context
---
.../metadata/MetadataLookupExtensionContext.java | 93 ++++++++++
.../MetadataLookupExtensionFlowDescriptor.java | 100 +++++++++++
...tadataLookupExtensionFlowDescriptorManager.java | 41 +++++
.../idp/plugin/oidc/op/metadata/package-info.java | 18 ++
.../impl/SelectMetadataLookupExtensionFlow.java | 198 +++++++++++++++++++++
...okupExtensionContextClientIDLookupFunction.java | 44 +++++
...kupExtensionContextProfileIDLookupFunction.java | 41 +++++
.../META-INF/net.shibboleth.idp/postconfig.xml | 6 +
.../oidc/metadata-lookup/metadata-lookup-beans.xml | 9 +
.../oidc/metadata-lookup/metadata-lookup-flow.xml | 22 ++-
.../oidc/op/profile/flow/AuthorizeFlowTest.java | 44 +++++
.../op/profile/flow/PopulateClientInformation.java | 69 +++++++
.../lookupext1/lookupext1-beans.xml | 23 +++
.../lookupext1/lookupext1-flow.xml | 22 +++
.../lookupext2/lookupext2-flow.xml | 22 +++
.../net/shibboleth/idp/module/conf/global.xml | 3 +
16 files changed, 753 insertions(+), 2 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionContext.java
new file mode 100644
index 00000000..f3e406a5
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionContext.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.metadata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.shared.annotation.constraint.Live;
+
+/**
+ * A {@link BaseContext} which holds generic information for the metadata lookup extension flows, including data which
+ * flows are already executed / attempted.
+ */
+public class MetadataLookupExtensionContext extends BaseContext {
+
+ /** The flows that are already attempted. */
+ @Nonnull private List<MetadataLookupExtensionFlowDescriptor> attemptedFlows;
+
+ /** The client ID to be looked up. */
+ @Nullable private String clientId;
+
+ /** The profile ID that initiated the lookup. */
+ @Nullable private String profileId;
+
+ /**
+ * Constructor.
+ */
+ public MetadataLookupExtensionContext() {
+ attemptedFlows = new ArrayList<>();
+ }
+
+ /**
+ * Get the flows that are already attempted.
+ *
+ * @return the flows that are already attempted
+ */
+ @Nonnull @Live public List<MetadataLookupExtensionFlowDescriptor> getAttemptedFlows() {
+ return attemptedFlows;
+ }
+
+ /**
+ * Set the client ID to be looked up.
+ *
+ * @param id client ID
+ */
+ public void setClientId(@Nullable final String id) {
+ clientId = id;
+ }
+
+ /**
+ * Get the client ID to be looked up.
+ *
+ * @return client ID
+ */
+ @Nullable public String getClientId() {
+ return clientId;
+ }
+
+ /**
+ * Set the profile ID that initiated the lookup.
+ *
+ * @param id profile ID
+ */
+ public void setProfileId(@Nullable final String id) {
+ profileId = id;
+ }
+
+ /**
+ * Get the profile ID that initiated the lookup.
+ *
+ * @return profile ID
+ */
+ @Nullable public String getProfileId() {
+ return profileId;
+ }
+}
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionFlowDescriptor.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionFlowDescriptor.java
new file mode 100644
index 00000000..2ea8a8ff
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionFlowDescriptor.java
@@ -0,0 +1,100 @@
+/*
+ * 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.idp.plugin.oidc.op.metadata;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.google.common.base.MoreObjects;
+
+import net.shibboleth.idp.profile.FlowDescriptor;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+
+/**
+ * A descriptor for a metadata lookup extension flow.
+ *
+ * <p>
+ * A metadata lookup extension flow is designed to be injected into a oidc/metadata-lookup to facilitate extensions for
+ * the metadata lookup process. The extension flows must include an activation predicate to indicate suitability based
+ * on the content of the {@link ProfileRequestContext}.
+ * </p>
+ */
+public class MetadataLookupExtensionFlowDescriptor extends AbstractIdentifiableInitializableComponent
+ implements FlowDescriptor, Predicate<ProfileRequestContext> {
+
+ /** Prefix convention for flow IDs. */
+ @Nonnull @NotEmpty public static final String FLOW_ID_PREFIX = "oidc/metadata-lookup-ext/";
+
+ /** Predicate that must be true for this flow to be usable for a given request. */
+ @Nonnull private Predicate<ProfileRequestContext> activationCondition;
+
+ /** Constructor. */
+ public MetadataLookupExtensionFlowDescriptor() {
+ activationCondition = PredicateSupport.alwaysTrue();
+ }
+
+ /**
+ * Set the activation condition in the form of a {@link Predicate} such that iff the condition evaluates to true
+ * should the corresponding flow be allowed/possible.
+ *
+ * @param condition predicate that controls activation of the flow
+ */
+ public void setActivationCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ checkSetterPreconditions();
+ activationCondition = Constraint.isNotNull(condition, "Activation condition predicate cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ return activationCondition.test(input);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return getId().hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof MetadataLookupExtensionFlowDescriptor flowDescriptor) {
+ return getId().equals(flowDescriptor.getId());
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("flowId", getId())
+ .toString();
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionFlowDescriptorManager.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionFlowDescriptorManager.java
new file mode 100644
index 00000000..f29ccf1d
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/MetadataLookupExtensionFlowDescriptorManager.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.metadata;
+
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import net.shibboleth.shared.spring.config.IdentifiedComponentManager;
+
+/**
+ * Manager of {@link MetadataLookupExtensionFlowDescriptor} objects.
+ */
+public class MetadataLookupExtensionFlowDescriptorManager
+ extends IdentifiedComponentManager<MetadataLookupExtensionFlowDescriptor> {
+
+ /**
+ * Constructor.
+ *
+ * @param freeObjects free-standing objects to add
+ */
+ @Autowired
+ public MetadataLookupExtensionFlowDescriptorManager(
+ @Nullable final Collection<MetadataLookupExtensionFlowDescriptor> freeObjects) {
+ super(freeObjects);
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/package-info.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/package-info.java
new file mode 100644
index 00000000..5406ba0f
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Interfaces and common classes related to OIDC metadata (or client information).
+ */
+package net.shibboleth.idp.plugin.oidc.op.metadata;
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/impl/SelectMetadataLookupExtensionFlow.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/impl/SelectMetadataLookupExtensionFlow.java
new file mode 100644
index 00000000..2a5ff735
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/metadata/impl/SelectMetadataLookupExtensionFlow.java
@@ -0,0 +1,198 @@
+/*
+ * 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.idp.plugin.oidc.op.metadata.impl;
+
+import java.util.Collection;
+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.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.DefaultClientIDLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext;
+import net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionFlowDescriptor;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+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;
+
+/**
+ * A profile action that selects metadata lookup extension flows to invoke.
+ *
+ * <p>
+ * The flows available to be executed are held by the {@link #availableFlows}. Available flows are executed in
+ * the order that they are configured if their activation condition evaluates to true.
+ * </p>
+ *
+ * <p>
+ * This action returns the flow ID to be executed or {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID} if
+ * there are no flows available to be executed.
+ * </p>
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event Selected flow ID to execute
+ */
+public class SelectMetadataLookupExtensionFlow extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SelectMetadataLookupExtensionFlow.class);
+
+ /** The creation strategy for the metadata lookup extension context. */
+ @Nonnull private Function<ProfileRequestContext, MetadataLookupExtensionContext>
+ metadataLookupExtensionContextCreationStrategy;
+
+ /** The available metadata lookup extension flows. */
+ @Nullable private Collection<MetadataLookupExtensionFlowDescriptor> availableFlows;
+
+ /** The strategy used to obtain the client id value to be stored in the context. */
+ @Nonnull private Function<MessageContext, ClientID> clientIDLookupStrategy;
+
+ /** The strategy used to obtain the profile id value to be stored in the context. */
+ @NonnullAfterInit private Function<ProfileRequestContext, String> profileIDLookupStrategy;
+
+ /** The context to operate on. */
+ @NonnullBeforeExec private MetadataLookupExtensionContext metadataLookupExtensionContext;
+
+ /**
+ * Constructor.
+ */
+ public SelectMetadataLookupExtensionFlow() {
+ final Function<ProfileRequestContext, MetadataLookupExtensionContext> mleccs =
+ new ChildContextLookup<>(MetadataLookupExtensionContext.class, true).compose(
+ new InboundMessageContextLookup());
+ assert mleccs != null;
+ metadataLookupExtensionContextCreationStrategy = mleccs;
+ clientIDLookupStrategy = new DefaultClientIDLookupFunction();
+ }
+
+ /**
+ * Set the creation strategy for the metadata lookup extension context.
+ *
+ * @param strategy creation strategy
+ */
+ public void setMetadataLookupExtensionContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext, MetadataLookupExtensionContext> strategy) {
+ checkSetterPreconditions();
+ metadataLookupExtensionContextCreationStrategy = Constraint.isNotNull(strategy,
+ "MetadataLookupExtensionContextCreationStrategy cannot be null");
+ }
+
+ /**
+ * Set the available metadata lookup extension flows.
+ *
+ * @param flows available metadata lookup flows
+ */
+ public void setAvailableFlows(@Nullable final Collection<MetadataLookupExtensionFlowDescriptor> flows) {
+ checkSetterPreconditions();
+ availableFlows = flows;
+ }
+
+ /**
+ * Set the strategy used to locate the client id of the request.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setClientIDLookupStrategy(@Nonnull final Function<MessageContext, ClientID> strategy) {
+ checkSetterPreconditions();
+ clientIDLookupStrategy =
+ Constraint.isNotNull(strategy, "ClientIDLookupStrategy lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the profile id to be used.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProfileIDLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+ checkSetterPreconditions();
+ profileIDLookupStrategy = Constraint.isNotNull(strategy, "ProfileIDLookupStrategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (profileIDLookupStrategy == null) {
+ throw new ComponentInitializationException("ProfileIDLookupStrategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ metadataLookupExtensionContext = metadataLookupExtensionContextCreationStrategy.apply(profileRequestContext);
+ if (metadataLookupExtensionContext == null) {
+ log.error("{} Could not create the metadata lookup extension context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+ if (availableFlows == null || availableFlows.isEmpty()) {
+ log.debug("{} No available metadata lookup extension flows, nothing to do", getLogPrefix());
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (metadataLookupExtensionContext.getClientId() == null) {
+ final ClientID clientId = clientIDLookupStrategy.apply(profileRequestContext.getInboundMessageContext());
+ if (clientId == null) {
+ log.debug("{} Could not resolve client ID to be looked up", getLogPrefix());
+ } else {
+ metadataLookupExtensionContext.setClientId(clientId.getValue());
+ }
+ }
+ if (metadataLookupExtensionContext.getProfileId() == null) {
+ final String profileId = profileIDLookupStrategy.apply(profileRequestContext);
+ if (profileId == null) {
+ log.debug("(} Could not resolve profile ID to be used", getLogPrefix());
+ } else {
+ metadataLookupExtensionContext.setProfileId(profileId);
+ }
+ }
+ for (final MetadataLookupExtensionFlowDescriptor flow : availableFlows) {
+ log.trace("{} Checking flow {}", getLogPrefix(), flow.getId());
+ if (metadataLookupExtensionContext.getAttemptedFlows().contains(flow)) {
+ log.debug("{} Flow (} has already been attempted", getLogPrefix(), flow.getId());
+ } else {
+ log.debug("{} Selecting flow {}", getLogPrefix(), flow.getId());
+ metadataLookupExtensionContext.getAttemptedFlows().add(flow);
+ ActionSupport.buildEvent(profileRequestContext, flow.ensureId());
+ return;
+ }
+ }
+ log.debug("{} No flows available to choose from", getLogPrefix());
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataLookupExtensionContextClientIDLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataLookupExtensionContextClientIDLookupFunction.java
new file mode 100644
index 00000000..5bda97f9
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataLookupExtensionContextClientIDLookupFunction.java
@@ -0,0 +1,44 @@
+/*
+ * 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.idp.plugin.oidc.op.profile.logic;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext;
+
+/**
+ * Lookup function to fetch {@link ClientID} via {@link MetadataLookupExtensionContext}.
+ */
+public class DefaultMetadataLookupExtensionContextClientIDLookupFunction
+ implements Function<MessageContext, ClientID>{
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public ClientID apply(@Nullable final MessageContext messageContext) {
+ return Optional.ofNullable(messageContext)
+ .map(msgCtx -> msgCtx.getSubcontext(MetadataLookupExtensionContext.class))
+ .map(extCtx -> extCtx.getClientId())
+ .map(ClientID::new)
+ .orElse(null);
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataLookupExtensionContextProfileIDLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataLookupExtensionContextProfileIDLookupFunction.java
new file mode 100644
index 00000000..8b56cb94
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataLookupExtensionContextProfileIDLookupFunction.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.logic;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext;
+
+/**
+ * Lookup function to fetch profile ID via {@link MetadataLookupExtensionContext}.
+ */
+public class DefaultMetadataLookupExtensionContextProfileIDLookupFunction
+ implements Function<ProfileRequestContext, String>{
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public String apply(@Nullable final ProfileRequestContext profileRequestContext) {
+ return Optional.ofNullable(profileRequestContext)
+ .map(prc -> prc.getInboundMessageContext())
+ .map(msgCtx -> msgCtx.getSubcontext(MetadataLookupExtensionContext.class))
+ .map(extCtx -> extCtx.getProfileId())
+ .orElse(null);
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 08c9b2b5..a92f3748 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -893,4 +893,10 @@
</property>
</bean>
+ <bean id="shibboleth.oidc.MetadataLookupExtensionFlowDescriptorManager"
+ class="net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionFlowDescriptorManager"/>
+
+ <bean id="shibboleth.oidc.MetadataLookupExtensionFlow" abstract="true"
+ class="net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionFlowDescriptor" />
+
</beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
index b898c7b0..6a46ad8c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
@@ -82,4 +82,13 @@
</constructor-arg>
</bean>
+ <bean id="SelectMetadataLookupExtensionFlow" scope="prototype"
+ class="net.shibboleth.idp.plugin.oidc.op.metadata.impl.SelectMetadataLookupExtensionFlow"
+ p:availableFlows="#{@'shibboleth.oidc.MetadataLookupExtensionFlowDescriptorManager'.getComponents()}"
+ p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy">
+ <property name="profileIDLookupStrategy">
+ <bean parent="shibboleth.Functions.Constant" c:target="#{getObject('shibboleth.oidc.profileId') ?: null}"/>
+ </property>
+ </bean>
+
</beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-flow.xml
index 53f58ddf..cb8cc3fb 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-flow.xml
@@ -17,7 +17,7 @@
<decision-state id="CheckIfFoundFromClientInformationService">
<if test="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.profile.context.RelyingPartyContext)).isVerified()"
- then="SelectConfiguration" else="LookupFromSAMLMetadataService" />
+ then="SelectMetadataLookupExtensionFlow" else="LookupFromSAMLMetadataService" />
</decision-state>
<action-state id="LookupFromSAMLMetadataService">
@@ -30,16 +30,34 @@
<decision-state id="CheckIfFoundFromSAMLMetadata">
<if test="opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext)) and opensamlProfileRequestContext.ensureInboundMessageContext().ensureSubcontext(T(org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext)).containsSubcontext(T(org.opensaml.saml.common.messaging.context.SAMLMetadataContext))"
- then="PopulateOIDCMetadataContextFromSAML" else="SelectConfiguration" />
+ then="PopulateOIDCMetadataContextFromSAML" else="SelectMetadataLookupExtensionFlow" />
</decision-state>
<action-state id="PopulateOIDCMetadataContextFromSAML">
<evaluate expression="PopulateOIDCMetadataContext" />
<evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
<evaluate expression="'proceed'" />
+ <transition on="proceed" to="SelectMetadataLookupExtensionFlow" />
+ </action-state>
+
+ <action-state id="SelectMetadataLookupExtensionFlow">
+ <evaluate expression="SelectMetadataLookupExtensionFlow" />
+ <evaluate expression="'proceed'" />
+
+ <!-- Call a subflow with the same ID as the event. -->
+ <transition on="#{currentEvent.id.startsWith('oidc/metadata-lookup-ext/')}" to="CallMetadataLookupExtensionFlow" />
<transition on="proceed" to="SelectConfiguration" />
</action-state>
+ <!--
+ This invokes a flow. Anything but proceed is a terminating state, otherwise control passes
+ back to this flow to select another flow to be executed.
+ -->
+ <subflow-state id="CallMetadataLookupExtensionFlow" subflow="#{currentEvent.id}">
+ <input name="calledAsSubflow" value="true" />
+ <transition on="proceed" to="SelectMetadataLookupExtensionFlow" />
+ </subflow-state>
+
<bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml" />
</flow>
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 2bbd6b4b..9aaa7876 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -2312,6 +2312,50 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
assertErrorDescriptionContains(result, "AccessDenied");
}
+ @Test
+ public void testWithAuthorizationCodeFlow_metadataViaLookupExtension1() throws IOException, SessionException {
+ setRequestParameters(List.of(new Pair<>("client_id", "clientIdForLookupExtension1"),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ storeMetadata(storageService, clientId, clientSecret, scope, redirectUri);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNull(successResponse.getIDToken());
+ Assert.assertNull(successResponse.getAccessToken());
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
+ Assert.assertNull(successResponse.getIssuer());
+ }
+
+ @Test
+ public void testWithAuthorizationCodeFlow_metadataViaLookupExtension2() throws IOException, SessionException {
+ setRequestParameters(List.of(new Pair<>("client_id", "clientIdForLookupExtension2"),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ storeMetadata(storageService, clientId, clientSecret, scope, redirectUri);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNull(successResponse.getIDToken());
+ Assert.assertNull(successResponse.getAccessToken());
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
+ Assert.assertNull(successResponse.getIssuer());
+ }
+
@Factory
public Object[] createIdTokenSecurityTests() {
return new Object[] {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PopulateClientInformation.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PopulateClientInformation.java
new file mode 100644
index 00000000..25103e48
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PopulateClientInformation.java
@@ -0,0 +1,69 @@
+/*
+ * 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.idp.plugin.oidc.op.profile.flow;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Support action for the metadata-lookup extension test flows.
+ */
+public class PopulateClientInformation extends AbstractProfileAction {
+
+ /** Strategy used to obtain the client id value for authorize/token request. */
+ @NonnullAfterInit private Function<MessageContext, ClientID> clientIDLookupStrategy;
+
+ /**
+ * Set the strategy used to locate the client id of the request.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setClientIDLookupStrategy(@Nonnull final Function<MessageContext, ClientID> strategy) {
+ ifInitializedThrowUnmodifiabledComponentException();
+ clientIDLookupStrategy =
+ Constraint.isNotNull(strategy, "ClientIDLookupStrategy lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final ClientID clientId = clientIDLookupStrategy.apply(profileRequestContext.getInboundMessageContext());
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(URI.create("https://example.org/cb"));
+ metadata.setScope(Scope.parse("openid profile"));
+ final OIDCClientInformation clientInformation =
+ new OIDCClientInformation(clientId, new Date(), metadata,
+ new Secret("mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret"));
+ final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+ oidcCtx.setClientInformation(clientInformation);
+ profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
+ }
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-beans.xml b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-beans.xml
new file mode 100644
index 00000000..9695caa2
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-beans.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+ default-init-method="initialize" default-destroy-method="destroy">
+
+ <bean id="InitializeRelyingPartyContext"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRelyingPartyContext" scope="prototype">
+ <property name="clientIDLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextClientIDLookupFunction" />
+ </property>
+ </bean>
+
+ <bean id="PopulateClientInformation" scope="prototype"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.flow.PopulateClientInformation">
+ <property name="clientIDLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextClientIDLookupFunction" />
+ </property>
+ </bean>
+</beans>
diff --git a/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-flow.xml b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-flow.xml
new file mode 100644
index 00000000..a76378cd
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-flow.xml
@@ -0,0 +1,22 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd">
+
+ <decision-state id="CheckIfClientIdMatch">
+ <if test="opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext)) and opensamlProfileRequestContext.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext)).getClientId() != null and opensamlProfileRequestContext.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLo [...]
+ then="SetMetadata" else="proceed" />
+ </decision-state>
+
+ <action-state id="SetMetadata">
+ <evaluate expression="PopulateClientInformation" />
+ <evaluate expression="InitializeRelyingPartyContext" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="proceed">
+ </transition>
+ </action-state>
+
+ <end-state id="proceed"/>
+
+ <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-beans.xml" />
+
+</flow>
diff --git a/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext2/lookupext2-flow.xml b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext2/lookupext2-flow.xml
new file mode 100644
index 00000000..bd88b347
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext2/lookupext2-flow.xml
@@ -0,0 +1,22 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd">
+
+ <decision-state id="CheckIfClientIdMatch">
+ <if test="opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext)) and opensamlProfileRequestContext.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext)).getClientId() != null and opensamlProfileRequestContext.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLo [...]
+ then="SetMetadata" else="proceed" />
+ </decision-state>
+
+ <action-state id="SetMetadata">
+ <evaluate expression="PopulateClientInformation" />
+ <evaluate expression="InitializeRelyingPartyContext" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="proceed">
+ </transition>
+ </action-state>
+
+ <end-state id="proceed"/>
+
+ <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext1/lookupext1-beans.xml" />
+
+</flow>
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
index f0408d27..23ee01ca 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
@@ -113,4 +113,7 @@
<bean id="alwaysFalsePolicyOperator" class="net.shibboleth.idp.plugin.oidc.op.profile.flow.AlwaysFalseCustomMetadataPolicyOperator" />
+ <bean p:id="oidc/metadata-lookup-ext/lookupext1" parent="shibboleth.oidc.MetadataLookupExtensionFlow" />
+ <bean p:id="oidc/metadata-lookup-ext/lookupext2" parent="shibboleth.oidc.MetadataLookupExtensionFlow" />
+
</beans>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list