[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation

Henri Mikkonen henri.mikkonen at iki.fi
Fri Nov 15 10:35:58 UTC 2024


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

hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=9df87708eb52f7b985bca8cd90403c1b11a8cb77

The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
     new 9df87708 JOIDC-222 - Support for OpenID Federation
9df87708 is described below

commit 9df87708eb52f7b985bca8cd90403c1b11a8cb77
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Nov 15 12:35:21 2024 +0200

    JOIDC-222 - Support for OpenID Federation
    
    https://shibboleth.atlassian.net/browse/JOIDC-222
    
    Initial implementation for the automatic registration in the authorization flow
    - If the feature is enabled (TBD in the profile configurations), the trust chains are resolved
    - Metadata policy merging & encforcing is done via configurable strategies
    - Trust chain is selected via configurable strategy
      - By default, the shortest is selected
    - After successful authentication, the metadata is registered via configurable ClientInformationManager
      - Similar to dynamic registration flow
---
 .../impl/RelyingPartyTrustChainContext.java        | 135 +++++++++++
 .../op/oidfed/profile/impl/ResolveTrustChains.java | 255 +++++++++++++++++++++
 .../op/oidfed/profile/impl/SelectTrustChain.java   | 156 +++++++++++++
 .../profile/impl/StoreAutomaticRegistration.java   | 154 +++++++++++++
 ...ultTrustChainMetadataPolicyMergingStrategy.java | 174 ++++++++++++++
 .../DefaultTrustChainSelectionStrategy.java        | 100 ++++++++
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  52 +++++
 .../idp/flows/oidc/authorize/authorize-flow.xml    |  37 ++-
 8 files changed, 1060 insertions(+), 3 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
new file mode 100644
index 00000000..8948c452
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
@@ -0,0 +1,135 @@
+/*
+ * 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.oidfed.profile.impl;
+
+import java.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.shared.collection.Pair;
+
+/**
+ * Subcontext carrying information for trust chains related to a relying party.
+ * 
+ * @since 4.3.0
+ */
+public final class RelyingPartyTrustChainContext extends BaseContext {
+
+    /** All resolved trust chains for the relying party. */
+    @Nullable private List<List<EntityStatement>> resolvedTrustChains;
+
+    /** Policy-compliant trust chains for the relying party. */
+    @Nullable private List<Pair<List<EntityStatement>,OIDCClientInformation>> policyCompliantTrustChains;
+
+    /** Selected trust chain for the relying party. */
+    @Nullable private Pair<List<EntityStatement>,OIDCClientInformation> selectedTrustChain;
+
+    /** Expiration instant for the selected metadata. */
+    @Nullable private Instant selectedMetadataExpiration;
+
+    /**
+     * Get the resolved trust chains for the relying party.
+     * 
+     * @return the trust chains
+     */
+    @Nullable public List<List<EntityStatement>> getResolvedTrustChains() {
+        return resolvedTrustChains;
+    }
+
+    /**
+     * Set the resolved trust chains for the relying party.
+     * 
+     * @param trustChains the trust chains
+     * 
+     * @return this context
+     */
+    @Nonnull public RelyingPartyTrustChainContext setResolvedTrustChains(
+            @Nullable final List<List<EntityStatement>> trustChains) {
+        resolvedTrustChains = trustChains;
+        return this;
+    }
+
+    /**
+     * Get the policy-compliant trust chains for the relying party.
+     * 
+     * @return the trust chains
+     */
+    @Nullable public List<Pair<List<EntityStatement>,OIDCClientInformation>> getPolicyCompliantTrustChains() {
+        return policyCompliantTrustChains;
+    }
+
+    /**
+     * Set the policy-compliant trust chains for the relying party.
+     * 
+     * @param chains the trust chains and client informations
+     * 
+     * @return this context
+     */
+    @Nonnull public RelyingPartyTrustChainContext setPolicyCompliantTrustChains(
+            @Nullable final List<Pair<List<EntityStatement>,OIDCClientInformation>> chains) {
+        policyCompliantTrustChains = chains;
+        return this;
+    }
+
+    /**
+     * Get the selected trust chain for the relying party.
+     * 
+     * @return the trust chain
+     */
+    @Nullable public Pair<List<EntityStatement>,OIDCClientInformation> getSelectedTrustChain() {
+        return selectedTrustChain;
+    }
+
+    /**
+     * Set the selected trust chain for the relying party.
+     * 
+     * @param chais the selected trust chain
+     * 
+     * @return this context
+     */
+    @Nonnull public RelyingPartyTrustChainContext setSelectedTrustChains(
+            @Nullable final Pair<List<EntityStatement>,OIDCClientInformation> chain) {
+        selectedTrustChain = chain;
+        return this;
+    }
+
+    /**
+     * Get the expiration instant for the selected metadata
+     * 
+     * @return the expiration instant
+     */
+    @Nullable public Instant getSelectedMetadataExpiration() {
+        return selectedMetadataExpiration;
+    }
+
+    /**
+     * Set the expiration instant for the selected metadata.
+     * 
+     * @param expiration the expiration instant
+     * 
+     * @return this context
+     */
+    @Nonnull public RelyingPartyTrustChainContext setSelectedMetadataExpiration(@Nullable final Instant expiration) {
+        selectedMetadataExpiration = expiration;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
new file mode 100644
index 00000000..d01db3d9
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
@@ -0,0 +1,255 @@
+/*
+ * 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.oidfed.profile.impl;
+
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.ParseException;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+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 org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Resolves metadata policy-compliant trust chains from the configurable trust chain cache, metadata policy merging
+ * strategy and enforcer. The data is populated to the {@link RelyingPartyTrustChainContext}.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * 
+ * @since 4.3.0
+ */
+public class ResolveTrustChains extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ResolveTrustChains.class);
+
+    /** Metadata cache for trust chains. */
+    @NonnullAfterInit private MetadataCache<List<List<EntityStatement>>> trustChainCache;
+
+    /** Strategy used to obtain the client id value for authorize/token request. */
+    @NonnullAfterInit private Function<MessageContext, ClientID> clientIDLookupStrategy;
+
+    /** Strategy used to create the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextCreationStrategy;
+
+    /** Strategy used to merge metadata policies in trust chain for specific entity type. */
+    @NonnullAfterInit private BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>>
+        metadataPolicyMergingStrategy;
+
+    /** Enforcer function for applying metadata policy for an item. */
+    @NonnullAfterInit private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> metadataPolicyEnforcer;
+
+    /** OAuth2 client id. */
+    @NonnullBeforeExec private String clientId;
+
+    /**
+     * Constructor.
+     */
+    public ResolveTrustChains() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tccs =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        assert tccs != null;
+        trustChainContextCreationStrategy = tccs;
+    }
+
+    /**
+     * Set the metadata cache for trust chains.
+     * 
+     * @param cache metadata cache
+     */
+    public void setTrustChainCache(@Nonnull final MetadataCache<List<List<EntityStatement>>> cache) {
+        checkSetterPreconditions();
+        trustChainCache = Constraint.isNotNull(cache, "TrustChainCache cannot be null");
+    }
+
+    /**
+     * 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 merge metadata policies in trust chain for specific entity type.
+     * 
+     * @param strategy merging strategy
+     */
+    public void setMetadataPolicyMergingStrategy(@Nonnull final
+            BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>> strategy) {
+        checkSetterPreconditions();
+        metadataPolicyMergingStrategy =
+                Constraint.isNotNull(strategy, "MetadataPolicyMergingStrategy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the enforcer function for applying metadata policy for an item.
+     * 
+     * @param enforcer policy enforcer
+     */
+    public void setMetadataPolicyEnforcer(
+            @Nonnull final BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> enforcer) {
+        checkSetterPreconditions();
+        metadataPolicyEnforcer = Constraint.isNotNull(enforcer, "Metadata policy enforcer cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (trustChainCache == null) {
+            throw new ComponentInitializationException("TrustChainCache cannot be null");
+        }
+        if (clientIDLookupStrategy == null) {
+            throw new ComponentInitializationException("ClientIDLookupStrategy cannot be null");
+        }
+        if (metadataPolicyEnforcer == null) {
+            throw new ComponentInitializationException("MetadataPolicyEnforcer cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        final ClientID id = clientIDLookupStrategy.apply(profileRequestContext.getInboundMessageContext());
+        if (id == null) {
+            log.error("{} Unable to obtain client ID", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        clientId = id.getValue();
+        try {
+            new URL(clientId).toURI();
+        } catch (final URISyntaxException | MalformedURLException e) {
+            log.debug("{} The client ID {} is not a valid URL, nothing to do", getLogPrefix(), clientId);
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        log.debug("Resolving trust chain for {)", clientId);
+        final List<List<List<EntityStatement>>> cacheResult;
+        try {
+            assert clientId != null;
+            cacheResult = trustChainCache.get(new CriteriaSet(new SubjectEntityIDCriterion(clientId)));
+        } catch (final MetadataCacheException e) {
+            log.warn("{} Could fetch trust chains for {}", getLogPrefix(), clientId);
+            return;
+        }
+        if (cacheResult.isEmpty()) {
+            log.debug("{} No trust chains resolved for {}", getLogPrefix(), clientId);
+            return;
+        }
+
+        final RelyingPartyTrustChainContext trustChainContext =
+                trustChainContextCreationStrategy.apply(profileRequestContext);
+        trustChainContext.setResolvedTrustChains(cacheResult.get(0));
+        final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains = new ArrayList<>();
+
+        for (final List<EntityStatement> chain : cacheResult.get(0)) {
+            final Map<String, MetadataPolicy> mergedPolicies =
+                    metadataPolicyMergingStrategy.apply(chain, EntityType.OPENID_RELYING_PARTY.getValue());
+            log.debug("{} Merged policy for chain {}", getLogPrefix(), mergedPolicies);
+            final OIDCClientMetadata metadata = chain.get(0).getClaimsSet().getRPMetadata();
+            if (metadata != null) {
+                final OIDCClientInformation clientInformation = new OIDCClientInformation(
+                        new ClientID(chain.get(0).getEntityID().getValue()), metadata);
+                boolean compliant = true;
+                final JSONObject requestMetadata = clientInformation.toJSONObject();
+                for (final String claim : mergedPolicies.keySet()) {
+                    final MetadataPolicy policy = mergedPolicies.get(claim);
+                    final Object value = requestMetadata.get(claim);
+                    log.debug("{} Claim {} set in policy included in the request: {}", getLogPrefix(), claim,
+                            value == null);
+                    final Pair<Object,Boolean> mergeResult = metadataPolicyEnforcer.apply(value, policy);
+                    final Boolean enforcerResult = mergeResult != null ? mergeResult.getSecond() : null;
+                    if (enforcerResult == null || !enforcerResult.booleanValue()) {
+                        log.warn("{} Metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
+                        compliant = false;
+                    } else {
+                        log.trace("{} Validation result is OK for claim {}", getLogPrefix(), claim);
+                        final Object enforcedValue = mergeResult != null ? mergeResult.getFirst() : null;
+                        requestMetadata.put(claim, enforcedValue);
+                    }
+                }
+
+                if (!compliant) {
+                    log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
+                } else {
+                    log.debug("{} The requested metadata is compliant with the policy", getLogPrefix());
+                    try {
+                        policyCompliantChains.add(new Pair<>(chain, OIDCClientInformation.parse(requestMetadata)));
+                    } catch (final ParseException e) {
+                        log.error("{} Could not parse the metadata object", getLogPrefix(), e);
+                    }
+                }
+                log.debug("{} Policy-enforced metadata {}", getLogPrefix(), requestMetadata.toJSONString());
+            } else {
+                log.error("{} No client information found from the entity statement", getLogPrefix());
+            }
+        }
+
+        log.debug("{} Setting the policy compliant trust chains into the context: {}", getLogPrefix(),
+                policyCompliantChains);
+        trustChainContext.setPolicyCompliantTrustChains(policyCompliantChains);
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
new file mode 100644
index 00000000..048638ad
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.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.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainSelectionStrategy;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Selects the trust chain to be used for automatic registration via configurable lookup strategy and stores it to the
+ * {@link RelyingPartyTrustChainContext}.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * 
+ * @since 4.3.0
+ */
+public class SelectTrustChain extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(SelectTrustChain.class);
+
+    /** Strategy used to lookup the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /** Strategy used to fetch the selected trust chain and metadata. */
+    @NonnullAfterInit private Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>>
+        selectedTrustChainLookupStrategy;
+
+    /** Trust chain context to operate on. */
+    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+
+    /**
+     * Constructor.
+     */
+    public SelectTrustChain() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+        selectedTrustChainLookupStrategy = new DefaultTrustChainSelectionStrategy();
+    }
+
+    /**
+     * Set the strategy used to lookup the trust chain context.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustChainContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+        checkSetterPreconditions();
+        trustChainContextLookupStrategy =
+                Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to fetch the selected trust chain and metadata.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setSelectedTrustChainLookupStrategy(@Nonnull final
+            Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>> strategy) {
+        checkSetterPreconditions();
+        selectedTrustChainLookupStrategy =
+                Constraint.isNotNull(strategy, "SelectedTrustChainLookupStrategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (selectedTrustChainLookupStrategy == null) {
+            throw new ComponentInitializationException("Trust chain selection strategy cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        trustChainContext = trustChainContextLookupStrategy.apply(profileRequestContext);
+        if (trustChainContext == null || trustChainContext.getPolicyCompliantTrustChains() == null) {
+            log.error("{} Unable to locate policy-compliant trust chains", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final Pair<List<EntityStatement>, OIDCClientInformation> selectedChain =
+                selectedTrustChainLookupStrategy.apply(profileRequestContext);
+
+        if (selectedChain == null || selectedChain.getFirst() == null || selectedChain.getSecond() == null) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} No selected trust chain could be resolved", getLogPrefix());
+            return;
+        }
+
+        trustChainContext.setSelectedTrustChains(selectedChain);
+        final List<EntityStatement> selectedTrustChain = selectedChain.getFirst();
+        assert selectedTrustChain != null;
+        Instant metadataExpiration = null;
+        for (final EntityStatement statement : selectedTrustChain) {
+            final Instant statementExpiration = statement.getClaimsSet().getExpirationTime().toInstant();
+            metadataExpiration = metadataExpiration == null ? statementExpiration : 
+                statementExpiration.isBefore(metadataExpiration) ? statementExpiration : metadataExpiration;
+        }
+        trustChainContext.setSelectedMetadataExpiration(metadataExpiration);
+
+        final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+        oidcCtx.setClientInformation(selectedChain.getSecond());
+        profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
+        log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
new file mode 100644
index 00000000..5074365e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
@@ -0,0 +1,154 @@
+/*
+ * 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.oidfed.profile.impl;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.ClientInformationManager;
+import net.shibboleth.oidc.metadata.ClientInformationManagerException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Stores the automatically registered client information via configurable {@link ClientInformationManager}.
+ * The client information and its expiration time are fetched from {@link RelyingPartyTrustChainContext}.
+ */
+public class StoreAutomaticRegistration extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(StoreAutomaticRegistration.class);
+
+    /** The client information manager used for storing the information. */
+    @NonnullAfterInit private ClientInformationManager clientInformationManager;
+
+    /** Strategy used to lookup the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /** The client information to be stored. */
+    @NonnullBeforeExec private OIDCClientInformation clientInformation;
+
+    /** The expiration instant for the client informatiom. */
+    @NonnullBeforeExec private Instant expiration;
+
+    /**
+     * Constructor.
+     */
+    public StoreAutomaticRegistration() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+    }
+
+    /**
+     * Get the client information manager used for storing the information.
+     * 
+     * @return The client information manager used for storing the information
+     */
+    @NonnullAfterInit public ClientInformationManager getClientInformationManager() {
+        return clientInformationManager;
+    }
+
+    /**
+     * Set the client information manager used for storing the information.
+     * @param manager The client information manager used for storing the information.
+     */
+    public void setClientInformationManager(@Nonnull final ClientInformationManager manager) {
+        ifInitializedThrowUnmodifiabledComponentException();
+        clientInformationManager = Constraint.isNotNull(manager, "The client information manager cannot be null!");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (clientInformationManager == null) {
+            throw new ComponentInitializationException("ClientInformationManager cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        final RelyingPartyTrustChainContext trustChainContext =
+                trustChainContextLookupStrategy.apply(profileRequestContext);
+        if (trustChainContext == null || trustChainContext.getSelectedTrustChain() == null) {
+            log.error("{} Unable to locate selected trust chain", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+
+        final Pair<List<EntityStatement>,OIDCClientInformation> selectedTrustChain =
+                trustChainContext.getSelectedTrustChain();
+        assert selectedTrustChain != null;
+        clientInformation = selectedTrustChain.getSecond();
+        if (clientInformation == null) {
+            log.error("{} Unable to locate selected metadata", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+
+        expiration = trustChainContext.getSelectedMetadataExpiration();
+        if (expiration == null) {
+            log.error("{} Unable to resolve expiration time for the selected metadata", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        log.debug("{} Storing automatically registered client information", getLogPrefix());
+
+        try {
+            log.debug("{} Registration will expire on {}", getLogPrefix(), expiration);
+            assert clientInformation != null;
+            clientInformationManager.storeClientInformation(clientInformation, expiration, true);               
+        } catch (final ClientInformationManagerException e) {
+            log.error("{} Could not store the client information", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+            return;
+        }
+        log.info("{} Client information successfully stored for {}", getLogPrefix(), 
+                clientInformation.getID().getValue());
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java
new file mode 100644
index 00000000..513e307a
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java
@@ -0,0 +1,174 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
+import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataPolicyMergingStrategy;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Default strategy to merge the metadata policies for the given entity type from the given trust chain. Finally,
+ * a configurable local metadata policy is merged to the resulting map of metadata policies.
+ */
+public class DefaultTrustChainMetadataPolicyMergingStrategy extends AbstractIdentifiableInitializableComponent
+        implements BiFunction<List<EntityStatement>,String, Map<String, MetadataPolicy>> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainMetadataPolicyMergingStrategy.class);
+
+    /** The strategy used for merging two metadata policies. */
+    @Nonnull private BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
+        Pair<Map<String, MetadataPolicy>, Boolean>> metadataPolicyMergingStrategy;
+
+    /** The strategy used for local (additional) metadata policy. */
+    @NonnullAfterInit private Function<List<EntityStatement>, Map<String, MetadataPolicy>>
+        localMetadataPolicyStrategy;
+
+    /**
+     * Constructor.
+     */
+    public DefaultTrustChainMetadataPolicyMergingStrategy() {
+        metadataPolicyMergingStrategy = new DefaultMetadataPolicyMergingStrategy();
+    }
+
+    /**
+     * Set the strategy used for merging two metadata policies.
+     * 
+     * @param strategy What to set.
+     */
+    public void setMetadataPolicyMergingStrategy(@Nonnull final BiFunction<Map<String,MetadataPolicy>,
+            Map<String,MetadataPolicy>, Pair<Map<String, MetadataPolicy>, Boolean>> strategy) {
+        checkSetterPreconditions();
+        metadataPolicyMergingStrategy = Constraint.isNotNull(strategy,
+                "Metadata policy merging strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used for local (additional) metadata policy.
+     * 
+     * @param strategy What to set.
+     */
+    public void setLocalMetadataPolicyStrategy(
+            @Nonnull final Function<List<EntityStatement>, Map<String, MetadataPolicy>> strategy) {
+        checkSetterPreconditions();
+        localMetadataPolicyStrategy = Constraint.isNotNull(strategy,
+                "Local metadata policy strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nonnull
+    public Map<String, MetadataPolicy> apply(@Nullable final List<EntityStatement> trustChain,
+            @Nullable final String entityType) throws ConstraintViolationException {
+        checkComponentActive();
+        if (trustChain == null || trustChain.isEmpty()) {
+            return CollectionSupport.emptyMap();
+        }
+        final Map<String, MetadataPolicy> result = new HashMap<>();
+        for (int i = trustChain.size(); i > 0; i--) {
+            final EntityStatement entityStatement = trustChain.get(i - 1);
+            final Map<String, Object> policy = entityStatement.getClaimsSet().getMetadataPolicyJSONObject();
+            if (policy == null || policy.isEmpty()) {
+                continue;
+            }
+
+            if (StringSupport.trimOrNull(entityType) != null) {
+                try {
+                    assert entityType != null;
+                    result.putAll(mergePolicies(result, parseMetadataPolicy(entityStatement, entityType)));
+                } catch (final ConstraintViolationException e) {
+                    throw new ConstraintViolationException("Could not merge federation metadata policies");
+                }
+            }
+        }
+        try {
+            result.putAll(mergePolicies(result, localMetadataPolicyStrategy.apply(trustChain)));
+        } catch (final ConstraintViolationException e) {
+            throw new ConstraintViolationException("Could not merge local metadata policy in the federation policy");
+        }
+        return result;
+    }
+
+    /**
+     * Merges the two maps of metadata policies.
+     * 
+     * @param first the first map of policies
+     * @param second the second map of policies
+     * @return the map containing merged policies
+     * @throws ConstraintViolationException if the merging fails
+     */
+    @Nonnull
+    protected Map<String, MetadataPolicy> mergePolicies(@Nullable final Map<String, MetadataPolicy> first,
+            @Nullable final Map<String, MetadataPolicy> second) throws ConstraintViolationException {
+        final Pair<Map<String, MetadataPolicy>, Boolean> result = metadataPolicyMergingStrategy.apply(first, second);
+        if (result != null && Boolean.TRUE.equals(result.getSecond())) {
+            final Map<String, MetadataPolicy> mergedMap = result.getFirst();
+            return mergedMap == null ? CollectionSupport.emptyMap() : mergedMap;
+        }
+        throw new ConstraintViolationException("Merge failed");
+    }
+
+    /**
+     * Parse a map of metadata policies from the given entity statement for a specified entity type.
+     * 
+     * @param entityStatement the source for the map of metadata policies
+     * @param entityType the entity type to use
+     * @return the map of metadata policies for the entity type or null if not found
+     * @throws ConstraintViolationException if the map of policies could not be parsed from the entity statement
+     */
+    @Nullable
+    protected Map<String, MetadataPolicy> parseMetadataPolicy(@Nonnull final EntityStatement entityStatement,
+            @Nonnull final String entityType) throws ConstraintViolationException {
+        final ObjectMapper objectMapper = new ObjectMapper();
+        final TypeReference<HashMap<String,MetadataPolicy>> typeRef =
+                new TypeReference<HashMap<String,MetadataPolicy>>() {};
+        final EntityType type = new EntityType(entityType);
+        try {
+            if (entityStatement.getClaimsSet().getMetadataPolicy(type) != null) {
+                return objectMapper.readValue(
+                        entityStatement.getClaimsSet().getMetadataPolicy(type).toJSONString(), typeRef);
+            }
+        } catch (final JsonProcessingException | PolicyViolationException e) {
+            log.debug("Could not parse metadata policy of type {} from the claims set", entityType, e);
+            throw new ConstraintViolationException("Could not parse metadata policy of type " + entityType +
+                    " from the claims set");
+        }
+        return null;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
new file mode 100644
index 00000000..702d373d
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.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.oidfed.profile.navigate;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default strategy for choosing a specific trust chain: it simply selects the first one in the list whose size is the
+ * shortest.
+ */
+public class DefaultTrustChainSelectionStrategy implements
+        Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainSelectionStrategy.class);
+
+    /** Strategy used to locate the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public DefaultTrustChainSelectionStrategy() {
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the trust chain context
+     */
+    public DefaultTrustChainSelectionStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+        trustChainContextLookupStrategy =
+                Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public Pair<List<EntityStatement>, OIDCClientInformation> apply(@Nullable final ProfileRequestContext input) {
+        final RelyingPartyTrustChainContext trustChainContext = trustChainContextLookupStrategy.apply(input);
+        if (trustChainContext == null) {
+            log.debug("No trust chain context located");
+            return null;
+        }
+        final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains =
+                trustChainContext.getPolicyCompliantTrustChains();
+
+        if (policyCompliantChains == null || policyCompliantChains.isEmpty()) {
+            log.debug("No policy compliant chains located");
+            return null;
+        }
+        
+        int shortestIndex = 0;
+        if (policyCompliantChains.size() > 1) {
+            for (int i = 0; i < policyCompliantChains.size(); i++) {
+                final List<EntityStatement> candidate = policyCompliantChains.get(i).getFirst();
+                final List<EntityStatement> shortest = policyCompliantChains.get(shortestIndex).getFirst();
+                if (candidate != null && shortest != null && candidate.size() < shortest.size()) {
+                    shortestIndex = i;
+                }
+            }
+        }
+        return policyCompliantChains.get(shortestIndex);
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index a2369b79..2491fd0f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -1146,4 +1146,56 @@
         </constructor-arg>
     </bean>
 
+    <alias alias="AutomaticRegistrationCondition" name="%{idp.oidfed.authorize.automaticRegistrationCondition:shibboleth.Conditions.FALSE}" />
+
+    <bean id="ResolveTrustChains" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustChains"
+        scope="prototype"
+        p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+        p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
+        p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
+        p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"/>
+
+    <bean id="DefaultMetadataPolicyEnforcer"
+        class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer" />
+
+    <bean id="DefaultTrustChainMetadataPolicyMergingStrategy" 
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainMetadataPolicyMergingStrategy"
+        p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.authorize.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"/>
+
+    <bean id="DefaultLocalMetadataPolicyStrategy"
+        parent="shibboleth.Functions.Constant">
+        <constructor-arg name="target">
+            <util:map>
+                <entry key="scope">
+                    <bean class="net.shibboleth.oidc.metadata.policy.MetadataPolicy" p:defaultValue="openid" />
+                </entry>
+                <entry key="token_endpoint_auth_method">
+                    <bean class="net.shibboleth.oidc.metadata.policy.MetadataPolicy" p:defaultValue="private_key_jwt" />
+                </entry>
+            </util:map>
+        </constructor-arg>
+    </bean>
+
+    <bean id="SelectTrustChain" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.SelectTrustChain"
+        scope="prototype">
+        <property name="activationCondition">
+            <bean parent="shibboleth.Conditions.Expression"
+                c:expression="#input.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext))" />
+        </property>
+    </bean>
+
+    <bean id="InitializeRelyingPartyContext"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRelyingPartyContext" scope="prototype"
+        p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy">
+        <property name="activationCondition">
+            <bean parent="shibboleth.Conditions.Expression"
+                c:expression="#input.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)) and #input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)).getSelectedTrustChain() != null" />
+        </property>
+    </bean>
+
+    <bean id="StoreAutomaticRegistration" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.StoreAutomaticRegistration"
+        scope="prototype"
+        p:clientInformationManager-ref="#{'%{idp.oidc.dynreg.clientInformationManager:shibboleth.oidc.ClientInformationManager}'.trim()}">
+    </bean>
+
 </beans>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 4ffc9741..413675da 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -36,7 +36,25 @@
         <transition on="proceed" to="DoMetadataLookup" />
     </action-state>
 
-    <action-state id="SelectConfiguration">
+    <decision-state id="SelectConfiguration">
+        <on-entry>
+            <set name="flowScope.automaticallyRegistered" value="'false'" />
+        </on-entry>
+        <if test="AutomaticRegistrationCondition.test(opensamlProfileRequestContext) and !opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext))"
+            then="DoAutomaticRegistration" else="DoSelectConfiguration" />
+    </decision-state>
+
+    <action-state id="DoAutomaticRegistration">
+        <evaluate expression="ResolveTrustChains" />
+        <evaluate expression="SelectTrustChain" />
+        <evaluate expression="InitializeRelyingPartyContext" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="DoSelectConfiguration">
+            <set name="flowScope.automaticallyRegistered" value="opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext))" />
+        </transition>
+    </action-state>
+
+    <action-state id="DoSelectConfiguration">
         <evaluate expression="SelectRelyingPartyConfiguration" />
         <evaluate expression="SelectProfileConfiguration" />
         <evaluate expression="PostLookupPopulateAuditContext" />
@@ -299,16 +317,29 @@
         <evaluate expression="PopulateClientStorageSaveContext" />
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="ClientStorageSave" />
-        <transition on="NoSaveNeeded" to="BuildResponseMessage" />
+        <transition on="NoSaveNeeded" to="CheckIfAutomaticRegistration" />
         <transition to="HandleError" />
     </action-state>
 
     <subflow-state id="ClientStorageSave" subflow="client-storage/write">
         <input name="calledAsSubflow" value="true" />
-        <transition on="proceed" to="BuildResponseMessage"/>
+        <transition on="proceed" to="CheckIfAutomaticRegistration"/>
         <transition to="HandleError" />
     </subflow-state>
 
+    <decision-state id="CheckIfAutomaticRegistration">
+        <if test="flowScope.automaticallyRegistered and opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)) and opensamlProfileRequestContext.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)).getSelectedTrustChain() != null"
+            then="StoreAutomaticRegistration"
+            else="BuildResponseMessage" />
+    </decision-state>
+
+    <action-state id="StoreAutomaticRegistration">
+        <evaluate expression="StoreAutomaticRegistration" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="BuildResponseMessage" />
+        <transition to="HandleError" />
+    </action-state>
+
     <!-- Error views, handling and end states -->
 
     <!-- Passthrough state if an exception is thrown. -->

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


More information about the commits mailing list