[java-identity-provider] branch main updated: OSJ-304: Support hash-based directory structure in LocalDynamic

Brent Putman putmanb at georgetown.edu
Fri Nov 6 02:41:17 UTC 2020


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=39427322864632ff12dc30bf398f3413a04bb8dc

The following commit(s) were added to refs/heads/main by this push:
       new  394273228 OSJ-304: Support hash-based directory structure in LocalDynamic
394273228 is described below

commit 39427322864632ff12dc30bf398f3413a04bb8dc
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Oct 29 20:24:57 2020 -0400

    OSJ-304: Support hash-based directory structure in LocalDynamic
---
 .../impl/LocalDynamicMetadataProviderParser.java   | 136 +++++++++++++++++++--
 .../LocalDynamicMetadataProviderParserTest.java    | 134 ++++++++++++++++++--
 .../profile/spring/relyingparty/metadata/beans.xml |   8 ++
 ...namicMissingSourceManagerAndSourceDirectory.xml |  12 ++
 ...DirectoryAndIntermediateLengthWithoutNumber.xml |  14 +++
 ...urceDirectoryAndIntermediateNumberAndLength.xml |  15 +++
 ...DirectoryAndIntermediateNumberWithoutLength.xml |  14 +++
 ...thSourceDirectoryAndIntermediateStrategyRef.xml |  14 +++
 .../main/resources/schema/shibboleth-metadata.xsd  |  59 +++++++++
 9 files changed, 388 insertions(+), 18 deletions(-)

diff --git a/idp-profile-spring/src/main/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/impl/LocalDynamicMetadataProviderParser.java b/idp-profile-spring/src/main/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/impl/LocalDynamicMetadataProviderParser.java
index 1ccd34b0b..039f171a8 100644
--- a/idp-profile-spring/src/main/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/impl/LocalDynamicMetadataProviderParser.java
+++ b/idp-profile-spring/src/main/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/impl/LocalDynamicMetadataProviderParser.java
@@ -20,11 +20,14 @@ package net.shibboleth.idp.profile.spring.relyingparty.metadata.impl;
 import javax.xml.namespace.QName;
 
 import org.opensaml.core.xml.persist.FilesystemLoadSaveManager;
+import org.opensaml.core.xml.persist.impl.PassthroughSourceStrategy;
+import org.opensaml.core.xml.persist.impl.SegmentingIntermediateDirectoryStrategy;
 import org.opensaml.saml.metadata.resolver.MetadataResolver;
 import org.opensaml.saml.metadata.resolver.impl.DefaultLocalDynamicSourceKeyGenerator;
 import org.opensaml.saml.metadata.resolver.impl.LocalDynamicMetadataResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
 import org.springframework.beans.factory.parsing.Location;
 import org.springframework.beans.factory.parsing.Problem;
@@ -66,20 +69,17 @@ public class LocalDynamicMetadataProviderParser extends AbstractDynamicMetadataP
         Object sourceManagerRefOrBean = null;
         if (element.hasAttributeNS(null, "sourceManagerRef")) {
             sourceManagerRefOrBean = StringSupport.trimOrNull(element.getAttributeNS(null, "sourceManagerRef"));
-            if (element.hasAttributeNS(null, "sourceDirectory")) {
-                log.warn("Presence of sourceManagerRef will override sourceDirectory");
-            }
+            checkAndLogSourceDirectoryOverrides(element);
         } else if (element.hasAttributeNS(null, "sourceDirectory")) {
-            final BeanDefinitionBuilder sourceManagerBuilder = 
-                    BeanDefinitionBuilder.genericBeanDefinition(FilesystemLoadSaveManager.class);
-            sourceManagerBuilder.addConstructorArgValue(
-                    StringSupport.trimOrNull(element.getAttributeNS(null, "sourceDirectory")));
-            sourceManagerBuilder.addConstructorArgValue(Boolean.TRUE);
-            sourceManagerRefOrBean = sourceManagerBuilder.getBeanDefinition();
-            
+            // Default in source key generator if not supplied
+            boolean isDefaultSourceKeyGenerator = false;
             if (sourceKeyGeneratorRefOrBean == null) {
                 sourceKeyGeneratorRefOrBean = new DefaultLocalDynamicSourceKeyGenerator(null, ".xml", null);
+                isDefaultSourceKeyGenerator = true;
             }
+
+            sourceManagerRefOrBean = buildFilesystemManagerDefinition(element, parserContext,
+                    isDefaultSourceKeyGenerator);
         } else {
             log.error("{} LocalDynamicMetadataProvider contained neither a sourceManagerRef nor a sourceDirectory", 
                     parserContext.getReaderContext().getResource().getDescription());
@@ -102,4 +102,120 @@ public class LocalDynamicMetadataProviderParser extends AbstractDynamicMetadataP
 
     }
 
