[java-idp-plugin-oidc-op-oidfed] 02/03: Add configurable cache container lifetime strategy for cases where response cannot be fetched

Codeberg noreply at shibboleth.net
Thu Jan 22 11:41:11 UTC 2026


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

codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.

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

commit 4b6448eaf44fd2da87834db5e70164dbf7034f75
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Jan 21 11:45:16 2026 +0200

    Add configurable cache container lifetime strategy for cases where response cannot be fetched
    
    - Covers for instance network issues
    - Wired configuration properties for each metadata cache, all default to PT0S (no caching)
      - idp.oidfed.cache.entityConfiguration.exceptionContainerLifetime
      - idp.oidfed.cache.subordinateStatement.exceptionContainerLifetime
      - idp.oidfed.cache.resolveEntity.exceptionContainerLifetime
      - idp.oidfed.cache.trustMarkStatus.exceptionContainerLifetime
      - idp.oidfed.cache.trustMark.exceptionContainerLifetime
---
 ...FederationEndpointResponseFetchingStrategy.java | 53 +++++++++++++++++++---
 ...DefaultEntityConfigurationFetchingStrategy.java | 13 ++++--
 ...ultResolveEntityTrustChainFetchingStrategy.java | 12 ++++-
 ...efaultSubordinateStatementFetchingStrategy.java | 13 ++++--
 .../DefaultTrustMarkFetchingStrategy.java          | 14 ++++--
 .../DefaultTrustMarkStatusFetchingStrategy.java    | 14 ++++--
 .../META-INF/net.shibboleth.idp/postconfig.xml     | 35 ++++++++++++++
 .../flow/oidfed/AbstractFederationFlowTest.java    |  2 +-
 .../EntityConfigurationMetadataCacheTest.java      | 21 +++++++++
 .../SubordinateStatementMetadataCacheTest.java     | 35 +++++++++++++-
 10 files changed, 189 insertions(+), 23 deletions(-)

diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/AbstractFederationEndpointResponseFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/AbstractFederationEndpointResponseFetchingStrategy.java
index 5eb34eb..7897a17 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/AbstractFederationEndpointResponseFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/AbstractFederationEndpointResponseFetchingStrategy.java
@@ -72,6 +72,9 @@ public abstract class
     /** Strategy to fetch lifetime for container with invalid contents. */
     @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToInvalidContainerLifetimeStrategy;
 
+    /** Strategy to fetch lifetime for containers whose contents could not be fetched. */
+    @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToExceptionContainerLifetimeStrategy;
+
     /**
      * Set the {@link HttpClient} to use.
      * 
@@ -140,6 +143,19 @@ public abstract class
                 Constraint.isNotNull(strategy, "Criteria to invalid container lifetime strategy cannot be null");
     }
 
+    /**
+     * Set the strategy to fetch lifetime for container whose contents could not be fetched.
+     * 
+     * @param strategy lifetime strategy
+     */
+    public void setCriteriaToExceptionContainerLifetimeStrategy(
+            @Nonnull final Function<CriteriaSet, Duration> strategy) {
+        checkSetterPreconditions();
+
+        criteriaToExceptionContainerLifetimeStrategy =
+                Constraint.isNotNull(strategy, "Criteria to exception container lifetime strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -160,6 +176,10 @@ public abstract class
             throw new ComponentInitializationException(
                     "Criteria to invalid container lifetime strategy cannot be null");
         }
+        if (criteriaToExceptionContainerLifetimeStrategy == null) {
+            throw new ComponentInitializationException(
+                    "Criteria to exception container lifetime strategy cannot be null");
+        }
     }
 
     /** {@inheritDoc} */
@@ -187,6 +207,12 @@ public abstract class
             return null;
         }
         final Instant invalidExpiration = Instant.now().plus(invalidLifetime);
