[java-identity-provider] 09/13: IDP-2279 - incorrect filter template encoding

Scott Cantor cantor.2 at osu.edu
Thu Jul 25 18:22:32 UTC 2024


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

scantor pushed a commit to branch maint-5.1
in repository java-identity-provider.

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

commit a14fac33cf3dfe3975b0f413b63d3d3fdb875a64
Author: Daniel Fisher <dfisher at vt.edu>
AuthorDate: Sun Jun 16 09:47:38 2024 -0400

    IDP-2279 - incorrect filter template encoding
    
    Change filter template encoding from US_ASCII to UTF_8.
    Add unit tests for multi-byte characters in the filter.
    
    https://shibboleth.atlassian.net/browse/IDP-2279
---
 .../authn/AbstractTemplateSearchDnResolver.java    |   5 +-
 .../authn/impl/LDAPCredentialValidatorTest.java    | 296 ++++++++++++---------
 .../shibboleth/idp/authn/impl/loginLDAPTest.ldif   |  17 ++
 3 files changed, 185 insertions(+), 133 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
index 40e60b958..ec09a4fbd 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.authn;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -43,7 +44,7 @@ public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver
     @Nonnull private final Template template;
 
     /** Event handler used for escaping. */
-    private ReferenceInsertionEventHandler eventHandler = new EscapingReferenceInsertionEventHandler();
+    private final ReferenceInsertionEventHandler eventHandler = new EscapingReferenceInsertionEventHandler();
 
     /**
      * Creates a new abstract template search DN resolver.
@@ -55,7 +56,7 @@ public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver
      */
     public AbstractTemplateSearchDnResolver(@Nonnull final VelocityEngine engine,
             @Nonnull @NotEmpty final String filter) throws VelocityException {
-        template = Template.fromTemplate(engine, filter);
+        template = Template.fromTemplate(engine, filter, StandardCharsets.UTF_8);
         setUserFilter(filter);
     }
 
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
index 57a58de5b..17d2ea2b4 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
@@ -437,30 +437,14 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         doExtract();
 
         final Event event = action.execute(src);
