This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push: new 256c89dec7 JAMES-3945 Subaddressing - case sensitivity and subfolders (#2435) 256c89dec7 is described below commit 256c89dec70a8aa4db0a807b42b2602b4716cc20 Author: florentos17 <fazav...@linagora.com> AuthorDate: Mon Oct 7 08:36:09 2024 +0200 JAMES-3945 Subaddressing - case sensitivity and subfolders (#2435) --- .../model/search/ExactNameCaseInsensitive.java | 70 ++++++++++++++ .../search/PrefixedWildcardCaseInsensitive.java | 69 ++++++++++++++ .../model/search/ExactNameCaseInsensitiveTest.java | 88 +++++++++++++++++ .../PrefixedWildcardCaseInsensitiveTest.java | 105 +++++++++++++++++++++ .../james/mailets/configuration/Constants.java | 10 +- .../apache/james/mailets/SubAddressingTest.java | 102 +++++++++++++++++++- .../james/transport/mailets/SubAddressing.java | 49 ++++++++-- .../james/transport/mailets/SubAddressingTest.java | 76 ++++++++------- 8 files changed, 513 insertions(+), 56 deletions(-) diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitive.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitive.java new file mode 100644 index 0000000000..8acced2857 --- /dev/null +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitive.java @@ -0,0 +1,70 @@ +/**************************************************************** + * 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.mailbox.model.search; + +import java.util.Objects; + +import com.google.common.base.Preconditions; + +public class ExactNameCaseInsensitive implements MailboxNameExpression { + + private final String name; + + public ExactNameCaseInsensitive(String name) { + Preconditions.checkNotNull(name); + this.name = name; + } + + @Override + public boolean isExpressionMatch(String mailboxName) { + Preconditions.checkNotNull(mailboxName); + return name.equalsIgnoreCase(mailboxName); + } + + @Override + public String getCombinedName() { + return name; + } + + @Override + public boolean isWild() { + return false; + } + + @Override + public MailboxNameExpression includeChildren() { + return new PrefixedWildcardCaseInsensitive(name); + } + + @Override + public final boolean equals(Object o) { + if (o instanceof ExactNameCaseInsensitive) { + ExactNameCaseInsensitive exactName = (ExactNameCaseInsensitive) o; + + return Objects.equals(this.name, exactName.name); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(name); + } +} diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitive.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitive.java new file mode 100644 index 0000000000..85e490d775 --- /dev/null +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitive.java @@ -0,0 +1,69 @@ +/**************************************************************** + * 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.mailbox.model.search; + +import java.util.Objects; + +import com.google.common.base.Preconditions; + +public class PrefixedWildcardCaseInsensitive implements MailboxNameExpression { + + private final String prefix; + + public PrefixedWildcardCaseInsensitive(String prefix) { + Preconditions.checkNotNull(prefix); + this.prefix = prefix; + } + + @Override + public boolean isExpressionMatch(String name) { + return name.toLowerCase().startsWith(prefix.toLowerCase()); + } + + @Override + public String getCombinedName() { + return prefix + FREEWILDCARD; + } + + @Override + public boolean isWild() { + return true; + } + + @Override + public MailboxNameExpression includeChildren() { + return this; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof PrefixedWildcardCaseInsensitive) { + PrefixedWildcardCaseInsensitive that = (PrefixedWildcardCaseInsensitive) o; + + return Objects.equals(this.prefix, that.prefix); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(prefix); + } +} diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitiveTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitiveTest.java new file mode 100644 index 0000000000..1c46759d40 --- /dev/null +++ b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitiveTest.java @@ -0,0 +1,88 @@ +/**************************************************************** + * 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.mailbox.model.search; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; + +public class ExactNameCaseInsensitiveTest { + public static final String NAME = "toto"; + public static final String NAME_DIFFERENT_CASE_1 = "Toto"; + public static final String NAME_DIFFERENT_CASE_2 = "TOTO"; + + @Test + void shouldMatchBeanContract() { + EqualsVerifier.forClass(ExactNameCaseInsensitive.class) + .verify(); + } + + @Test + void constructorShouldThrowOnNullName() { + assertThatThrownBy(() -> new ExactNameCaseInsensitive(null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void isWildShouldReturnFalse() { + assertThat(new ExactNameCaseInsensitive(NAME).isWild()) + .isFalse(); + } + + @Test + void getCombinedNameShouldReturnName() { + assertThat(new ExactNameCaseInsensitive(NAME).getCombinedName()) + .isEqualTo(NAME); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenName() { + assertThat(new ExactNameCaseInsensitive(NAME).isExpressionMatch(NAME)) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenNameDifferentCase1() { + assertThat(new ExactNameCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_1)) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenNameDifferentCase2() { + assertThat(new ExactNameCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_2)) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnFalseWhenOtherValue() { + assertThat(new ExactNameCaseInsensitive(NAME).isExpressionMatch("other")) + .isFalse(); + } + + @Test + void isExpressionMatchShouldThrowOnNullValue() { + assertThatThrownBy(() -> new ExactNameCaseInsensitive(NAME).isExpressionMatch(null)) + .isInstanceOf(NullPointerException.class); + } + +} \ No newline at end of file diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitiveTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitiveTest.java new file mode 100644 index 0000000000..597632c91c --- /dev/null +++ b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitiveTest.java @@ -0,0 +1,105 @@ +/**************************************************************** + * 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.mailbox.model.search; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +import nl.jqno.equalsverifier.EqualsVerifier; + +public class PrefixedWildcardCaseInsensitiveTest { + public static final String NAME = "toto"; + public static final String NAME_DIFFERENT_CASE_1 = "Toto"; + public static final String NAME_DIFFERENT_CASE_2 = "TOTO"; + + @Test + void shouldMatchBeanContract() { + EqualsVerifier.forClass(PrefixedWildcardCaseInsensitive.class) + .verify(); + } + + @Test + void constructorShouldThrowOnNullName() { + assertThatThrownBy(() -> new PrefixedWildcardCaseInsensitive(null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void isWildShouldReturnTrue() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isWild()) + .isTrue(); + } + + @Test + void getCombinedNameShouldReturnName() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).getCombinedName()) + .isEqualTo(NAME + MailboxNameExpression.FREEWILDCARD); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenName() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME)) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase1() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_1)) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase2() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_2)) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenNameAndPostfix() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME + "any")) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase1AndPostfix() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_1 + "any")) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase2AndPostfix() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_2 + "any")) + .isTrue(); + } + + @Test + void isExpressionMatchShouldReturnFalseWhenOtherValue() { + assertThat(new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch("other")) + .isFalse(); + } + + @Test + void isExpressionMatchShouldThrowOnNullValue() { + assertThatThrownBy(() -> new PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(null)) + .isInstanceOf(NullPointerException.class); + } +} \ No newline at end of file diff --git a/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java b/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java index c5bdaf4b13..4e2ebcc1f2 100644 --- a/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java +++ b/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java @@ -42,9 +42,9 @@ public class Constants { public static final String DEFAULT_DOMAIN = "james.org"; public static final String LOCALHOST_IP = "127.0.0.1"; public static final String PASSWORD = "secret"; - public static final String FROM = "user@" + DEFAULT_DOMAIN; - public static final String FROM2 = "user4@" + DEFAULT_DOMAIN; - public static final String RECIPIENT = "user2@" + DEFAULT_DOMAIN; - public static final String ALIAS = "user2alias@" + DEFAULT_DOMAIN; - public static final String RECIPIENT2 = "user3@" + DEFAULT_DOMAIN; + public static final String FROM = "from@" + DEFAULT_DOMAIN; + public static final String FROM2 = "from2@" + DEFAULT_DOMAIN; + public static final String RECIPIENT = "recipient@" + DEFAULT_DOMAIN; + public static final String ALIAS = "recipientalias@" + DEFAULT_DOMAIN; + public static final String RECIPIENT2 = "recipient2@" + DEFAULT_DOMAIN; } diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java index 55f656cc00..fa1225cd1f 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java @@ -27,6 +27,7 @@ import static org.apache.james.mailets.configuration.Constants.FROM2; import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP; import static org.apache.james.mailets.configuration.Constants.PASSWORD; import static org.apache.james.mailets.configuration.Constants.RECIPIENT; +import static org.apache.james.mailets.configuration.Constants.RECIPIENT2; import static org.apache.james.mailets.configuration.Constants.awaitAtMostOneMinute; import java.io.File; @@ -55,6 +56,8 @@ import org.junit.jupiter.api.io.TempDir; class SubAddressingTest { private static final String TARGETED_MAILBOX = "any"; + private static final String TARGETED_MAILBOX_LOWER = TARGETED_MAILBOX; + private static final String TARGETED_MAILBOX_UPPER = "ANY"; @RegisterExtension public TestIMAPClient testIMAPClient = new TestIMAPClient(); @@ -78,6 +81,7 @@ class SubAddressingTest { DataProbe dataProbe = jamesServer.getProbe(DataProbeImpl.class); dataProbe.addDomain(DEFAULT_DOMAIN); dataProbe.addUser(RECIPIENT, PASSWORD); + dataProbe.addUser(RECIPIENT2, PASSWORD); dataProbe.addUser(FROM, PASSWORD); dataProbe.addUser(FROM2, PASSWORD); @@ -100,7 +104,95 @@ class SubAddressingTest { // do not create mailbox - sendSubAddressedMail(); + sendSubAddressedMail(TARGETED_MAILBOX); + awaitSubAddressedMail(MailboxConstants.INBOX); + } + + + @Test + void subAddressedEmailShouldBeDeliveredInINBOXWhenSpecifiedFolderExistsForAnotherUser(@TempDir File temporaryFolder) throws Exception { + setup(temporaryFolder); + + // create mailbox for recipient 1 + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX + " " + "anyone" + " p"); + + // send to recipient 2 + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(FROM, PASSWORD) + .sendMessage(FROM, "recipient2+" + TARGETED_MAILBOX + "@" + DEFAULT_DOMAIN); + + testIMAPClient + .connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort()) + .login(RECIPIENT2, PASSWORD) + .select(MailboxConstants.INBOX) + .awaitMessage(awaitAtMostOneMinute); + } + + @Test + void subAddressedEmailShouldBeDeliveredInSpecifiedFolderWhenItExistsInUpperCase(@TempDir File temporaryFolder) throws Exception { + setup(temporaryFolder); + + // create mailbox + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_UPPER + " " + "anyone" + " p"); + + sendSubAddressedMail(TARGETED_MAILBOX_LOWER); + awaitSubAddressedMail(TARGETED_MAILBOX_UPPER); + } + + @Test + void subAddressedEmailShouldBeDeliveredInSpecifiedFolderWhenItExistsInLowerCase(@TempDir File temporaryFolder) throws Exception { + setup(temporaryFolder); + + // create mailbox + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + "anyone" + " p"); + + sendSubAddressedMail(TARGETED_MAILBOX_UPPER); + awaitSubAddressedMail(TARGETED_MAILBOX_LOWER); + } + + @Test + void subAddressedEmailShouldBeDeliveredInSpecifiedFolderWithCorrectLowerCaseWhenSeveralCasesExist(@TempDir File temporaryFolder) throws Exception { + setup(temporaryFolder); + + // create mailboxes + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + "anyone" + " p"); + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_UPPER + " " + "anyone" + " p"); + + sendSubAddressedMail(TARGETED_MAILBOX_LOWER); + awaitSubAddressedMail(TARGETED_MAILBOX_LOWER); + } + + @Test + void subAddressedEmailShouldBeDeliveredInSpecifiedFolderWithCorrectUpperCaseWhenSeveralCasesExist(@TempDir File temporaryFolder) throws Exception { + setup(temporaryFolder); + + // create mailboxes + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + "anyone" + " p"); + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_UPPER + " " + "anyone" + " p"); + + sendSubAddressedMail(TARGETED_MAILBOX_UPPER); + awaitSubAddressedMail(TARGETED_MAILBOX_UPPER); + } + + @Test + void subAddressedEmailShouldBeDeliveredInINBOXWhenSpecifiedFolderExistsWithCorrectCaseButNoRightAndOtherCaseButRight(@TempDir File temporaryFolder) throws Exception { + setup(temporaryFolder); + + // create mailbox with incorrect case and give posting right + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER); + testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + "anyone" + " p"); + + // create mailbox with correct case but don't give posting right + testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER); + + sendSubAddressedMail(TARGETED_MAILBOX_UPPER); awaitSubAddressedMail(MailboxConstants.INBOX); } @@ -114,7 +206,7 @@ class SubAddressingTest { // do not give posting rights testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX + " " + "anyone" + " -p"); - sendSubAddressedMail(); + sendSubAddressedMail(TARGETED_MAILBOX); awaitSubAddressedMail(MailboxConstants.INBOX); } @@ -127,14 +219,14 @@ class SubAddressingTest { // give posting rights for anyone testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX + " " + "anyone" + " +p"); - sendSubAddressedMail(); + sendSubAddressedMail(TARGETED_MAILBOX); awaitSubAddressedMail(TARGETED_MAILBOX); } - private void sendSubAddressedMail() throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + private void sendSubAddressedMail(String targetMailbox) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) .authenticate(FROM, PASSWORD) - .sendMessage(FROM, "user2+" + TARGETED_MAILBOX + "@" + DEFAULT_DOMAIN); + .sendMessage(FROM,"recipient+" + targetMailbox + "@" + DEFAULT_DOMAIN); } private void awaitSubAddressedMail(String expectedMailbox) throws IOException { diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java index 41c4e1119f..f3d1c5c1c6 100644 --- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java +++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java @@ -21,6 +21,9 @@ package org.apache.james.transport.mailets; import static org.apache.james.mailbox.MessageManager.MailboxMetaData.RecentMode.IGNORE; +import java.util.Comparator; +import java.util.Optional; + import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.mail.MessagingException; @@ -36,7 +39,10 @@ import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.MailboxNotFoundException; import org.apache.james.mailbox.exception.UnsupportedRightException; import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxMetaData; import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.search.ExactNameCaseInsensitive; +import org.apache.james.mailbox.model.search.MailboxQuery; import org.apache.james.user.api.UsersRepository; import org.apache.james.user.api.UsersRepositoryException; import org.apache.mailet.Mail; @@ -83,33 +89,56 @@ public class SubAddressing extends GenericMailet { public void service(Mail mail) throws MessagingException { mail.getRecipients().forEach(recipient -> recipient.getLocalPartDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER) - .ifPresent(Throwing.consumer(targetFolder -> postIfHasRight(mail, recipient, targetFolder)))); + .ifPresent(Throwing.consumer(targetFolder -> postIfHasRight( + mail, + recipient.stripDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER), + getPathWithCorrectCase(recipient, targetFolder))))); + } + + private Optional<MailboxPath> getPathWithCorrectCase(MailAddress recipient, String targetFolder) throws UsersRepositoryException, MailboxException { + Username recipientUsername = usersRepository.getUsername(recipient); + MailboxSession session = mailboxManager.createSystemSession(recipientUsername); + + Comparator<MailboxPath> exactMatchFirst = Comparator.comparing(mailboxPath -> mailboxPath.getName().equals(targetFolder) ? 0 : 1); + + return mailboxManager.search( + MailboxQuery.privateMailboxesBuilder(session).expression(new ExactNameCaseInsensitive(targetFolder)).build(), + session) + .toStream() + .map(MailboxMetaData::getPath) + .sorted(exactMatchFirst) + .findFirst() + .or(() -> { + LOG.info("{}'s subfolder `{}` was tried to be addressed but it does not exist", recipient, targetFolder); + return Optional.empty(); + }); } - private void postIfHasRight(Mail mail, MailAddress recipient, String targetFolder) throws UsersRepositoryException, MailboxException { - if (hasPostRight(mail, recipient, targetFolder)) { - StorageDirective.builder().targetFolders(ImmutableList.of(targetFolder)).build() + private void postIfHasRight(Mail mail, MailAddress recipient, Optional<MailboxPath> targetFolderPath) throws UsersRepositoryException, MailboxException { + if (hasPostRight(mail, recipient, targetFolderPath)) { + StorageDirective.builder().targetFolders(ImmutableList.of(targetFolderPath.get().getName())).build() .encodeAsAttributes(usersRepository.getUsername(recipient)) .forEach(mail::setAttribute); } else { LOG.info("{} tried to address {}'s subfolder `{}` but they did not have the right to", - mail.getMaybeSender().toString(), recipient.stripDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER), targetFolder); + mail.getMaybeSender().toString(), recipient, targetFolderPath); } } - private Boolean hasPostRight(Mail mail, MailAddress recipient, String targetFolder) throws MailboxException, UsersRepositoryException { + private Boolean hasPostRight(Mail mail, MailAddress recipient, Optional<MailboxPath> targetFolderPath) throws MailboxException, UsersRepositoryException { try { - return resolvePostRight(retrieveMailboxACL(recipient, targetFolder), mail.getMaybeSender(), recipient); + return targetFolderPath.isPresent() && resolvePostRight(retrieveMailboxACL(recipient, targetFolderPath.get()), mail.getMaybeSender(), recipient); } catch (MailboxNotFoundException e) { - LOG.info("{}'s subfolder `{}` was tried to be addressed but it does not exist", recipient.stripDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER), targetFolder); + LOG.info("{}'s subfolder `{}` was tried to be addressed but it does not exist", recipient, targetFolderPath); return false; } } - private MailboxACL retrieveMailboxACL(MailAddress recipient, String targetFolder) throws MailboxException, UsersRepositoryException { + private MailboxACL retrieveMailboxACL(MailAddress recipient, MailboxPath targetFolderPath) throws MailboxException, UsersRepositoryException { Username recipientUsername = usersRepository.getUsername(recipient); MailboxSession session = mailboxManager.createSystemSession(recipientUsername); - return mailboxManager.getMailbox(MailboxPath.forUser(recipientUsername, targetFolder), session) + + return mailboxManager.getMailbox(targetFolderPath, session) .getMetaData(IGNORE, session, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT) .getACL(); } diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java index c7b160f480..3307083262 100644 --- a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java +++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java @@ -52,15 +52,15 @@ import org.junit.jupiter.api.Test; public class SubAddressingTest { private static final String SENDER1 = "sender1@localhost"; - private static final String SENDER2 = "sender2@localhost"; private static final String RECIPIENT = "recipient@localhost"; private static final String TARGET = "targetfolder"; - private static final String UNEXISTING_TARGET = "unexistingfolder"; + private static final String TARGET_UPPERCASE = "Targetfolder"; + private static final String TARGET_UNEXISTING = "unexistingfolder"; private MailboxManager mailboxManager; private SubAddressing testee; private UsersRepository usersRepository; - private Username sender1Username; + private Username senderUsername; private Username recipientUsername; private MailboxSession recipientSession; private MailboxId targetMailboxId; @@ -71,7 +71,7 @@ public class SubAddressingTest { usersRepository = MemoryUsersRepository.withVirtualHosting(NO_DOMAIN_LIST); recipientUsername = usersRepository.getUsername(new MailAddress(RECIPIENT)); - sender1Username = usersRepository.getUsername(new MailAddress(SENDER1)); + senderUsername = usersRepository.getUsername(new MailAddress(SENDER1)); recipientSession = mailboxManager.createSystemSession(recipientUsername); targetMailboxId = mailboxManager.createMailbox( @@ -83,73 +83,78 @@ public class SubAddressingTest { @Test void shouldNotAddStorageDirectiveWhenTargetMailboxDoesNotExist() throws Exception { - Mail mail = mailBuilder(UNEXISTING_TARGET).sender(SENDER1).build(); + Mail mail = mailBuilder(TARGET_UNEXISTING).sender(SENDER1).build(); testee.service(mail); AttributeName recipient = AttributeName.of("DeliveryPaths_recipient@localhost"); assertThat(mail.attributes().map(this::unbox)) - .doesNotContain(Pair.of(recipient, UNEXISTING_TARGET)); + .doesNotContain(Pair.of(recipient, TARGET_UNEXISTING)); } @Test - void shouldNotAddStorageDirectiveWhenNobodyHasRight() throws Exception { - removePostRightForKey(MailboxACL.ANYONE_KEY); + void shouldAddStorageDirectiveWhenTargetMailboxExistsButLowerCase() throws Exception { + givePostRightForKey(MailboxACL.ANYONE_KEY); - Mail mail = mailBuilder(TARGET).sender(SENDER1).build(); + Mail mail = mailBuilder(TARGET_UPPERCASE).sender(SENDER1).build(); testee.service(mail); AttributeName recipient = AttributeName.of("DeliveryPaths_recipient@localhost"); assertThat(mail.attributes().map(this::unbox)) - .doesNotContain(Pair.of(recipient, TARGET)); + .containsOnly(Pair.of(recipient, TARGET)); } - @Test - void shouldAddStorageDirectiveWhenAnyoneHasRight() throws Exception { - givePostRightForKey(MailboxACL.ANYONE_KEY); + void shouldAddStorageDirectiveWhenTargetMailboxExistsButUpperCase() throws Exception { + targetMailboxId = mailboxManager.createMailbox( + MailboxPath.forUser(recipientUsername, "Target"), recipientSession).get(); - Mail mail = mailBuilder(TARGET).sender(SENDER1).build(); + MailboxACL.ACLCommand command = MailboxACL.command() + .key(MailboxACL.ANYONE_KEY) + .rights(MailboxACL.Right.Post) + .asAddition(); + mailboxManager.applyRightsCommand(targetMailboxId, command, recipientSession); + + Mail mail = mailBuilder("target").sender(SENDER1).build(); testee.service(mail); AttributeName recipient = AttributeName.of("DeliveryPaths_recipient@localhost"); assertThat(mail.attributes().map(this::unbox)) - .containsOnly(Pair.of(recipient, TARGET)); + .containsOnly(Pair.of(recipient, "Target")); } - //@Disabled @Test - void shouldAddStorageDirectiveWhenSenderIsWhiteListed() throws Exception { - // whitelist sender 1 and send from sender 1 - removePostRightForKey(MailboxACL.ANYONE_KEY); - givePostRightForKey(MailboxACL.EntryKey.createUserEntryKey(sender1Username)); + void shouldAddStorageDirectiveWhenTargetFolderIsASubFolder() throws Exception { + givePostRightForKey(MailboxACL.ANYONE_KEY); - Mail mail = mailBuilder(TARGET).sender(SENDER1).build(); + MailboxPath newPath = MailboxPath.forUser(recipientUsername, "folder" + recipientSession.getPathDelimiter() + TARGET); + mailboxManager.renameMailbox(targetMailboxId, newPath, recipientSession).getFirst().getMailboxId(); + + Mail mail = mailBuilder("folder" + recipientSession.getPathDelimiter() + TARGET).sender(SENDER1).build(); testee.service(mail); AttributeName recipient = AttributeName.of("DeliveryPaths_recipient@localhost"); assertThat(mail.attributes().map(this::unbox)) - .containsOnly(Pair.of(recipient, TARGET)); + .containsOnly(Pair.of(recipient, "folder" + recipientSession.getPathDelimiter() + TARGET)); } @Test - void shouldNotAddStorageDirectiveWhenSenderIsNotWhiteListed() throws Exception { - // whitelist sender 1 and send from sender 2 - removePostRightForKey(MailboxACL.ANYONE_KEY); - givePostRightForKey(MailboxACL.EntryKey.createUserEntryKey(sender1Username)); + void shouldAddStorageDirectiveWhenTargetFolderIsASubFolderWithDifferentCase() throws Exception { + givePostRightForKey(MailboxACL.ANYONE_KEY); + + MailboxPath newPath = MailboxPath.forUser(recipientUsername, "Folder" + recipientSession.getPathDelimiter() + TARGET_UPPERCASE); + mailboxManager.renameMailbox(targetMailboxId, newPath, recipientSession).getFirst().getMailboxId(); - Mail mail = mailBuilder(TARGET).sender(SENDER2).build(); + Mail mail = mailBuilder("folder" + recipientSession.getPathDelimiter() + TARGET).sender(SENDER1).build(); testee.service(mail); AttributeName recipient = AttributeName.of("DeliveryPaths_recipient@localhost"); assertThat(mail.attributes().map(this::unbox)) - .doesNotContain(Pair.of(recipient, TARGET)); + .containsOnly(Pair.of(recipient, "Folder" + recipientSession.getPathDelimiter() + TARGET_UPPERCASE)); } @Test - void shouldNotAddStorageDirectiveWhenSenderIsBlackListed() throws Exception { - // blacklist sender 1 and send from sender 1 - givePostRightForKey(MailboxACL.ANYONE_KEY); - givePostRightForKey(MailboxACL.EntryKey.createNegativeUserEntryKey(sender1Username)); + void shouldNotAddStorageDirectiveWhenNobodyHasRight() throws Exception { + removePostRightForKey(MailboxACL.ANYONE_KEY); Mail mail = mailBuilder(TARGET).sender(SENDER1).build(); testee.service(mail); @@ -159,13 +164,12 @@ public class SubAddressingTest { .doesNotContain(Pair.of(recipient, TARGET)); } + @Test - void shouldAddStorageDirectiveWhenSenderIsNotBlackListed() throws Exception { - // blacklist sender 1 and send from sender 2 + void shouldAddStorageDirectiveWhenAnyoneHasRight() throws Exception { givePostRightForKey(MailboxACL.ANYONE_KEY); - removePostRightForKey(MailboxACL.EntryKey.createUserEntryKey(sender1Username)); - Mail mail = mailBuilder(TARGET).sender(SENDER2).build(); + Mail mail = mailBuilder(TARGET).sender(SENDER1).build(); testee.service(mail); AttributeName recipient = AttributeName.of("DeliveryPaths_recipient@localhost"); --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org