[java-idp-plugin-oidc-op-oidfed] branch main updated: Improvements to trust mark handling

Codeberg noreply at shibboleth.net
Mon Oct 5 13:56:55 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-idp-plugin-oidc-op-oidfed.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/7c3ee1c3531aca89a11474867ee4d2a254022fb4

The following commit(s) were added to refs/heads/main by this push:
     new 7c3ee1c  Improvements to trust mark handling
7c3ee1c is described below

commit 7c3ee1c3531aca89a11474867ee4d2a254022fb4
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon Oct 5 16:56:29 2026 +0300

    Improvements to trust mark handling
    
    - Store expiration time of trust marks into the client information extensions
    - Split ValidateAutomaticRegistrationProfileConfiguration into ValidateFederationRegistrationProfileConfiguration for serving explicit registration better
    - Modify trust mark attribute filter to also server sxplicit registration (client information extensions)
      - Cover in tests
---
 .../policyrule/impl/EntityTrustMarkPolicyRule.java | 112 +++++++--
 .../impl/EntityTrustMarkPolicyRuleParser.java      |  16 ++
 .../impl/AddExtensionsToClientInformation.java     |  53 ++++-
 ...eAutomaticRegistrationProfileConfiguration.java | 262 ++++-----------------
 ...ederationRegistrationProfileConfiguration.java} | 107 ++-------
 .../oidfed/metadata-lookup-ext-oidfed-beans.xml    |   4 +
 .../idp/flows/oidfed/register/register-beans.xml   |   2 +-
 .../resources/schema/shibboleth-afp-oidfed.xsd     |  10 +-
 .../UserInfoFlowAutomaticRegistrationTest.java     |  65 +++++
 .../idp/module/conf/attribute-filter.xml           |   2 +-
 10 files changed, 306 insertions(+), 327 deletions(-)

diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/policyrule/impl/EntityTrustMarkPolicyRule.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/policyrule/impl/EntityTrustMarkPolicyRule.java
index 8635ddc..ad17442 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/policyrule/impl/EntityTrustMarkPolicyRule.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/policyrule/impl/EntityTrustMarkPolicyRule.java
@@ -14,6 +14,9 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.policyrule.impl;
 
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
 
@@ -26,9 +29,15 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
 import net.shibboleth.idp.attribute.filter.PolicyRequirementRule;
 import net.shibboleth.idp.attribute.filter.context.AttributeFilterContext;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.oidfed.support.ClientInformationExtensionSupport;
+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.primitive.LoggerFactory;
@@ -48,9 +57,15 @@ public class EntityTrustMarkPolicyRule extends AbstractIdentifiableInitializable
     /** String to match for a positive evaluation. */
     @Nullable private String matchString;
 
+    /** Whether the match evaluation is case sensitive. */
+    private boolean caseSensitive = true;
+
     /** The String used to prefix log message. */
     @Nullable private String logPrefix;
 
+    /** Positive clock skew adjustment to consider when checking trust mark expiration. */
+    @Nonnull private Duration clockSkew;
+
     /**
      * Constructor.
      */
@@ -61,6 +76,9 @@ public class EntityTrustMarkPolicyRule extends AbstractIdentifiableInitializable
         assert tcls != null;
         trustChainContextLookupStrategy = tcls;
         logPrefix = null;