-        AuthenticationResult result = ac.getAuthenticationResult();
-        LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        assert lrc != null;
-        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
-        assert lar != null;
-        Assert.assertEquals(lar.getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
 
-        final AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertNull(aec);
-        final AuthenticationWarningContext awc = ac.getSubcontext(AuthenticationWarningContext.class);
-        assert awc != null && result != null;
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
 
+        final AuthenticationWarningContext awc = ac.getSubcontext(AuthenticationWarningContext.class);
+        assert awc != null;
         ActionTestingSupport.assertEvent(event, "ExpiredPassword");
         Assert.assertEquals(awc.getClassifiedWarnings().size(), 1);
         Assert.assertTrue(awc.isClassifiedWarning("ExpiredPassword"));
-
-        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
-        Assert.assertNotNull(up);
-        Assert.assertEquals(up.getName(), "PETER_THE_PRINCIPAL");
-        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
-        Assert.assertNotNull(lp);
-        Assert.assertEquals(lp.getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertNotNull(lp.getLdapEntry());
     }
 
     @Test public void testExpiringPassword() throws ComponentInitializationException {
@@ -484,32 +468,13 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
         final Event event = action.execute(src);
 
-        final AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertNull(aec);
-
-        final AuthenticationResult result = ac.getAuthenticationResult();
-        Assert.assertNotNull(result);
-        final LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        assert lrc != null;
-        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
-        assert lar != null;
-        Assert.assertEquals(lar.getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
 
         ActionTestingSupport.assertEvent(event, "ExpiringPassword");
-
         final AuthenticationWarningContext awc = ac.getSubcontext(AuthenticationWarningContext.class);
-        assert awc != null && result != null;
+        assert awc != null;
         Assert.assertEquals(awc.getClassifiedWarnings().size(), 1);
         Assert.assertTrue(awc.isClassifiedWarning("ExpiringPassword"));
-
-        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
-        Assert.assertNotNull(up);
-        Assert.assertEquals(up.getName(), "PETER_THE_PRINCIPAL");
-        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
-        Assert.assertNotNull(lp);
-        Assert.assertEquals(lp.getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertNotNull(lp.getLdapEntry());
     }
 
     @Test public void testAuthorized() throws ComponentInitializationException {
@@ -529,26 +494,8 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
-        
-        final AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertNull(aec);
-
-        final AuthenticationResult result = ac.getAuthenticationResult();
-        assert result != null;
-        final LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        assert lrc != null;
-        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
-        assert lar != null;
-        Assert.assertEquals(lar.getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
 
-        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
-        Assert.assertNotNull(up);
-        Assert.assertEquals(up.getName(), "PETER_THE_PRINCIPAL");
-        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
-        Assert.assertNotNull(lp);
-        Assert.assertEquals(lp.getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertNotNull(lp.getLdapEntry());
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
     }
 
     @Test public void testComputedAndAuthorized() throws ComponentInitializationException {
@@ -578,26 +525,8 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
-        
-        final AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertNull(aec);
 
-        final AuthenticationResult result = ac.getAuthenticationResult();
-        assert result != null;
-        final LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        assert lrc != null;
-        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
-        assert lar != null;
-        Assert.assertEquals(lar.getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
-
-        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
-        Assert.assertNotNull(up);
-        Assert.assertEquals(up.getName(), "PETER_THE_PRINCIPAL");
-        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
-        Assert.assertNotNull(lp);
-        Assert.assertEquals(lp.getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertNotNull(lp.getLdapEntry());
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
     }
 
     @Test public void testDefaultFilterSyntax() throws ComponentInitializationException {
@@ -623,28 +552,92 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
 
-        final AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertNull(aec);
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
+    }
 
-        final AuthenticationResult result = ac.getAuthenticationResult();
-        assert result != null;
-        final LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        assert lrc != null;
-        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
-        assert lar != null;
-        Assert.assertEquals(lar.getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
+    @Test public void testDefaultFilterSyntaxMultiByte() throws ComponentInitializationException {
+        final TemplateSearchDnResolver testResolver = new TemplateSearchDnResolver(
+          new DefaultConnectionFactory("ldap://localhost:10389"), VelocityEngine.newVelocityEngine(), "(uid={user})");
+        testResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
 
-        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
-        Assert.assertNotNull(up);
-        Assert.assertEquals(up.getName(), "PETER_THE_PRINCIPAL");
-        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
-        Assert.assertNotNull(lp);
-        Assert.assertEquals(lp.getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertNotNull(lp.getLdapEntry());
+
+        final Authenticator defaultFilterAuthenticator = new Authenticator(testResolver, authHandler);
+        getMockHttpServletRequest(action).addParameter("username", "RAPHAËL_WEIß");
+        getMockHttpServletRequest(action).addParameter("password", "changeit");
+
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+        assert ac != null;
+        ac.setAttemptedFlow(authenticationFlows.get(0));
+        validator.setAuthenticator(defaultFilterAuthenticator);
+        validator.initialize();
+
+        action.initialize();
+
+        doExtract();
+
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+
+        assertAuthSuccess(ac, "RAPHAËL_WEIß");
+    }
+
+    @Test public void testVelocityFilterSyntax() throws ComponentInitializationException {
+        final TemplateSearchDnResolver testResolver = new TemplateSearchDnResolver(
+          new DefaultConnectionFactory("ldap://localhost:10389"),
+          VelocityEngine.newVelocityEngine(),
+          "(uid=$usernamePasswordContext.username)");
+        testResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
+
+
+        final Authenticator defaultFilterAuthenticator = new Authenticator(testResolver, authHandler);
+        getMockHttpServletRequest(action).addParameter("username", "PETER_THE_PRINCIPAL");
+        getMockHttpServletRequest(action).addParameter("password", "changeit");
+
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+        assert ac != null;
+        ac.setAttemptedFlow(authenticationFlows.get(0));
+        validator.setAuthenticator(defaultFilterAuthenticator);
+        validator.initialize();
+
+        action.initialize();
+
+        doExtract();
+
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
+    }
+
+    @Test public void testVelocityFilterSyntaxMultiByte() throws ComponentInitializationException {
+        final TemplateSearchDnResolver testResolver = new TemplateSearchDnResolver(
+          new DefaultConnectionFactory("ldap://localhost:10389"),
+          VelocityEngine.newVelocityEngine(),
+          "(uid=$usernamePasswordContext.username)");
+        testResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
+
+
+        final Authenticator defaultFilterAuthenticator = new Authenticator(testResolver, authHandler);
+        getMockHttpServletRequest(action).addParameter("username", "RAPHAËL_WEIß");
+        getMockHttpServletRequest(action).addParameter("password", "changeit");
+
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+        assert ac != null;
+        ac.setAttemptedFlow(authenticationFlows.get(0));
+        validator.setAuthenticator(defaultFilterAuthenticator);
+        validator.initialize();
+
+        action.initialize();
+
+        doExtract();
+
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+
+        assertAuthSuccess(ac, "RAPHAËL_WEIß");
     }
 
-    @Test public void testCombinedFilterSyntax() throws ComponentInitializationException {
+    @Test public void testCombinedVelocityFilterSyntax() throws ComponentInitializationException {
         final TemplateSearchDnResolver testResolver = new TemplateSearchDnResolver(
                 new DefaultConnectionFactory("ldap://localhost:10389"),
                 VelocityEngine.newVelocityEngine(), "(|(mail=$usernamePasswordContext.username)(uid={user}))");
@@ -668,25 +661,62 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
 
-        final AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertNull(aec);
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
+    }
 
-        final AuthenticationResult result = ac.getAuthenticationResult();
-        assert result != null;
-        final LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        assert lrc != null;
-        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
-        assert lar != null;
-        Assert.assertEquals(lar.getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
+    @Test public void testCombinedVelocityFilterSyntaxMultiByte() throws ComponentInitializationException {
+        final TemplateSearchDnResolver testResolver = new TemplateSearchDnResolver(
+          new DefaultConnectionFactory("ldap://localhost:10389"),
+          VelocityEngine.newVelocityEngine(), "(&(mail=$usernamePasswordContext.username)(description=März Äpfel))");
+        testResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
 
-        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
-        Assert.assertNotNull(up);
-        Assert.assertEquals(up.getName(), "PETER_THE_PRINCIPAL");
-        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
-        Assert.assertNotNull(lp);
-        Assert.assertEquals(lp.getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertNotNull(lp.getLdapEntry());
+
+        final Authenticator defaultFilterAuthenticator = new Authenticator(testResolver, authHandler);
+        getMockHttpServletRequest(action).addParameter("username", "raphaël.weiß@shibboleth.net");
+        getMockHttpServletRequest(action).addParameter("password", "changeit");
+
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+        assert ac != null;
+        ac.setAttemptedFlow(authenticationFlows.get(0));
+        validator.setAuthenticator(defaultFilterAuthenticator);
+        validator.initialize();
+
+        action.initialize();
+
+        doExtract();
+
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+
+        assertAuthSuccess(ac, "raphaël.weiß@shibboleth.net");
+    }
+
+    @Test public void testCombinedVelocityFilterSyntaxMultiByteEscaped() throws ComponentInitializationException {
+        final TemplateSearchDnResolver testResolver = new TemplateSearchDnResolver(
+          new DefaultConnectionFactory("ldap://localhost:10389"),
+          VelocityEngine.newVelocityEngine(),
+          "(&(uid=$usernamePasswordContext.username)(description=M\\C3\\A4rz \\C3\\84pfel))");
+        testResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
+
+
+        final Authenticator defaultFilterAuthenticator = new Authenticator(testResolver, authHandler);
+        getMockHttpServletRequest(action).addParameter("username", "RAPHAËL_WEIß");
+        getMockHttpServletRequest(action).addParameter("password", "changeit");
+
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+        assert ac != null;
+        ac.setAttemptedFlow(authenticationFlows.get(0));
+        validator.setAuthenticator(defaultFilterAuthenticator);
+        validator.initialize();
+
+        action.initialize();
+
+        doExtract();
+
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+
+        assertAuthSuccess(ac, "RAPHAËL_WEIß");
     }
 
     @Test public void testMatchAndAuthorized() throws ComponentInitializationException {
@@ -708,24 +738,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
         
-        final AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertNull(aec);
-        final AuthenticationResult result = ac.getAuthenticationResult();
-        assert result != null ;
-        final LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        assert lrc != null;
-        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
-        assert lar != null;
-        Assert.assertEquals(lar.getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
-
-        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
-        Assert.assertNotNull(up);
-        Assert.assertEquals(up.getName(), "PETER_THE_PRINCIPAL");
-        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
-        Assert.assertNotNull(lp);
-        Assert.assertEquals(lp.getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertNotNull(lp.getLdapEntry());
+        assertAuthSuccess(ac, "PETER_THE_PRINCIPAL");
     }
 
     @Test public void testAuthorizedAndKeepContext() throws ComponentInitializationException {
@@ -747,6 +760,27 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ActionTestingSupport.assertProceedEvent(event);
     }
 
+    private void assertAuthSuccess(final AuthenticationContext authContext, final String principalName) {
+        final AuthenticationErrorContext aec = authContext.getSubcontext(AuthenticationErrorContext.class);
+        Assert.assertNull(aec);
+        final AuthenticationResult result = authContext.getAuthenticationResult();
+        assert result != null;
+        final LDAPResponseContext lrc = authContext.getSubcontext(LDAPResponseContext.class);
+        assert lrc != null;
+        final AuthenticationResponse lar = lrc.getAuthenticationResponse();
+        assert lar != null;
+        Assert.assertEquals(lar.getAuthenticationResultCode(),
+          AuthenticationResultCode.AUTHENTICATION_HANDLER_SUCCESS);
+
+        final UsernamePrincipal up = result.getSubject().getPrincipals(UsernamePrincipal.class).iterator().next();
+        Assert.assertNotNull(up);
+        Assert.assertEquals(up.getName(), principalName);
+        final LdapPrincipal lp = result.getSubject().getPrincipals(LdapPrincipal.class).iterator().next();
+        Assert.assertNotNull(lp);
+        Assert.assertEquals(lp.getName(), principalName);
+        Assert.assertNotNull(lp.getLdapEntry());
+    }
+
     private void doExtract() throws ComponentInitializationException {
         final ExtractUsernamePasswordFromFormRequest extract = new ExtractUsernamePasswordFromFormRequest();
         extract.setHttpServletRequestSupplier(action.getHttpServletRequestSupplier());
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/loginLDAPTest.ldif b/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/loginLDAPTest.ldif
index 7c48f70cd..335ca43b4 100644
--- a/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/loginLDAPTest.ldif
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/loginLDAPTest.ldif
@@ -33,3 +33,20 @@ mail: paul.principal at shibboleth.net
 mail: paulprincipal at shibboleth.net
 description: test principal
 
+# dn: cn=Raphaël Weiß,ou=people,dc=shibboleth,dc=net
+dn:: Y249UmFwaGHDq2wgV2Vpw58sb3U9cGVvcGxlLGRjPXNoaWJib2xldGgsZGM9bmV0
+objectclass: inetOrgPerson
+# cn: Raphaël_Weiß
+cn:: UmFwaGHDq2wgV2Vpw58=
+# sn: Weiß
+sn:: V2Vpw58=
+# uid: RAPHAËL_WEIß
+uid:: UkFQSEHDi0xfV0VJw58=
+userpassword: changeit
+homephone: 555-111-4444
+# mail: raphaël.weiß@shibboleth.net
+mail:: cmFwaGHDq2wud2Vpw59Ac2hpYmJvbGV0aC5uZXQ=
+# mail: raphaëlweiß@shibboleth.net
+mail:: cmFwaGHDq2x3ZWnDn0BzaGliYm9sZXRoLm5ldA==
+# description: März Äpfel
+description:: TcOkcnogw4RwZmVs

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


More information about the commits mailing list