+    /**
+     * Check and log the options related to 'sourceDirectory' which are being overridden by 'sourceManagerRef'.
+     *
+     * @param element the element being parsed
+     */
+    private void checkAndLogSourceDirectoryOverrides(final Element element) {
+        if (element.hasAttributeNS(null, "sourceDirectory")) {
+            log.warn("Presence of sourceManagerRef will override sourceDirectory");
+        }
+
+        if (element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentNumber")
+                || element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentLength")
+                || element.hasAttributeNS(null, "sourceDirectoryIntermediateStrategyRef")) {
+            log.warn("Presence of sourceManagerRef will override sourceDirectoryIntermediateSegmentNumber, "
+                    + "sourceDirectoryIntermediateSegmentLength and sourceDirectoryIntermediateStrategyRef");
+        }
+    }
+
+    /**
+     * Build bean definition for {@link FilesystemLoadSaveManager} source manager.
+     *
+     * @param element the element being parsed
+     * @param parserContext the current parser context
+     * @param isDefaultSourceKeyGenerator whether the internal default source key manager is being used
+     *
+     * @return the bean definition for the filesystem source manager
+     */
+    private BeanDefinition buildFilesystemManagerDefinition(final Element element, final ParserContext parserContext,
+            final boolean isDefaultSourceKeyGenerator) {
+
+        final BeanDefinitionBuilder sourceManagerBuilder =
+                BeanDefinitionBuilder.genericBeanDefinition(FilesystemLoadSaveManager.class);
+
+        sourceManagerBuilder.addConstructorArgValue(
+                StringSupport.trimOrNull(element.getAttributeNS(null, "sourceDirectory")));
+
+        sourceManagerBuilder.addConstructorArgValue(Boolean.TRUE);
+
+       // Figure out intermediate directory strategy, if configured, and apply
+        processIntermediateDirectoryStrategy(element, parserContext, sourceManagerBuilder,
+                isDefaultSourceKeyGenerator);
+
+        return sourceManagerBuilder.getBeanDefinition();
+    }
+
+    /**
+     * Process options related to intermediate directory strategy and apply to the builder for the
+     * {@link FilesystemLoadSaveManager}.
+     *
+     * @param element the element being parsed
+     * @param parserContext the current parser context
+     * @param sourceManagerBuilder the source build manager for the FilesystemLoadSaveManager being built
+     * @param isDefaultSourceKeyGenerator whether the internal default source key manager is being used
+     */
+    private void processIntermediateDirectoryStrategy(final Element element, final ParserContext parserContext,
+            final BeanDefinitionBuilder sourceManagerBuilder, final boolean isDefaultSourceKeyGenerator) {
+
+        if (element.hasAttributeNS(null, "sourceDirectoryIntermediateStrategyRef")) {
+            sourceManagerBuilder.addConstructorArgReference(
+                    StringSupport.trimOrNull(
+                            element.getAttributeNS(null, "sourceDirectoryIntermediateStrategyRef")));
+        } else if (isDefaultSourceKeyGenerator) {
+            final BeanDefinition intermediateDirectoryStrategy =
+                    checkAndBuildPassthroughIntermediateDirectoryStrategy(element, parserContext);
+            if (intermediateDirectoryStrategy != null) {
+                sourceManagerBuilder.addConstructorArgValue(intermediateDirectoryStrategy);
+            }
+        } else {
+            if (element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentNumber")
+                    || element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentLength")) {
+                log.warn("{} Non-default source key manger in use, sourceDirectoryIntermediateSegmentNumber and "
+                    + "sourceDirectoryIntermediateSegmentLength will be ignored. "
+                    + "Use sourceDirectoryIntermediateStrategyRef instead");
+            }
+        }
+    }
+
+    /**
+     * Return bean definition for the default intermediate directory strategy with passthrough source strategy,
+     * if segmenting configuration params specified.
+     *
+     * @param element the element being parsed
+     * @param parserContext the current parser context
+     * @return the strategy bean definition, or null if not configured
+     */
+    private BeanDefinition checkAndBuildPassthroughIntermediateDirectoryStrategy(final Element element,
+            final ParserContext parserContext) {
+
+        if (!element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentNumber")
+                && !element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentLength") ) {
+            return null;
+        }
+
+        // Must have both params
+        if (!element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentNumber")
+                || !element.hasAttributeNS(null, "sourceDirectoryIntermediateSegmentLength")) {
+
+            log.error("{} LocalDynamicMetadataProvider contained either sourceDirectoryIntermediateSegmentNumber "
+                    + "or sourceDirectoryIntermediateSegmentLength without the other",
+                    parserContext.getReaderContext().getResource().getDescription());
+            throw new BeanDefinitionParsingException(
+                    new Problem("Either sourceDirectoryIntermediateSegmentNumber or "
+                            + "sourceDirectoryIntermediateSegmentLength was supplied without the other",
+                            new Location( parserContext.getReaderContext().getResource())));
+        }
+
+        final BeanDefinitionBuilder strategyBuilder =
+                BeanDefinitionBuilder.genericBeanDefinition(SegmentingIntermediateDirectoryStrategy.class);
+        strategyBuilder.addConstructorArgValue(StringSupport.trimOrNull(
+                element.getAttributeNS(null, "sourceDirectoryIntermediateSegmentNumber")));
+        strategyBuilder.addConstructorArgValue(StringSupport.trimOrNull(
+                element.getAttributeNS(null, "sourceDirectoryIntermediateSegmentLength")));
+        strategyBuilder.addConstructorArgValue(new PassthroughSourceStrategy());
+        return strategyBuilder.getBeanDefinition();
+    }
+
 }
