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 441d729c6073073587d5db6342429f0799fdf609 Author: RĂ©mi KOWALSKI <rkowal...@linagora.com> AuthorDate: Mon Feb 10 11:43:46 2020 +0100 JAMES-3032 allow user to send a mail from an alias, allow alias in SetMessageUpdateProcessor --- .../methods/integration/SetMessagesMethodTest.java | 54 ++++++ .../draft/methods/SetMessagesUpdateProcessor.java | 23 ++- .../methods/SetMessagesUpdateProcessorTest.java | 211 +++++++++++++++++++-- 3 files changed, 258 insertions(+), 30 deletions(-) diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java index a5776b2..85a6743 100644 --- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java +++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java @@ -2164,6 +2164,60 @@ public abstract class SetMessagesMethodTest { } @Test + public void setMessagesShouldSendMessageByMovingDraftToOutboxForAMailSentFromAnAlias() throws Exception { + dataProbe.addUserAliasMapping(Username.of(ALIAS_OF_USERNAME_MAIL).getLocalPart(), ALIAS_OF_USERNAME.getDomainPart().get().asString(), USERNAME.asString()); + + String draftCreationId = "creationId1337"; + String fromAddress = USERNAME.asString(); + String createDraft = "[" + + " [" + + " \"setMessages\"," + + " {" + + " \"create\": { \"" + draftCreationId + "\" : {" + + " \"from\": { \"name\": \"Me\", \"email\": \"" + ALIAS_OF_USERNAME.asString() + "\"}," + + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB.asString() + "\"}]," + + " \"subject\": \"subject\"," + + " \"keywords\": {\"$Draft\": true}," + + " \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + String draftId = + with() + .header("Authorization", accessToken.asString()) + .body(createDraft) + .post("/jmap") + .then() + .extract() + .path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id"); + + String moveDraftToOutBox = "[" + + " [" + + " \"setMessages\"," + + " {" + + " \"update\": { \"" + draftId + "\" : {" + + " \"keywords\": {}," + + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + with() + .header("Authorization", accessToken.asString()) + .body(moveDraftToOutBox) + .post("/jmap"); + + calmlyAwait + .pollDelay(Duration.FIVE_HUNDRED_MILLISECONDS) + .atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken)); + } + + @Test public void setMessagesShouldRejectDraftCopyToOutbox() { String draftCreationId = "creationId1337"; String fromAddress = USERNAME.asString(); diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java index 07816cb..054e7ab 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java @@ -35,7 +35,6 @@ import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.MimeMessage; -import org.apache.james.core.MailAddress; import org.apache.james.core.Username; import org.apache.james.jmap.draft.exceptions.DraftMessageMailboxUpdateException; import org.apache.james.jmap.draft.exceptions.InvalidOutboxMoveException; @@ -64,6 +63,7 @@ import org.apache.james.mailbox.model.MessageMoves; import org.apache.james.mailbox.model.MessageResult; import org.apache.james.metrics.api.MetricFactory; import org.apache.james.metrics.api.TimeMetric; +import org.apache.james.rrt.api.CanSendFrom; import org.apache.james.server.core.MailImpl; import org.apache.james.util.OptionalUtils; import org.slf4j.Logger; @@ -87,6 +87,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { private final MessageSender messageSender; private final ReferenceUpdater referenceUpdater; + private final CanSendFrom canSendFrom; @Inject @VisibleForTesting SetMessagesUpdateProcessor( @@ -96,7 +97,8 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { Factory mailboxIdFactory, MessageSender messageSender, MetricFactory metricFactory, - ReferenceUpdater referenceUpdater) { + ReferenceUpdater referenceUpdater, + CanSendFrom canSendFrom) { this.updatePatchConverter = updatePatchConverter; this.messageIdManager = messageIdManager; this.systemMailboxesProvider = systemMailboxesProvider; @@ -104,6 +106,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { this.metricFactory = metricFactory; this.messageSender = messageSender; this.referenceUpdater = referenceUpdater; + this.canSendFrom = canSendFrom; } @Override @@ -180,7 +183,10 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { if (maybeMessageToSend.isPresent()) { MessageResult messageToSend = maybeMessageToSend.get(); MailImpl mail = buildMailFromMessage(messageToSend); - assertUserIsSender(mailboxSession, mail.getMaybeSender().asOptional()); + Optional<Username> fromUser = mail.getMaybeSender() + .asOptional() + .map(Username::fromMailAddress); + assertUserCanSendFrom(mailboxSession.getUser(), fromUser); messageSender.sendMessage(messageId, mail, mailboxSession); referenceUpdater.updateReferences(messageToSend.getHeaders(), mailboxSession); } else { @@ -189,12 +195,11 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { } } - private void assertUserIsSender(MailboxSession session, Optional<MailAddress> sender) throws MailboxSendingNotAllowedException { - boolean userIsSender = sender.map(address -> session.getUser().equals(Username.fromMailAddress(address))) - .orElse(false); - - if (!userIsSender) { - String allowedSender = session.getUser().asString(); + @VisibleForTesting + void assertUserCanSendFrom(Username connectedUser, Optional<Username> fromUser) throws MailboxSendingNotAllowedException { + if (!fromUser.filter(from -> canSendFrom.userCanSendFrom(connectedUser, from)) + .isPresent()) { + String allowedSender = connectedUser.asString(); throw new MailboxSendingNotAllowedException(allowedSender); } } diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java index 9a60e03..9b6f88a 100644 --- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java @@ -20,21 +20,63 @@ package org.apache.james.jmap.draft.methods; 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 static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.net.UnknownHostException; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import javax.mail.internet.AddressException; + +import org.apache.commons.configuration2.ex.ConfigurationException; +import org.apache.james.core.Domain; +import org.apache.james.core.Username; +import org.apache.james.dnsservice.api.DNSService; +import org.apache.james.domainlist.api.DomainListException; +import org.apache.james.domainlist.lib.DomainListConfiguration; +import org.apache.james.domainlist.memory.MemoryDomainList; +import org.apache.james.jmap.draft.model.CreationMessage; +import org.apache.james.jmap.draft.model.CreationMessageId; import org.apache.james.jmap.draft.model.MessageProperties; import org.apache.james.jmap.draft.model.SetMessagesRequest; import org.apache.james.jmap.draft.model.SetMessagesResponse; import org.apache.james.jmap.draft.model.UpdateMessagePatch; +import org.apache.james.jmap.draft.send.MailSpool; +import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor; +import org.apache.james.mailbox.BlobManager; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MailboxSessionUtil; import org.apache.james.mailbox.MessageIdManager; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.MessageUid; +import org.apache.james.mailbox.Role; import org.apache.james.mailbox.SystemMailboxesProvider; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.inmemory.InMemoryId; +import org.apache.james.mailbox.model.ComposedMessageId; import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.TestMessageId; import org.apache.james.metrics.tests.RecordingMetricFactory; +import org.apache.james.rrt.api.CanSendFrom; +import org.apache.james.rrt.api.RecipientRewriteTableException; +import org.apache.james.rrt.lib.CanSendFromImpl; +import org.apache.james.rrt.lib.MappingSource; +import org.apache.james.rrt.memory.MemoryRecipientRewriteTable; +import org.apache.james.util.OptionalUtils; +import org.apache.james.util.html.HtmlTextExtractor; +import org.apache.james.util.mime.MessageContentExtractor; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -43,21 +85,131 @@ import com.google.common.collect.ImmutableMap; public class SetMessagesUpdateProcessorTest { - @Test - public void processShouldReturnEmptyUpdatedWhenRequestHasEmptyUpdate() { - UpdateMessagePatchConverter updatePatchConverter = null; - MessageIdManager messageIdManager = null; - SystemMailboxesProvider systemMailboxesProvider = null; - MailboxId.Factory mailboxIdFactory = null; - MessageSender messageSender = null; - ReferenceUpdater referenceUpdater = null; - SetMessagesUpdateProcessor sut = new SetMessagesUpdateProcessor(updatePatchConverter, + + private static final Username USER = Username.of("u...@example.com"); + private static final Username OTHER_USER = Username.of("ot...@example.com"); + private static final String OUTBOX = "outbox"; + private static final InMemoryId OUTBOX_ID = InMemoryId.of(12345); + private static final String DRAFTS = "drafts"; + private static final InMemoryId DRAFTS_ID = InMemoryId.of(12); + + public static class TestSystemMailboxesProvider implements SystemMailboxesProvider { + + private final Supplier<Optional<MessageManager>> outboxSupplier; + private final Supplier<Optional<MessageManager>> draftsSupplier; + + private TestSystemMailboxesProvider(Supplier<Optional<MessageManager>> outboxSupplier, + Supplier<Optional<MessageManager>> draftsSupplier) { + this.outboxSupplier = outboxSupplier; + this.draftsSupplier = draftsSupplier; + } + + @Override + public Stream<MessageManager> getMailboxByRole(Role aRole, Username username) { + if (aRole.equals(Role.OUTBOX)) { + return OptionalUtils.toStream(outboxSupplier.get()); + } else if (aRole.equals(Role.DRAFTS)) { + return OptionalUtils.toStream(draftsSupplier.get()); + } + return Stream.empty(); + } + } + + private final CreationMessage.Builder creationMessageBuilder = CreationMessage.builder() + .from(CreationMessage.DraftEmailer.builder().name("alice").email("al...@example.com").build()) + .to(ImmutableList.of(CreationMessage.DraftEmailer.builder().name("bob").email("b...@example.com").build())) + .subject("Hey! "); + + private final CreationMessageId creationMessageId = CreationMessageId.of("dlkja"); + + private final SetMessagesRequest createMessageInOutbox = SetMessagesRequest.builder() + .create( + creationMessageId, + creationMessageBuilder + .mailboxId(OUTBOX_ID.serialize()) + .from(CreationMessage.DraftEmailer.builder().name("user").email("u...@example.com").build()) + .build()) + .build(); + + private MailSpool mockedMailSpool; + private SystemMailboxesProvider fakeSystemMailboxesProvider; + private MailboxSession session; + private MailboxManager mockedMailboxManager; + private MailboxId.Factory mockedMailboxIdFactory; + private MemoryRecipientRewriteTable recipientRewriteTable; + private CanSendFrom canSendFrom; + private SetMessagesUpdateProcessor sut; + private MessageIdManager mockMessageIdManager; + private MessageManager outbox; + private MessageManager drafts; + private Optional<MessageManager> optionalOutbox; + private Optional<MessageManager> optionalDrafts; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private MessageSender messageSender; + private ReferenceUpdater referenceUpdater; + + @Before + public void setUp() throws MailboxException, DomainListException, UnknownHostException, ConfigurationException { + MessageContentExtractor messageContentExtractor = new MessageContentExtractor(); + HtmlTextExtractor htmlTextExtractor = new JsoupHtmlTextExtractor(); + BlobManager blobManager = mock(BlobManager.class); + when(blobManager.toBlobId(any(MessageId.class))).thenReturn(org.apache.james.mailbox.model.BlobId.fromString("fake")); + MessageIdManager messageIdManager = mock(MessageIdManager.class); + recipientRewriteTable = new MemoryRecipientRewriteTable(); + + DNSService dnsService = mock(DNSService.class); + MemoryDomainList domainList = new MemoryDomainList(dnsService); + domainList.configure(DomainListConfiguration.builder() + .autoDetect(false) + .autoDetectIp(false)); + domainList.addDomain(Domain.of("example.com")); + domainList.addDomain(Domain.of("other.org")); + recipientRewriteTable.setDomainList(domainList); + canSendFrom = new CanSendFromImpl(recipientRewriteTable); + mockedMailSpool = mock(MailSpool.class); + mockedMailboxManager = mock(MailboxManager.class); + mockedMailboxIdFactory = mock(MailboxId.Factory.class); + + mockMessageIdManager = mock(MessageIdManager.class); + + fakeSystemMailboxesProvider = new TestSystemMailboxesProvider(() -> optionalOutbox, () -> optionalDrafts); + session = MailboxSessionUtil.create(USER); + messageSender = new MessageSender(mockedMailSpool); + referenceUpdater = new ReferenceUpdater(mockMessageIdManager, mockedMailboxManager); + + UpdateMessagePatchConverter updateMessagePatchConverter = null; + sut = new SetMessagesUpdateProcessor(updateMessagePatchConverter, messageIdManager, - systemMailboxesProvider, - mailboxIdFactory, + fakeSystemMailboxesProvider, + mockedMailboxIdFactory, messageSender, new RecordingMetricFactory(), - referenceUpdater); + referenceUpdater, + canSendFrom); + + outbox = mock(MessageManager.class); + when(mockedMailboxIdFactory.fromString(OUTBOX_ID.serialize())) + .thenReturn(OUTBOX_ID); + when(mockedMailboxManager.getMailbox(OUTBOX_ID, session)) + .thenReturn(outbox); + + when(outbox.getId()).thenReturn(OUTBOX_ID); + when(outbox.getMailboxPath()).thenReturn(MailboxPath.forUser(USER, OUTBOX)); + + when(outbox.appendMessage(any(MessageManager.AppendCommand.class), any(MailboxSession.class))) + .thenReturn(new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1))); + + drafts = mock(MessageManager.class); + when(drafts.getId()).thenReturn(DRAFTS_ID); + when(drafts.getMailboxPath()).thenReturn(MailboxPath.forUser(USER, DRAFTS)); + optionalOutbox = Optional.of(outbox); + optionalDrafts = Optional.of(drafts); + } + + @Test + public void processShouldReturnEmptyUpdatedWhenRequestHasEmptyUpdate() { SetMessagesRequest requestWithEmptyUpdate = SetMessagesRequest.builder().build(); SetMessagesResponse result = sut.process(requestWithEmptyUpdate, null); @@ -81,18 +233,15 @@ public class SetMessagesUpdateProcessorTest { when(mockConverter.fromJsonNode(any(ObjectNode.class))) .thenReturn(mockInvalidPatch); - MessageIdManager messageIdManager = null; - SystemMailboxesProvider systemMailboxesProvider = null; - MailboxId.Factory mailboxIdFactory = null; - MessageSender messageSender = null; - ReferenceUpdater referenceUpdater = null; + SetMessagesUpdateProcessor sut = new SetMessagesUpdateProcessor(mockConverter, - messageIdManager, - systemMailboxesProvider, - mailboxIdFactory, + mockMessageIdManager, + fakeSystemMailboxesProvider, + mockedMailboxIdFactory, messageSender, new RecordingMetricFactory(), - referenceUpdater); + referenceUpdater, + canSendFrom); MessageId requestMessageId = TestMessageId.of(1); SetMessagesRequest requestWithInvalidUpdate = SetMessagesRequest.builder() .update(ImmutableMap.of(requestMessageId, JsonNodeFactory.instance.objectNode())) @@ -109,4 +258,24 @@ public class SetMessagesUpdateProcessorTest { assertThat(result.getUpdated()).isEmpty(); } + @Test + public void assertUserCanSendFromShouldNotThrowWhenSenderIsAnAliasOfTheConnectedUser() throws RecipientRewriteTableException, AddressException { + Username sender = Username.of("al...@example.com"); + + recipientRewriteTable.addAliasMapping(MappingSource.fromUser("alias", "example.com"), USER.asString()); + + assertThatCode(() -> sut.assertUserCanSendFrom(USER, Optional.of(sender))) + .doesNotThrowAnyException(); + } + + @Test + public void assertUserCanSendFromShouldThrowWhenSenderIsAnAliasOfAnotherUser() throws RecipientRewriteTableException, AddressException { + Username sender = Username.of("al...@example.com"); + + recipientRewriteTable.addAliasMapping(MappingSource.fromUser("alias", "example.com"), OTHER_USER.asString()); + + assertThatThrownBy(() -> sut.assertUserCanSendFrom(USER, Optional.of(sender))) + .isInstanceOf(MailboxSendingNotAllowedException.class); + } + } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org