[java-identity-provider] 03/04: IdP-1206 Resolver Config: Optional filtering of null values after resolution

Rod Widdowson rdw at steadingsoftware.com
Tue Apr 17 11:14:35 EDT 2018


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

rdw pushed a commit to branch master
in repository java-identity-provider.

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

commit bd719f4ddaaaadb516567f58e8dd5f8763345144
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Tue Apr 17 14:25:28 2018 +0100

    IdP-1206 Resolver Config: Optional filtering of null values after resolution
    
    https://issues.shibboleth.net/jira/browse/IDP-1206
    
    Make the AttributeResolverServiceStrategy configurable as whether to strip
    nulls from the output of the service.
---
 .../impl/AttributeResolverServiceStrategy.java     |  31 +++++--
 .../resolver/spring/AttributeResolverTest.java     | 101 ++++++++++++---------
 .../spring/dc/StaticDataConnectorParserTest.java   |   8 +-
 .../resolver/spring/dc/staticAttributesNative.xml  |   5 +-
 .../attribute/resolver/spring/serviceNullStrip.xml |  51 +++++++++++
 5 files changed, 144 insertions(+), 52 deletions(-)

diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverServiceStrategy.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverServiceStrategy.java
index ed42783..9ea9af3 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverServiceStrategy.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverServiceStrategy.java
@@ -22,6 +22,12 @@ import java.util.Collection;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+
+import com.google.common.base.Function;
+
 import net.shibboleth.idp.attribute.resolver.AttributeDefinition;
 import net.shibboleth.idp.attribute.resolver.AttributeResolver;
 import net.shibboleth.idp.attribute.resolver.DataConnector;
@@ -33,12 +39,6 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import net.shibboleth.utilities.java.support.service.ServiceException;
 import net.shibboleth.utilities.java.support.service.ServiceableComponent;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationContext;