+        final Duration exceptionLifetime = criteriaToExceptionContainerLifetimeStrategy.apply(criteria);
+        if (exceptionLifetime == null) {
+            log.warn("Could not fetch expiration time for exception container");
+            return null;
+        }
+        final Instant exceptionExpiration = Instant.now().plus(exceptionLifetime);
         final ClassicHttpRequest httpRequest = initializeHttpRequest(criteria, requestData);
         if (httpRequest == null) {
             log.debug("Could not initialize HTTP request for {}", requestData);
@@ -199,13 +225,14 @@ public abstract class
                 throw new URISyntaxException(httpRequest.getUri().toString(), "Could not parse scheme");
             }
             HttpClientSecuritySupport.checkTLSCredentialEvaluated(httpContext, scheme);
-            assert validExpiration != null; assert invalidExpiration != null;
-            return parseHttpResponse(criteria, requestData, response, validExpiration, invalidExpiration);
+            assert validExpiration != null; assert invalidExpiration != null; assert exceptionExpiration != null;
+            return parseHttpResponse(criteria, requestData, response, validExpiration, invalidExpiration,
+                    exceptionExpiration);
         } catch (final ProtocolException | URISyntaxException | IOException e) {
             log.debug("Unable to fetch resolve entity response via request data: {}", requestData, e);
+            assert exceptionExpiration != null;
+            return handleException(criteria, requestData, e, exceptionExpiration);
         }
-
-        return null;
     }
 
     /**
@@ -241,12 +268,26 @@ public abstract class
      * @param response the HTTP response obtained from {@link #httpClient}
      * @param validExpiration expiration instant for containers with valid content
      * @param invalidExpiration expiration instant for container with invalid content
+     * @param exceptionExpiration expiration instant for container whose contents could not be fetched
      * @return the response message container
      * @throws ProtocolException if HTTP protocol violation occurs
      * @throws IOException if generic I/O exception occurs
      */
     @Nullable protected abstract C parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final R requestData, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration)
-                    throws ProtocolException, IOException;
+            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+            @Nonnull final Instant exceptionExpiration) throws ProtocolException, IOException;
+
+    /**
+     * Handles the exception catched while communicating with the remote API.
+     * 
+     * @param criteria criteria set
+     * @param requestData the request data
+     * @param throwable exception catched while communicating with the remote API
+     * @param expiration exoiration instant for container whose contents could not be fetched
+     * @return the container to be stored in the metadata cache
+     */
+    @Nullable protected abstract C handleException(@Nonnull final CriteriaSet criteria,
+            @Nonnull final R requestData, @Nonnull final Throwable throwable, @Nonnull final Instant expiration);
+
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationFetchingStrategy.java
index 9e7e7a6..f59a82a 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/configuration/DefaultEntityConfigurationFetchingStrategy.java
@@ -96,8 +96,8 @@ public class DefaultEntityConfigurationFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected RemoteEntityConfigurationContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final String entityId, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration)
-                    throws ProtocolException, IOException {
+            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
         if (response != null) {
             if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
                 log.warn("Unexpected content type: {}", response.getEntity().getContentType());
@@ -121,10 +121,17 @@ public class DefaultEntityConfigurationFetchingStrategy
             } catch (java.text.ParseException e) {
                 log.warn("Could not parse JWT from the response", e);
             }
+            return new RemoteEntityConfigurationContainer(entityId, null, validExpiration, invalidExpiration);
         } else {
             log.debug("Unable to fetch entity configuration for: {} (null response)", entityId);
+            return new RemoteEntityConfigurationContainer(entityId, null, validExpiration, nullExpiration);
         }
+    }
 
-        return new RemoteEntityConfigurationContainer(entityId, null, validExpiration, invalidExpiration);
+    /** {@inheritDoc} */
+    @Nullable protected RemoteEntityConfigurationContainer handleException(@Nonnull final CriteriaSet criteria,
+            @Nonnull final String entityId, @Nonnull final Throwable throwable, @Nonnull final Instant expiration) {
+        return new RemoteEntityConfigurationContainer(entityId, null, expiration, expiration);
     }
