[java-idp-oidc] 06/31: JOIDC-222 - Support for OpenID Federation

Henri Mikkonen henri.mikkonen at iki.fi
Tue Jun 24 08:52:42 UTC 2025


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=ff8c0579ea576735189ec562b66aefc9923f6c4c

commit ff8c0579ea576735189ec562b66aefc9923f6c4c
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Jan 16 18:08:24 2025 +0200

    JOIDC-222 - Support for OpenID Federation
    
    https://shibboleth.atlassian.net/browse/JOIDC-222
    
    Perform trust chain reselection if the selected trust chain does not meet profile configuration
    - Rejected trust chains are stored in the trust chain context to avoid reselection
---
 .../impl/RelyingPartyTrustChainContext.java        | 27 ++++++++++++++-
 .../op/oidfed/profile/impl/ResolveTrustChains.java |  2 +-
 .../op/oidfed/profile/impl/SelectTrustChain.java   |  6 ++++
 ...eAutomaticRegistrationProfileConfiguration.java | 17 +++++++++-
 .../DefaultTrustChainSelectionStrategy.java        | 39 ++++++++++++++++++----
 .../idp/flows/oidc/authorize/authorize-flow.xml    |  1 +
 6 files changed, 83 insertions(+), 9 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
index 94442062..37f0a031 100644
--- 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
@@ -50,6 +50,9 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
     /** Verified trust mark IDs for the selected trust chain. */
     @Nullable private Map<String, List<String>> verifiedTrustMarkIds;
 