+        final Duration sixtySeconds = Duration.ofSeconds(60);
+        assert sixtySeconds != null;
+        clockSkew = sixtySeconds;
     }
 
     /**
@@ -85,6 +103,27 @@ public class EntityTrustMarkPolicyRule extends AbstractIdentifiableInitializable
         matchString = match;
     }
 
+    /**
+     * Sets whether the policy evaluation is case sensitive.
+     * 
+     * @param isCaseSensitive whether the policy evaluation is case sensitive
+     */
+    public void setCaseSensitive(final boolean isCaseSensitive) {
+        checkSetterPreconditions();
+        caseSensitive = isCaseSensitive;
+    }
+
+    /**
+     * Set the clock skew.
+     * 
+     * @param skew clock skew to set
+     */
+    public void setClockSkew(@Nonnull final Duration skew) {
+        checkSetterPreconditions();
+        clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
+        Constraint.isFalse(clockSkew.isNegative(), "Clock skew cannot be negative");
+    }
+
     /**
      * Return a string which is to be prepended to all log messages.
      * 
@@ -114,26 +153,69 @@ public class EntityTrustMarkPolicyRule extends AbstractIdentifiableInitializable
     @Override
     public Tristate matches(@Nonnull final AttributeFilterContext filterContext) {
         checkComponentActive();
-        final RelyingPartyTrustChainContext trustChainContext = trustChainContextLookupStrategy.apply(
-                new RecursiveTypedParentContextLookup<>(ProfileRequestContext.class).apply(filterContext));
-        if (trustChainContext == null) {
-            log.debug("{} No RelyingPartyTrustChainContext resolved", getLogPrefix());
-            return Tristate.FALSE;
+        final ProfileRequestContext profileRequestContext =
+                new RecursiveTypedParentContextLookup<>(ProfileRequestContext.class).apply(filterContext);
+        final RelyingPartyTrustChainContext trustChainContext =
+                trustChainContextLookupStrategy.apply(profileRequestContext);
+        if (trustChainContext != null) {
+            final String entityId = Optional.ofNullable(trustChainContext.getSelectedTrustChain())
+                    .map(verifiedChain -> verifiedChain.getTrustChain())
+                    .map(chain -> chain.get(0))
+                    .map(statement -> statement.getSubject())
+                    .orElse(null);
+            if (entityId == null) {
+                log.debug("{} Could not resolve entity ID from selected trust chain", getLogPrefix());
+                return Tristate.FALSE;
+            }
+            final Boolean result = Optional.ofNullable(trustChainContext.getVerifiedTrustMarkIds())
+                    .map(map -> map.get(entityId))
+                    .map(ids -> listContains(ids, matchString))
+                    .orElse(Boolean.FALSE);
+            log.debug("{} Returning {}", getLogPrefix(), result);
+            return result.booleanValue() ? Tristate.TRUE : Tristate.FALSE;
         }
-        final String entityId = Optional.ofNullable(trustChainContext.getSelectedTrustChain())
-                .map(verifiedChain -> verifiedChain.getTrustChain())
-                .map(chain -> chain.get(0))
-                .map(statement -> statement.getSubject())
+
+        final OIDCClientInformation clientInformation = Optional.ofNullable(profileRequestContext)
+                .map(prc -> prc.getInboundMessageContext())
+                .map(msgCtx -> msgCtx.getSubcontext(OIDCMetadataContext.class))
+                .map(oidcCtx -> oidcCtx.getClientInformation())
                 .orElse(null);
-        if (entityId == null) {
-            log.debug("{} Could not resolve entity ID from selected trust chain", getLogPrefix());
+        if (clientInformation == null) {
+            log.debug("{} Could not resolve client information or RelyingPartyTrustChainContext, unable to proceed",
+                    getLogPrefix());
             return Tristate.FALSE;
         }
-        final Boolean result = Optional.ofNullable(trustChainContext.getVerifiedTrustMarkIds())
+
+        final String entityId = clientInformation.getID().getValue();
+        final List<Pair<String,Instant>> trustMarkIds =
+                Optional.ofNullable(ClientInformationExtensionSupport.parseValidatedTrustMarkIds(clientInformation))
                 .map(map -> map.get(entityId))
-                .map(ids -> ids.contains(matchString))
-                .orElse(Boolean.FALSE);
+                .orElse(CollectionSupport.emptyList());
+        log.debug("{} Resolved trust mark IDs via client information {}", getLogPrefix(), trustMarkIds);
+        final Instant expiration = Instant.now().minus(clockSkew);
+        final boolean result = listContains(trustMarkIds.stream()
+                .filter(pair -> pair.getSecond() == null || expiration.isBefore(pair.getSecond()))
+                .map(pair -> pair.getFirst()).toList(), matchString);
         log.debug("{} Returning {}", getLogPrefix(), result);
-        return result.booleanValue() ? Tristate.TRUE : Tristate.FALSE;
+        return result ? Tristate.TRUE : Tristate.FALSE;
+    }
+
+    /**
+     * Checks whether the given list contains the string to match, taking {@link #caseSensitive} into account.
+     * 
+     * @param list the list to iterate over
+     * @param matchString the string to match
+     * @return true if the list contains the string, false otherwise
+     */
+    protected boolean listContains(@Nullable final List<String> list, @Nullable final String matchString) {
+        if (list == null || matchString == null) {
+            return false;
+        }
+        for (final String item : list) {
+            if (caseSensitive ? matchString.equals(item) : matchString.equalsIgnoreCase(item)) {
+                return true;
+            }
+        }
+        return false;
     }
 }
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/policyrule/impl/EntityTrustMarkPolicyRuleParser.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/policyrule/impl/EntityTrustMarkPolicyRuleParser.java
index 0891184..424ea9b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/policyrule/impl/EntityTrustMarkPolicyRuleParser.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/attribute/filter/spring/policyrule/impl/EntityTrustMarkPolicyRuleParser.java
@@ -17,9 +17,14 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.spring.policyr
 import javax.annotation.Nonnull;
 import javax.xml.namespace.QName;
 
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.w3c.dom.Element;
+
 import net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.policyrule.impl.EntityTrustMarkPolicyRule;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.attribute.filter.spring.impl.AttributeFilterNamespaceHandler;
 import net.shibboleth.idp.attribute.filter.spring.policyrule.impl.AbstractStringPolicyRuleParser;
+import net.shibboleth.shared.primitive.StringSupport;
 
 /**
  * Bean definition parser for {@link EntityTrustMarkPolicyRule}.
@@ -37,4 +42,15 @@ public class EntityTrustMarkPolicyRuleParser extends AbstractStringPolicyRulePar
         return EntityTrustMarkPolicyRule.class;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    protected void doNativeParse(@Nonnull final Element element, @Nonnull final ParserContext parserContext,
+            @Nonnull final BeanDefinitionBuilder builder) {
+        super.doNativeParse(element, parserContext, builder);
+
+        if (element.hasAttributeNS(null, "clockSkew")) {
+            builder.addPropertyValue("clockSkew", StringSupport.trimOrNull(element.getAttributeNS(null, "clockSkew")));
+        }
+    }
+
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
index adf351d..979ab13 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
@@ -14,6 +14,8 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
+import java.time.Duration;
+import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
@@ -34,10 +36,14 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationResponseContext;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.TrustMark;
+import net.shibboleth.oidfed.metadata.util.EntityStatementHelper;
+import net.shibboleth.oidfed.profile.config.navigate.MaximumTrustMarkLifetimeLookupFunction;
 import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
 import net.shibboleth.oidfed.profile.context.VerifiedTrustChain;
 import net.shibboleth.oidfed.support.ClientInformationExtensionSupport;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -56,6 +62,9 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
     /** Strategy used to lookup the trust chain context. */
     @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
 