+
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
index 158c824..284c4dd 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
@@ -100,7 +100,7 @@ public class DefaultResolveEntityTrustChainFetchingStrategy
     @Nullable protected RemoteResolveEntityResponseContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final RemoteResolveEntityCacheContainerIdentifier identifier,
             @Nullable final ClassicHttpResponse response, @Nonnull final Instant validExpiration,
-            @Nonnull final Instant invalidExpiration)
+            @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
                     throws ProtocolException, IOException {
         if (response != null) {
             if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
@@ -126,10 +126,18 @@ public class DefaultResolveEntityTrustChainFetchingStrategy
             } catch (java.text.ParseException e) {
                 log.warn("Could not parse JWT from the response", e);
             }
+            return new RemoteResolveEntityResponseContainer(identifier, null, validExpiration, invalidExpiration);
         } else {
             log.debug("Unable to fetch resolve entity response: {} (null response)", identifier.getEndpoint());
+            return new RemoteResolveEntityResponseContainer(identifier, null, validExpiration, nullExpiration);
         }
+    }
 
-        return new RemoteResolveEntityResponseContainer(identifier, null, validExpiration, invalidExpiration);
+    /** {@inheritDoc} */
+    @Nullable protected RemoteResolveEntityResponseContainer handleException(@Nonnull final CriteriaSet criteria,
+            @Nonnull final RemoteResolveEntityCacheContainerIdentifier identifier, @Nonnull final Throwable exception,
+            @Nonnull final Instant expiration) {
+        return new RemoteResolveEntityResponseContainer(identifier, null, expiration, expiration);
     }
+
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java
index 03b4125..d031e0b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/subordinate/DefaultSubordinateStatementFetchingStrategy.java
@@ -163,8 +163,8 @@ public class DefaultSubordinateStatementFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected RemoteSubordinateStatementContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final SubordinateStatementCacheIdentifier id, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration)
-                    throws ProtocolException, IOException {
+            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
         if (response != null) {
             if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
                 log.warn("Unexpected content type: {}", response.getEntity().getContentType());
@@ -188,10 +188,17 @@ public class DefaultSubordinateStatementFetchingStrategy
             } catch (java.text.ParseException e) {
                 log.warn("Could not parse JWT from the response", e);
             }
+            return new RemoteSubordinateStatementContainer(id, null, validExpiration, invalidExpiration);
         } else {
             log.debug("Unable to fetch entity configuration for: {} (null response)", id);
+            return new RemoteSubordinateStatementContainer(id, null, validExpiration, nullExpiration);
         }
+    }
 
-        return new RemoteSubordinateStatementContainer(id, null, validExpiration, invalidExpiration);
+    /** {@inheritDoc} */
+    @Nullable protected RemoteSubordinateStatementContainer handleException(@Nonnull final CriteriaSet criteria,
+            @Nonnull final SubordinateStatementCacheIdentifier id, @Nonnull final Throwable exception,
+            @Nonnull final Instant expiration) {
+        return new RemoteSubordinateStatementContainer(id, null, expiration, expiration);
     }
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkFetchingStrategy.java
index 71d1c84..8ac59e1 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkFetchingStrategy.java
@@ -97,8 +97,8 @@ public class DefaultTrustMarkFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected RemoteTrustMarkContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final TrustMarkCacheIdentifier request, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration)
-                    throws ProtocolException, IOException {
+            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
         if (response != null) {
             if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
                 log.warn("Unexpected content type: {}", response.getEntity().getContentType());
@@ -122,10 +122,18 @@ public class DefaultTrustMarkFetchingStrategy
             } catch (java.text.ParseException e) {
                 log.warn("Could not parse JWT from the response", e);
             }
+            return new RemoteTrustMarkContainer(request, null, validExpiration, invalidExpiration);
         } else {
             log.debug("Unable to fetch trust mark: {} (null response)", request.getTrustMarkType());
+            return new RemoteTrustMarkContainer(request, null, validExpiration, nullExpiration);
         }
