[java-plugin-shibd] branch main updated: Bug fixes amd unit tests for flat file Agent loader.

Codeberg noreply at shibboleth.net
Tue Nov 3 23:57:20 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/3b140c0569472362e3725671295993248eef58bd

The following commit(s) were added to refs/heads/main by this push:
     new 3b140c0  Bug fixes amd unit tests for flat file Agent loader.
3b140c0 is described below

commit 3b140c0569472362e3725671295993248eef58bd
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue Nov 3 18:57:06 2026 -0500

    Bug fixes amd unit tests for flat file Agent loader.
---
 .../sp/impl/AbstractExternalAgentResolver.java     |  57 +++++++-
 .../sp/impl/FlatFileExternalAgentResolver.java     |  64 +++++++--
 .../sp/impl/FlatFileExternalAgentResolverTest.java | 143 +++++++++++++++++++++
 .../ResourceBackedMemoryStorageServiceTest.java    |   3 +-
 .../resources/net/shibboleth/sp/impl/empty.txt     |   0
 .../resources/net/shibboleth/sp/impl/flatfile.txt  |  12 ++
 6 files changed, 263 insertions(+), 16 deletions(-)

diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/AbstractExternalAgentResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/AbstractExternalAgentResolver.java
index 3a58d0a..edb20fb 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/AbstractExternalAgentResolver.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/AbstractExternalAgentResolver.java
@@ -43,6 +43,7 @@ import net.shibboleth.idp.attribute.resolver.AttributeResolver;
 import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
 import net.shibboleth.profile.config.ProfileConfiguration;
 import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.profile.relyingparty.VerifiedProfileCriterion;
 import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NonNegative;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -179,6 +180,15 @@ public abstract class AbstractExternalAgentResolver extends AbstractIdentifiable
     @Nonnull
     private Function<ProfileRequestContext,Map<String,ProfileConfiguration>> profileConfigurationsLookupStrategy;
     
+    /** Unverified RP config. */
+    @Nullable private RelyingPartyConfiguration unverifiedRelyingPartyConfiguration;
+
+    /** RP config overrides. */
+    @Nonnull private Collection<RelyingPartyConfiguration> relyingPartyConfigurationOverrides;
+    
+    /** Metrics name. */
+    @Nullable private String metricName;
+    
     /**
      * Constructor.
      * 
@@ -210,6 +220,8 @@ public abstract class AbstractExternalAgentResolver extends AbstractIdentifiable
         securityConfigurationLookupStrategy = FunctionSupport.constant(null);
         profileConfigurationsLookupStrategy = FunctionSupport.constant(null);
         
+        relyingPartyConfigurationOverrides = CollectionSupport.emptyList();
+        
     }
     
     /**
@@ -717,6 +729,44 @@ public abstract class AbstractExternalAgentResolver extends AbstractIdentifiable
         profileConfigurationsLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }    
     
+    /**
+     * Set the verified relying party configurations.
+     * 
+     * @param configs list of verified relying party configurations
+     */
+    public void setRelyingPartyConfigurations(@Nullable final Collection<? extends RelyingPartyConfiguration> configs) {
+        checkSetterPreconditions();
+        
+        if (configs != null) {
+            relyingPartyConfigurationOverrides = CollectionSupport.copyToList(configs);
+        } else {
+            relyingPartyConfigurationOverrides = CollectionSupport.emptyList();   
+        }
+    }
+
+    /**
+     * Set the {@link RelyingPartyConfiguration} to use if the configuration is found to be "unverified"
+     * (via use of {@link VerifiedProfileCriterion}).
+     * 
+     * @param configuration unverified configuration
+     */
+    public void setUnverifiedConfiguration(@Nullable final RelyingPartyConfiguration configuration) {
+        checkSetterPreconditions();
+        
+        unverifiedRelyingPartyConfiguration = configuration;
+    }
+    
+    /**
+     * Set name of metric to use for counters to track use of configurations.
+     * 
+     * @param name name for counter metrics
+     */
+    public void setMetricName(@Nullable final String name) {
+        checkSetterPreconditions();
+        
+        metricName = StringSupport.trimOrNull(name);
+    }
+    
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -834,6 +884,10 @@ public abstract class AbstractExternalAgentResolver extends AbstractIdentifiable
             agent.setSecurityConfigurationLookupStrategy(securityConfigurationLookupStrategy);
             agent.setProfileConfigurationsLookupStrategy(profileConfigurationsLookupStrategy);
             
