[java-oidfed-common] branch main updated: Improvements for the trust chain fetching

Codeberg noreply at shibboleth.net
Fri Sep 18 11:47:47 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-oidfed-common.

View the commit online:
https://codeberg.org/Shibboleth/java-oidfed-common/commit/1dd71e954f97fbf2f73427093b72ceb9974dead5

The following commit(s) were added to refs/heads/main by this push:
     new 1dd71e9  Improvements for the trust chain fetching
1dd71e9 is described below

commit 1dd71e954f97fbf2f73427093b72ceb9974dead5
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Sep 18 14:45:38 2026 +0300

    Improvements for the trust chain fetching
    
    - Comparison against the predefined trust chains now works
    - Improved handling of unresolvable authority hints
---
 .../DefaultTrustChainFetchingStrategy.java         |  21 ++-
 .../DefaultTrustChainFetchingStrategyTest.java     | 148 +++++++++++++++++++--
 2 files changed, 153 insertions(+), 16 deletions(-)

diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategy.java
index 088c627..38bb8a1 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategy.java
@@ -252,7 +252,7 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
             return null;
         }
         final Integer maximumAuthorityHints = criteriaToMaximumAuthorityHintsStrategy.apply(criteria);
-        if (maximumAuthorityHints == null || maximumLength.intValue() < 1) {
+        if (maximumAuthorityHints == null || maximumAuthorityHints.intValue() < 1) {
             log.warn("Could not fetch valid maximum authority hints, it must be greater than 0: {}",
                     maximumAuthorityHints);
             return null;
@@ -355,7 +355,7 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
                         .map(id -> fetchAuthority(entityStatement, id))
                         .filter(pair -> pair != null && pair.getFirst() != null && pair.getSecond() != null)
                         .toList();
-                hints = !authorities.isEmpty();
+                hints = hints || !authorities.isEmpty();
                 authorities.forEach(authority -> {
                     final ArrayList<EntityStatement<?>> newChain = new ArrayList<>(chain.size() > 2 ? 
                             chain.subList(0, chain.size() - 1) : chain);
@@ -401,7 +401,8 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
         if (preSelectedChain.isEmpty()) {
             return true;
         }
-        if (preSelectedChain.size() <= chain.size() || !preSelectedChain.get(chain.size()).equals(authority)) {
+        int index = chain.size() > 1 ? chain.size() - 1 : chain.size();
+        if (preSelectedChain.size() < chain.size() || !preSelectedChain.get(index).equals(authority)) {
             log.debug("Ignoring authority {} as it doesn't match with the preselected chain {}", authority,
                     preSelectedChain);
             return false;
@@ -444,12 +445,20 @@ public class DefaultTrustChainFetchingStrategy extends AbstractIdentifiableIniti
         try {
             final List<EntityConfigurationContainer> authorityConfigurations = 
                     entityConfigurationCache.get(new CriteriaSet(new SubjectEntityIDCriterion(authorityHint)));
-            log.trace("Fetched comfigurations {} for authority {}", authorityConfigurations, authorityHint);
+            log.trace("Fetched configurations {} for authority {}", authorityConfigurations, authorityHint);
+            if (authorityConfigurations.isEmpty()) {
+                log.warn("Could not fetch configuration for authority {}", authorityHint);
+                return null;
+            }
             final List<SubordinateStatementContainer> subordinateStatements =
                     subordinateStatementCache.get(new CriteriaSet(new SubjectEntityIDCriterion(entityId),
                             new IssuerEntityIDCriterion(authorityHint)));
-            log.trace("Fetched subordinate statements {} for authority {}", subordinateStatements, authorityHint);
-            return new Pair<>(authorityConfigurations.isEmpty() ? null : authorityConfigurations.get(0).getStatement(),
+            log.trace("Fetched subordinate statements {} from authority {} for {}", subordinateStatements,
+                    authorityHint, entityId);
+            if (subordinateStatements.isEmpty()) {
+                log.warn("Could not fetch subordinate statement of {} from {}", entityId, authorityHint);
+            }
+            return new Pair<>(authorityConfigurations.get(0).getStatement(),
                     subordinateStatements.isEmpty() ? null : subordinateStatements.get(0).getStatement());
         } catch (final MetadataCacheException e) {
             log.error("Could not resolve authority hint {} for {}", authorityHint, entityId);
diff --git a/oidfed-common-impl/src/test/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategyTest.java b/oidfed-common-impl/src/test/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategyTest.java
index b69c37d..124a893 100644
--- a/oidfed-common-impl/src/test/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategyTest.java
+++ b/oidfed-common-impl/src/test/java/net/shibboleth/oidfed/metadata/cache/trustchain/DefaultTrustChainFetchingStrategyTest.java
@@ -256,7 +256,7 @@ public class DefaultTrustChainFetchingStrategyTest {
 
     @SuppressWarnings("unchecked")
     @Test
-    public void entityConfigurationOneResolvableLocalAuthorityWithWorkingHint_maxlnght3_returnsContainerWithTwoChains()
+    public void entityConfigurationOneResolvableLocalAuthorityWithWorkingHint_maxlnght3_returnsContainerWithOneChain()
             throws MetadataCacheException {
         final String leaf = "https://federation.local/leaf";
         final String immediate = "https://federation.local/immediate";
@@ -289,34 +289,162 @@ public class DefaultTrustChainFetchingStrategyTest {
                 List.of(leaf, immediate));
     }
 
+    @SuppressWarnings("unchecked")
+    @Test
+    public void entityConfigurationTwoHints_latterOneNotWorking_returnsContainerWithTwoChains()
+            throws MetadataCacheException {
+        final String leaf = "https://federation.local/leaf";
+        final String localAnchor = "https://federation.local/localAnchor";
+        final String anchor = "https://federation.local/anchor";
+        final String workingIntermediate = "http://federation.local/intermediate1";
+        final String anotherIntermediate = "https://federation.local/intermediate2";
+        final String untrustedAnchor = "https://federation.local/untrustedAnchor";
+        when(criteriaToValidContainerLifetimeStrategy.apply(any())).thenReturn(Duration.ofMinutes(5));
+        when(criteriaToInvalidContainerLifetimeStrategy.apply(any())).thenReturn(Duration.ofMinutes(5));
+        when(criteriaToMaximumChainLengthStrategy.apply(any())).thenReturn(5);
+        when(criteriaToMaximumAuthorityHintsStrategy.apply(any())).thenReturn(2);
+        final EntityConfigurationContainer ecContainer =
+                ecContainer(leaf, CollectionSupport.listOf(workingIntermediate, anotherIntermediate));
+        final EntityConfigurationContainer workingIntermediateContainer =
+                ecContainer(workingIntermediate, List.of(localAnchor));
+        final EntityConfigurationContainer authorityContainer =
+                ecContainer(anchor, CollectionSupport.emptyList());
+        final EntityConfigurationContainer anotherIntermediateContainer =
+                ecContainer(anotherIntermediate, List.of(untrustedAnchor));
+        final EntityConfigurationContainer localAnchorContainer =
+                ecContainer(localAnchor, CollectionSupport.listOf(anchor));
+        final EntityConfigurationContainer untrustedContainer =
+                ecContainer(untrustedAnchor, CollectionSupport.emptyList());
+        when(entityConfigurationCache.get(any())).thenReturn(
+                CollectionSupport.listOf(ecContainer),
+                CollectionSupport.listOf(workingIntermediateContainer),
+                CollectionSupport.listOf(anotherIntermediateContainer),
+                CollectionSupport.listOf(localAnchorContainer),
+                CollectionSupport.listOf(untrustedContainer),
+                CollectionSupport.listOf(authorityContainer),
+                CollectionSupport.emptyList());
+        final SubordinateStatementContainer ssContainer1 = ssContainer(leaf, workingIntermediate);
+        final SubordinateStatementContainer ssContainer2 = ssContainer(leaf, anotherIntermediate);
+        final SubordinateStatementContainer ssContainer3 = ssContainer(workingIntermediate, localAnchor);
+        final SubordinateStatementContainer ssContainer5 = ssContainer(localAnchor, anchor);
+        when(subordinateStatementCache.get(any())).thenReturn(
+                CollectionSupport.listOf(ssContainer1),
+                CollectionSupport.listOf(ssContainer2),
+                CollectionSupport.listOf(ssContainer3),
+                CollectionSupport.emptyList(),
+                CollectionSupport.listOf(ssContainer5),
+                CollectionSupport.emptyList());
+        when(localTrustAnchorsCache.get(any())).thenReturn(
+                CollectionSupport.emptyList(),
+                CollectionSupport.emptyList(),
+                CollectionSupport.listOf(CollectionSupport.singletonMap(localAnchor, mock(LocalKeyContainer.class))),
+                CollectionSupport.emptyList());
+        final TrustChainsContainer result = function.apply(new CriteriaSet());
+        Assert.assertNotNull(result);
+        assert result != null;
+        Assert.assertEquals(result.getTrustChains().size(), 2);
+        Assert.assertEquals(EntityStatementHelper.getEntityIds(result.getTrustChains().get(0)),
+                List.of(leaf, workingIntermediate, localAnchor));
+        Assert.assertEquals(EntityStatementHelper.getEntityIds(result.getTrustChains().get(1)),
+                List.of(leaf, workingIntermediate, localAnchor, anchor));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void entityConfigurationTwoHints_firstOneNotWorking_returnsContainerWithTwoChains()
+            throws MetadataCacheException {
+        final String leaf = "https://federation.local/leaf";
+        final String localAnchor = "https://federation.local/localAnchor";
+        final String anchor = "https://federation.local/anchor";
+        final String workingIntermediate = "http://federation.local/intermediate1";
+        final String anotherIntermediate = "https://federation.local/intermediate2";
+        final String untrustedAnchor = "https://federation.local/untrustedAnchor";
+        when(criteriaToValidContainerLifetimeStrategy.apply(any())).thenReturn(Duration.ofMinutes(5));
+        when(criteriaToInvalidContainerLifetimeStrategy.apply(any())).thenReturn(Duration.ofMinutes(5));
+        when(criteriaToMaximumChainLengthStrategy.apply(any())).thenReturn(5);
+        when(criteriaToMaximumAuthorityHintsStrategy.apply(any())).thenReturn(2);
+        final EntityConfigurationContainer ecContainer =
+                ecContainer(leaf, CollectionSupport.listOf(anotherIntermediate, workingIntermediate));
+        final EntityConfigurationContainer workingIntermediateContainer =
+                ecContainer(workingIntermediate, List.of(localAnchor));
+        final EntityConfigurationContainer authorityContainer =
+                ecContainer(anchor, CollectionSupport.emptyList());
+        final EntityConfigurationContainer anotherIntermediateContainer =
+                ecContainer(anotherIntermediate, List.of(untrustedAnchor));
+        final EntityConfigurationContainer localAnchorContainer =
+                ecContainer(localAnchor, CollectionSupport.listOf(anchor));
+        final EntityConfigurationContainer untrustedContainer =
+                ecContainer(untrustedAnchor, CollectionSupport.emptyList());
+        when(entityConfigurationCache.get(any())).thenReturn(
+                CollectionSupport.listOf(ecContainer),
+                CollectionSupport.listOf(anotherIntermediateContainer),
+                CollectionSupport.listOf(workingIntermediateContainer),
+                CollectionSupport.listOf(untrustedContainer),
+                CollectionSupport.listOf(localAnchorContainer),
+                CollectionSupport.listOf(authorityContainer),
+                CollectionSupport.emptyList());
+        final SubordinateStatementContainer ssContainer1 = ssContainer(leaf, anotherIntermediate);
+        final SubordinateStatementContainer ssContainer2 = ssContainer(leaf, workingIntermediate);
+        final SubordinateStatementContainer ssContainer3 = ssContainer(workingIntermediate, localAnchor);
+        final SubordinateStatementContainer ssContainer5 = ssContainer(localAnchor, anchor);
+        when(subordinateStatementCache.get(any())).thenReturn(
+                CollectionSupport.listOf(ssContainer1),
+                CollectionSupport.listOf(ssContainer2),
+                CollectionSupport.emptyList(),
+                CollectionSupport.listOf(ssContainer3),
+                CollectionSupport.listOf(ssContainer5),
+                CollectionSupport.emptyList());
+        when(localTrustAnchorsCache.get(any())).thenReturn(
+                CollectionSupport.emptyList(),
+                CollectionSupport.emptyList(),
+                CollectionSupport.listOf(CollectionSupport.singletonMap(localAnchor, mock(LocalKeyContainer.class))),
+                CollectionSupport.emptyList());
+        final TrustChainsContainer result = function.apply(new CriteriaSet());
+        Assert.assertNotNull(result);
+        assert result != null;
+        Assert.assertEquals(result.getTrustChains().size(), 2);
+        Assert.assertEquals(EntityStatementHelper.getEntityIds(result.getTrustChains().get(0)),
+                List.of(leaf, workingIntermediate, localAnchor));
+        Assert.assertEquals(EntityStatementHelper.getEntityIds(result.getTrustChains().get(1)),
+                List.of(leaf, workingIntermediate, localAnchor, anchor));
+    }
+
     @SuppressWarnings("unchecked")
     @Test
     public void entityConfigurationOneResolvableHint_matchPreSelected_returnsContainerWithOneChain()
             throws MetadataCacheException {
         final String leaf = "https://federation.local/leaf";
-        final String anchor = "https://federation.local/immediate";
+        final String intermediate = "https://federation.local/intermediate";
+        final String anchor = "https://federation.local/anchor";
         final PreSelectedTrustChainCriterion preSelectedCriterion =
-                new PreSelectedTrustChainCriterion(CollectionSupport.listOf(leaf, anchor));
+                new PreSelectedTrustChainCriterion(CollectionSupport.listOf(leaf, intermediate, anchor));
         when(criteriaToValidContainerLifetimeStrategy.apply(any())).thenReturn(Duration.ofMinutes(5));
         when(criteriaToInvalidContainerLifetimeStrategy.apply(any())).thenReturn(Duration.ofMinutes(5));
         when(criteriaToMaximumChainLengthStrategy.apply(any())).thenReturn(4);
         when(criteriaToMaximumAuthorityHintsStrategy.apply(any())).thenReturn(2);
         final EntityConfigurationContainer ecContainer =
-                ecContainer(leaf, CollectionSupport.listOf(anchor, "https://federation.local/other"));
+                ecContainer(leaf, CollectionSupport.listOf(intermediate, "https://federation.local/other"));
+        final EntityConfigurationContainer intermediateContainer =
+                ecContainer(intermediate, CollectionSupport.listOf(anchor));
         final EntityConfigurationContainer authorityContainer =
                 ecContainer(anchor, CollectionSupport.emptyList());
-        when(entityConfigurationCache.get(any()))
-            .thenReturn(CollectionSupport.listOf(ecContainer), CollectionSupport.listOf(authorityContainer),
-                    CollectionSupport.emptyList());
-        final SubordinateStatementContainer ssContainer = ssContainer(leaf, anchor);
-        when(subordinateStatementCache.get(any())).thenReturn(CollectionSupport.listOf(ssContainer),
+        when(entityConfigurationCache.get(any())).thenReturn(
+                CollectionSupport.listOf(ecContainer),
+                CollectionSupport.listOf(intermediateContainer),
+                CollectionSupport.listOf(authorityContainer),
+                CollectionSupport.emptyList());
+        final SubordinateStatementContainer ssContainer = ssContainer(leaf, intermediate);
+        final SubordinateStatementContainer ssContainer2 = ssContainer(intermediate, anchor);
+        when(subordinateStatementCache.get(any())).thenReturn(
+                CollectionSupport.listOf(ssContainer),
+                CollectionSupport.listOf(ssContainer2),
                 CollectionSupport.emptyList());
         final TrustChainsContainer result = function.apply(new CriteriaSet(preSelectedCriterion));
         Assert.assertNotNull(result);
         assert result != null;
         Assert.assertEquals(result.getTrustChains().size(), 1);
         Assert.assertEquals(EntityStatementHelper.getEntityIds(result.getTrustChains().get(0)),
-                List.of(leaf, anchor));
+                List.of(leaf, intermediate, anchor));
     }
 
     @SuppressWarnings("unchecked")

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


More information about the commits mailing list