[java-oidfed-common] 02/02: Improve response handling for the metadata cache fetching strategies

Codeberg noreply at shibboleth.net
Fri Sep 25 05:48:38 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/cb09a9554048b2d4a7950d66f5626cdebc4062be

commit cb09a9554048b2d4a7950d66f5626cdebc4062be
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Sep 25 08:47:09 2026 +0300

    Improve response handling for the metadata cache fetching strategies
    
    - Configurable status code validation strategy (defaults to require 200 as mandated by the spec for all defined federation endpoints)
    - Parse content type solely for non-null HTTP entities
---
 ...FederationEndpointResponseFetchingStrategy.java | 41 ++++++++++++++++++++--
 ...DefaultEntityConfigurationFetchingStrategy.java | 10 +++---
 .../DefaultSignedKeysetFetchingStrategy.java       | 10 +++---
 ...ultResolveEntityTrustChainFetchingStrategy.java | 11 +++---
 ...efaultSubordinateStatementFetchingStrategy.java | 10 +++---
 .../DefaultTrustMarkFetchingStrategy.java          | 10 +++---
 .../DefaultTrustMarkStatusFetchingStrategy.java    | 10 +++---
 7 files changed, 68 insertions(+), 34 deletions(-)

diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractFederationEndpointResponseFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractFederationEndpointResponseFetchingStrategy.java
index 7599059..858f82a 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractFederationEndpointResponseFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/AbstractFederationEndpointResponseFetchingStrategy.java
@@ -21,6 +21,7 @@ import java.time.Instant;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -30,6 +31,7 @@ import org.apache.hc.client5.http.classic.HttpClient;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.ProtocolException;
@@ -80,10 +82,23 @@ public abstract class
     /** Strategy to fetch lifetime for containers whose contents could not be fetched. */
     @NonnullAfterInit private Function<CriteriaSet, Duration> criteriaToExceptionContainerLifetimeStrategy;
 
+    /** Condition to validate the response status code. */
+    @Nonnull private BiPredicate<R, ClassicHttpResponse> responseStatusCodeValidationCondition;
+
     /** Strategy to fetch client authentication decorator for HTTP request and its parameters. */
     @Nullable protected
     Function<CriteriaSet, BiConsumer<HttpRequest,List<NameValuePair>>> clientAuthenticationLookupStrategy;
 
