[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