-
-import com.google.common.base.Function;
-
 /**
  * Strategy for summoning up an {@link AttributeResolverImpl} from a populated {@link ApplicationContext}. We do this by
  * finding all the configured {@link AttributeDefinition}, {@link DataConnector} and {@link PrincipalConnector} beans
@@ -49,6 +49,24 @@ public class AttributeResolverServiceStrategy extends AbstractIdentifiableInitia
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeResolverServiceStrategy.class);
+    
+    /** Whether to strip null attribute values. */
+    private boolean stripNulls;
+    
+    /** Do we strip nulls from attribute values.
+    * @return Returns whether to strip nulls from attribute values
+    */
+   public boolean isStripNulls() {
+       return stripNulls;
+   }
+
+   /** 
+    * Sets whether to strip nulls from attribute values.
+    * @param doStripNulls what to set 
+    */
+   public void setStripNulls(final Boolean doStripNulls) {
+       stripNulls = doStripNulls;
+   }
 
     /** {@inheritDoc} */
     @Override @Nullable public ServiceableComponent<AttributeResolver> apply(
@@ -71,6 +89,7 @@ public class AttributeResolverServiceStrategy extends AbstractIdentifiableInitia
         resolver.setDataConnectors(connectors);
         resolver.setPrincipalDecoder(pcc);
         resolver.setId(getId());
+        resolver.setStripNulls(isStripNulls());
         resolver.setApplicationContext(appContext);
 
         try {
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/AttributeResolverTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/AttributeResolverTest.java
index 050107d..4b56df1 100644
--- a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/AttributeResolverTest.java
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/AttributeResolverTest.java
@@ -24,25 +24,6 @@ import javax.annotation.Nullable;
 import javax.security.auth.Subject;
 import javax.sql.DataSource;
 
-import net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor;
-import net.shibboleth.ext.spring.util.SchemaTypeAwareXMLBeanDefinitionReader;
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.IdPAttributeValue;
-import net.shibboleth.idp.attribute.StringAttributeValue;
-import net.shibboleth.idp.attribute.resolver.AttributeResolver;
-import net.shibboleth.idp.attribute.resolver.LegacyPrincipalDecoder;
-import net.shibboleth.idp.attribute.resolver.ResolutionException;
-import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
-import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
-import net.shibboleth.idp.saml.authn.principal.NameIDPrincipal;
-import net.shibboleth.idp.saml.impl.TestSources;
-import net.shibboleth.idp.testing.DatabaseTestingSupport;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.service.ReloadableService;
-import net.shibboleth.utilities.java.support.service.ServiceException;
-import net.shibboleth.utilities.java.support.service.ServiceableComponent;
-
 import org.opensaml.core.OpenSAMLInitBaseTestCase;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.saml.saml2.core.NameID;
@@ -64,6 +45,25 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
 import com.unboundid.ldap.listener.InMemoryListenerConfig;
 import com.unboundid.ldap.sdk.LDAPException;
 
+import net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor;
+import net.shibboleth.ext.spring.util.SchemaTypeAwareXMLBeanDefinitionReader;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.resolver.LegacyPrincipalDecoder;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.idp.saml.authn.principal.NameIDPrincipal;
+import net.shibboleth.idp.saml.impl.TestSources;
+import net.shibboleth.idp.testing.DatabaseTestingSupport;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceException;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
 /** A work in progress to test the attribute resolver service. */
 
 public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
@@ -95,7 +95,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
         pendingTeardownContext = null;
     }
     
-    protected void setTestContext(GenericApplicationContext context) {
+    protected void setTestContext(final GenericApplicationContext context) {
         tearDownTestContext();
         pendingTeardownContext = context;
     }
@@ -103,7 +103,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
     @BeforeTest public void setupDataConnectors() throws LDAPException {
 
         // LDAP
-        InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=shibboleth,dc=net");
+        final InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=shibboleth,dc=net");
         config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", 10391));
         config.addAdditionalBindCredentials("cn=Directory Manager", "password");
         directoryServer = new InMemoryDirectoryServer(config);
@@ -123,13 +123,13 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
         directoryServer.shutDown(true);
     }
     
-    private ReloadableService<AttributeResolver> getResolver(String file) {
-        GenericApplicationContext context = new GenericApplicationContext();
+    private ReloadableService<AttributeResolver> getResolver(final String file) {
+        final GenericApplicationContext context = new GenericApplicationContext();
         context.getBeanFactory().addBeanPostProcessor(new IdentifiableBeanPostProcessor());
         setTestContext(context);
         context.setDisplayName("ApplicationContext: " + AttributeResolverTest.class);
 
-        SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
+        final SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
                 new SchemaTypeAwareXMLBeanDefinitionReader(context);
 
         beanDefinitionReader.loadBeanDefinitions(file);
@@ -139,10 +139,28 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
 
         
     }
+    
+    @Test public void service() throws ComponentInitializationException, ServiceException, ResolutionException {
+        helper(true);
+    }
+    
+    @Test public void serviceNullStrip() throws ComponentInitializationException, ServiceException, ResolutionException {
+        helper(false);
+    }
+
+    private void helper(final boolean stripNulls) throws ComponentInitializationException, ServiceException, ResolutionException {
 
-    @Test public void one() throws ComponentInitializationException, ServiceException, ResolutionException {
+        final String inputFile;
+        final int expectedEPAValues;
+        if (stripNulls) {
+            inputFile = "net/shibboleth/idp/attribute/resolver/spring/serviceNullStrip.xml";
+            expectedEPAValues = 1;
+        } else {
+            inputFile ="net/shibboleth/idp/attribute/resolver/spring/service.xml";
+            expectedEPAValues = 2;
+        }
 
-        final ReloadableService<AttributeResolver> attributeResolverService = getResolver("net/shibboleth/idp/attribute/resolver/spring/service.xml");
+        final ReloadableService<AttributeResolver> attributeResolverService = getResolver(inputFile);
 
         attributeResolverService.initialize();
 
@@ -162,7 +180,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
             }
         }
 
-        Map<String, IdPAttribute> resolvedAttributes = resolutionContext.getResolvedIdPAttributes();
+        final Map<String, IdPAttribute> resolvedAttributes = resolutionContext.getResolvedIdPAttributes();
         log.debug("resolved attributes '{}'", resolvedAttributes);
 
         Assert.assertEquals(resolvedAttributes.size(), 15);
@@ -171,14 +189,15 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
         IdPAttribute attribute = resolvedAttributes.get("eduPersonAffiliation");
         Assert.assertNotNull(attribute);
         List<IdPAttributeValue<?>> values = attribute.getValues();
-        Assert.assertEquals(values.size(), 1);
+        
+        Assert.assertEquals(values.size(), expectedEPAValues);
         Assert.assertTrue(values.contains(new StringAttributeValue("member")));
         
         // Broken (case 665)
         attribute =  resolvedAttributes.get("broken");
-        Assert.assertEquals(attribute.getValues().size(), 3);
+        Assert.assertEquals(attribute.getValues().size(), 2+expectedEPAValues);
         attribute =  resolvedAttributes.get("broken2");
-        Assert.assertEquals(attribute.getValues().size(), 3);
+        Assert.assertEquals(attribute.getValues().size(), 2+expectedEPAValues);
         
 
         // LDAP
@@ -257,7 +276,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
         final Subject subject = new Subject();
         subject.getPrincipals().add(new NameIDPrincipal(nameId));
 
-        SubjectCanonicalizationContext ctx = new SubjectCanonicalizationContext();
+        final SubjectCanonicalizationContext ctx = new SubjectCanonicalizationContext();
         ctx.setSubject(subject);
         ctx.setRequesterId("REQ");
         ctx.setResponderId("RES");
@@ -266,7 +285,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
             serviceableComponent = attributeResolverService.getServiceableComponent();
 
             final AttributeResolver resolver = serviceableComponent.getComponent();
-            LegacyPrincipalDecoder decoder = (LegacyPrincipalDecoder) resolver;
+            final LegacyPrincipalDecoder decoder = (LegacyPrincipalDecoder) resolver;
             Assert.assertTrue(decoder.hasValidConnectors());
             Assert.assertEquals(decoder.canonicalize(ctx), "MyHovercraftIsFullOfEels");
 
@@ -299,7 +318,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
             }
         }
 
-        Map<String, IdPAttribute> resolvedAttributes = resolutionContext.getResolvedIdPAttributes();
+        final Map<String, IdPAttribute> resolvedAttributes = resolutionContext.getResolvedIdPAttributes();
         log.debug("output {}", resolvedAttributes);
         Assert.assertEquals(resolvedAttributes.get("testing").getValues().size(), 2);
     }