diff --git a/idp-profile-spring/src/test/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/LocalDynamicMetadataProviderParserTest.java b/idp-profile-spring/src/test/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/LocalDynamicMetadataProviderParserTest.java
index bb81c4c7c..67c041726 100644
--- a/idp-profile-spring/src/test/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/LocalDynamicMetadataProviderParserTest.java
+++ b/idp-profile-spring/src/test/java/net/shibboleth/idp/profile/spring/relyingparty/metadata/LocalDynamicMetadataProviderParserTest.java
@@ -19,7 +19,11 @@ package net.shibboleth.idp.profile.spring.relyingparty.metadata;
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.time.Duration;
+import java.util.Comparator;
 import java.util.concurrent.TimeUnit;
 
 import org.opensaml.core.criterion.EntityIdCriterion;
@@ -30,6 +34,7 @@ import org.opensaml.saml.metadata.resolver.impl.AbstractDynamicMetadataResolver;
 import org.opensaml.saml.metadata.resolver.impl.LocalDynamicMetadataResolver;
 import org.opensaml.saml.saml2.metadata.EntityDescriptor;
 import org.opensaml.security.crypto.JCAConstants;
+import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
 import org.springframework.context.ApplicationContext;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
@@ -53,18 +58,14 @@ public class LocalDynamicMetadataProviderParserTest extends AbstractMetadataPars
     private final int TIME_GRANULARITY_MS = 25; // Dither for Windows clocks
     
     @BeforeMethod
