[java-plugin-shibd] branch main updated: JSHIBD-25 - Develop necessary CredentialResolvers for SP service

Codeberg noreply at shibboleth.net
Tue Sep 1 01:18:52 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/33904288dac90cb3643c12c1029e9940a0b810f6

The following commit(s) were added to refs/heads/main by this push:
     new 3390428  JSHIBD-25 - Develop necessary CredentialResolvers for SP service
3390428 is described below

commit 33904288dac90cb3643c12c1029e9940a0b810f6
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Mon Aug 31 21:18:36 2026 -0400

    JSHIBD-25 - Develop necessary CredentialResolvers for SP service
    
    https://shibboleth.atlassian.net/browse/JSHIBD-25
    
    Embed Cache settings and construction in resolver base class.
---
 .../shibboleth/idp/module/conf/sp/credentials.xml  |   4 +-
 .../AbstractStorageServiceCredentialResolver.java  | 115 ++++++++++++++++-----
 .../impl/X509CredentialStorageServiceResolver.java |   6 +-
 .../X509CredentialStorageServiceResolverTest.java  |   8 +-
 4 files changed, 94 insertions(+), 39 deletions(-)

diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/credentials.xml b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/credentials.xml
index aabbae3..ee50782 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/credentials.xml
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/credentials.xml
@@ -4,12 +4,10 @@
        xmlns:util="http://www.springframework.org/schema/util"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:c="http://www.springframework.org/schema/c"
-       xmlns:resolver="urn:mace:shibboleth:2.0:resolver"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
-                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
-                           urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd"
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
                            
        default-init-method="initialize"
        default-destroy-method="destroy">
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 320a645..073c97d 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.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -38,11 +39,13 @@ import org.opensaml.storage.StorageService;
 import org.slf4j.Logger;
 
 import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 
 import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.codec.StringDigester;
 import net.shibboleth.shared.codec.StringDigester.OutputFormat;
@@ -133,8 +136,8 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
     /** Credential type. */
     @Nonnull private final Class<T> credentialType;
     
-    /** Whether to suppress PRC criterion. */
-    private boolean supportProfileRequestContextCriterion;
+    /** Whether to enable caching. */
+    private boolean cacheEnabled;
     
     /** Storage service to use. */
     @NonnullAfterInit private StorageService storageService;
@@ -145,6 +148,15 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
     /** Function to transform the entityID. */
     @NonnullAfterInit private Function<String,String> entityIDTransformStrategy;
 
+    /** Size of cache. */
+    @Nonnull @Positive private Integer maximumCachedElements;
+    
+    /** Sets cache expiration based on last access time. */
+    @Nullable @Positive private Duration expireAfterAccess;
+
+    /** Sets cache expiration based on entry creation. */
+    @Nullable @Positive private Duration expireAfterWrite;
+    
     /** Credential resolution cache. */
     @Nullable private Cache<CriteriaSet,List<Credential>> resultsCache;
     
@@ -170,20 +182,22 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
         usageMap = Map.of(UsageType.SIGNING, "-signing", UsageType.ENCRYPTION, "-encryption",
                 UsageType.UNSPECIFIED, "-signing");
         protocolMap = CollectionSupport.emptyMap();
+        
+        maximumCachedElements = 500;
+        expireAfterAccess = Duration.ofHours(4);
     }
     
     /**
-     * Sets whether the {@link ProfileRequestContextCriterion} should be passed in if present.
+     * Sets whether to enable caching.
      * 
      * <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>
+     * <p>When enabled, any {@link ProfileRequestContextCriterion} is remoed from input criteria.</p>
      * 
      * @param flag flag to set
      */