@@ -329,7 +348,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
         final Subject subject = new Subject();
         subject.getPrincipals().add(new NameIDPrincipal(nameId));
 
-        SubjectCanonicalizationContext ctx = new SubjectCanonicalizationContext();
+        final SubjectCanonicalizationContext ctx = new SubjectCanonicalizationContext();
         ctx.setSubject(subject);
         ctx.setRequesterId("REQ");
         ctx.setResponderId("RES");
@@ -338,7 +357,7 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
             serviceableComponent = attributeResolverService.getServiceableComponent();
 
             final AttributeResolver resolver = serviceableComponent.getComponent();
-            LegacyPrincipalDecoder decoder = (LegacyPrincipalDecoder) resolver;
+            final LegacyPrincipalDecoder decoder = (LegacyPrincipalDecoder) resolver;
             Assert.assertFalse(decoder.hasValidConnectors());
             Assert.assertNull(decoder.canonicalize(ctx));
 
@@ -350,11 +369,11 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
     }
 
     @Test public void selective() throws ResolutionException {
-        GenericApplicationContext context = new GenericApplicationContext();
+        final GenericApplicationContext context = new GenericApplicationContext();
         setTestContext(context);
         context.setDisplayName("ApplicationContext: " + AttributeResolverTest.class);
 
-        SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
+        final SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
                 new SchemaTypeAwareXMLBeanDefinitionReader(context);
 
         beanDefinitionReader.loadBeanDefinitions(new ClassPathResource(
@@ -382,11 +401,11 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
     }
     
     @Test public void selectiveNavigate() throws ResolutionException {
-        GenericApplicationContext context = new GenericApplicationContext();
+        final GenericApplicationContext context = new GenericApplicationContext();
         setTestContext(context);
         context.setDisplayName("ApplicationContext: " + AttributeResolverTest.class);
 
-        SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
+        final SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
                 new SchemaTypeAwareXMLBeanDefinitionReader(context);
 
         beanDefinitionReader.loadBeanDefinitions(new ClassPathResource(
@@ -449,13 +468,13 @@ public class AttributeResolverTest extends OpenSAMLInitBaseTestCase {
         
         private final Function<ProfileRequestContext, String> navigate; 
 
-        public TestPredicate(Function<ProfileRequestContext, String> profileFinder, String compare) {
+        public TestPredicate(final Function<ProfileRequestContext, String> profileFinder, final String compare) {
             value = Constraint.isNotNull(compare, "provided compare name must not be null");
             navigate = Constraint.isNotNull(profileFinder, "provided prinicpal locator must not be null");
         }
 
         /** {@inheritDoc} */
-        @Override public boolean apply(@Nullable ProfileRequestContext input) {
+        @Override public boolean apply(@Nullable final ProfileRequestContext input) {
             return value.equals(navigate.apply(input));
         }
     }
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StaticDataConnectorParserTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StaticDataConnectorParserTest.java
index 6407017..06f9c35 100644
--- a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StaticDataConnectorParserTest.java
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/StaticDataConnectorParserTest.java
@@ -19,6 +19,9 @@ package net.shibboleth.idp.attribute.resolver.spring.dc;
 
 import java.util.List;
 
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.StringAttributeValue;
@@ -26,9 +29,6 @@ import net.shibboleth.idp.attribute.resolver.dc.impl.StaticDataConnector;
 import net.shibboleth.idp.attribute.resolver.spring.BaseAttributeDefinitionParserTest;
 import net.shibboleth.idp.attribute.resolver.spring.dc.impl.StaticDataConnectorParser;
 
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 /**
  * test for {@link StaticDataConnectorParser}
  */
@@ -106,7 +106,7 @@ public class StaticDataConnectorParserTest extends BaseAttributeDefinitionParser
         Assert.assertTrue(values.contains(new StringAttributeValue("urn:mace:dir:entitlement:common-lib-terms")));
         
         values = connector.getAttributes().get("staticEpA").getValues();
-        Assert.assertEquals(values.size(), 1);
+        Assert.assertEquals(values.size(), 2);
         Assert.assertTrue(values.contains(new StringAttributeValue("member")));
     }
 }
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/staticAttributesNative.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/staticAttributesNative.xml
index 6b09568..722b4dd 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/staticAttributesNative.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/staticAttributesNative.xml
@@ -12,7 +12,10 @@
             <list>
                 <bean class="net.shibboleth.idp.attribute.IdPAttribute" c:attributeId="staticEpA">
                     <property name="values">
-                        <bean class="net.shibboleth.idp.attribute.StringAttributeValue" c:attributeValue="member" />
+                        <list>
+                            <bean class="net.shibboleth.idp.attribute.StringAttributeValue" c:attributeValue="member" />
+                            <bean factory-method="valueOf" class="net.shibboleth.idp.attribute.StringAttributeValue" c:value="" />
+                        </list>
                     </property>
                 </bean>
                 <bean class="net.shibboleth.idp.attribute.IdPAttribute" c:attributeId="eduPersonEntitlement">
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/serviceNullStrip.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/serviceNullStrip.xml
new file mode 100644
index 0000000..25fca0a
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/serviceNullStrip.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xmlns:util="http://www.springframework.org/schema/util"
+        xmlns:p="http://www.springframework.org/schema/p"
+        xmlns:c="http://www.springframework.org/schema/c"
+        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+        default-init-method="initialize"
+        default-destroy-method="destroy"
+                            >
+                            
+    <!-- This BeanPostProcessor auto-sets identifiable beans with the bean name (if not already set). -->
+    <bean id="shibboleth.IdentifiableBeanPostProcessor"
+        class="net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor" />     
+                            
+    <bean id="shibboleth.VelocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
+        <property name="velocityProperties">
+            <props>
+                <prop key="resource.loader">classpath, string</prop>
+                <prop key="classpath.resource.loader.class">
+                    org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+                </prop>
+                <prop key="string.resource.loader.class">
+                    org.apache.velocity.runtime.resource.loader.StringResourceLoader
+                </prop>
+            </props>
+        </property>
+    </bean>
+        <bean id="shibboleth.AttributeResolverService" class="net.shibboleth.ext.spring.service.ReloadableSpringService"
+        depends-on="shibboleth.VelocityEngine"
+        p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
+        p:failFast="false" p:reloadCheckDelay="0">
+        
+        <constructor-arg name="claz"
+            value="net.shibboleth.idp.attribute.resolver.AttributeResolver" />
+        <constructor-arg name="strategy">
+            <bean
+                class="net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverServiceStrategy"
+                p:id="Shibboleth.Resolver"
+                p:stripNulls="true"/>
+        </constructor-arg>
+        <property name="serviceConfigurations">
+            <util:list>
+            	<value>net/shibboleth/idp/attribute/resolver/spring/externalBeans.xml</value>
+                <value>net/shibboleth/idp/attribute/resolver/spring/storageService.xml</value>
+                <value>net/shibboleth/idp/attribute/resolver/spring/attribute-resolver.xml</value>
+                <value>net/shibboleth/idp/attribute/resolver/spring/dc/staticAttributesNative.xml</value>
+            </util:list>
+        </property>
+    </bean>
+</beans>
\ 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