-    public void setUp() {
+    public void setUp() throws IOException {
         sourceDirectory = new File(System.getProperty("java.io.tmpdir"), "localDynamicMD");
+        resetSourceDirectory();
     }
     
     @AfterMethod
-    public void tearDown() {
-        if (sourceDirectory.exists()) {
-            for (File child : sourceDirectory.listFiles()) {
-                child.delete();
-            }
-            sourceDirectory.delete();
-        }
+    public void tearDown() throws IOException {
+        resetSourceDirectory();
     }
     
     @Test
@@ -176,5 +177,122 @@ public class LocalDynamicMetadataProviderParserTest extends AbstractMetadataPars
         Assert.assertEquals(resolved.getEntityID(), entityID);
     }
     
+    @Test
+    public void testSourceDirectoryWithIntermediateNumberAndLength() throws Exception {
+        // Test that can correctly resolve when sourceDirectory is supplied with intermediate dir segment number and length
+        // SHA-1 hashes to: 9339685fdddbf8e45faac94340a3260468aa65bd
+        String entityID = "urn:test:entity1";
+        EntityDescriptor entity = (EntityDescriptor) XMLObjectSupport.buildXMLObject(EntityDescriptor.DEFAULT_ELEMENT_NAME);
+        entity.setEntityID(entityID);
+        
+        if (!sourceDirectory.exists()) {
+            Assert.assertTrue(sourceDirectory.mkdirs());
+        }
+        StringDigester digester = new StringDigester(JCAConstants.DIGEST_SHA1, OutputFormat.HEX_LOWER);
+        String sourceKey = digester.apply(entityID) + ".xml";
+        // Using segment number=1 and length=2
+        File sourceFile = Path.of(sourceDirectory.getAbsolutePath(), "93", sourceKey).toFile();
+        
+        // First clear anything from previous test, and sanity check the setup
+        if (sourceFile.exists()) {
+            sourceFile.delete();
+            Assert.assertFalse(sourceFile.exists());
+        }
+        
+        LocalDynamicMetadataResolver resolver = getBean(LocalDynamicMetadataResolver.class, 
+                "localDynamicWithSourceDirectoryAndIntermediateNumberAndLength.xml", "beans.xml");
+        
+        CriteriaSet criteria = new CriteriaSet(new EntityIdCriterion(entityID));
+        
+        Assert.assertNull(resolver.resolveSingle(criteria));
+        
+        sourceFile.getParentFile().mkdirs();
+        XMLObjectSupport.marshallToOutputStream(entity, new FileOutputStream(sourceFile));
+        Assert.assertTrue(sourceFile.exists());
+        
+        // Configured negative lookup cache should still be in effect
+        Assert.assertNull(resolver.resolveSingle(criteria));
+        
+        // Sleep past the negative lookup cache expiration
+        Uninterruptibles.sleepUninterruptibly(resolver.getNegativeLookupCacheDuration().toMillis()+TIME_GRANULARITY_MS, TimeUnit.MILLISECONDS);
+        
+        EntityDescriptor resolved = resolver.resolveSingle(criteria);
+        Assert.assertNotNull(resolved);
+        Assert.assertEquals(resolved.getEntityID(), entityID);
+    }
+    
+    @Test
+    public void testSourceDirectoryWithIntermediateStrategyRef() throws Exception {
+        // Test that can correctly resolve when sourceDirectory is supplied with intermediate dir strategy ref
+        // SHA-1 hashes to: 9339685fdddbf8e45faac94340a3260468aa65bd
+        String entityID = "urn:test:entity1";
+        EntityDescriptor entity = (EntityDescriptor) XMLObjectSupport.buildXMLObject(EntityDescriptor.DEFAULT_ELEMENT_NAME);
+        entity.setEntityID(entityID);
+        
+        if (!sourceDirectory.exists()) {
+            Assert.assertTrue(sourceDirectory.mkdirs());
+        }
+        StringDigester digester = new StringDigester(JCAConstants.DIGEST_SHA1, OutputFormat.HEX_LOWER);
+        String sourceKey = digester.apply(entityID) + ".xml";
+        // Using segment number=2 and length=2
+        File sourceFile = Path.of(sourceDirectory.getAbsolutePath(), "93", "39", sourceKey).toFile();
+        
+        // First clear anything from previous test, and sanity check the setup
+        if (sourceFile.exists()) {
+            sourceFile.delete();
+            Assert.assertFalse(sourceFile.exists());
+        }
+        
+        LocalDynamicMetadataResolver resolver = getBean(LocalDynamicMetadataResolver.class, 
+                "localDynamicWithSourceDirectoryAndIntermediateStrategyRef.xml", "beans.xml");
+        
+        CriteriaSet criteria = new CriteriaSet(new EntityIdCriterion(entityID));
+        
+        Assert.assertNull(resolver.resolveSingle(criteria));
+        
+        sourceFile.getParentFile().mkdirs();
+        XMLObjectSupport.marshallToOutputStream(entity, new FileOutputStream(sourceFile));
+        Assert.assertTrue(sourceFile.exists());
+        
+        // Configured negative lookup cache should still be in effect
+        Assert.assertNull(resolver.resolveSingle(criteria));
+        
+        // Sleep past the negative lookup cache expiration
+        Uninterruptibles.sleepUninterruptibly(resolver.getNegativeLookupCacheDuration().toMillis()+TIME_GRANULARITY_MS, TimeUnit.MILLISECONDS);
+        
+        EntityDescriptor resolved = resolver.resolveSingle(criteria);
+        Assert.assertNotNull(resolved);
+        Assert.assertEquals(resolved.getEntityID(), entityID);
+    }
+    
+    @Test(expectedExceptions=BeanDefinitionParsingException.class)
+    public void testSourceDirectoryIntermediateNumberWithoutLength() throws Exception {
+        getBean(LocalDynamicMetadataResolver.class, "localDynamicWithSourceDirectoryAndIntermediateNumberWithoutLength.xml", "beans.xml");
+    }
+    
+    @Test(expectedExceptions=BeanDefinitionParsingException.class)
+    public void testSourceDirectoryIntermediateLengthWithoutNumber() throws Exception {
+        getBean(LocalDynamicMetadataResolver.class, "localDynamicWithSourceDirectoryAndIntermediateLengthWithoutNumber.xml", "beans.xml");
+    }
+    
+    @Test(expectedExceptions=BeanDefinitionParsingException.class)
+    public void testMissingSourceDirectoryAndSourceManager() throws Exception {
+        getBean(LocalDynamicMetadataResolver.class, "localDynamicMissingSourceManagerAndSourceDirectory.xml", "beans.xml");
+    }
+    
+    // Helpers
 
+    private void resetSourceDirectory() throws IOException {
+        if (sourceDirectory.exists()) {
+            if (sourceDirectory.isDirectory()) {
+                Files.walk(sourceDirectory.toPath())
+                .sorted(Comparator.reverseOrder())
+                .map(Path::toFile)
+                .forEach(File::delete);
+            } else {
+                sourceDirectory.delete();
+            }
+        }
+    }
+    
 }
