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


The following commit(s) were added to refs/heads/master by this push:
     new 64afb1e31c [AUDIT TRAIL] Log message moves in JMAP
64afb1e31c is described below

commit 64afb1e31cb3f8752680b8da420ddccbde1dcdba
Author: Benoit TELLIER <[email protected]>
AuthorDate: Mon Mar 23 15:29:20 2026 +0100

    [AUDIT TRAIL] Log message moves in JMAP
---
 .../james/jmap/method/EmailSetUpdatePerformer.scala  | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetUpdatePerformer.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetUpdatePerformer.scala
index 8ace8e1f95..4aea71c270 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetUpdatePerformer.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetUpdatePerformer.scala
@@ -32,6 +32,7 @@ import org.apache.james.mailbox.MessageManager.FlagsUpdateMode
 import org.apache.james.mailbox.exception.{MailboxNotFoundException, 
OverQuotaException}
 import org.apache.james.mailbox.model.{ComposedMessageIdWithMetaData, 
MailboxId, MessageId, MessageRange}
 import org.apache.james.mailbox.{MailboxManager, MailboxSession, 
MessageIdManager, MessageManager}
+import org.apache.james.util.AuditTrail
 import org.slf4j.LoggerFactory
 import play.api.libs.json.JsObject
 import reactor.core.scala.publisher.{SFlux, SMono}
@@ -176,6 +177,7 @@ class EmailSetUpdatePerformer @Inject() (serializer: 
EmailSetSerializer,
 
     SMono(mailboxManager.moveMessagesReactive(ranges.asJava, mailboxId, 
targetId, session)).`then`()
       .`then`(SMono.just(messageIds.map(EmailUpdateSuccess)))
+      .doOnSuccess(_ => auditMove(messageIds, Seq(mailboxId), Seq(targetId), 
session))
       .onErrorResume(e => SMono.just(messageIds.map(id => 
EmailUpdateFailure(EmailSet.asUnparsed(id), e))))
   }
 
@@ -222,6 +224,7 @@ class EmailSetUpdatePerformer @Inject() (serializer: 
EmailSetSerializer,
           .asFlagsWithRecentAndDeletedFrom(originalFlags)
         SMono(messageIdManager.updateEmail(messageId, targetIds.value.asJava, 
newFlags, FlagsUpdateMode.REPLACE, session))
           .`then`(SMono.just[EmailUpdateResult](EmailUpdateSuccess(messageId)))
+          .doOnSuccess(_ => auditMove(Seq(messageId), mailboxIds.value, 
targetIds.value, session))
           .onErrorResume(e => 
SMono.just[EmailUpdateResult](EmailUpdateFailure(EmailSet.asUnparsed(messageId),
 e)))
           
.switchIfEmpty(SMono.just[EmailUpdateResult](EmailUpdateSuccess(messageId)))
       } else {
@@ -241,12 +244,29 @@ class EmailSetUpdatePerformer @Inject() (serializer: 
EmailSetSerializer,
       val targetIds = update.mailboxIdsTransformation.apply(mailboxIds)
       SMono(messageIdManager.setInMailboxesReactive(messageId, 
targetIds.value.asJava, session))
         .`then`(SMono.just[EmailUpdateResult](EmailUpdateSuccess(messageId)))
+        .doOnSuccess(_ => auditMove(Seq(messageId), mailboxIds.value, 
targetIds.value, session))
         .onErrorResume(e => 
SMono.just[EmailUpdateResult](EmailUpdateFailure(EmailSet.asUnparsed(messageId),
 e)))
         
.switchIfEmpty(SMono.just[EmailUpdateResult](EmailUpdateSuccess(messageId)))
     } else {
       SMono.just[EmailUpdateResult](EmailUpdateSuccess(messageId))
     }
 
+  private def auditMove(messageIds: Iterable[MessageId], fromIds: 
Iterable[MailboxId], toIds: Iterable[MailboxId], session: MailboxSession): Unit 
= {
+    val from = fromIds.map(_.serialize()).mkString(", ")
+    val to = toIds.map(_.serialize()).mkString(", ")
+    messageIds.foreach { msgId =>
+      AuditTrail.entry()
+        .username(() => session.getUser.asString())
+        .protocol("JMAP")
+        .action("Email/set move")
+        .parameters(() => com.google.common.collect.ImmutableMap.of(
+          "messageId", msgId.serialize(),
+          "from", from,
+          "to", to))
+        .log("Email moved between mailboxes")
+    }
+  }
+
   private def updateFlags(messageId: MessageId, update: 
ValidatedEmailSetUpdate, mailboxIds: MailboxIds, storedMetaData: 
List[ComposedMessageIdWithMetaData], session: MailboxSession): 
SMono[EmailUpdateResult] =
     if (update.update.isFlagUpdate) {
       val originalFlags: Flags = storedMetaData


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to