This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit ce3820d0ef1f42ddfdc4f84fb0469902f34d179c
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri Jun 28 14:42:35 2024 +0200

    JAMES-4044 Implement IsInLDAPGroup
---
 docs/modules/servers/partials/LDAPMatchers.adoc    |  10 ++
 server/mailet/ldap/README.md                       |   9 ++
 .../james/transport/matchers/IsInLDAPGroup.java    |  99 +++++++++++++++
 .../transport/matchers/IsInLDAPGroupTest.java      | 138 +++++++++++++++++++++
 .../src/test/resources/ldif-files/populate.ldif    |  13 ++
 5 files changed, 269 insertions(+)

diff --git a/docs/modules/servers/partials/LDAPMatchers.adoc 
b/docs/modules/servers/partials/LDAPMatchers.adoc
index be110817bf..3331358909 100644
--- a/docs/modules/servers/partials/LDAPMatchers.adoc
+++ b/docs/modules/servers/partials/LDAPMatchers.adoc
@@ -7,6 +7,7 @@ Those include:
 
  - LDAP conditions based on attributes of the recipients
  - LDAP conditions based on attributes of the sender
+ - LDAP groups of the recipients
 
 Support is planned for LDAP groups both for sender and recipients.
 
@@ -61,3 +62,12 @@ or
 
 </mailet>
 ....
+
+
+For matching recipients against a specific LDAP group:
+
+....xml
+<mailet matcher="IsInLDAPGroup=cn=mygroup,ou=groups, dc=james,dc=org" 
class="Null">
+    
+</mailet>
+....
diff --git a/server/mailet/ldap/README.md b/server/mailet/ldap/README.md
index cb27980881..118261bc20 100644
--- a/server/mailet/ldap/README.md
+++ b/server/mailet/ldap/README.md
@@ -7,6 +7,7 @@ Those include:
 
  - LDAP conditions based on attributes of the recipients
  - LDAP conditions based on attributes of the sender
+ - LDAP groups of the recipients
 
 Support is planned for LDAP groups both for sender and recipients.
 