-    public void setSupportProfileRequestContextCriterion(final boolean flag) {
-        supportProfileRequestContextCriterion = flag;
+    public void setCacheEnabled(final boolean flag) {
+        cacheEnabled = flag;
     }
     
     /**
@@ -240,19 +254,56 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
     }
     
     /**
-     * Sets the cache used to cache search results.
+     * Sets the maximum cache size.
      * 
-     * <p>All entries in the cache are invalidated prior to use.</p>
+     * <p>Defaults to 500.</p>
      * 
-     * @param cache cache used to cache search results
+     * @param max maximum size
      */
-    public void setResultsCache(@Nullable final Cache<CriteriaSet,List<Credential>> cache) {
+    public void setMaximumCachedElements(@Nonnull @Positive final Integer max) {
         checkSetterPreconditions();
         
-        if (cache != null) {
-            cache.invalidateAll();
+        maximumCachedElements = Constraint.isGreaterThan(0, max, "Maximum cache size must be greater than zero");
+    }
+    
+    /**
+     * Sets the cache policy to expire entries after access, with the time reset on each access.
+     * 
+     * <p>Defaults to 4 hours.</p>
+     * 
+     * <p>Mutually exclusive with {@link #setExpireAfterWrite(Duration)}.</p>
+     * 
+     * @param exp threshold
+     */
+    public void setExpireAfterAccess(@Nullable @Positive final Duration exp) {
+        checkSetterPreconditions();
+        
+        if (exp != null) {
+            Constraint.isFalse(exp.isNegative() || exp.isZero(), "Thresold must be greater than zero");
+            expireAfterAccess = exp;
+        } else {
+            expireAfterAccess = null;
+        }
+    }
+
+    /**
+     * Sets the cache policy to expire entries after creation.
+     * 
+     * <p>Defaults to null.</p>
+     * 
+     * <p>Mutually exclusive with {@link #setExpireAfterAccess(Duration)}.</p>
+     * 
+     * @param exp threshold
+     */
+    public void setExpireAfterWrite(@Nullable @Positive final Duration exp) {
+        checkSetterPreconditions();
+        
+        if (exp != null) {
+            Constraint.isFalse(exp.isNegative() || exp.isZero(), "Thresold must be greater than zero");
+            expireAfterWrite = exp;
+        } else {
+            expireAfterWrite = null;
         }
-        resultsCache = cache;
     }
     
     /**
@@ -296,11 +347,11 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
      * 
      * @param map map of usage types to string values
      */
-    public void setProtocolMap(@Nullable final Map<UsageType,String> map) {
+    public void setProtocolMap(@Nullable final Map<String,String> map) {
         if (map != null) {
-            usageMap = CollectionSupport.copyToMap(map);
+            protocolMap = CollectionSupport.copyToMap(map);
         } else {
-            usageMap = CollectionSupport.emptyMap();
+            protocolMap = CollectionSupport.emptyMap();
         }
     }
     
@@ -315,9 +366,20 @@ 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 (cacheEnabled) {
+            final CacheBuilder<Object,Object> builder = CacheBuilder.newBuilder();
+            builder.maximumSize(maximumCachedElements);
+            if (expireAfterAccess != null) {
+                if (expireAfterWrite != null) {
+                    log.warn("CredentialResolver {}: ignoring expireAfterWrite, expireAfterAccess is set", getId());
+                }
+                builder.expireAfterAccess(expireAfterAccess);
+            } else if (expireAfterWrite != null) {
+                builder.expireAfterWrite(expireAfterWrite);
+            } else {
+                throw new ComponentInitializationException("Cache enabled, but expireAfterAccess/expireAfterWrite both null");
+            }
+            resultsCache = builder.build();
         }
         
         if (contextTemplateString == null) {
@@ -340,16 +402,15 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
             // Check type.
             final ClassCriterion<?> classCriterion = criteria.get(ClassCriterion.class);
             if (classCriterion != null && !classCriterion.getClass().isAssignableFrom(credentialType)) {
-                log.debug("{}: ClassCriterion specified {}, not compatible with this resolver", getId(),
+                log.debug("CredentialResolver {}: ClassCriterion ({}), not compatible with this resolver", getId(),
                         classCriterion.getClass());
                 return CollectionSupport.emptyList();
             }
         }
         
-        // Suppress PRC if present but unsupported. This is to preserve cache fidelity.
         final CriteriaSet manipulatedCriteria;
-        if (!supportProfileRequestContextCriterion &&
-                criteria != null && criteria.contains(ProfileRequestContextCriterion.class)) {
+        if (resultsCache != null && criteria != null && criteria.contains(ProfileRequestContextCriterion.class)) {
+            log.debug("CredentialResolver (): Cache enabled, suppressing ProfileRequestContextCriterion", getId());
             manipulatedCriteria = new CriteriaSet();
             criteria.forEach(c -> {
                 if (!(c instanceof ProfileRequestContextCriterion)) {
@@ -366,7 +427,7 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
         if (localCache != null) {
             final List<Credential> cached = localCache.getIfPresent(manipulatedCriteria);
             if (cached != null) {
-                log.debug("{}: Resolved {} credential(s) from cache", getId(), cached.size());
+                log.debug("CredentialResolver {}: Resolved {} credential(s) from cache", getId(), cached.size());
                 return CollectionSupport.copyToList(cached);
             }
         }
@@ -382,7 +443,7 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
             throw new ResolverException("Error executing context template", e);
         }
         
-        log.debug("{}: Resolved storage context ({})", getId(), storageContext);
+        log.debug("CredentialResolver {}: Resolved storage context ({})", getId(), storageContext);
         
         final Iterable<Credential> resolved = doResolve(ctx, manipulatedCriteria, storageContext);
 
@@ -393,7 +454,7 @@ public abstract class AbstractStorageServiceCredentialResolver<T extends Credent
             // 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());
+            log.debug("CredentialResolver {}: Adding {} resolved credential(s) to cache", getId(), toCache.size());
             localCache.put(manipulatedCriteria, toCache);
             return CollectionSupport.copyToList(toCache);
         }
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
index b89deea..f4e8e2f 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/credential/impl/X509CredentialStorageServiceResolver.java
@@ -171,7 +171,7 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
             final String keyStorage = privateKeyTemplate.merge(velocityContext);
             final String certStorage = certificateTemplate.merge(velocityContext);
 
-            log.debug("{}: Resolved storage keys for key ({}), certificate ({})", getId(), keyStorage, certStorage);
+            log.debug("CredentialResolver {}: Resolved storage keys for key ({}), certificate ({})", getId(), keyStorage, certStorage);
             
             privateKeyData = getStorageService().read(storageContext, keyStorage);
             certificateData = getStorageService().read(storageContext, certStorage);
@@ -192,7 +192,7 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
             return CollectionSupport.emptyList();
         }
         
-        log.debug("{}: Resolved data for key and/or certificate, constructing credential", getId());
+        log.debug("CredentialResolver {}: Resolved data for key and/or certificate, constructing credential", getId());
         
         // Populate a factory bean from the resolved material.
         
@@ -219,7 +219,7 @@ public class X509CredentialStorageServiceResolver extends AbstractStorageService
             factory.afterPropertiesSet();
             final X509Credential credential = factory.getObject();
             if (credential != null) {
-                log.debug("{}: Resolved X509Credential for caller", getId());
+                log.debug("CredentialResolver {}: Resolved X509Credential for caller", getId());
                 return CollectionSupport.singletonList(credential);
             }
             
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 47d16f6..2d18b49 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
@@ -39,8 +39,6 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-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;
@@ -229,15 +227,13 @@ public class X509CredentialStorageServiceResolverTest {
         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.setCacheEnabled(true);
+        resolver.setExpireAfterAccess(Duration.ofSeconds(10));
         resolver.initialize();
         
         X509Credential credential = (X509Credential) resolver.resolveSingle(

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


More information about the commits mailing list