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 68b4fc95f3f7403f2a3412a5678112f58f9d3689 Author: Benoit TELLIER <[email protected]> AuthorDate: Fri Jun 14 15:58:07 2024 +0200 JAMES-4044 Implement HasLDAPAttribute matcher --- .../james/user/ldap/LDAPConnectionFactory.java | 107 +++++++++++++ .../user/ldap/LdapRepositoryConfiguration.java | 8 + .../james/user/ldap/ReadOnlyLDAPUsersDAO.java | 76 +-------- server/mailet/ldap/README.md | 21 +++ server/mailet/ldap/pom.xml | 71 +++++++++ .../james/transport/matchers/HasLDAPAttribute.java | 127 +++++++++++++++ .../transport/matchers/HasLDAPAttributeTest.java | 170 +++++++++++++++++++++ .../src/test/resources/ldif-files/populate.ldif | 29 ++++ server/pom.xml | 1 + 9 files changed, 536 insertions(+), 74 deletions(-) diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LDAPConnectionFactory.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LDAPConnectionFactory.java new file mode 100644 index 0000000000..4c8f015181 --- /dev/null +++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LDAPConnectionFactory.java @@ -0,0 +1,107 @@ +/**************************************************************** + * 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.user.ldap; + +import java.net.URI; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.util.List; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import com.github.fge.lambdas.Throwing; +import com.github.fge.lambdas.functions.ThrowingFunction; +import com.google.common.collect.ImmutableList; +import com.unboundid.ldap.sdk.BindRequest; +import com.unboundid.ldap.sdk.FailoverServerSet; +import com.unboundid.ldap.sdk.LDAPConnectionOptions; +import com.unboundid.ldap.sdk.LDAPConnectionPool; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.ServerSet; +import com.unboundid.ldap.sdk.SimpleBindRequest; +import com.unboundid.ldap.sdk.SingleServerSet; + +public class LDAPConnectionFactory { + + private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + // Always trust + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + // Always trust + } + }; + + private final LdapRepositoryConfiguration configuration; + private final LDAPConnectionPool ldapConnectionPool; + + public LDAPConnectionFactory(LdapRepositoryConfiguration configuration) throws LDAPException { + this.configuration = configuration; + + LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions(); + connectionOptions.setConnectTimeoutMillis(configuration.getConnectionTimeout()); + connectionOptions.setResponseTimeoutMillis(configuration.getReadTimeout()); + + BindRequest bindRequest = new SimpleBindRequest(configuration.getPrincipal(), configuration.getCredentials()); + + List<ServerSet> serverSets = configuration.getLdapHosts() + .stream() + .map(toSingleServerSet(connectionOptions, bindRequest)) + .collect(ImmutableList.toImmutableList()); + + FailoverServerSet failoverServerSet = new FailoverServerSet(serverSets); + ldapConnectionPool = new LDAPConnectionPool(failoverServerSet, bindRequest, 4); + ldapConnectionPool.setRetryFailedOperationsDueToInvalidConnections(true); + } + + private ThrowingFunction<URI, SingleServerSet> toSingleServerSet(LDAPConnectionOptions connectionOptions, BindRequest bindRequest) { + return Throwing.function(uri -> new SingleServerSet(uri.getHost(), uri.getPort(), supportLDAPS(uri), connectionOptions, bindRequest, null)); + } + + private SocketFactory supportLDAPS(URI uri) throws KeyManagementException, NoSuchAlgorithmException { + if (uri.getScheme().equals("ldaps")) { + if (configuration.isTrustAllCerts()) { + SSLContext context = SSLContext.getInstance("TLSv1.2"); + context.init(null, new TrustManager[]{DUMMY_TRUST_MANAGER}, null); + return context.getSocketFactory(); + } + return SSLSocketFactory.getDefault(); + } else { + return null; + } + } + + public LDAPConnectionPool getLdapConnectionPool() { + return ldapConnectionPool; + } +} diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java index 33ba7aaa92..73992034cc 100644 --- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java +++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java @@ -458,6 +458,14 @@ public class LdapRepositoryConfiguration { return perDomainBaseDN; } + public String[] getReturnedAttributes() { + if (getUsernameAttribute().isPresent()) { + return new String[]{getUserIdAttribute(), getUsernameAttribute().get()}; + } else { + return new String[]{getUserIdAttribute()}; + } + } + @Override public final boolean equals(Object o) { if (o instanceof LdapRepositoryConfiguration) { diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java index 11808ae00d..52f8c2c795 100644 --- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java +++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java @@ -19,10 +19,6 @@ package org.apache.james.user.ldap; -import java.net.URI; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -32,11 +28,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; import jakarta.annotation.PreDestroy; import jakarta.inject.Inject; @@ -54,17 +45,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.fge.lambdas.Throwing; -import com.github.fge.lambdas.functions.ThrowingFunction; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.unboundid.ldap.sdk.Attribute; -import com.unboundid.ldap.sdk.BindRequest; import com.unboundid.ldap.sdk.DN; import com.unboundid.ldap.sdk.Entry; -import com.unboundid.ldap.sdk.FailoverServerSet; import com.unboundid.ldap.sdk.Filter; -import com.unboundid.ldap.sdk.LDAPConnectionOptions; import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPSearchException; @@ -72,30 +58,10 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import com.unboundid.ldap.sdk.ServerSet; -import com.unboundid.ldap.sdk.SimpleBindRequest; -import com.unboundid.ldap.sdk.SingleServerSet; public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable { private static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUsersDAO.class); - private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - // Always trust - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - // Always trust - } - }; - private LdapRepositoryConfiguration ldapConfiguration; private LDAPConnectionPool ldapConnectionPool; private Optional<Filter> userExtraFilter; @@ -140,20 +106,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable { + ldapConfiguration.getConnectionTimeout() + '\n' + "readTimeout: " + ldapConfiguration.getReadTimeout()); } - LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions(); - connectionOptions.setConnectTimeoutMillis(ldapConfiguration.getConnectionTimeout()); - connectionOptions.setResponseTimeoutMillis(ldapConfiguration.getReadTimeout()); - - BindRequest bindRequest = new SimpleBindRequest(ldapConfiguration.getPrincipal(), ldapConfiguration.getCredentials()); - - List<ServerSet> serverSets = ldapConfiguration.getLdapHosts() - .stream() - .map(toSingleServerSet(connectionOptions, bindRequest)) - .collect(ImmutableList.toImmutableList()); - - FailoverServerSet failoverServerSet = new FailoverServerSet(serverSets); - ldapConnectionPool = new LDAPConnectionPool(failoverServerSet, bindRequest, 4); - ldapConnectionPool.setRetryFailedOperationsDueToInvalidConnections(true); + ldapConnectionPool = new LDAPConnectionFactory(ldapConfiguration).getLdapConnectionPool(); userExtraFilter = Optional.ofNullable(ldapConfiguration.getFilter()) .map(Throwing.function(Filter::create).sneakyThrow()); @@ -166,23 +119,6 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable { } } - private SocketFactory supportLDAPS(URI uri) throws KeyManagementException, NoSuchAlgorithmException { - if (uri.getScheme().equals("ldaps")) { - if (ldapConfiguration.isTrustAllCerts()) { - SSLContext context = SSLContext.getInstance("TLSv1.2"); - context.init(null, new TrustManager[]{DUMMY_TRUST_MANAGER}, null); - return context.getSocketFactory(); - } - return SSLSocketFactory.getDefault(); - } else { - return null; - } - } - - private ThrowingFunction<URI, SingleServerSet> toSingleServerSet(LDAPConnectionOptions connectionOptions, BindRequest bindRequest) { - return Throwing.function(uri -> new SingleServerSet(uri.getHost(), uri.getPort(), supportLDAPS(uri), connectionOptions, bindRequest, null)); - } - @PreDestroy void dispose() { ldapConnectionPool.close(); @@ -305,7 +241,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable { SearchResult searchResult = ldapConnectionPool.search(userBase(retrievalName), SearchScope.SUB, createFilter(retrievalName.asString(), evaluateLdapUserRetrievalAttribute(retrievalName, resolveLocalPartAttribute)), - getReturnedAttributes()); + ldapConfiguration.getReturnedAttributes()); SearchResultEntry result = searchResult.getSearchEntries() .stream() @@ -326,14 +262,6 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable { return Optional.empty(); } - private String[] getReturnedAttributes() { - if (ldapConfiguration.getUsernameAttribute().isPresent()) { - return new String[]{ldapConfiguration.getUserIdAttribute(), ldapConfiguration.getUsernameAttribute().get()}; - } else { - return new String[]{ldapConfiguration.getUserIdAttribute()}; - } - } - private String evaluateLdapUserRetrievalAttribute(Username retrievalName, Optional<String> resolveLocalPartAttribute) { if (retrievalName.asString().contains("@")) { return ldapConfiguration.getUserIdAttribute(); diff --git a/server/mailet/ldap/README.md b/server/mailet/ldap/README.md new file mode 100644 index 0000000000..ed43e3e84b --- /dev/null +++ b/server/mailet/ldap/README.md @@ -0,0 +1,21 @@ +# LDAP matchers for Apache James + +This project is an extension for Apache James that allows configuring conditions based on LDAP content within Apache +James mailetcontainer. + +Those include: + + - LDAP conditions based on attributes of the recipients + - LDAP conditions based on attributes of the sender + +Support is planned for LDAP groups both for sender and recipients. + +## Set up + +Build this project. For the root of the repository: + +```bash +mvn clean isntall -DskipTests --am --pl :james-server-mailet-ldap +``` + +Then copy \ No newline at end of file diff --git a/server/mailet/ldap/pom.xml b/server/mailet/ldap/pom.xml new file mode 100644 index 0000000000..29980258d6 --- /dev/null +++ b/server/mailet/ldap/pom.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.james</groupId> + <artifactId>james-server</artifactId> + <version>3.9.0-SNAPSHOT</version> + <relativePath>../../pom.xml</relativePath> + </parent> + <artifactId>james-server-mailet-ldap</artifactId> + <packaging>jar</packaging> + + <name>Apache James :: Server :: Matcher :: LDAP</name> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>james-server-data-ldap</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>james-server-data-ldap</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>james-server-mailets</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>james-server-testing</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>testing-base</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/HasLDAPAttribute.java b/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/HasLDAPAttribute.java new file mode 100644 index 0000000000..f922ff484c --- /dev/null +++ b/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/HasLDAPAttribute.java @@ -0,0 +1,127 @@ +/**************************************************************** + * 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 jakarta.inject.Inject; +import jakarta.mail.MessagingException; + +import org.apache.james.core.Domain; +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.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.Filter; +import com.unboundid.ldap.sdk.LDAPConnectionPool; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPSearchException; +import com.unboundid.ldap.sdk.SearchResult; +import com.unboundid.ldap.sdk.SearchScope; + +public class HasLDAPAttribute extends GenericMatcher { + private final LDAPConnectionPool ldapConnectionPool; + private final LdapRepositoryConfiguration configuration; + private final Filter objectClassFilter; + private final Optional<Filter> userExtraFilter; + private String attributeName; + private String attributeValue; + private String[] attributes; + + @Inject + public HasLDAPAttribute(LdapRepositoryConfiguration configuration) throws LDAPException { + this.configuration = configuration; + ldapConnectionPool = new LDAPConnectionFactory(this.configuration).getLdapConnectionPool(); + + userExtraFilter = Optional.ofNullable(configuration.getFilter()) + .map(Throwing.function(Filter::create).sneakyThrow()); + objectClassFilter = Filter.createEqualityFilter("objectClass", configuration.getUserObjectClass()); + } + + @Override + public void init() throws MessagingException { + String condition = getCondition().trim(); + int commaPosition = condition.indexOf(':'); + + if (-1 == commaPosition) { + throw new MessagingException("Syntax Error. Missing ':'."); + } + + if (0 == commaPosition) { + throw new MessagingException("Syntax Error. Missing attribute name."); + } + + attributeName = condition.substring(0, commaPosition).trim(); + attributeValue = condition.substring(commaPosition + 1).trim(); + attributes = ImmutableSet.builder() + .add(configuration.getReturnedAttributes()) + .add(attributeName) + .build().toArray(String[]::new); + } + + @Override + public Collection<MailAddress> match(Mail mail) { + return mail.getRecipients() + .stream() + .filter(this::hasAttribute) + .collect(ImmutableList.toImmutableList()); + } + + private boolean hasAttribute(MailAddress rcpt) { + try { + SearchResult searchResult = ldapConnectionPool.search(userBase(rcpt), + SearchScope.SUB, + createFilter(rcpt.asString(), configuration.getUserIdAttribute()), + attributes); + + return searchResult.getSearchEntries().stream() + .anyMatch(entry -> Optional.ofNullable(entry.getAttribute(attributeName)) + .map(Attribute::getValue) + .map(attributeValue::equals) + .orElse(false)); + + } catch (LDAPSearchException e) { + throw new RuntimeException("Failed searching LDAP", e); + } + } + + private Filter createFilter(String retrievalName, String ldapUserRetrievalAttribute) { + Filter specificUserFilter = Filter.createEqualityFilter(ldapUserRetrievalAttribute, retrievalName); + return userExtraFilter + .map(extraFilter -> Filter.createANDFilter(objectClassFilter, specificUserFilter, extraFilter)) + .orElseGet(() -> Filter.createANDFilter(objectClassFilter, specificUserFilter)); + } + + private String userBase(MailAddress mailAddress) { + return userBase(mailAddress.getDomain()); + } + + private String userBase(Domain domain) { + return configuration.getPerDomainBaseDN() + .getOrDefault(domain, configuration.getUserBase()); + } +} diff --git a/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/HasLDAPAttributeTest.java b/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/HasLDAPAttributeTest.java new file mode 100644 index 0000000000..f1cdddaca4 --- /dev/null +++ b/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/HasLDAPAttributeTest.java @@ -0,0 +1,170 @@ +/**************************************************************** + * 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 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; + +class HasLDAPAttributeTest { + + static LdapGenericContainer ldapContainer = DockerLdapSingleton.ldapContainer; + + @BeforeAll + static void setUpAll() { + ldapContainer.start(); + } + + @AfterAll + static void afterAll() { + ldapContainer.stop(); + } + + @Test + void shouldReturnRecipientWhenHasAttribute() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("description:abcdef") + .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 shouldReturnEmptyWhenHasNoAttribute() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("description:abcdef") + .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 shouldReturnEmptyWhenHasBadAttribute() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("description:abcdefg") + .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 shouldReturnEmptyWhenHasPartialAttribute() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("description:abcde") + .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 shouldReturnEmptyWhenHasLongerAttributeName() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("descriptionaaa:abcdef") + .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 new file mode 100644 index 0000000000..b67e297117 --- /dev/null +++ b/server/mailet/ldap/src/test/resources/ldif-files/populate.ldif @@ -0,0 +1,29 @@ +dn: ou=people, dc=james,dc=org +ou: people +objectClass: organizationalUnit + +dn: ou=empty, dc=james,dc=org +ou: empty +objectClass: organizationalUnit + +dn: ou=whatever, dc=james,dc=org +ou: whatever +objectClass: organizationalUnit + +dn: uid=james-user, ou=people, dc=james,dc=org +objectClass: inetOrgPerson +uid: james-user +cn: james-user +sn: james-user +mail: [email protected] +userPassword: secret +description: abcdef + +dn: uid=bob, ou=people, dc=james,dc=org +objectClass: inetOrgPerson +uid: bob +cn: bob +sn: bob +mail: [email protected] +userPassword: secret +description: Extra user diff --git a/server/pom.xml b/server/pom.xml index fbd8eeabb6..a9ee64b77d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -79,6 +79,7 @@ <module>mailet/dkim</module> <module>mailet/integration-testing</module> + <module>mailet/ldap</module> <module>mailet/mailetcontainer-api</module> <module>mailet/mailetcontainer-impl</module> <module>mailet/mailets</module> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
