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 9fbcaca010f10c74d5b2f9d7f58c0be54de92786 Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Jun 19 15:10:01 2024 +0200 JAMES-4044 Handle both presence and value matching --- docs/modules/servers/partials/LDAPMatchers.adoc | 30 +++++++++- server/mailet/ldap/README.md | 26 +++++++- .../james/transport/matchers/HasLDAPAttribute.java | 37 +++++++----- .../transport/matchers/SenderHasLDAPAttribute.java | 33 +++++++---- .../transport/matchers/HasLDAPAttributeTest.java | 67 ++++++++++++++++++++- .../matchers/SenderHasLDAPAttributeTest.java | 69 ++++++++++++++++++++++ 6 files changed, 228 insertions(+), 34 deletions(-) diff --git a/docs/modules/servers/partials/LDAPMatchers.adoc b/docs/modules/servers/partials/LDAPMatchers.adoc index a202ba65ec..44980ca084 100644 --- a/docs/modules/servers/partials/LDAPMatchers.adoc +++ b/docs/modules/servers/partials/LDAPMatchers.adoc @@ -24,18 +24,42 @@ Then copy the JAR located in `/target` into the `extensions-jars` folder of your The LDAP settings are reused from the `usersrepository.xml` configuration file. -Then LDAP matchers can be configured within `mailetcontainer.xml`. For instance: +Then LDAP matchers can be configured within `mailetcontainer.xml`. + +Then LDAP matchers can be configured within `mailetcontainer.xml`. + +For instance: ....xml +<!-- Matches recipients that have the following attribute with the specified value--> <mailet matcher="HasLDAPAttibute=description:blocked" class="Null"> - + </mailet> .... or ....xml +<!-- Matches sender that have the following attribute with the specified value--> <mailet matcher="SenderHasLDAPAttibute=description:blocked" class="Null"> - + +</mailet> +.... + +or + +....xml +<!-- Matches recipients that have the following attribute regardless of the actual value--> +<mailet matcher="HasLDAPAttibute=description" class="Null"> + +</mailet> +.... + +or + +....xml +<!-- Matches sender that have the following attribute regardless of the actual value--> +<mailet matcher="SenderHasLDAPAttibute=description" class="Null"> + </mailet> .... diff --git a/server/mailet/ldap/README.md b/server/mailet/ldap/README.md index 16c21dbd83..b2ccb82d26 100644 --- a/server/mailet/ldap/README.md +++ b/server/mailet/ldap/README.md @@ -24,18 +24,40 @@ Then copy the JAR located in `/target` into the `extensions-jars` folder of your The LDAP settings are reused from the `usersrepository.xml` configuration file. -Then LDAP matchers can be configured within `mailetcontainer.xml`. For instance: +Then LDAP matchers can be configured within `mailetcontainer.xml`. + +For instance: ```xml +<!-- Matches recipients that have the following attribute with the specified value--> <mailet matcher="HasLDAPAttibute=description:blocked" class="Null"> - + </mailet> ``` or ```xml +<!-- Matches sender that have the following attribute with the specified value--> <mailet matcher="SenderHasLDAPAttibute=description:blocked" class="Null"> + +</mailet> +``` + +or + +```xml +<!-- Matches recipients that have the following attribute regardless of the actual value--> +<mailet matcher="HasLDAPAttibute=description" class="Null"> + +</mailet> +``` + +or + +```xml +<!-- Matches sender that have the following attribute regardless of the actual value--> +<mailet matcher="SenderHasLDAPAttibute=description" class="Null"> </mailet> ``` 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 index f922ff484c..ce529f5edd 100644 --- 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 @@ -41,6 +41,7 @@ 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.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; public class HasLDAPAttribute extends GenericMatcher { @@ -49,7 +50,7 @@ public class HasLDAPAttribute extends GenericMatcher { private final Filter objectClassFilter; private final Optional<Filter> userExtraFilter; private String attributeName; - private String attributeValue; + private Optional<String> attributeValue; private String[] attributes; @Inject @@ -67,16 +68,17 @@ public class HasLDAPAttribute extends GenericMatcher { String condition = getCondition().trim(); int commaPosition = condition.indexOf(':'); - if (-1 == commaPosition) { - throw new MessagingException("Syntax Error. Missing ':'."); + if (commaPosition == -1) { + attributeName = condition; + attributeValue = Optional.empty(); + } else { + if (commaPosition == 0) { + throw new MessagingException("Syntax Error. Missing attribute name."); + } + attributeName = condition.substring(0, commaPosition).trim(); + attributeValue = Optional.of(condition.substring(commaPosition + 1).trim()); } - 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) @@ -98,17 +100,22 @@ public class HasLDAPAttribute extends GenericMatcher { 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)); - + return searchResult.getSearchEntries() + .stream() + .anyMatch(this::hasAttribute); } catch (LDAPSearchException e) { throw new RuntimeException("Failed searching LDAP", e); } } + private boolean hasAttribute(SearchResultEntry entry) { + return attributeValue.map(value -> Optional.ofNullable(entry.getAttribute(attributeName)) + .map(Attribute::getValue) + .map(value::equals) + .orElse(false)) + .orElseGet(() -> entry.hasAttribute(attributeName)); + } + private Filter createFilter(String retrievalName, String ldapUserRetrievalAttribute) { Filter specificUserFilter = Filter.createEqualityFilter(ldapUserRetrievalAttribute, retrievalName); return userExtraFilter diff --git a/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/SenderHasLDAPAttribute.java b/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/SenderHasLDAPAttribute.java index e4103d4f00..54915d4ff3 100644 --- a/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/SenderHasLDAPAttribute.java +++ b/server/mailet/ldap/src/main/java/org/apache/james/transport/matchers/SenderHasLDAPAttribute.java @@ -41,6 +41,7 @@ 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.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; public class SenderHasLDAPAttribute extends GenericMatcher { @@ -49,7 +50,7 @@ public class SenderHasLDAPAttribute extends GenericMatcher { private final Filter objectClassFilter; private final Optional<Filter> userExtraFilter; private String attributeName; - private String attributeValue; + private Optional<String> attributeValue; private String[] attributes; @Inject @@ -67,16 +68,17 @@ public class SenderHasLDAPAttribute extends GenericMatcher { String condition = getCondition().trim(); int commaPosition = condition.indexOf(':'); - if (-1 == commaPosition) { - throw new MessagingException("Syntax Error. Missing ':'."); + if (commaPosition == -1) { + attributeName = condition; + attributeValue = Optional.empty(); + } else { + if (commaPosition == 0) { + throw new MessagingException("Syntax Error. Missing attribute name."); + } + attributeName = condition.substring(0, commaPosition).trim(); + attributeValue = Optional.of(condition.substring(commaPosition + 1).trim()); } - 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) @@ -102,16 +104,21 @@ public class SenderHasLDAPAttribute extends GenericMatcher { attributes); return searchResult.getSearchEntries().stream() - .anyMatch(entry -> Optional.ofNullable(entry.getAttribute(attributeName)) - .map(Attribute::getValue) - .map(attributeValue::equals) - .orElse(false)); + .anyMatch(this::hasAttribute); } catch (LDAPSearchException e) { throw new RuntimeException("Failed searching LDAP", e); } } + private boolean hasAttribute(SearchResultEntry entry) { + return attributeValue.map(value -> Optional.ofNullable(entry.getAttribute(attributeName)) + .map(Attribute::getValue) + .map(value::equals) + .orElse(false)) + .orElseGet(() -> entry.hasAttribute(attributeName)); + } + private Filter createFilter(String retrievalName, String ldapUserRetrievalAttribute) { Filter specificUserFilter = Filter.createEqualityFilter(ldapUserRetrievalAttribute, retrievalName); return userExtraFilter 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 index f1cdddaca4..7be38f426a 100644 --- 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 @@ -22,10 +22,14 @@ 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 jakarta.mail.MessagingException; + import org.apache.commons.configuration2.HierarchicalConfiguration; import org.apache.commons.configuration2.plist.PropertyListConfiguration; import org.apache.commons.configuration2.tree.ImmutableNode; @@ -55,7 +59,7 @@ class HasLDAPAttributeTest { } @Test - void shouldReturnRecipientWhenHasAttribute() throws Exception { + void shouldReturnRecipientWhenHasAttributeWithValue() throws Exception { HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() .matcherName("HasLDAPAttribute") @@ -72,6 +76,67 @@ class HasLDAPAttributeTest { assertThat(matched).containsOnly(recipient); } + @Test + void shouldThrowWhenPrefixedWithDelimiter() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition(":abcdef") + .build(); + + assertThatThrownBy(() -> testee.init(matcherConfig)) + .isInstanceOf(MessagingException.class); + } + + @Test + void shouldNotThrowWhenValueContainsDelimiter() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("description:abc:def") + .build(); + testee.init(matcherConfig); + + assertThatCode(() -> testee.init(matcherConfig)) + .doesNotThrowAnyException();; + } + + @Test + void shouldReturnRecipientWhenHasAttribute() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("description") + .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 shouldNotReturnRecipientWhenDoesNotHaveAttibure() throws Exception { + HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("descriptionaa") + .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 shouldReturnEmptyWhenHasNoAttribute() throws Exception { HasLDAPAttribute testee = new HasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); diff --git a/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/SenderHasLDAPAttributeTest.java b/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/SenderHasLDAPAttributeTest.java index 02ebabc2b3..cbf1cfc500 100644 --- a/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/SenderHasLDAPAttributeTest.java +++ b/server/mailet/ldap/src/test/java/org/apache/james/transport/matchers/SenderHasLDAPAttributeTest.java @@ -22,10 +22,14 @@ 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 jakarta.mail.MessagingException; + import org.apache.commons.configuration2.HierarchicalConfiguration; import org.apache.commons.configuration2.plist.PropertyListConfiguration; import org.apache.commons.configuration2.tree.ImmutableNode; @@ -54,6 +58,71 @@ class SenderHasLDAPAttributeTest { ldapContainer.stop(); } + @Test + void shouldThrowWhenPrefixedWithDelimiter() throws Exception { + SenderHasLDAPAttribute testee = new SenderHasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition(":abcdef") + .build(); + + assertThatThrownBy(() -> testee.init(matcherConfig)) + .isInstanceOf(MessagingException.class); + } + + @Test + void shouldNotThrowWhenValueContainsDelimiter() throws Exception { + SenderHasLDAPAttribute testee = new SenderHasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("HasLDAPAttribute") + .condition("description:abc:def") + .build(); + testee.init(matcherConfig); + + assertThatCode(() -> testee.init(matcherConfig)) + .doesNotThrowAnyException();; + } + + @Test + void shouldReturnRecipientWhenHasAttribute() throws Exception { + SenderHasLDAPAttribute testee = new SenderHasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("SenderHasLDAPAttribute") + .condition("description") + .build(); + testee.init(matcherConfig); + + MailAddress sender = new MailAddress("[email protected]"); + MailAddress recipient = new MailAddress("[email protected]"); + Collection<MailAddress> matched = testee.match(FakeMail.builder() + .name("default-id") + .sender(sender) + .recipient(recipient) + .build()); + + assertThat(matched).containsOnly(recipient); + } + + @Test + void shouldNotReturnRecipientWhenDoesNotHaveAttribute() throws Exception { + SenderHasLDAPAttribute testee = new SenderHasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); + FakeMatcherConfig matcherConfig = FakeMatcherConfig.builder() + .matcherName("SenderHasLDAPAttribute") + .condition("descriptionaa") + .build(); + testee.init(matcherConfig); + + MailAddress sender = new MailAddress("[email protected]"); + MailAddress recipient = new MailAddress("[email protected]"); + Collection<MailAddress> matched = testee.match(FakeMail.builder() + .name("default-id") + .sender(sender) + .recipient(recipient) + .build()); + + assertThat(matched).isEmpty(); + } + @Test void shouldReturnSenderWhenHasAttribute() throws Exception { SenderHasLDAPAttribute testee = new SenderHasLDAPAttribute(LdapRepositoryConfiguration.from(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer))); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