+    /** Strategy used to lookup the maximum lifetime for trust marks. */
+    @Nonnull private Function<ProfileRequestContext, Duration> maximumTrustMarkLifetimeLookupStrategy;
+
     /** Client information to operate on. */
     @NonnullBeforeExec private OIDCClientInformation clientInformation;
 
@@ -65,6 +74,9 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
     /** Selected trust chain to operate on. */
     @NonnullBeforeExec private VerifiedTrustChain selectedTrustChain;
 
+    /** Maximum lifetime for trust marks. */
+    @NonnullBeforeExec private Duration maximumTrustMarkLifetime;
+
     /** Constructor. */
     public AddExtensionsToClientInformation() {
         final Function<ProfileRequestContext,OIDCClientRegistrationResponseContext> orcls =
@@ -77,6 +89,7 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
                         new InboundMessageContextLookup());
         assert tcls != null;
         trustChainContextLookupStrategy = tcls;
+        maximumTrustMarkLifetimeLookupStrategy = new MaximumTrustMarkLifetimeLookupFunction();
     }
 
     /**
@@ -106,10 +119,21 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
                 Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
     }
 
+    /**
+     * Set the strategy to lookup the maximum lifetime for trust marks.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setMaximumTrustMarkLifetimeLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Duration> strategy) {
+        checkSetterPreconditions();
+        maximumTrustMarkLifetimeLookupStrategy =
+                Constraint.isNotNull(strategy, "MaximumTrustMarkLifetimeLookupStrategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
@@ -138,6 +162,13 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
             return false;
         }
 
+        maximumTrustMarkLifetime = maximumTrustMarkLifetimeLookupStrategy.apply(profileRequestContext);
+        if (maximumTrustMarkLifetime == null) {
+            log.error("{} Could not resolve maximum trust mark lifetime", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+
         return true;
     }
 
@@ -154,13 +185,21 @@ public class AddExtensionsToClientInformation extends AbstractProfileAction {
         clientInformation.getOIDCMetadata().setCustomField(
                 ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_CHAIN,
                 trustChain.stream().map(es -> es.getJwt().serialize()).toList());
-        final Map<String,List<String>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarkIds();
-        if (verifiedTrustMarks != null) {
-            clientInformation.getOIDCMetadata().setCustomField(
-                    ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_MARK_IDS, verifiedTrustMarks);
+        final Map<String,List<TrustMark>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarks();
+        if (verifiedTrustMarks != null && !verifiedTrustMarks.isEmpty()) {
+            assert maximumTrustMarkLifetime != null;
+            final Map<String,List<Pair<String,Instant>>> result =
+                    EntityStatementHelper.parseExpirableTrustMarkIds(verifiedTrustMarks, maximumTrustMarkLifetime);
+            if (result == null) {
+                log.warn("{} Could not parse expirable trust marks", getLogPrefix());
+                return;
+            }
+            log.debug("{} Set trust mark IDs extension to the client information: {}", getLogPrefix(), result);
+            assert clientInformation != null;
+            ClientInformationExtensionSupport.addValidatedTrustMarkIds(clientInformation, result);
+        } else {
+            log.trace("{} No trust marks resolved to be included in the client information", getLogPrefix());
         }
-
-    
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
index 72307e3..8f53f8f 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
@@ -14,43 +14,29 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
+import java.time.Duration;
 import java.time.Instant;
-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 javax.annotation.Nullable;
 
 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 com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.AutomaticRegistrationContext;
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
-import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
-import net.shibboleth.oidfed.metadata.EntityStatement;
-import net.shibboleth.oidfed.metadata.payload.claim.impl.MetadataImpl;
+import net.shibboleth.oidfed.metadata.TrustMark;
+import net.shibboleth.oidfed.metadata.util.EntityStatementHelper;
 import net.shibboleth.oidfed.profile.config.navigate.ClaimsSetDecorationTypeLookupFunction;
-import net.shibboleth.oidfed.profile.config.navigate.LocalMetadataPolicyLookupFunction;
-import net.shibboleth.oidfed.profile.config.navigate.MandatoryTrustMarksLookupFunction;
+import net.shibboleth.oidfed.profile.config.navigate.MaximumTrustMarkLifetimeLookupFunction;
 import net.shibboleth.oidfed.profile.config.navigate.ProvidedChainClaimsSetDecorationTypeLookupFunction;
-import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
-import net.shibboleth.oidfed.profile.context.VerifiedTrustChain;
-import net.shibboleth.oidfed.profile.navigate.DefaultSelectedTrustChainMetadataLookupStrategy;
 import net.shibboleth.oidfed.support.ClientInformationExtensionSupport;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -58,84 +44,45 @@ 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}.
+ * Extends {@link ValidateFederationRegistrationProfileConfiguration} by including the parsed trust mark IDs and their
+ * expiration instants into the client information object and calling the configured claims set decorator.
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_MSG_CTX}
  * @event {@link ValidateAutomaticRegistrationProfileConfiguration#RESELECT_TRUST_CHAIN}
  */