+            agent.setUnverifiedConfiguration(unverifiedRelyingPartyConfiguration);
+            agent.setRelyingPartyConfigurations(relyingPartyConfigurationOverrides);
+            agent.setMetricName(metricName);
+            
             agent.initialize();
             return agent;
             
@@ -902,8 +956,7 @@ public abstract class AbstractExternalAgentResolver extends AbstractIdentifiable
             authority = StringSupport.trimOrNull(auth);
             
             if (ranges != null) {
-                addressRanges = new HashSet<>();
-                ranges.stream()
+                addressRanges = ranges.stream()
                     .map(IPRange::parseCIDRBlock)
                     .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableSet())).get();
             } else {
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolver.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolver.java
index a961c59..681b217 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolver.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolver.java
@@ -44,6 +44,7 @@ import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
 import net.shibboleth.shared.primitive.TimerSupport;
 import net.shibboleth.shared.resolver.CriteriaSet;
 import net.shibboleth.shared.resolver.ResolverException;
@@ -221,9 +222,11 @@ public class FlatFileExternalAgentResolver extends AbstractExternalAgentResolver
 
         AgentTemplate template = null;
 
-        final Lock readLock = lock.readLock();
+        final Lock readLock = lock != null ? lock.readLock() : null;
         try {
-            readLock.lock();
+            if (readLock != null) {
+                readLock.lock();
+            }
         
             final AgentCredentialCriterion credentialCrit = criteria.get(AgentCredentialCriterion.class);
             if (credentialCrit != null) {
@@ -237,7 +240,9 @@ public class FlatFileExternalAgentResolver extends AbstractExternalAgentResolver
                 }
             }
         } finally {
-            readLock.unlock();
+            if (readLock != null) {
+                readLock.unlock();
+            }
         }
         
         return template;
@@ -319,7 +324,9 @@ public class FlatFileExternalAgentResolver extends AbstractExternalAgentResolver
 
                 if (line.isEmpty() || line.isBlank()) {
                     if (id != null) {
-                        theMap.put(id, new AgentTemplate(id, issuer, authority, ranges, usernames));
+                        installMappings(theMap, new AgentTemplate(id, issuer, authority, ranges, usernames));
+                    } else {
+                        log.error("Ignoring agent definition with no '{}' field", ID_FIELD);
                     }
                     
                     id = null;
@@ -331,22 +338,30 @@ public class FlatFileExternalAgentResolver extends AbstractExternalAgentResolver
                 } else if (line.startsWith("#")) {
                     continue;
                 } else if (line.startsWith(ID_FIELD)) {
-                    id = line.substring(ID_FIELD.length());
+                    id = StringSupport.trimOrNull(line.substring(ID_FIELD.length()));
                 } else if (line.startsWith(ALLOWED_RANGE_FIELD)) {
-                    ranges.add(line.substring(ALLOWED_RANGE_FIELD.length()));
+                    final String temp = StringSupport.trimOrNull(line.substring(ALLOWED_RANGE_FIELD.length()));
+                    if (temp != null) {
+                        ranges.add(temp);
+                    }
                 } else if (line.startsWith(USERNAME_FIELD)) {
-                    usernames.add(line.substring(USERNAME_FIELD.length()));
+                    final String temp = StringSupport.trimOrNull(line.substring(USERNAME_FIELD.length()));
+                    if (temp != null) {
+                        usernames.add(temp);
+                    }
                 } else if (line.startsWith(ISSUER_FIELD)) {
-                    issuer = line.substring(ISSUER_FIELD.length());
+                    issuer = StringSupport.trimOrNull(line.substring(ISSUER_FIELD.length()));
                 } else if (line.startsWith(AUTHORITY_FIELD)) {
-                    authority = line.substring(AUTHORITY_FIELD.length());
+                    authority = StringSupport.trimOrNull(line.substring(AUTHORITY_FIELD.length()));
                 } else {
-                    log.warn("Invalid line ({}) found", lineno);
+                    log.info("Ignoring unrecognized line ({})", lineno);
                 }
             }
             
             if (id != null) {
-                theMap.put(id, new AgentTemplate(id, issuer, authority, ranges, usernames));
+                installMappings(theMap, new AgentTemplate(id, issuer, authority, ranges, usernames));
+            } else if (lineno > 0) {
+                log.error("Ignoring agent definition with no '{}' field", ID_FIELD);
             }
             
         }
@@ -354,4 +369,29 @@ public class FlatFileExternalAgentResolver extends AbstractExternalAgentResolver
         return theMap;
     }
     