diff --git a/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/beans.xml b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/beans.xml
index 2dff5dfeb..53c024208 100644
--- a/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/beans.xml
+++ b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/beans.xml
@@ -94,4 +94,12 @@
     
     <bean id="metadata.classpathRef" class="org.springframework.core.io.ClassPathResource" c:_0="/net/shibboleth/idp/profile/spring/relyingparty/metadata/entity.xml"/>
             
+    <bean id="metadata.IntermediateDirectoryStrategy" class="org.opensaml.core.xml.persist.impl.SegmentingIntermediateDirectoryStrategy" >
+        <constructor-arg value="2" />
+        <constructor-arg value="2" />
+        <constructor-arg>
+            <bean class="org.opensaml.core.xml.persist.impl.PassthroughSourceStrategy" />
+        </constructor-arg> 
+    </bean>
+    
 </beans>
\ No newline at end of file
diff --git a/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicMissingSourceManagerAndSourceDirectory.xml b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicMissingSourceManagerAndSourceDirectory.xml
new file mode 100644
index 000000000..fa7f789c1
--- /dev/null
+++ b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicMissingSourceManagerAndSourceDirectory.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata:MetadataProvider xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+	xmlns:metadata="urn:mace:shibboleth:2.0:metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd
+                       urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd"
+
+	id="localDynamicWithSourceDirectory" xsi:type="metadata:LocalDynamicMetadataProvider"
+    negativeLookupCacheDuration="PT1S"
+    >
+
+</metadata:MetadataProvider>
+                                   
\ No newline at end of file
diff --git a/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateLengthWithoutNumber.xml b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateLengthWithoutNumber.xml
new file mode 100644
index 000000000..d27dfbb4d
--- /dev/null
+++ b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateLengthWithoutNumber.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata:MetadataProvider xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+	xmlns:metadata="urn:mace:shibboleth:2.0:metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd
+                       urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd"
+
+	id="localDynamicWithSourceDirectory" xsi:type="metadata:LocalDynamicMetadataProvider"
+    negativeLookupCacheDuration="PT1S"
+    sourceDirectory="#{systemProperties['java.io.tmpdir']}/localDynamicMD"
+    sourceDirectoryIntermediateSegmentLength="2"
+    >
+
+</metadata:MetadataProvider>
+                                   
\ No newline at end of file
diff --git a/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateNumberAndLength.xml b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateNumberAndLength.xml
new file mode 100644
index 000000000..c86e5dbe0
--- /dev/null
+++ b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateNumberAndLength.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata:MetadataProvider xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+	xmlns:metadata="urn:mace:shibboleth:2.0:metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd
+                       urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd"
+
+	id="localDynamicWithSourceDirectory" xsi:type="metadata:LocalDynamicMetadataProvider"
+    negativeLookupCacheDuration="PT1S"
+    sourceDirectory="#{systemProperties['java.io.tmpdir']}/localDynamicMD"
+    sourceDirectoryIntermediateSegmentNumber="1"
+    sourceDirectoryIntermediateSegmentLength="2"
+    >
+
+</metadata:MetadataProvider>
+                                   
\ No newline at end of file
diff --git a/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateNumberWithoutLength.xml b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateNumberWithoutLength.xml
new file mode 100644
index 000000000..a685d7f1c
--- /dev/null
+++ b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateNumberWithoutLength.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata:MetadataProvider xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+	xmlns:metadata="urn:mace:shibboleth:2.0:metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd
+                       urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd"
+
+	id="localDynamicWithSourceDirectory" xsi:type="metadata:LocalDynamicMetadataProvider"
+    negativeLookupCacheDuration="PT1S"
+    sourceDirectory="#{systemProperties['java.io.tmpdir']}/localDynamicMD"
+    sourceDirectoryIntermediateSegmentNumber="1"
+    >
+
+</metadata:MetadataProvider>
+                                   
\ No newline at end of file
diff --git a/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateStrategyRef.xml b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateStrategyRef.xml
new file mode 100644
index 000000000..d1b2bb407
--- /dev/null
+++ b/idp-profile-spring/src/test/resources/net/shibboleth/idp/profile/spring/relyingparty/metadata/localDynamicWithSourceDirectoryAndIntermediateStrategyRef.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata:MetadataProvider xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+	xmlns:metadata="urn:mace:shibboleth:2.0:metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd
+                       urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd"
+
+	id="localDynamicWithSourceDirectory" xsi:type="metadata:LocalDynamicMetadataProvider"
+    negativeLookupCacheDuration="PT1S"
+    sourceDirectory="#{systemProperties['java.io.tmpdir']}/localDynamicMD"
+    sourceDirectoryIntermediateStrategyRef="metadata.IntermediateDirectoryStrategy"
+    >
+
+</metadata:MetadataProvider>
+                                   
\ No newline at end of file
diff --git a/idp-schema/src/main/resources/schema/shibboleth-metadata.xsd b/idp-schema/src/main/resources/schema/shibboleth-metadata.xsd
index d6cf9917f..679ec8cd6 100644
--- a/idp-schema/src/main/resources/schema/shibboleth-metadata.xsd
+++ b/idp-schema/src/main/resources/schema/shibboleth-metadata.xsd
@@ -515,6 +515,65 @@
                        </documentation>
                     </annotation>
                 </attribute>