@@ -61,3 +62,11 @@ or
     
 </mailet>
 ```
+
+For matching recipients against a specific LDAP group:
+
+```xml
+<mailet matcher="IsInLDAPGroup=cn=mygroup,ou=groups, dc=james,dc=org" 
class="Null">
+    
+</mailet>
+```
diff --git 
a/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/IsInLDAPGroup.java
 
b/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/IsInLDAPGroup.java
new file mode 100644
index 0000000000..65bf989c1b
--- /dev/null
+++ 
b/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/IsInLDAPGroup.java
@@ -0,0 +1,99 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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 org.apache.james.transport.matchers;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.user.ldap.LDAPConnectionFactory;
+import org.apache.james.user.ldap.LdapRepositoryConfiguration;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMatcher;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.unboundid.ldap.sdk.Attribute;
+import com.unboundid.ldap.sdk.LDAPConnectionPool;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+import jakarta.inject.Inject;
+import jakarta.mail.MessagingException;
+
+/**
+ * Matchers that allow looking up for LDAP group membership for each recipient.
+ *
+ * To enable this matcher one need first to add the 
james-server-mailet-ldap.jar in the externals-jars folder of your
+ * James installation.
+ *
+ * In order to match the group membership:
+ *
+ * <pre><code>
+ * &lt;mailet matcher=&quot;IsInLDAPGroup=cn=mygroup,ou=groups, 
dc=james,dc=org&quot; class=&quot;Null&quot;&gt;
+ *
+ * &lt;/mailet&gt;
+ * </code></pre>
+ */
+public class IsInLDAPGroup extends GenericMatcher {
+    private final LDAPConnectionPool ldapConnectionPool;
+    private final LdapRepositoryConfiguration configuration;
+    private String groupDN;
+
+    @Inject
+    public IsInLDAPGroup(LdapRepositoryConfiguration configuration) throws 
LDAPException {
+        this.configuration = configuration;
+        ldapConnectionPool = new 
LDAPConnectionFactory(this.configuration).getLdapConnectionPool();
+    }
+
+    @Override
+    public void init() {
+        groupDN = getCondition();
+    }
+
+    @Override
+    public Collection<MailAddress> match(Mail mail) {
+        return Sets.intersection(ImmutableSet.copyOf(mail.getRecipients()), 
groupMembers());
+    }
+
+    private Set<MailAddress> groupMembers() {
+        try {
+            String[] attributes = {"member"};
+            SearchResultEntry groupEntry = 
ldapConnectionPool.getEntry(groupDN, attributes);
+
+            if (groupEntry == null) {
+                return ImmutableSet.of();
+            }
+
+            return groupEntry.getAttributes().stream()
+                .filter(a -> a.getName().equals("member"))
+                .map(Attribute::getValue)
+                .map(Throwing.function(memberDn -> 
Optional.ofNullable(ldapConnectionPool.getEntry(memberDn, 
configuration.getReturnedAttributes()))))
+                .flatMap(Optional::stream)
+                .map(memberEntry -> 
memberEntry.getAttribute(configuration.getUserIdAttribute()).getValue())
+                .map(Throwing.function(MailAddress::new))
+                .collect(ImmutableSet.toImmutableSet());
+        } catch (LDAPException e) {
+            throw new RuntimeException("Failed searching LDAP", e);
+        }
+    }
+}
diff --git 
a/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/IsInLDAPGroupTest.java
 
b/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/IsInLDAPGroupTest.java
new file mode 100644
index 0000000000..c599b30cf7
--- /dev/null
+++ 
b/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/IsInLDAPGroupTest.java
@@ -0,0 +1,138 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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 org.apache.james.transport.matchers;
+
+import static org.apache.james.user.ldap.DockerLdapSingleton.ADMIN;
+import static org.apache.james.user.ldap.DockerLdapSingleton.ADMIN_PASSWORD;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.plist.PropertyListConfiguration;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.user.ldap.DockerLdapSingleton;
+import org.apache.james.user.ldap.LdapGenericContainer;
+import org.apache.james.user.ldap.LdapRepositoryConfiguration;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMatcherConfig;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import jakarta.mail.MessagingException;
+
+class IsInLDAPGroupTest {
+
+    static LdapGenericContainer ldapContainer = 
DockerLdapSingleton.ldapContainer;
+
+    @BeforeAll
+    static void setUpAll() {
+        ldapContainer.start();
+    }
+
+    @AfterAll
+    static void afterAll() {
+        ldapContainer.stop();
+    }
+
+    @Test
+    void shouldMatchGroupMember() throws Exception {
+        IsInLDAPGroup testee = new 
IsInLDAPGroup(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer)));
+        FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder()
+            .matcherName("IsInLDAPGroup")
+            .condition("cn=mygroup,ou=groups, dc=james,dc=org")
+            .build();
+        testee.init(matcherConfig);
+
+        MailAddress recipient = new MailAddress("[email protected]");
+        Collection<MailAddress> matched = testee.match(FakeMail.builder()
+            .name("default-id")
+            .recipient(recipient)
+            .build());
+
+        assertThat(matched).containsOnly(recipient);
+    }
+
+    @Test
+    void shouldNotMatchNotGroupMember() throws Exception {
+        IsInLDAPGroup testee = new 
IsInLDAPGroup(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer)));
+        FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder()
+            .matcherName("IsInLDAPGroup")
+            .condition("cn=mygroup,ou=groups, dc=james,dc=org")
+            .build();
+        testee.init(matcherConfig);
+
+        MailAddress recipient = new MailAddress("[email protected]");
+        Collection<MailAddress> matched = testee.match(FakeMail.builder()
+            .name("default-id")
+            .recipient(recipient)
+            .build());
+
+        assertThat(matched).isEmpty();
+    }
+
+    @Test
+    void shouldNotMatchWhenGroupNotFound() throws Exception {
+        IsInLDAPGroup testee = new 
IsInLDAPGroup(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer)));
+        FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder()
+            .matcherName("IsInLDAPGroup")
+            .condition("cn=notfound,ou=groups, dc=james,dc=org")
+            .build();
+        testee.init(matcherConfig);
+
+        MailAddress recipient = new MailAddress("[email protected]");
+        Collection<MailAddress> matched = testee.match(FakeMail.builder()
+            .name("default-id")
+            .recipient(recipient)
+            .build());
+
+        assertThat(matched).isEmpty();
+    }
+
+    static HierarchicalConfiguration<ImmutableNode> 
ldapRepositoryConfigurationWithVirtualHosting(LdapGenericContainer 
ldapContainer) {
+        return ldapRepositoryConfigurationWithVirtualHosting(ldapContainer, 
Optional.of(ADMIN));
+    }
+
+    static HierarchicalConfiguration<ImmutableNode> 
ldapRepositoryConfigurationWithVirtualHosting(LdapGenericContainer 
ldapContainer, Optional<Username> administrator) {
+        PropertyListConfiguration configuration = 
baseConfiguration(ldapContainer);
+        configuration.addProperty("[@userIdAttribute]", "mail");
+        configuration.addProperty("supportsVirtualHosting", true);
+        administrator.ifPresent(username -> 
configuration.addProperty("[@administratorId]", username.asString()));
+        return configuration;
+    }
+
+    private static PropertyListConfiguration 
baseConfiguration(LdapGenericContainer ldapContainer) {
+        PropertyListConfiguration configuration = new 
PropertyListConfiguration();
+        configuration.addProperty("[@ldapHost]", ldapContainer.getLdapHost());
+        configuration.addProperty("[@principal]", "cn=admin,dc=james,dc=org");
+        configuration.addProperty("[@credentials]", ADMIN_PASSWORD);
+        configuration.addProperty("[@userBase]", "ou=people,dc=james,dc=org");
+        configuration.addProperty("[@userObjectClass]", "inetOrgPerson");
+        configuration.addProperty("[@connectionTimeout]", "2000");
+        configuration.addProperty("[@readTimeout]", "2000");
+        return configuration;
+    }
+}
\ No newline at end of file
diff --git a/server/mailet/ldap/src/test/resources/ldif-files/populate.ldif 
b/server/mailet/ldap/src/test/resources/ldif-files/populate.ldif
index b67e297117..8284b64424 100644
--- a/server/mailet/ldap/src/test/resources/ldif-files/populate.ldif
+++ b/server/mailet/ldap/src/test/resources/ldif-files/populate.ldif
@@ -6,6 +6,10 @@ dn: ou=empty, dc=james,dc=org
 ou: empty
 objectClass: organizationalUnit
 
+dn: ou=groups, dc=james,dc=org
+ou: groups
+objectClass: organizationalUnit
+
 dn: ou=whatever, dc=james,dc=org
 ou: whatever
 objectClass: organizationalUnit
@@ -27,3 +31,12 @@ sn: bob
 mail: [email protected]
 userPassword: secret
 description: Extra user
+
+dn: cn=mygroup,ou=groups, dc=james,dc=org
+objectclass: top
+objectclass: groupofnames
+member: uid=james-user,ou=people,dc=james,dc=org
+member: uid=not-found,ou=people,dc=james,dc=org
+cn: mygroup
+ou: groups
+


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to