-public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractProfileAction {
-
-    /** ID of event returned if a flow wishes to indicate that another trust chain should be selected instead. */
-    @Nonnull @NotEmpty public static final String RESELECT_TRUST_CHAIN = "ReselectTrustChain";
+public class ValidateAutomaticRegistrationProfileConfiguration
+    extends ValidateFederationRegistrationProfileConfiguration {
 
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(ValidateAutomaticRegistrationProfileConfiguration.class);
 
-    /** Strategy used to lookup the trust chain context. */
-    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
-
     /** Strategy used to create the automatic registration context. */
     @Nonnull
     private Function<ProfileRequestContext, AutomaticRegistrationContext> automaticRegistrationContextCreationStrategy;
 
-    /** Strategy used to lookup the selected metadata. */
-    @Nonnull private Function<ProfileRequestContext, OIDCClientMetadata> selectedMetadataLookupStrategy;
-
-    /** Strategy used to lookup mandatory trust marks. */
-    @Nonnull private Function<ProfileRequestContext, List<String>> mandatoryTrustMarksLookupStrategy;
-
-    /** Strategy used to lookup local metadata policy to be merged to the client metadata. */
-    @Nonnull private Function<ProfileRequestContext, Map<String, MetadataPolicy>> localMetadataPolicyLookupStrategy;
-
-    /** Strategy used to merge local metadata policy into the client metadata. */
-    @NonnullAfterInit private BiFunction<OIDCClientInformation,Map<String, MetadataPolicy>,OIDCClientInformation>
-        localMetadataPolicyMergingStrategy;
-
     /** Strategy used to lookup claims set decoration type. */
     @Nonnull private Function<ProfileRequestContext, String> claimsSetDecorationTypeLookupStrategy;
 
     /** Strategy used to lookup claims set decoration type for clients with request-provided trust chain. */
     @Nonnull private Function<ProfileRequestContext, String> providedChainClaimsSetDecorationTypeLookupStrategy;
 
-    /** Trust chain context to operate on. */
-    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
-
-    /** Selected trust chain to operate on. */
-    @NonnullBeforeExec private VerifiedTrustChain selectedTrustChain;
+    /** Strategy used to lookup the maximum lifetime for trust marks. */
+    @Nonnull private Function<ProfileRequestContext, Duration> maximumTrustMarkLifetimeLookupStrategy;
 
     /**
      * Constructor.
      */
     public ValidateAutomaticRegistrationProfileConfiguration() {
-        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
-                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
-                        new InboundMessageContextLookup());
-        assert tcls != null;
-        trustChainContextLookupStrategy = tcls;
+        super();
         final Function<ProfileRequestContext, AutomaticRegistrationContext> arccs =
                 new ChildContextLookup<>(AutomaticRegistrationContext.class, true).compose(
                         new InboundMessageContextLookup());
         assert arccs != null;
         automaticRegistrationContextCreationStrategy = arccs;
-        selectedMetadataLookupStrategy = new DefaultSelectedTrustChainMetadataLookupStrategy();
-        mandatoryTrustMarksLookupStrategy = new MandatoryTrustMarksLookupFunction();
-        localMetadataPolicyLookupStrategy = new LocalMetadataPolicyLookupFunction();
         claimsSetDecorationTypeLookupStrategy = new ClaimsSetDecorationTypeLookupFunction();
         providedChainClaimsSetDecorationTypeLookupStrategy = new ProvidedChainClaimsSetDecorationTypeLookupFunction();
-    }
-
-    /**
-     * 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");
+        maximumTrustMarkLifetimeLookupStrategy = new MaximumTrustMarkLifetimeLookupFunction();
     }
 
     /**
@@ -150,54 +97,6 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
                 Constraint.isNotNull(strategy, "AutomaticRegistrationContextCreationStrategy cannot be null");
     }
 
-    /**
-     * Set the strategy used to lookup the selected metadata.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setSelectedMetadataLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, OIDCClientMetadata> strategy) {
-        checkSetterPreconditions();
-        selectedMetadataLookupStrategy =
-                Constraint.isNotNull(strategy, "SelectedMetadataLookupStrategy cannot be null");
-    }
-
-    /**
-     * Set the strategy used to lookup mandatory trust marks.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setMandatoryTrustMarksLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
-        checkSetterPreconditions();
-        mandatoryTrustMarksLookupStrategy = Constraint.isNotNull(strategy,
-                "Mandatory trust marks lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the strategy used to lookup local metadata policy to be merged to the client metadata.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setLocalMetadataPolicyLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, Map<String, MetadataPolicy>> strategy) {
-        checkSetterPreconditions();
-        localMetadataPolicyLookupStrategy = Constraint.isNotNull(strategy,
-                "Local metadata policy lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the strategy used to merge local metadata policy into the client metadata.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setLocalMetadataPolicyMergingStrategy(@Nonnull
-            final BiFunction<OIDCClientInformation,Map<String, MetadataPolicy>,OIDCClientInformation> strategy) {
-        checkSetterPreconditions();
-        localMetadataPolicyMergingStrategy =
-                Constraint.isNotNull(strategy, "Local metadata policy merging strategy cannot be null");
-    }
-
     /**
      * Set the strategy used to lookup claims set decoration type.
      * 
@@ -222,109 +121,51 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
                 Constraint.isNotNull(strategy, "Claims set decoration type lookup 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.getSelectedTrustChain() == null) {
-            log.error("{} Unable to locate selected trust chain", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-            return false;
-        }
+    /**
+     * Set the strategy to lookup the maximum lifetime for trust marks.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setMaximumTrustMarkLifetimeLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Duration> strategy) {
+        checkSetterPreconditions();
+        maximumTrustMarkLifetimeLookupStrategy =
+                Constraint.isNotNull(strategy, "MaximumTrustMarkLifetimeLookupStrategy cannot be null");
+    }
 
-        selectedTrustChain = trustChainContext.getSelectedTrustChain();
-        if (selectedTrustChain == null) {
-            log.error("{} Selected trust chain contents is not populated", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+    protected boolean extendedExecution(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final OIDCClientInformation clientInformation) {
+        
+        final Duration maximumTrustMarkLifetime = maximumTrustMarkLifetimeLookupStrategy.apply(profileRequestContext);
+        if (maximumTrustMarkLifetime == null) {
+            log.error("{} Could not resolve maximum trust mark lifetime", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
 
-        return true;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final OIDCClientMetadata metadata = selectedMetadataLookupStrategy.apply(profileRequestContext);
-        if (metadata == null) {
-            log.error("{} Unable to parse the RP metadata for storing", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-            return;
-        }
-        final OIDCClientInformation clientInformation;
-        final Map<String, MetadataPolicy> localMetadataPolicy =
-                localMetadataPolicyLookupStrategy.apply(profileRequestContext);
-        if (localMetadataPolicy != null && !localMetadataPolicy.isEmpty()) {
-            log.debug("{} Applying local metadata policy into the client metadata", getLogPrefix());
-            clientInformation = localMetadataPolicyMergingStrategy.apply(
-                    new OIDCClientInformation(
-                            new ClientID(selectedTrustChain.getTrustChain().get(0).getSubject()),
-                            metadata), localMetadataPolicy);
-            if (clientInformation == null) {
-                log.error("{} Could not apply the local metadata policy into the client metadata", getLogPrefix());
+        final Map<String,List<TrustMark>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarks();
+        if (verifiedTrustMarks != null && !verifiedTrustMarks.isEmpty()) {
+            assert maximumTrustMarkLifetime != null;
+            final Map<String,List<Pair<String,Instant>>> result =
+                    EntityStatementHelper.parseExpirableTrustMarkIds(verifiedTrustMarks, maximumTrustMarkLifetime);
+            if (result == null) {
+                log.error("{} Could not parse expirable trust marks", getLogPrefix());
                 ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-                return;
+                return false;
             }
-            final MetadataImpl selectedMetadata = new MetadataImpl();
-            selectedMetadata.setOpenidRelyingPartyMetadata(clientInformation.toJSONObject());
-            selectedTrustChain.setMetadata(selectedMetadata);
+            log.debug("{} Set trust mark IDs extension to the client information: {}", getLogPrefix(), result);
+            ClientInformationExtensionSupport.addValidatedTrustMarkIds(clientInformation, result);
         } else {
-            clientInformation = new OIDCClientInformation(
-                    new ClientID(selectedTrustChain.getTrustChain().get(0).getSubject()), metadata);
-
-        }
-        final String clientId = clientInformation.getID().getValue();
-        final Map<String,List<String>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarkIds();
-        final List<String> mandatoryTrustMarks = mandatoryTrustMarksLookupStrategy.apply(profileRequestContext);
-        if (mandatoryTrustMarks != null && !mandatoryTrustMarks.isEmpty()) {
-            log.debug("{} Verifying the mandatory trust marks {}", getLogPrefix(), mandatoryTrustMarks);
-            if (verifiedTrustMarks == null || verifiedTrustMarks.get(clientId) == null 
-                    || !verifiedTrustMarks.get(clientId).containsAll(mandatoryTrustMarks)) {
-                log.info("{} Rejecting registration as some of the following mandatory trust marks are missing: {}",
-                        getLogPrefix(), mandatoryTrustMarks);
-                final List<List<EntityStatement<?>>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
-                if (rejectedTrustChains == null) {
-                    trustChainContext.setRejectedTrustChains(List.of(selectedTrustChain.getTrustChain()));
-                } else {
-                    final List<List<EntityStatement<?>>> rejectedChains = new ArrayList<>(rejectedTrustChains);
-                    rejectedChains.add(selectedTrustChain.getTrustChain());
-                    trustChainContext.setRejectedTrustChains(CollectionSupport.copyToList(rejectedChains));
-                }
-                ActionSupport.buildEvent(profileRequestContext, RESELECT_TRUST_CHAIN);
-                return;
-            }
-        }
-
-        final List<EntityStatement<?>> trustChain = selectedTrustChain.getTrustChain();
-        assert trustChain != null;
-        trustChainContext.setSelectedMetadataExpiration(resolveTrustChainExpiration(trustChain));
-
-        final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
-        clientInformation.getOIDCMetadata().setCustomField(
-                ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_ANCHOR,
-                trustChain.get(trustChain.size() - 1).getSubject());
-        clientInformation.getOIDCMetadata().setCustomField(
-                ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_CHAIN,
-                trustChain.stream().map(es -> es.getJwt().serialize()).toList());
-        if (verifiedTrustMarks != null) {
-            clientInformation.getOIDCMetadata().setCustomField(
-                    ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_MARK_IDS, verifiedTrustMarks);
+            log.trace("{} No trust marks resolved to be included in the client information", getLogPrefix());
         }
         log.debug("{} Client information after adding custom extension {}", getLogPrefix(),
                 clientInformation.toJSONObject().toJSONString());
-        oidcCtx.setClientInformation(clientInformation);
-        profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
-        log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
         final AutomaticRegistrationContext automaticRegistrationContext =
                 automaticRegistrationContextCreationStrategy.apply(profileRequestContext);
         if (automaticRegistrationContext == null) {
             log.error("{} Could not create automatic registration context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-            return;
+            return false;
         }
         final String decorationType;
         if (trustChainContext.getProvidedTrustChain() != null) {
@@ -335,21 +176,6 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
             log.debug("{} Resolved default claims set decoration type {}", getLogPrefix(), decorationType);
         }
         automaticRegistrationContext.setClaimsSetDecorationType(decorationType);
-    }
-
-    /**
-     * Resolve expiration time for the given trust chain.
-     * 
-     * @param trustChain trust chain
-     * @return expiration time
-     */
-    @Nullable private Instant resolveTrustChainExpiration(@Nonnull final List<EntityStatement<?>> trustChain) {
-        Instant metadataExpiration = null;
-        for (final EntityStatement<?> statement : trustChain) {
-            final Instant statementExpiration = statement.getParsedPayload().getExpiration();
-            metadataExpiration = metadataExpiration == null ? statementExpiration : 
-                statementExpiration.isBefore(metadataExpiration) ? statementExpiration : metadataExpiration;
-        }
-        return metadataExpiration;
+        return true;
     }
 }
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateFederationRegistrationProfileConfiguration.java
similarity index 73%
copy from idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
copy to idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateFederationRegistrationProfileConfiguration.java
index 72307e3..27dc0bd 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateFederationRegistrationProfileConfiguration.java
@@ -33,16 +33,13 @@ 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.plugin.oidc.op.oidfed.profile.context.AutomaticRegistrationContext;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.oidfed.metadata.EntityStatement;
 import net.shibboleth.oidfed.metadata.payload.claim.impl.MetadataImpl;
