JAMES-2220 Implement sending draft with move to Outbox
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/29d233cc Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/29d233cc Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/29d233cc Branch: refs/heads/master Commit: 29d233cc497e6e7a6f62f38285e751940fae5e48 Parents: 85ad874 Author: Matthieu Baechler <matth...@apache.org> Authored: Tue Nov 14 21:40:30 2017 +0100 Committer: Antoine Duprat <adup...@linagora.com> Committed: Thu Nov 16 12:30:31 2017 +0100 ---------------------------------------------------------------------- .../org/apache/james/server/core/MailImpl.java | 29 +++ .../integration/SetMessagesMethodTest.java | 214 ++++++++++++++++++- .../exceptions/InvalidOutboxMoveException.java | 23 ++ .../james/jmap/methods/MessageSender.java | 19 +- .../methods/SetMessagesUpdateProcessor.java | 102 +++++++-- .../org/apache/james/jmap/send/MailFactory.java | 2 + .../methods/SetMessagesUpdateProcessorTest.java | 16 +- 7 files changed, 386 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/29d233cc/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java ---------------------------------------------------------------------- diff --git a/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java b/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java index c5c7f9b..af9020a 100644 --- a/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java +++ b/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java @@ -28,6 +28,7 @@ import java.io.ObjectOutputStream; import java.io.OptionalDataException; import java.io.OutputStream; import java.io.Serializable; +import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -35,7 +36,10 @@ import java.util.Iterator; import java.util.Map; import java.util.UUID; +import javax.mail.Address; import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.mail.internet.ParseException; @@ -49,6 +53,8 @@ import org.apache.mailet.PerRecipientHeaders.Header; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.github.fge.lambdas.Throwing; +import com.github.steveash.guavate.Guavate; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -84,6 +90,29 @@ public class MailImpl implements Disposable, Mail { return new MailImpl(mail, deriveNewName(mail.getName())); } + public static MailImpl fromMimeMessage(String name, MimeMessage mimeMessage) throws MessagingException { + MailAddress sender = getSender(mimeMessage); + ImmutableList<MailAddress> recipients = getRecipients(mimeMessage); + return new MailImpl(name, sender, recipients, mimeMessage); + } + + private static ImmutableList<MailAddress> getRecipients(MimeMessage mimeMessage) throws MessagingException { + return Arrays.stream(mimeMessage.getAllRecipients()) + .map(Throwing.function(MailImpl::castToMailAddress).sneakyThrow()) + .collect(Guavate.toImmutableList()); + } + + private static MailAddress getSender(MimeMessage mimeMessage) throws MessagingException { + Address[] sender = mimeMessage.getFrom(); + Preconditions.checkArgument(sender.length == 1); + return castToMailAddress(sender[0]); + } + + private static MailAddress castToMailAddress(Address address) throws AddressException { + Preconditions.checkArgument(address instanceof InternetAddress); + return new MailAddress((InternetAddress) address); + } + /** * Create a unique new primary key name for the given MailObject. * Detect if this has been called more than 8 times recursively http://git-wip-us.apache.org/repos/asf/james-project/blob/29d233cc/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java index c29302b..924c4a2 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java @@ -198,6 +198,11 @@ public abstract class SetMessagesMethodTest { return getMailboxId(accessToken, Role.DRAFTS); } + private String getInboxId(AccessToken accessToken) { + return getMailboxId(accessToken, Role.INBOX); + } + + private String getMailboxId(AccessToken accessToken, Role role) { return getAllMailboxesIds(accessToken).stream() .filter(x -> x.get("role").equalsIgnoreCase(role.serialize())) @@ -564,7 +569,7 @@ public abstract class SetMessagesMethodTest { } @Test - @Ignore("gitlab-446 should allowed in drafts mailbox, rejected outside") + @Ignore("JAMES-2220 should allowed in drafts mailbox, rejected outside") public void setMessagesShouldReturnAnErrorWhenKeywordsWithAddingDraftArePassed() throws MailboxException { mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, "mailbox"); @@ -700,7 +705,7 @@ public abstract class SetMessagesMethodTest { } @Test - @Ignore("gitlab-446 should allowed outside drafts mailbox, rejected inside") + @Ignore("JAMES-2220 should allowed outside drafts mailbox, rejected inside") public void setMessagesShouldReturnAnErrorWhenKeywordsWithRemoveDraftArePassed() throws MailboxException { mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, "mailbox"); @@ -1445,7 +1450,7 @@ public abstract class SetMessagesMethodTest { } @Test - public void setMessageShouldAllowDraftCreationWhenUsingIsDraftProperty() { + public void setMessagesShouldAllowDraftCreationWhenUsingIsDraftProperty() { String messageCreationId = "creationId1337"; String fromAddress = USERNAME; String requestBody = "[" + @@ -1894,6 +1899,209 @@ public abstract class SetMessagesMethodTest { } @Test + public void setMessagesShouldSendMessageByMovingDraftToOutbox() { + String draftCreationId = "creationId1337"; + String fromAddress = USERNAME; + String createDraft = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"create\": { \"" + draftCreationId + "\" : {" + + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB + "\"}]," + + " \"subject\": \"subject\"," + + " \"keywords\": {\"$Draft\": true}," + + " \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + String draftId = + with() + .header("Authorization", accessToken.serialize()) + .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.serialize()) + .body(moveDraftToOutBox) + .post("/jmap"); + + calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken)); + } + + @Test + @Ignore("WIP") + public void setMessagesShouldRejectDraftCopyToOutbox() { + String draftCreationId = "creationId1337"; + String fromAddress = USERNAME; + String createDraft = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"create\": { \"" + draftCreationId + "\" : {" + + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB + "\"}]," + + " \"subject\": \"subject\"," + + " \"keywords\": {\"$Draft\": true}," + + " \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + String draftId = + with() + .header("Authorization", accessToken.serialize()) + .body(createDraft) + .post("/jmap") + .then() + .extract() + .path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id"); + + String copyDraftToOutBox = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"update\": { \"" + draftId + "\" : {" + + " \"keywords\": {}," + + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\",\"" + getDraftId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .header("Authorization", accessToken.serialize()) + .body(copyDraftToOutBox) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messagesSet")) + .body(ARGUMENTS + ".notUpdated", hasKey(draftId)) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].type", equalTo("invalidArguments")) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].description", endsWith("One can not have a message in mailboxes that don't have all the `draft` role")) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].properties", hasSize(1)) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].properties", contains("mailboxIds")) + .body(ARGUMENTS + ".created", aMapWithSize(0)); + } + + @Test + public void setMessagesShouldRejectMovingMessageToOutboxWhenNotInDraft() throws MailboxException { + ComposedMessageId message = mailboxProbe.appendMessage(USERNAME, MailboxPath.forUser(USERNAME, MailboxConstants.INBOX), + new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); + await(); + + String messageId = message.getMessageId().serialize(); + String moveMessageToOutBox = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"update\": { \"" + messageId + "\" : {" + + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .header("Authorization", accessToken.serialize()) + .body(moveMessageToOutBox) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messagesSet")) + .body(ARGUMENTS + ".notUpdated", hasKey(messageId)) + .body(ARGUMENTS + ".notUpdated[\""+messageId+"\"].type", equalTo("invalidProperties")) + .body(ARGUMENTS + ".notUpdated[\""+messageId+"\"].description", endsWith("only drafts can be moved to Outbox")) + .body(ARGUMENTS + ".notUpdated[\""+messageId+"\"].properties", hasSize(1)) + .body(ARGUMENTS + ".notUpdated[\""+messageId+"\"].properties", contains("mailboxIds")) + .body(ARGUMENTS + ".created", aMapWithSize(0)); + } + + @Test + public void setMessagesShouldRejectMovingMessageToOutboxWhenDraftKeyworkSet() throws MailboxException { + String draftCreationId = "creationId1337"; + String fromAddress = USERNAME; + String createDraft = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"create\": { \"" + draftCreationId + "\" : {" + + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB + "\"}]," + + " \"subject\": \"subject\"," + + " \"keywords\": {\"$Draft\": true}," + + " \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + String draftId = + with() + .header("Authorization", accessToken.serialize()) + .body(createDraft) + .post("/jmap") + .then() + .extract() + .path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id"); + + String moveDraftToOutBox = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"update\": { \"" + draftId + "\" : {" + + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + + given() + .header("Authorization", accessToken.serialize()) + .body(moveDraftToOutBox) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messagesSet")) + .body(ARGUMENTS + ".notUpdated", hasKey(draftId)) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].type", equalTo("invalidProperties")) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].description", endsWith("message with $Draft keyword can't be moved outside outbox")) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].properties", hasSize(1)) + .body(ARGUMENTS + ".notUpdated[\""+draftId+"\"].properties", contains("keywords")) + .body(ARGUMENTS + ".created", aMapWithSize(0)); + } + + + @Test public void setMessagesShouldSupportArbitraryMessageId() { String messageCreationId = "1717fcd1-603e-44a5-b2a6-1234dbcd5723"; String fromAddress = USERNAME; http://git-wip-us.apache.org/repos/asf/james-project/blob/29d233cc/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/InvalidOutboxMoveException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/InvalidOutboxMoveException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/InvalidOutboxMoveException.java new file mode 100644 index 0000000..a70a750 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/InvalidOutboxMoveException.java @@ -0,0 +1,23 @@ +/**************************************************************** + * 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.jmap.exceptions; + +public class InvalidOutboxMoveException extends RuntimeException { +} http://git-wip-us.apache.org/repos/asf/james-project/blob/29d233cc/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java index 3ce4086..d6835c9 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java @@ -22,6 +22,7 @@ package org.apache.james.jmap.methods; import javax.inject.Inject; import javax.mail.MessagingException; +import org.apache.james.core.MailAddress; import org.apache.james.jmap.model.Envelope; import org.apache.james.jmap.model.MessageFactory; import org.apache.james.jmap.send.MailFactory; @@ -30,6 +31,7 @@ import org.apache.james.jmap.send.MailSpool; import org.apache.james.lifecycle.api.LifecycleUtil; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MessageId; import org.apache.mailet.Mail; public class MessageSender { @@ -55,9 +57,22 @@ public class MessageSender { } } + public void sendMessage(MessageId messageId, + Mail mail, + MailboxSession session) throws MailboxException, MessagingException { + assertUserIsSender(session, mail.getSender()); + MailMetadata metadata = new MailMetadata(messageId, session.getUser().getUserName()); + mailSpool.send(mail, metadata); + } + private void assertUserIsInSenders(Envelope envelope, MailboxSession session) throws MailboxSendingNotAllowedException { - String allowedSender = session.getUser().getUserName(); - if (!session.getUser().isSameUser(envelope.getFrom().asString())) { + MailAddress sender = envelope.getFrom(); + assertUserIsSender(session, sender); + } + + private void assertUserIsSender(MailboxSession session, MailAddress sender) throws MailboxSendingNotAllowedException { + if (!session.getUser().isSameUser(sender.asString())) { + String allowedSender = session.getUser().getUserName(); throw new MailboxSendingNotAllowedException(allowedSender); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/29d233cc/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java index c54deec..e3865c0 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java @@ -21,16 +21,23 @@ package org.apache.james.jmap.methods; import static org.apache.james.jmap.methods.Method.JMAP_PREFIX; +import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; import javax.mail.Flags; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; import org.apache.james.jmap.exceptions.DraftMessageMailboxUpdateException; +import org.apache.james.jmap.exceptions.InvalidOutboxMoveException; import org.apache.james.jmap.model.MessageProperties; import org.apache.james.jmap.model.SetError; import org.apache.james.jmap.model.SetMessagesRequest; @@ -42,6 +49,7 @@ import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageIdManager; import org.apache.james.mailbox.MessageManager; import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.exception.MailboxNotFoundException; import org.apache.james.mailbox.model.FetchGroupImpl; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MailboxId.Factory; @@ -49,6 +57,8 @@ import org.apache.james.mailbox.model.MessageId; 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.server.core.MailImpl; +import org.apache.james.util.OptionalUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,17 +77,22 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { private final SystemMailboxesProvider systemMailboxesProvider; private final Factory mailboxIdFactory; private final MetricFactory metricFactory; + private final MessageSender messageSender; @Inject @VisibleForTesting SetMessagesUpdateProcessor( - UpdateMessagePatchConverter updatePatchConverter, - MessageIdManager messageIdManager, - SystemMailboxesProvider systemMailboxesProvider, Factory mailboxIdFactory, MetricFactory metricFactory) { + UpdateMessagePatchConverter updatePatchConverter, + MessageIdManager messageIdManager, + SystemMailboxesProvider systemMailboxesProvider, + Factory mailboxIdFactory, + MessageSender messageSender, + MetricFactory metricFactory) { this.updatePatchConverter = updatePatchConverter; this.messageIdManager = messageIdManager; this.systemMailboxesProvider = systemMailboxesProvider; this.mailboxIdFactory = mailboxIdFactory; this.metricFactory = metricFactory; + this.messageSender = messageSender; } @Override @@ -116,10 +131,18 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { } else { builder.updated(ImmutableList.of(messageId)); } + sendMessageWhenOutboxInTargetMailboxIds(messageId, updateMessagePatch, mailboxSession, builder); } } catch (DraftMessageMailboxUpdateException e) { handleDraftMessageMailboxUpdateException(messageId, builder, e); - } catch (MailboxException e) { + } catch (InvalidOutboxMoveException e) { + ValidationResult invalidPropertyMailboxIds = ValidationResult.builder() + .property(MessageProperties.MessageProperty.mailboxIds.asFieldName()) + .message("only drafts can be moved to Outbox") + .build(); + + handleInvalidRequest(builder, messageId, ImmutableList.of(invalidPropertyMailboxIds)); + } catch (MailboxException|IOException|MessagingException e) { handleMessageUpdateException(messageId, builder, e); } catch (IllegalArgumentException e) { ValidationResult invalidPropertyKeywords = ValidationResult.builder() @@ -129,24 +152,54 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { handleInvalidRequest(builder, messageId, ImmutableList.of(invalidPropertyKeywords)); } + } + private void sendMessageWhenOutboxInTargetMailboxIds(MessageId messageId, UpdateMessagePatch updateMessagePatch, MailboxSession mailboxSession, SetMessagesResponse.Builder builder) throws MailboxException, MessagingException, IOException { + if (isTargetingOutbox(mailboxSession, listTargetMailboxIds(updateMessagePatch))) { + Optional<MessageResult> messagesToSend = + messageIdManager.getMessages( + ImmutableList.of(messageId), FetchGroupImpl.FULL_CONTENT, mailboxSession) + .stream() + .findFirst(); + if (messagesToSend.isPresent()) { + MailImpl mail = buildMailFromMessage(messagesToSend.get()); + messageSender.sendMessage(messageId, mail, mailboxSession); + } else { + addMessageIdNotFoundToResponse(messageId, builder); + } + } } private void assertValidUpdate(List<MessageResult> messagesToBeUpdated, UpdateMessagePatch updateMessagePatch, MailboxSession session) throws MailboxException { List<MailboxId> draftMailboxes = mailboxIdFor(Role.DRAFTS, session); + List<MailboxId> outboxMailboxes = mailboxIdFor(Role.OUTBOX, session); + + ImmutableList<MailboxId> previousMailboxes = messagesToBeUpdated.stream() + .map(MessageResult::getMailboxId) + .collect(Guavate.toImmutableList()); List<Flags> futureFlags = patchFlags(messagesToBeUpdated, updateMessagePatch); - List<MailboxId> targetMailboxes = getTargetedMailboxes(messagesToBeUpdated, updateMessagePatch); + List<MailboxId> targetMailboxes = getTargetedMailboxes(previousMailboxes, updateMessagePatch); + boolean originIsDraft = previousMailboxes.stream().allMatch(draftMailboxes::contains); + boolean targetIsOutbox = targetMailboxes.stream().anyMatch(outboxMailboxes::contains); boolean targetHasDraft = targetMailboxes.stream().anyMatch(draftMailboxes::contains); boolean targetHasNonDraft = targetMailboxes.stream().anyMatch(id -> !draftMailboxes.contains(id)); - assertValidUpdate(futureFlags, targetHasDraft, targetHasNonDraft); + assertValidUpdate(futureFlags, targetHasDraft, targetHasNonDraft, targetIsOutbox, originIsDraft); } - private void assertValidUpdate(List<Flags> futureFlags, boolean targetHasDraft, boolean targetHasNonDraft) throws DraftMessageMailboxUpdateException { + private void assertValidUpdate(List<Flags> futureFlags, boolean targetHasDraft, boolean targetHasNonDraft, + boolean targetIsOutbox, boolean originIsDraft) throws DraftMessageMailboxUpdateException { assertMessageIsNotInDraftAndNonDraftMailboxes(targetHasDraft, targetHasNonDraft); assertNoNonDraftMessageInsideDraftMailbox(futureFlags, targetHasDraft); assertNoDraftMessageOutOfDraftMailbox(futureFlags, targetHasNonDraft); + assertOutboxMoveOnlyFromDraft(targetIsOutbox, originIsDraft); + } + + private void assertOutboxMoveOnlyFromDraft(boolean targetIsOutbox, boolean originIsDraft) { + if (targetIsOutbox && !originIsDraft) { + throw new InvalidOutboxMoveException(); + } } private void assertMessageIsNotInDraftAndNonDraftMailboxes(boolean targetHasDraft, boolean targetHasNonDraft) throws DraftMessageMailboxUpdateException { @@ -173,10 +226,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { } } - private List<MailboxId> getTargetedMailboxes(List<MessageResult> messagesToBeUpdated, UpdateMessagePatch updateMessagePatch) { - ImmutableList<MailboxId> previousMailboxes = messagesToBeUpdated.stream() - .map(MessageResult::getMailboxId) - .collect(Guavate.toImmutableList()); + private List<MailboxId> getTargetedMailboxes(ImmutableList<MailboxId> previousMailboxes, UpdateMessagePatch updateMessagePatch) { return updateMessagePatch.getMailboxIds() .map(ids -> ids.stream().map(mailboxIdFactory::fromString).collect(Guavate.toImmutableList())) .orElse(previousMailboxes); @@ -195,6 +245,34 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { .collect(Guavate.toImmutableList()); } + private MailImpl buildMailFromMessage(MessageResult message) throws MessagingException, IOException, MailboxException { + return MailImpl.fromMimeMessage(message.getMessageId().serialize(), + new MimeMessage( + Session.getDefaultInstance(new Properties()), + message.getFullContent().getInputStream())); + } + + private Set<MailboxId> listTargetMailboxIds(UpdateMessagePatch updateMessagePatch) { + return OptionalUtils.toStream(updateMessagePatch.getMailboxIds()) + .flatMap(Collection::stream) + .map(mailboxIdFactory::fromString) + .collect(Guavate.toImmutableSet()); + } + + private boolean isTargetingOutbox(MailboxSession mailboxSession, Set<MailboxId> targetMailboxIds) throws MailboxException { + Set<MailboxId> outboxes = listMailboxIdsForRole(mailboxSession, Role.OUTBOX); + if (outboxes.isEmpty()) { + throw new MailboxNotFoundException("At least one outbox should be accessible"); + } + return targetMailboxIds.stream().anyMatch(outboxes::contains); + } + + private Set<MailboxId> listMailboxIdsForRole(MailboxSession session, Role role) throws MailboxException { + return systemMailboxesProvider.getMailboxByRole(role, session) + .map(MessageManager::getId) + .collect(Guavate.toImmutableSet()); + } + private Stream<MailboxException> updateFlags(MessageId messageId, UpdateMessagePatch updateMessagePatch, MailboxSession mailboxSession, MessageResult messageResult) { try { if (!updateMessagePatch.isFlagsIdentity()) { @@ -240,7 +318,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { private SetMessagesResponse.Builder handleMessageUpdateException(MessageId messageId, SetMessagesResponse.Builder builder, - MailboxException e) { + Exception e) { LOGGER.error("An error occurred when updating a message", e); return builder.notUpdated(ImmutableMap.of(messageId, SetError.builder() .type("anErrorOccurred") http://git-wip-us.apache.org/repos/asf/james-project/blob/29d233cc/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java index 3d156d3..966f659 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/MailFactory.java @@ -40,4 +40,6 @@ public class MailFactory { message.getContent()); } + + } http://git-wip-us.apache.org/repos/asf/james-project/blob/29d233cc/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java index de2f809..2264fda 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessorTest.java @@ -49,10 +49,13 @@ public class SetMessagesUpdateProcessorTest { MessageIdManager messageIdManager = null; SystemMailboxesProvider systemMailboxesProvider = null; MailboxId.Factory mailboxIdFactory = null; + MessageSender messageSender = null; SetMessagesUpdateProcessor sut = new SetMessagesUpdateProcessor(updatePatchConverter, messageIdManager, systemMailboxesProvider, - mailboxIdFactory, new NoopMetricFactory()); + mailboxIdFactory, + messageSender, + new NoopMetricFactory()); SetMessagesRequest requestWithEmptyUpdate = SetMessagesRequest.builder().build(); SetMessagesResponse result = sut.process(requestWithEmptyUpdate, null); @@ -76,7 +79,16 @@ public class SetMessagesUpdateProcessorTest { when(mockConverter.fromJsonNode(any(ObjectNode.class))) .thenReturn(mockInvalidPatch); - SetMessagesUpdateProcessor sut = new SetMessagesUpdateProcessor(mockConverter, null, null, null, new NoopMetricFactory()); + MessageIdManager messageIdManager = null; + SystemMailboxesProvider systemMailboxesProvider = null; + MailboxId.Factory mailboxIdFactory = null; + MessageSender messageSender = null; + SetMessagesUpdateProcessor sut = new SetMessagesUpdateProcessor(mockConverter, + messageIdManager, + systemMailboxesProvider, + mailboxIdFactory, + messageSender, + new NoopMetricFactory()); MessageId requestMessageId = TestMessageId.of(1); SetMessagesRequest requestWithInvalidUpdate = SetMessagesRequest.builder() .update(ImmutableMap.of(requestMessageId, JsonNodeFactory.instance.objectNode())) --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org