[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