+    /** All previously selected but rejected trust chains. */
+    @Nullable private List<List<EntityStatement>> rejectedTrustChains;
+
     /**
      * Get the resolved trust chains for the relying party.
      * 
@@ -158,5 +161,27 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
         verifiedTrustMarkIds = ids;
         return this;
     }
-    
+
+    /**
+     * Get the previously selected but rejected trust chains for the relying party.
+     * 
+     * @return the trust chains
+     */
+    @Nullable public List<List<EntityStatement>> getRejectedTrustChains() {
+        return rejectedTrustChains;
+    }
+
+    /**
+     * Set the previously selected but rejected trust chains for the relying party.
+     * 
+     * @param trustChains the trust chains
+     * 
+     * @return this context
+     */
+    @Nonnull public RelyingPartyTrustChainContext setRejectedTrustChains(
+            @Nullable final List<List<EntityStatement>> trustChains) {
+        rejectedTrustChains = trustChains;
+        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
index e55c5f81..2f5d5d34 100644
--- 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
@@ -201,7 +201,7 @@ public class ResolveTrustChains extends AbstractProfileAction {
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        log.debug("Resolving trust chain for {)", clientId);
+        log.debug("{} Resolving trust chain for {}", getLogPrefix(), clientId);
         final List<List<List<EntityStatement>>> cacheResult;
         try {
             assert clientId != null;
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
index 897dfc69..cec81961 100644
--- 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
@@ -156,6 +156,12 @@ public class SelectTrustChain extends AbstractProfileAction {
             return;
         }
 
+        final List<List<EntityStatement>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+        if (rejectedTrustChains != null && rejectedTrustChains.contains(selectedChain.getFirst())) {
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            log.error("{} The selected trust chain has been previously rejected", getLogPrefix());
+            return;
+        }
         trustChainContext.setSelectedTrustChains(selectedChain);
         final List<EntityStatement> selectedTrustChain = selectedChain.getFirst();
         assert selectedTrustChain != null;
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
index 24b0f7de..0e4e4ad4 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
@@ -15,6 +15,7 @@
 package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiFunction;
@@ -38,6 +39,8 @@ import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 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.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;
@@ -51,11 +54,15 @@ import org.opensaml.profile.action.ActionSupport;
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link ValidateAutomaticRegistrationProfileConfiguration#RESELECT_TRUST_CHAIN}
  * 
  * @since 4.3.0
  */
 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";
+
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(ValidateAutomaticRegistrationProfileConfiguration.class);
 
@@ -196,7 +203,15 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
                     || !verifiedTrustMarks.get(clientId).containsAll(mandatoryTrustMarks)) {
                 log.info("{} Rejecting registration as some of the following mandatory trust marks are missing: {}",
                         getLogPrefix(), mandatoryTrustMarks);
-                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+                final List<List<EntityStatement>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+                if (rejectedTrustChains == null) {
+                    trustChainContext.setRejectedTrustChains(List.of(selectedTrustChain.getFirst()));
+                } else {
+                    final List<List<EntityStatement>> rejectedChains = new ArrayList<>(rejectedTrustChains);
+                    rejectedChains.add(selectedTrustChain.getFirst());
+                    trustChainContext.setRejectedTrustChains(CollectionSupport.copyToList(rejectedChains));
+                }
+                ActionSupport.buildEvent(profileRequestContext, RESELECT_TRUST_CHAIN);
                 return;
             }
         }
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
index 702d373d..1e3b84a1 100644
--- 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
@@ -35,7 +35,8 @@ 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.
+ * shortest. The selection must not be included in the list of previously rejected trust chains, obtained via
+ * {@link RelyingPartyTrustChainContext#getRejectedTrustChains()}.
  */
 public class DefaultTrustChainSelectionStrategy implements
         Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>> {
@@ -83,18 +84,44 @@ public class DefaultTrustChainSelectionStrategy implements
             log.debug("No policy compliant chains located");
             return null;
         }
-        
-        int shortestIndex = 0;
+
+        log.trace("Policy-compatible trust chains: {}", policyCompliantChains.size());
         if (policyCompliantChains.size() > 1) {
+            int shortestIndex = -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()) {
+                if (isTrustChainRejected(trustChainContext, candidate)) {
+                    continue;
+                }
+                if (shortestIndex == -1) {
                     shortestIndex = i;
+                } else {
+                    final List<EntityStatement> shortest = policyCompliantChains.get(shortestIndex).getFirst();
+                    if (candidate != null && shortest != null && candidate.size() < shortest.size()) {
+                        shortestIndex = i;
+                    }
                 }
             }
+            log.trace("Shortest non-rejected index {}", shortestIndex);
+            return shortestIndex == -1 ? null : policyCompliantChains.get(shortestIndex);
         }
-        return policyCompliantChains.get(shortestIndex);
+        final List<EntityStatement> candidate = policyCompliantChains.get(0).getFirst();
+        return isTrustChainRejected(trustChainContext, candidate) ? null : policyCompliantChains.get(0);
     }
 
+    /**
+     * Checks whether the trust chain has been previously rejected in the given context.
+     * 
+     * @param trustChainContext context containing the previously rejected trust chain
+     * @param trustChain trust chain to be verified
+     * @return true if trust chain is null or previously rejected, false otherwise
+     */
+    private boolean isTrustChainRejected(@Nonnull final RelyingPartyTrustChainContext trustChainContext,
+            @Nullable final List<EntityStatement> trustChain) {
+        if (trustChain == null) {
+            return true;
+        }
+        final List<List<EntityStatement>> rejectedTrustChains = trustChainContext.getRejectedTrustChains();
+        return rejectedTrustChains != null && rejectedTrustChains.contains(trustChain);
+    }
 }
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 9846f0f2..5fe04f32 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
@@ -53,6 +53,7 @@
         <evaluate expression="ValidateAutomaticRegistrationProfileConfiguration" />
         <evaluate expression="InitializeRelyingPartyContext" />
         <evaluate expression="'proceed'" />
+        <transition on="ReselectTrustChain" to="DoAutomaticRegistration" />
         <transition on="proceed" to="DoSelectConfiguration">
             <set name="flowScope.automaticallyRegistered" value="opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext))" />
         </transition>

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


More information about the commits mailing list