+                <attribute name="sourceDirectoryIntermediateSegmentNumber" type="shibmd:string">
+                    <annotation>
+                        <documentation>
+                        Convenience mechanism for specifying the number of intermediate directory segments
+                        underneath the 'sourceDirectory', when a hashed directory approach is desired.
+                        If this is supplied, then 'sourceDirectoryIntermediateSegmentLength' MUST also be supplied.
+                        
+                        This attribute will be ignored if 'sourceManagerRef' is also specified.
+                        
+                        This attribute will be ignored if 'sourceDirectoryIntermediateStrategyRef' is also specified.
+                        
+                        This attribute will be ignored if 'sourceKeyGeneratorRef' is also specified. In that case
+                        if an intermediate directory strategy is desired, 'sourceDirectoryIntermediateStrategyRef'
+                        should be supplied instead.
+                        
+                        Example: When segment number = 2, and segment length = 2, the metadata for the entity with
+                        entityID "urn:test:foobar" in file "d278c9975472a6b4827b1a8723192b4e99aa969c.xml"
+                        will be resolved from:
+                        
+                            {sourceDirectory}/d2/78/d278c9975472a6b4827b1a8723192b4e99aa969c.xml
+                       </documentation>
+                    </annotation>
+                </attribute>
+                <attribute name="sourceDirectoryIntermediateSegmentLength" type="shibmd:string">
+                    <annotation>
+                        <documentation>
+                        Convenience mechanism for specifying the number of intermediate directory segments
+                        underneath the 'sourceDirectory', when a hashed directory approach is desired.
+                        If this is supplied, then 'sourceDirectoryIntermediateSegmentNumber' MUST also be supplied.
+                        
+                        This attribute will be ignored if 'sourceManagerRef' is also specified.
+                        
+                        This attribute will be ignored if 'sourceDirectoryIntermediateStrategyRef' is also specified.
+                        
+                        This attribute will be ignored if 'sourceKeyGeneratorRef' is also specified. In that case
+                        if an intermediate directory strategy is desired, 'sourceDirectoryIntermediateStrategyRef'
+                        should be supplied instead.
+                        
+                        Example: When segment number = 2, and segment length = 2, the metadata for the entity with
+                        entityID "urn:test:foobar" in file "d278c9975472a6b4827b1a8723192b4e99aa969c.xml"
+                        will be resolved from:
+                        
+                            {sourceDirectory}/d2/78/d278c9975472a6b4827b1a8723192b4e99aa969c.xml
+                       </documentation>
+                    </annotation>
+                </attribute>
+                <attribute name="sourceDirectoryIntermediateStrategyRef" type="shibmd:string">
+                    <annotation>
+                        <documentation>
+                        Bean reference to the strategy for computing intermediate directories underneath the
+                        'sourceDirectory'.
+                        
+                        This attribute overrides 'sourceDirectoryIntermediateSegmentNumber' and
+                        'sourceDirectoryIntermediateSegmentLength'.
+                        
+                        This attribute will be ignored if 'sourceManagerRef' is also specified.
+                       </documentation>
+                    </annotation>
+                </attribute>
             </extension>
         </complexContent>
     </complexType>

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


More information about the commits mailing list