-import net.shibboleth.oidfed.profile.config.navigate.ClaimsSetDecorationTypeLookupFunction;
 import net.shibboleth.oidfed.profile.config.navigate.LocalMetadataPolicyLookupFunction;
 import net.shibboleth.oidfed.profile.config.navigate.MandatoryTrustMarksLookupFunction;
-import net.shibboleth.oidfed.profile.config.navigate.ProvidedChainClaimsSetDecorationTypeLookupFunction;
 import net.shibboleth.oidfed.profile.context.RelyingPartyTrustChainContext;
 import net.shibboleth.oidfed.profile.context.VerifiedTrustChain;
 import net.shibboleth.oidfed.profile.navigate.DefaultSelectedTrustChainMetadataLookupStrategy;
@@ -63,23 +60,19 @@ import org.opensaml.profile.action.ActionSupport;
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_MSG_CTX}
- * @event {@link ValidateAutomaticRegistrationProfileConfiguration#RESELECT_TRUST_CHAIN}
+ * @event {@link ValidateFederationRegistrationProfileConfiguration#RESELECT_TRUST_CHAIN}
  */
-public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractProfileAction {
+public class ValidateFederationRegistrationProfileConfiguration extends AbstractProfileAction {
 
     /** ID of event returned if a flow wishes to indicate that another trust chain should be selected instead. */
     @Nonnull @NotEmpty public static final String RESELECT_TRUST_CHAIN = "ReselectTrustChain";
 
     /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(ValidateAutomaticRegistrationProfileConfiguration.class);
+    @Nonnull private Logger log = LoggerFactory.getLogger(ValidateFederationRegistrationProfileConfiguration.class);
 
     /** Strategy used to lookup the trust chain context. */
     @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
 
-    /** Strategy used to create the automatic registration context. */
-    @Nonnull
-    private Function<ProfileRequestContext, AutomaticRegistrationContext> automaticRegistrationContextCreationStrategy;
-
     /** Strategy used to lookup the selected metadata. */
     @Nonnull private Function<ProfileRequestContext, OIDCClientMetadata> selectedMetadataLookupStrategy;
 
@@ -93,37 +86,24 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
     @NonnullAfterInit private BiFunction<OIDCClientInformation,Map<String, MetadataPolicy>,OIDCClientInformation>
         localMetadataPolicyMergingStrategy;
 
-    /** Strategy used to lookup claims set decoration type. */
-    @Nonnull private Function<ProfileRequestContext, String> claimsSetDecorationTypeLookupStrategy;
-
-    /** Strategy used to lookup claims set decoration type for clients with request-provided trust chain. */
-    @Nonnull private Function<ProfileRequestContext, String> providedChainClaimsSetDecorationTypeLookupStrategy;
-
     /** Trust chain context to operate on. */
-    @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+    @NonnullBeforeExec protected RelyingPartyTrustChainContext trustChainContext;
 
     /** Selected trust chain to operate on. */
-    @NonnullBeforeExec private VerifiedTrustChain selectedTrustChain;
+    @NonnullBeforeExec protected VerifiedTrustChain selectedTrustChain;
 
     /**
      * Constructor.
      */
-    public ValidateAutomaticRegistrationProfileConfiguration() {
+    public ValidateFederationRegistrationProfileConfiguration() {
         final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
                 new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
                         new InboundMessageContextLookup());
         assert tcls != null;
         trustChainContextLookupStrategy = tcls;
-        final Function<ProfileRequestContext, AutomaticRegistrationContext> arccs =
-                new ChildContextLookup<>(AutomaticRegistrationContext.class, true).compose(
-                        new InboundMessageContextLookup());
-        assert arccs != null;
-        automaticRegistrationContextCreationStrategy = arccs;
         selectedMetadataLookupStrategy = new DefaultSelectedTrustChainMetadataLookupStrategy();
         mandatoryTrustMarksLookupStrategy = new MandatoryTrustMarksLookupFunction();
         localMetadataPolicyLookupStrategy = new LocalMetadataPolicyLookupFunction();
-        claimsSetDecorationTypeLookupStrategy = new ClaimsSetDecorationTypeLookupFunction();
-        providedChainClaimsSetDecorationTypeLookupStrategy = new ProvidedChainClaimsSetDecorationTypeLookupFunction();
     }
 
     /**
@@ -138,18 +118,6 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
                 Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
     }
 
-    /**
-     * Set the strategy used to create automatic registration context.
-     * 
-     * @param strategy creation strategy
-     */
-    public void setAutomaticRegistrationContextCreationStrategy(
-            @Nonnull final Function<ProfileRequestContext, AutomaticRegistrationContext> strategy) {
-        checkSetterPreconditions();
-        automaticRegistrationContextCreationStrategy =
-                Constraint.isNotNull(strategy, "AutomaticRegistrationContextCreationStrategy cannot be null");
-    }
-
     /**
      * Set the strategy used to lookup the selected metadata.
      * 
@@ -198,30 +166,6 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
                 Constraint.isNotNull(strategy, "Local metadata policy merging strategy cannot be null");
     }
 
-    /**
-     * Set the strategy used to lookup claims set decoration type.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setClaimsSetDecorationTypeLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, String> strategy) {
-        checkSetterPreconditions();
-        claimsSetDecorationTypeLookupStrategy =
-                Constraint.isNotNull(strategy, "Claims set decoration type lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the strategy used to lookup claims set decoration type for clients with request-provided trust chain.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setProvidedChainClaimsSetDecorationTypeLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, String> strategy) {
-        checkSetterPreconditions();
-        providedChainClaimsSetDecorationTypeLookupStrategy =
-                Constraint.isNotNull(strategy, "Claims set decoration type lookup strategy cannot be null");
-    }
-
     /** {@inheritDoc} */
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -310,31 +254,28 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
         clientInformation.getOIDCMetadata().setCustomField(
                 ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_CHAIN,
                 trustChain.stream().map(es -> es.getJwt().serialize()).toList());
-        if (verifiedTrustMarks != null) {
-            clientInformation.getOIDCMetadata().setCustomField(
-                    ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_MARK_IDS, verifiedTrustMarks);
+
+        if (!extendedExecution(profileRequestContext, clientInformation)) {
+            log.debug("{} Extended execution returned false", getLogPrefix());
+            return;
         }
-        log.debug("{} Client information after adding custom extension {}", getLogPrefix(),
-                clientInformation.toJSONObject().toJSONString());
+
         oidcCtx.setClientInformation(clientInformation);
         profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
         log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
-        final AutomaticRegistrationContext automaticRegistrationContext =
-                automaticRegistrationContextCreationStrategy.apply(profileRequestContext);
-        if (automaticRegistrationContext == null) {
-            log.error("{} Could not create automatic registration context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
-            return;
-        }
-        final String decorationType;
-        if (trustChainContext.getProvidedTrustChain() != null) {
-            decorationType = providedChainClaimsSetDecorationTypeLookupStrategy.apply(profileRequestContext);
-            log.debug("{} Resolved provided chain claims set decoration type {}", getLogPrefix(), decorationType);
-        } else {
-            decorationType = claimsSetDecorationTypeLookupStrategy.apply(profileRequestContext);
-            log.debug("{} Resolved default claims set decoration type {}", getLogPrefix(), decorationType);
-        }
-        automaticRegistrationContext.setClaimsSetDecorationType(decorationType);
+    }
+
+    /**
+     * The extension hooks for the classes extending this action. If the extension run is not successful, in addition
+     * to returning false the extension is expected to build appropriate event regarding the error.
+     * 
+     * @param profileRequestContext profile request context
+     * @param clientInformation client information that will be added to {@link OIDCMetadataContext}
+     * @return true if execution was successful, false otherwise
+     */
+    protected boolean extendedExecution(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final OIDCClientInformation clientInformation) {
+        return true;
     }
 
     /**
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
index c465923..d71596b 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
@@ -282,6 +282,10 @@
             <bean class="net.shibboleth.oidfed.profile.config.navigate.ProvidedChainClaimsSetDecorationTypeLookupFunction"
                 p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"/>
         </property>
+        <property name="maximumTrustMarkLifetimeLookupStrategy">
+            <bean class="net.shibboleth.oidfed.profile.config.navigate.MaximumTrustMarkLifetimeLookupFunction"
+                p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"/>
+        </property>
     </bean>
 
 </beans>
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index d4390e0..8b9eec8 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -397,7 +397,7 @@
         p:metadataPolicyValidationStrategy="#{getObject('shibboleth.oidc.dynreg.MetadataPolicyValidator') ?: getObject('shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator')}" />
 
     <bean id="ValidateExplicitRegistrationProfileConfiguration"
-        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateAutomaticRegistrationProfileConfiguration"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateFederationRegistrationProfileConfiguration"
         scope="prototype">
         <property name="localMetadataPolicyMergingStrategy">
             <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultLocalMetadataPolicyMergingStrategy"
diff --git a/idp-oidfed-op-impl/src/main/resources/schema/shibboleth-afp-oidfed.xsd b/idp-oidfed-op-impl/src/main/resources/schema/shibboleth-afp-oidfed.xsd
index 5f57c35..b1971a3 100644
--- a/idp-oidfed-op-impl/src/main/resources/schema/shibboleth-afp-oidfed.xsd
+++ b/idp-oidfed-op-impl/src/main/resources/schema/shibboleth-afp-oidfed.xsd
@@ -17,8 +17,14 @@
             </documentation>
         </annotation>
         <complexContent>
-            <extension base="afp:StringMatchType" />
+            <extension base="afp:StringMatchType">
+                <attribute name="clockSkew" type="afp:string">
+                    <annotation>
+                        <documentation>The clock skew value to use for calculating expiration time for trust marks. Time is expressed in ISO8601 duration format.</documentation>
+                    </annotation>
+                </attribute>
+            </extension>
         </complexContent>
     </complexType>
 
-</schema>
\ No newline at end of file
+</schema>
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
index d29b5e8..07d6373 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
@@ -14,11 +14,17 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.NoSuchAlgorithmException;
 import java.time.Instant;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -32,6 +38,7 @@ import org.testng.annotations.Test;
 
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.oauth2.sdk.OAuth2Error;
 import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.id.ClientID;
@@ -122,6 +129,64 @@ public class UserInfoFlowAutomaticRegistrationTest extends AbstractFederationFlo
         Assert.assertNull(response.getUserInfoJWT());
     }
 
+    @Test
+    public void testSuccess_explicit_extraClaimViaTrustMark() throws URISyntaxException, NoSuchAlgorithmException,
+        DataSealerException, ComponentInitializationException, IOException {
+        final String clientId = uniqueClientId();
+
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setRedirectionURI(new URI(redirectUri));
+        metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+        final String trustMark = FederationJwtSupport.trustMark(JWSAlgorithm.RS256, trustMarkIssuerKey,
+                trustMarkIssuerId, clientId, "https://example.org/email-allowing-trust-mark",
+                Instant.now().plusSeconds(300)).serialize();
+
+        final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(clientId).subject(clientId)
+                .issueTime(Date.from(Instant.now()))
+                .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+                .audience(issuer)
+                .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+                .claim("metadata", Map.of("openid_relying_party", metadata.toJSONObject()))
+                .claim("authority_hints", new String[] { anchorId })
+                .claim("trust_marks", List.of(Map.of(
+                        "trust_mark_type", "https://example.org/email-allowing-trust-mark",
+                        "trust_mark", trustMark)));
+        final String explicitRegistrationRequest =
+                FederationJwtSupport.entityStatement(JWSAlgorithm.RS256, leafKey, builder.build()).serialize();
+
+        rpConfigureMockHttpClient(clientId);
+        try {
+            mapResponse(entityConfigurationUrl(trustMarkIssuerId),
+                    mockResponse(trustMarkIssuerConfiguration(trustMarkIssuerId)));
+            mapResponse(subordinateStatementUrl(anchorFetchEndpoint, trustMarkIssuerId),
+                    mockResponse(subordinateStatement(trustMarkIssuerId,
+                            Map.of("federation_entity", CollectionSupport.emptyMap()), trustMarkIssuerKey)));
+            mapResponse(trustMarkStatusEndpoint, mockResponse(200, "application/trust-mark-status-response+jwt",
+                    trustMarkStatusResponse(trustMarkIssuerId, trustMark, "active", trustMarkIssuerKey)));
+        } catch (UnsupportedOperationException | IOException e) {
+            Assert.fail("Could not initialize mock HTTP client", e);
+        }
+        setRequest("POST", explicitRegistrationRequest, "application/entity-statement+jwt");
+        flowExecutor.launchExecution(RegistrationFlowTest.FLOW_ID, null, externalContext);
+        verify(federationHttpClient, times(0)).executeOpen(any(),
+                argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
+
+        initializeMocks();
+        initializeThreadLocals();
+        request.setMethod("GET");
+
+        final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+        request.addHeader("Authorization", token.toAuthorizationHeader());
+        final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+        final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+        Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+        final UserInfo userInfo = response.getUserInfo();
+        Assert.assertNotNull(userInfo);
+        Assert.assertEquals(userInfo.getEmailAddress(), "jdoe at example.org");
+        Assert.assertNull(userInfo.getNickname());
+        Assert.assertNull(response.getUserInfoJWT());
+    }
+
     @Test
     public void testSuccess_resolveApi() throws Exception {
         final String clientId = uniqueClientId();
diff --git a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml
index 606841a..b2a1981 100644
--- a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml
+++ b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-filter.xml
@@ -132,7 +132,7 @@
     </AttributeFilterPolicy>
 
     <AttributeFilterPolicy id="OIDFED_EMAIL">
-        <PolicyRequirementRule xsi:type="oidfed:OIDFEDEntityTrustMark" value="https://example.org/email-allowing-trust-mark" />
+        <PolicyRequirementRule xsi:type="oidfed:OIDFEDEntityTrustMark" value="https://example.org/email-allowing-trust-mark" clockSkew="PT30S"/>
         <AttributeRule attributeID="mail">
             <PermitValueRule xsi:type="ANY" />
         </AttributeRule>

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


More information about the commits mailing list