-}
+    /**
+     * Install mappings while guarding against duplicates.
+     * 
+     * @param map map of templates
+     * @param template template to add
+     */
+    private void installMappings(@Nonnull final Map<String,AgentTemplate> map, @Nonnull final AgentTemplate template) {
+        
+        if (map.containsKey(template.getId())) {
+            log.warn("Ignoring duplicate mapping for Agent ID: {}", template.getId());
+        } else {
+            map.put(template.getId(), template);
+        }
+        
+        for (final String altname : template.getUsernames()) {
+            if (altname != null) {
+                if (map.containsKey(altname)) {
+                    log.warn("Ignoring duplicate mapping for Agent username: {}", altname);
+                } else {
+                    map.put(altname, template);
+                }
+            }
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolverTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolverTest.java
new file mode 100644
index 0000000..7689f07
--- /dev/null
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/impl/FlatFileExternalAgentResolverTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.impl;
+
+import java.time.Duration;
+
+import javax.annotation.Nonnull;
+
+import org.springframework.core.io.ClassPathResource;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.profile.relyingparty.VerifiedProfileCriterion;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.net.IPRange;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.resource.Resource;
+import net.shibboleth.shared.spring.resource.ResourceHelper;
+import net.shibboleth.shared.testing.MockReloadableService;
+import net.shibboleth.sp.Agent;
+import net.shibboleth.sp.Application;
+import net.shibboleth.sp.criteria.AgentCredentialCriterion;
+import net.shibboleth.sp.state.impl.PassthroughStateManager;
+
+/**
+ * Unit tests for {@link FlatFileExternalAgentResolver}.
+ */
+ at SuppressWarnings("javadoc")
+public class FlatFileExternalAgentResolverTest {
+
+    @Nonnull @NotEmpty private static final String DATA_PATH = "/net/shibboleth/sp/impl/";
+    
+    @Test
+    public void testEmpty() throws ComponentInitializationException, ResolverException {
+        
+        final var resolver = getResolver(ResourceHelper.of(new ClassPathResource(DATA_PATH + "empty.txt")));
+        
+        Assert.assertNull(resolver.resolveSingle(null));
+        Assert.assertNull(resolver.resolveSingle(new CriteriaSet(new AgentCredentialCriterion("foo"))));
+    }
+
+    @Test
+    public void testFile() throws ComponentInitializationException, ResolverException {
+        
+        final var resolver = getResolver(ResourceHelper.of(new ClassPathResource(DATA_PATH + "flatfile.txt")));
+        
+        Assert.assertNull(resolver.resolveSingle(null));
+        Assert.assertNull(resolver.resolveSingle(new CriteriaSet(new AgentCredentialCriterion("zorkmid"))));
+        
+        Agent agent = resolver.resolveSingle(new CriteriaSet(new AgentCredentialCriterion("foo")));
+        assert agent != null;
+        Assert.assertEquals(agent.getId(), "foo");
+        Assert.assertEquals(agent.getAuthenticationMethod(), Agent.AUTH_METHOD_BASIC);
+        Assert.assertTrue(agent.getAllowedAddressRanges().isEmpty());
+        Assert.assertTrue(agent.getCredentials().isEmpty());
+        Assert.assertFalse(agent.isSupportsCachedAuthentication());
+        Assert.assertTrue(agent.isSupportsPostPreservation());
+        Application app = agent.getApplication("default");
+        assert app != null;
+        Assert.assertNull(app.getAuthenticatingAuthority(null));
+        RelyingPartyConfiguration rpc = app.resolveSingle(new CriteriaSet(new VerifiedProfileCriterion(true)));
+        assert rpc != null;
+        Assert.assertNull(rpc.getIssuer(null));
+        
+        agent = resolver.resolveSingle(new CriteriaSet(new AgentCredentialCriterion("bar")));
+        assert agent != null;
+        Assert.assertEquals(agent.getId(), "bar");
+        Assert.assertEquals(agent.getAuthenticationMethod(), Agent.AUTH_METHOD_BASIC);
+        Assert.assertTrue(agent.getAllowedAddressRanges().isEmpty());
+        Assert.assertTrue(agent.getCredentials().isEmpty());
+        Assert.assertFalse(agent.isSupportsCachedAuthentication());
+        Assert.assertTrue(agent.isSupportsPostPreservation());
+        app = agent.getApplication("default");
+        assert app != null;
+        Assert.assertEquals(app.getAuthenticatingAuthority(null), "https://idp.example.org");
+        rpc = app.resolveSingle(new CriteriaSet(new VerifiedProfileCriterion(true)));
+        assert rpc != null;
+        Assert.assertEquals(rpc.getIssuer(null), "https://bar.example.org");
+        
+        agent = resolver.resolveSingle(new CriteriaSet(new AgentCredentialCriterion("sa-baz2")));
+        assert agent != null;
+        Assert.assertEquals(agent.getId(), "baz");
+        Assert.assertEquals(agent.getAuthenticationMethod(), Agent.AUTH_METHOD_BASIC);
+        Assert.assertEqualsNoOrder(agent.getAllowedAddressRanges(),
+                CollectionSupport.setOf(IPRange.parseCIDRBlock("127.0.0.1/32"), IPRange.parseCIDRBlock("192.168.0.0/16")));
+        Assert.assertEquals(agent.getCredentials().size(), 2);
+        Assert.assertFalse(agent.isSupportsCachedAuthentication());
+        Assert.assertTrue(agent.isSupportsPostPreservation());
+        app = agent.getApplication("default");
+        assert app != null;
+        Assert.assertNull(app.getAuthenticatingAuthority(null));
+        rpc = app.resolveSingle(new CriteriaSet(new VerifiedProfileCriterion(true)));
+        assert rpc != null;
+        Assert.assertNull(rpc.getIssuer(null));
+    }
+    
+    /**
+     * Create a resolver with a specific resource.
+     * 
+     * @param resource resource to load
+     * 
+     * @return resolver to test
+     * 
+     * @throws ComponentInitializationException
+     */
+    @Nonnull private FlatFileExternalAgentResolver getResolver(@Nonnull final Resource resource) throws ComponentInitializationException {
+        final var resolver = new FlatFileExternalAgentResolver(new ProtocolSupportServiceManager(null));
+        
+        resolver.setId("test");
+        resolver.setResource(resource);
+        resolver.setReloadInterval(Duration.ZERO);
+        
+        resolver.setAuthenticationMethod(Agent.AUTH_METHOD_BASIC);
+        resolver.setSupportsCachedAuthentication(false);
+        resolver.setSupportsPostPreservation(true);
+        
+        resolver.setStateManager(new PassthroughStateManager());
+        resolver.setAttributeTranscoderRegistry(new MockReloadableService<>());
+        resolver.setAttributeResolver(new MockReloadableService<>());
+        resolver.setAttributeFilter(new MockReloadableService<>());
+        
+        resolver.initialize();
+        
+        return resolver;
+    }
+    
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/ResourceBackedMemoryStorageServiceTest.java b/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/ResourceBackedMemoryStorageServiceTest.java
index 3b5acf5..f699b47 100644
--- a/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/ResourceBackedMemoryStorageServiceTest.java
+++ b/sp-server-impl/src/test/java/net/shibboleth/sp/storage/impl/ResourceBackedMemoryStorageServiceTest.java
@@ -20,7 +20,6 @@ import javax.annotation.Nonnull;
 
 import org.opensaml.storage.StorageRecord;
 import org.opensaml.storage.StorageService;
-import org.opensaml.storage.VersionMismatchException;
 import org.springframework.core.io.ClassPathResource;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
@@ -32,7 +31,7 @@ import net.shibboleth.shared.resource.Resource;
 import net.shibboleth.shared.spring.resource.ResourceHelper;
 
 /**
- * Unit tests for {@link FilesystemStorageService}.
+ * Unit tests for {@link ResourceBackedMemoryStorageService}.
  */
 @SuppressWarnings("javadoc")
 public class ResourceBackedMemoryStorageServiceTest {
diff --git a/sp-server-impl/src/test/resources/net/shibboleth/sp/impl/empty.txt b/sp-server-impl/src/test/resources/net/shibboleth/sp/impl/empty.txt
new file mode 100644
index 0000000..e69de29
diff --git a/sp-server-impl/src/test/resources/net/shibboleth/sp/impl/flatfile.txt b/sp-server-impl/src/test/resources/net/shibboleth/sp/impl/flatfile.txt
new file mode 100644
index 0000000..e72c67b
--- /dev/null
+++ b/sp-server-impl/src/test/resources/net/shibboleth/sp/impl/flatfile.txt
@@ -0,0 +1,12 @@
+# Comment
+id: foo
+
+id: bar
+issuer: https://bar.example.org
+authority: https://idp.example.org
+
+id: baz
+allowedRange: 127.0.0.1/32
+allowedRange: 192.168.0.0/16
+username: sa-baz
+username: sa-baz2
\ No newline at end of file

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


More information about the commits mailing list