+    /**
+     * Constructor.
+     */
+    public AbstractFederationEndpointResponseFetchingStrategy() {
+        responseStatusCodeValidationCondition = ((req, resp) -> {
+            final int statusCode = resp != null ? resp.getCode() : 0;
+            return Integer.compare(statusCode, 200) == 0;
+        });
+    }
+
     /**
      * Set the {@link HttpClient} to use.
      * 
@@ -177,6 +192,18 @@ public abstract class
         clientAuthenticationLookupStrategy = strategy;
     }
 
+    /**
+     * Set the condition to validate the response status code.
+     * 
+     * @param condition validation condition
+     */
+    public void setResponseStatusCodeValidationCondition(@Nonnull final BiPredicate<R, ClassicHttpResponse> condition) {
+        checkSetterPreconditions();
+
+        responseStatusCodeValidationCondition =
+                Constraint.isNotNull(condition, "Response status code validation condition cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -247,7 +274,13 @@ public abstract class
             }
             HttpClientSecuritySupport.checkTLSCredentialEvaluated(httpContext, scheme);
             assert validExpiration != null; assert invalidExpiration != null; assert exceptionExpiration != null;
-            return parseHttpResponse(criteria, requestData, response, validExpiration, invalidExpiration,
+            if (!responseStatusCodeValidationCondition.test(requestData, response)) {
+                throw new ProtocolException("Unexpected status code in the response: "
+                        + (response != null ? response.getCode() : 0));
+            }
+            final HttpEntity httpEntity = response != null ? response.getEntity() : null;
+            final String contentType = httpEntity != null ? httpEntity.getContentType() : null;
+            return parseHttpResponse(criteria, requestData, response, contentType, validExpiration, invalidExpiration,
                     exceptionExpiration);
         } catch (final ProtocolException | URISyntaxException | IOException e) {
             log.debug("Unable to fetch resolve entity response via request data: {}", requestData, e);
@@ -301,6 +334,7 @@ public abstract class
      * @param criteria criteria set
      * @param requestData the request data
      * @param response the HTTP response obtained from {@link #httpClient}
+     * @param contentType the HTTP response content type, if it was available
      * @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
@@ -310,8 +344,9 @@ public abstract class
      */
     @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,
-            @Nonnull final Instant exceptionExpiration) throws ProtocolException, IOException;
+            @Nullable final String contentType, @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.
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
index a4f7066..5176257 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/configuration/DefaultEntityConfigurationFetchingStrategy.java
@@ -96,12 +96,12 @@ public class DefaultEntityConfigurationFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected EntityConfigurationContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final String entityId, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
-            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+            @Nullable final String contentType, @Nonnull final Instant validExpiration,
+            @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
+                    throws ProtocolException, IOException {
         if (response != null) {
-            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(
-                    ContentType.parse(response.getEntity().getContentType()))) {
-                log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(ContentType.parse(contentType))) {
+                log.warn("Unexpected content type: {}", contentType);
                 return new EntityConfigurationContainer(entityId, null, validExpiration, invalidExpiration);
             }
 
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetFetchingStrategy.java
index 1205f74..cfa974a 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/keyset/DefaultSignedKeysetFetchingStrategy.java
@@ -101,12 +101,12 @@ public class DefaultSignedKeysetFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected SignedKeysetContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final SignedKeysetCacheIdentifier identifier, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
-            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+            @Nullable final String contentType, @Nonnull final Instant validExpiration,
+            @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
+                    throws ProtocolException, IOException {
         if (response != null) {
-            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(
-                    ContentType.parse(response.getEntity().getContentType()))) {
-                log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(ContentType.parse(contentType))) {
+                log.warn("Unexpected content type: {}", contentType);
                 return new SignedKeysetContainer(identifier, null, validExpiration, invalidExpiration);
             }
 
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
index 28f3051..c26c7b3 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/resolver/DefaultResolveEntityTrustChainFetchingStrategy.java
@@ -118,13 +118,12 @@ public class DefaultResolveEntityTrustChainFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected ResolveEntityResponseContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final ResolveEntityCacheContainerIdentifier identifier,
-            @Nullable final ClassicHttpResponse response, @Nonnull final Instant validExpiration,
-            @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
-                    throws ProtocolException, IOException {
+            @Nullable final ClassicHttpResponse response, @Nullable final String contentType,
+            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
+            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
         if (response != null) {
-            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(
-                    ContentType.parse(response.getEntity().getContentType()))) {
-                log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(ContentType.parse(contentType))) {
+                log.warn("Unexpected content type: {}", contentType);
                 return new ResolveEntityResponseContainer(identifier, null, validExpiration, invalidExpiration);
             }
 
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
index db3a678..985af41 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/subordinate/DefaultSubordinateStatementFetchingStrategy.java
@@ -193,12 +193,12 @@ public class DefaultSubordinateStatementFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected SubordinateStatementContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final SubordinateStatementCacheIdentifier id, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
-            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+            @Nullable final String contentType, @Nonnull final Instant validExpiration,
+            @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
+                    throws ProtocolException, IOException {
         if (response != null) {
-            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(
-                    ContentType.parse(response.getEntity().getContentType()))) {
-                log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(ContentType.parse(contentType))) {
+                log.warn("Unexpected content type: {}", contentType);
                 return new SubordinateStatementContainer(id, null, validExpiration, invalidExpiration);
             }
 
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkFetchingStrategy.java
index 96cfe33..13e9df3 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkFetchingStrategy.java
@@ -116,12 +116,12 @@ public class DefaultTrustMarkFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected TrustMarkContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final TrustMarkCacheIdentifier request, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
-            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+            @Nullable final String contentType, @Nonnull final Instant validExpiration,
+            @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
+                    throws ProtocolException, IOException {
         if (response != null) {
-            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(
-                    ContentType.parse(response.getEntity().getContentType()))) {
-                log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(ContentType.parse(contentType))) {
+                log.warn("Unexpected content type: {}", contentType);
                 return new TrustMarkContainer(request, null, validExpiration, invalidExpiration);
             }
 
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkStatusFetchingStrategy.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
index e00c573..2a8bdae 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/cache/trustmark/DefaultTrustMarkStatusFetchingStrategy.java
@@ -102,12 +102,12 @@ public class DefaultTrustMarkStatusFetchingStrategy
     /** {@inheritDoc} */
     @Nullable protected TrustMarkStatusContainer parseHttpResponse(@Nonnull final CriteriaSet criteria,
             @Nonnull final TrustMarkStatusCacheIdentifier identifier, @Nullable final ClassicHttpResponse response,
-            @Nonnull final Instant validExpiration, @Nonnull final Instant invalidExpiration,
-            @Nonnull final Instant nullExpiration) throws ProtocolException, IOException {
+            @Nullable final String contentType, @Nonnull final Instant validExpiration,
+            @Nonnull final Instant invalidExpiration, @Nonnull final Instant nullExpiration)
+                    throws ProtocolException, IOException {
         if (response != null) {
-            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(
-                    ContentType.parse(response.getEntity().getContentType()))) {
-                log.warn("Unexpected content type: {}", response.getEntity().getContentType());
+            if (!ContentType.create(HTTP_RESPONSE_CONTENT_TYPE).isSameMimeType(ContentType.parse(contentType))) {
+                log.warn("Unexpected content type: {}", contentType);
                 return new TrustMarkStatusContainer(identifier, null, validExpiration, invalidExpiration);
             }
 

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


More information about the commits mailing list