+    }
 
-        return new RemoteTrustMarkContainer(request, null, validExpiration, invalidExpiration);
+    /** {@inheritDoc} */
+    @Nullable protected RemoteTrustMarkContainer handleException(@Nonnull final CriteriaSet criteria,
+            @Nonnull final TrustMarkCacheIdentifier request, @Nonnull final Throwable exception,
+            @Nonnull final Instant expiration) {
+        return new RemoteTrustMarkContainer(request, null, expiration, expiration);
     }
+
 }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkStatusFetchingStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
index c6b472c..e6f87c4 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/remote/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
@@ -96,8 +96,8 @@ public class DefaultTrustMarkStatusFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected RemoteTrustMarkStatusContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final TrustMarkStatusCacheIdentifier identifier, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration)
-                    throws ProtocolException, IOException {
+            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
         if (response != null) {
             if (!HTTP_RESPONSE_CONTENT_TYPE.equals(response.getEntity().getContentType())) {
                 log.warn("Unexpected content type: {}", response.getEntity().getContentType());
@@ -121,10 +121,18 @@ public class DefaultTrustMarkStatusFetchingStrategy
             } catch (java.text.ParseException e) {
                 log.warn("Could not parse JWT from the response", e);
             }
+            return new RemoteTrustMarkStatusContainer(identifier, null, validExpiration, invalidExpiration);
         } else {
             log.debug("Unable to fetch trust mark status: {} (null response)", identifier.getEndpoint());
+            return new RemoteTrustMarkStatusContainer(identifier, null, validExpiration, nullExpiration);
         }
+    }
 
-        return new RemoteTrustMarkStatusContainer(identifier, null, validExpiration, invalidExpiration);
+    /** {@inheritDoc} */
+    @Nullable protected RemoteTrustMarkStatusContainer handleException(@Nonnull final CriteriaSet criteria,
+            @Nonnull final TrustMarkStatusCacheIdentifier identifier, @Nonnull final Throwable exception,
+            @Nonnull final Instant expiration) {
+        return new RemoteTrustMarkStatusContainer(identifier, null, expiration, expiration);
     }
+
 }
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 95b4a1e..46b305c 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -114,6 +114,13 @@
                         </constructor-arg>
                     </bean>
                 </property>
+                <property name="criteriaToExceptionContainerLifetimeStrategy">
+                    <bean parent="shibboleth.Functions.Constant">
+                        <constructor-arg>
+                            <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.entityConfiguration.exceptionContainerLifetime:PT0S}" />
+                        </constructor-arg>
+                    </bean>
+                </property>
             </bean>
         </property>
         <property name="metadataFilterStrategy">
@@ -240,6 +247,13 @@
                         </constructor-arg>
                     </bean>
                 </property>
+                <property name="criteriaToExceptionContainerLifetimeStrategy">
+                    <bean parent="shibboleth.Functions.Constant">
+                        <constructor-arg>
+                            <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.subordinateStatement.exceptionContainerLifetime:PT0S}" />
+                        </constructor-arg>
+                    </bean>
+                </property>
             </bean>
         </property>
     </bean>
@@ -386,6 +400,13 @@
                         </constructor-arg>
                     </bean>
                 </property>
+                <property name="criteriaToExceptionContainerLifetimeStrategy">
+                    <bean parent="shibboleth.Functions.Constant">
+                        <constructor-arg>
+                            <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.resolveEntity.exceptionContainerLifetime:PT0S}" />
+                        </constructor-arg>
+                    </bean>
+                </property>
             </bean>
         </property>
     </bean>
@@ -505,6 +526,13 @@
                         </constructor-arg>
                     </bean>
                 </property>
+                <property name="criteriaToExceptionContainerLifetimeStrategy">
+                    <bean parent="shibboleth.Functions.Constant">
+                        <constructor-arg>
+                            <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMarkStatus.exceptionContainerLifetime:PT0S}" />
+                        </constructor-arg>
+                    </bean>
+                </property>
             </bean>
         </property>
     </bean>
