[java-plugin-shibd] branch main updated: JSHIBD-25 - Develop necessary CredentialResolvers for SP service
Codeberg
noreply at shibboleth.net
Mon Aug 31 18:17:22 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd/commit/3d5b568d3e7bde7fc04a51bc9676bdd2d6454dea
The following commit(s) were added to refs/heads/main by this push:
new 3d5b568 JSHIBD-25 - Develop necessary CredentialResolvers for SP service
3d5b568 is described below
commit 3d5b568d3e7bde7fc04a51bc9676bdd2d6454dea
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Mon Aug 31 14:17:12 2026 -0400
JSHIBD-25 - Develop necessary CredentialResolvers for SP service
https://shibboleth.atlassian.net/browse/JSHIBD-25
Add result caching to storage resolver base class.
---
.../AbstractStorageServiceCredentialResolver.java | 85 ++++++++++++++++++----
.../X509CredentialStorageServiceResolverTest.java | 61 ++++++++++++++++
2 files changed, 130 insertions(+), 16 deletions(-)
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
index 31a5a87..320a645 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/credential/AbstractStorageServiceCredentialResolver.java
@@ -16,6 +16,7 @@ package net.shibboleth.sp.credential;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -132,6 +133,9 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
/** Credential type. */
@Nonnull private final Class<T> credentialType;
+ /** Whether to suppress PRC criterion. */
+ private boolean supportProfileRequestContextCriterion;
+
/** Storage service to use. */
@NonnullAfterInit private StorageService storageService;
@@ -142,7 +146,7 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
@NonnullAfterInit private Function<String,String> entityIDTransformStrategy;
/** Credential resolution cache. */
- @Nullable private Cache<CriteriaSet,List<T>> resultsCache;
+ @Nullable private Cache<CriteriaSet,List<Credential>> resultsCache;
/** Velocity template source for context. */
@NonnullAfterInit private String contextTemplateString;
@@ -168,6 +172,20 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
protocolMap = CollectionSupport.emptyMap();
}
+ /**
+ * Sets whether the {@link ProfileRequestContextCriterion} should be passed in if present.
+ *
+ * <p>Defaults to false.</p>
+ *
+ * <p>Most use cases are unlikely to leverage this particular criterion type.
+ * Enabling this option will prevent caching so shoould only be enabled when required.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setSupportProfileRequestContextCriterion(final boolean flag) {
+ supportProfileRequestContextCriterion = flag;
+ }
+
/**
* Gets the {@link StorageService} to use.
*
@@ -221,15 +239,6 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
entityIDTransformStrategy = Constraint.isNotNull(strategy, "EntityID transform strategy cannot be null");
}
- /**
- * Gets the cache of resolution results.
- *
- * @return cache of results
- */
- @Nullable public Cache<CriteriaSet,List<T>> getResultsCache() {
- return resultsCache;
- }
-
/**
* Sets the cache used to cache search results.
*
@@ -237,7 +246,7 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
*
* @param cache cache used to cache search results
*/
- public void setResultsCache(@Nullable final Cache<CriteriaSet,List<T>> cache) {
+ public void setResultsCache(@Nullable final Cache<CriteriaSet,List<Credential>> cache) {
checkSetterPreconditions();
if (cache != null) {
@@ -306,6 +315,11 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
throw new ComponentInitializationException("VelocityEngine cannot be null");
}
+ if (resultsCache != null && supportProfileRequestContextCriterion) {
+ throw new ComponentInitializationException(
+ "Result caching is incompatible with support for ProfileRequestContextCriterion");
+ }
+
if (contextTemplateString == null) {
contextTemplateString = DEFAULT_CONTEXT_PREFIX + ensureId();
}
@@ -332,7 +346,34 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
}
}
- final VelocityContext ctx = populateVelocityContext(criteria);
+ // Suppress PRC if present but unsupported. This is to preserve cache fidelity.
+ final CriteriaSet manipulatedCriteria;
+ if (!supportProfileRequestContextCriterion &&
+ criteria != null && criteria.contains(ProfileRequestContextCriterion.class)) {
+ manipulatedCriteria = new CriteriaSet();
+ criteria.forEach(c -> {
+ if (!(c instanceof ProfileRequestContextCriterion)) {
+ manipulatedCriteria.add(c);
+ }
+ });
+ } else {
+ manipulatedCriteria = criteria;
+ }
+
+ final Cache<CriteriaSet,List<Credential>> localCache = resultsCache;
+
+ // Check cache first if it exists.
+ if (localCache != null) {
+ final List<Credential> cached = localCache.getIfPresent(manipulatedCriteria);
+ if (cached != null) {
+ log.debug("{}: Resolved {} credential(s) from cache", getId(), cached.size());
+ return CollectionSupport.copyToList(cached);
+ }
+ }
+
+ // Resolve from scratch.
+
+ final VelocityContext ctx = populateVelocityContext(manipulatedCriteria);
final String storageContext;
try {
@@ -343,7 +384,21 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
log.debug("{}: Resolved storage context ({})", getId(), storageContext);
- return doResolve(ctx, criteria, storageContext);
+ final Iterable<Credential> resolved = doResolve(ctx, manipulatedCriteria, storageContext);
+
+ // Cache if enabled.
+
+ if (localCache != null) {
+ // We need to walk the Iterarable to capture results and return a copy of that in case
+ // iteration is one-time only.
+ final List<Credential> toCache = new ArrayList<>();
+ resolved.forEach(toCache::add);
+ log.debug("{}: Adding {} resolved credential(s) to cache", getId(), toCache.size());
+ localCache.put(manipulatedCriteria, toCache);
+ return CollectionSupport.copyToList(toCache);
+ }
+
+ return resolved;
}
/**
@@ -420,9 +475,7 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
@Nonnull final VelocityContext velocityContext, @Nullable final CriteriaSet criteria,
@Nonnull final String storageContext) throws ResolverException;
- /**
- * Defaults to SHA-1 hash.
- */
+ /** Defaults to SHA-1 hash. */
private static class DefaultTransformStrategy implements Function<String,String> {
/** String digester. */
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolverTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolverTest.java
index 43dacb0..7aebb00 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolverTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolverTest.java
@@ -23,12 +23,15 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.crypto.KeySupport;
@@ -38,6 +41,9 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.resolver.CriteriaSet;
import net.shibboleth.shared.security.impl.SelfSignedCertificateGenerator;
@@ -209,6 +215,61 @@ public class X509CredentialStorageServiceResolverTest {
buildCriteriaSet("localhost", "default", "https://idp.example.org/idp2", UsageType.ENCRYPTION));
Assert.assertNull(credential);
}
+
+ @Test
+ public void testCaching() throws Exception {
+ // Prep directories for agent.
+ Files.createDirectories(Path.of(testRoot.toString(), "agents", "localhost", "override"));
+
+ // Generate keypair for testing.
+ final Path keyFile = Path.of(testRoot.toString(), "agents", "localhost", "override", "sp-signing.key");
+ final Path certFile = Path.of(testRoot.toString(), "agents", "localhost", "override", "sp-signing.crt");
+ generateKeyPair(keyFile, certFile, "localhost", null);
+
+ final FilesystemStorageService storage = new FilesystemStorageService();
+ storage.setId("test");
+ storage.setReadOnly(true);
+ storage.setStorageBase(testRoot.toString());
+ storage.initialize();
+
+ final CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
+ builder.expireAfterWrite(Duration.ofSeconds(10));
+
+ final X509CredentialStorageServiceResolver resolver = new X509CredentialStorageServiceResolver();
+ resolver.setId("test");
+ resolver.setStorageService(storage);
+ resolver.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ resolver.setContextTemplate("agents/$agentID/$applicationID");
+ resolver.setResultsCache(builder.build());
+ resolver.initialize();
+
+ X509Credential credential = (X509Credential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "override", "https://idp.example.org/idp", UsageType.SIGNING));
+ assert credential != null;
+
+ PrivateKey key = credential.getPrivateKey();
+ assert key != null;
+
+ X509Certificate cert = credential.getEntityCertificate();
+ assert cert != null;
+
+ Assert.assertTrue(KeySupport.matchKeyPair(cert.getPublicKey(), key));
+ Assert.assertEquals(cert.getSubjectAlternativeNames(), CollectionSupport.singletonList(
+ CollectionSupport.listOf(Integer.valueOf(2), "localhost")));
+
+ // Delete underlying files.
+ Files.delete(keyFile);
+ Files.delete(certFile);
+
+ credential = (X509Credential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "override", "https://idp.example.org/idp", UsageType.SIGNING));
+ Assert.assertNotNull(credential);
+
+ Thread.sleep(15000);
+ credential = (X509Credential) resolver.resolveSingle(
+ buildCriteriaSet("localhost", "override", "https://idp.example.org/idp", UsageType.SIGNING));
+ Assert.assertNull(credential);
+ }
/**
* Generate a self-signed keypair.
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list