@@ -583,6 +611,13 @@
                         </constructor-arg>
                     </bean>
                 </property>
+                <property name="criteriaToExceptionContainerLifetimeStrategy">
+                    <bean parent="shibboleth.Functions.Constant">
+                        <constructor-arg>
+                            <bean class="java.time.Duration" factory-method="parse" c:_0="%{idp.oidfed.cache.trustMark.exceptionContainerLifetime:PT0S}" />
+                        </constructor-arg>
+                    </bean>
+                </property>
             </bean>
         </property>
     </bean>
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
index 17d76b2..5cb4e4f 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -113,7 +113,7 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
 
     @Autowired
     @Qualifier("shibboleth.oidfed.HttpClient")
-    HttpClient federationHttpClient;
+    protected HttpClient federationHttpClient;
     
     protected AbstractFederationFlowTest(final String flowId) {
         super(flowId);
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
index ba9227e..5eee569 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
@@ -14,6 +14,10 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed.cache;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.when;
+
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.time.Instant;
@@ -224,6 +228,23 @@ public class EntityConfigurationMetadataCacheTest extends AbstractFederationFlow
         assertNoEntityConfiguration(entityId);
     }
 
+    @Test
+    public void testNullResponse()
+            throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+        final String entityId = uniqueClientId();
+        mapResponse(entityConfigurationUrl(entityId), null);
+        assertNoEntityConfiguration(entityId);
+    }
+
+    @Test
+    public void testExceptionResponse()
+            throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+        final String entityId = uniqueClientId();
+        when(federationHttpClient.executeOpen(any(), argThat(new RequestUriMatcher(entityConfigurationUrl(entityId))),
+                any())).thenThrow(IOException.class);
+        assertNoEntityConfiguration(entityId);
+    }
+
     protected void assertNoEntityConfiguration(final String entityId) {
         try {
             final List<RemoteEntityConfigurationContainer> result =
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
index 19b8970..2afb1d9 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
@@ -14,6 +14,10 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed.cache;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.when;
+
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.time.Instant;
@@ -229,15 +233,42 @@ public class SubordinateStatementMetadataCacheTest extends AbstractFederationFlo
         assertNoSubordinateStatement(entityId);
     }
 
+    @Test
+    public void testNullResponse()
+            throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+        final String entityId = uniqueClientId();
+        mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+        assertNoSubordinateStatement(entityId, false);
+    }
+
+    @Test
+    public void testExceptionResponse()
+            throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+        final String entityId = uniqueClientId();
+        mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+        when(federationHttpClient.executeOpen(any(),
+                argThat(new RequestUriMatcher(subordinateStatementUrl(anchorFetchEndpoint, entityId))),
+                any())).thenThrow(IOException.class);
+        assertNoSubordinateStatement(entityId, false);
+    }
+
     protected void assertNoSubordinateStatement(final String entityId) {
+        assertNoSubordinateStatement(entityId, true);
+    }
+
+    protected void assertNoSubordinateStatement(final String entityId, final boolean containerExists) {
         try {
             final List<RemoteSubordinateStatementContainer> result =
                     subordinateStatementCache.get(new CriteriaSet(new SubjectEntityIDCriterion(entityId),
                             new ResponseContainerExpirationCriterion(Instant.now().plusSeconds(300)), 
                             new IssuerEntityIDCriterion(anchorId)));
             Assert.assertNotNull(result);
-            Assert.assertEquals(result.size(), 1);
-            Assert.assertNull(result.get(0).getStatement());
+            if (containerExists) {
+                Assert.assertEquals(result.size(), 1);
+                Assert.assertNull(result.get(0).getStatement());
+            } else {
+                Assert.assertEquals(result.size(), 0);
+            }
         } catch (MetadataCacheException e) {
             Assert.fail("Could not resolve entity configuration", e);
         }

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


More information about the commits mailing list