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 7b6d4c2534 JAMES-3434 EmailSubmission/set should forbid sending email 
without Mi… (#2624)
7b6d4c2534 is described below

commit 7b6d4c253485ee096206551505f29fa13fa21ce6
Author: Rene Cordier <rcord...@linagora.com>
AuthorDate: Thu Feb 6 21:41:40 2025 +0700

    JAMES-3434 EmailSubmission/set should forbid sending email without Mi… 
(#2624)
---
 .../EmailSubmissionSetMethodContract.scala         | 55 ++++++++++++++++++++++
 .../jmap/method/EmailSubmissionSetMethod.scala     | 29 ++++++++----
 2 files changed, 75 insertions(+), 9 deletions(-)

diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
index 3903deec50..85515ba42f 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
@@ -1877,6 +1877,61 @@ trait EmailSubmissionSetMethodContract {
                    |}""".stripMargin)
   }
 
+  @Test
+  def setShouldRejectWhenMissingFromMimeField(server: GuiceJamesServer): Unit 
= {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setTo(ANDRE.asString)
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+    val messageId: MessageId = 
server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), 
bobDraftsPath, AppendCommand.builder()
+        .build(message))
+      .getMessageId
+
+    val requestBob =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", 
"urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+         |  "methodCalls": [
+         |     ["EmailSubmission/set", {
+         |       "accountId": "$ACCOUNT_ID",
+         |       "create": {
+         |         "k1490": {
+         |           "emailId": "${messageId.serialize}",
+         |           "envelope": {
+         |             "mailFrom": {"email": "${BOB.asString}"},
+         |             "rcptTo": [{"email": "${ANDRE.asString}"}]
+         |           }
+         |         }
+         |    }
+         |  }, "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(requestBob)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated")
+      .isEqualTo("""{
+                   |  "k1490": {
+                   |    "type": "forbiddenFrom",
+                   |    "description": "Attempt to send a mail whose 
MimeMessage From is missing"
+                   |  }
+                   |}""".stripMargin)
+  }
+
   @Test
   def setShouldRejectOtherUserUsageInFromEnvelopeField(server: 
GuiceJamesServer): Unit = {
     val message: Message = Message.Builder
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
index 31e1c287fc..55c76d6449 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
@@ -98,6 +98,10 @@ object EmailSubmissionSetMethod {
         SetError(EmailSubmissionSetMethod.forbiddenFrom,
           SetErrorDescription(s"Attempt to send a mail whose envelope From not 
allowed for connected user: ${e.from}"),
           Some(Properties("envelope.mailFrom")))
+      case e: ForbiddenHeaderFromException =>
+        LOGGER.warn(s"Attempt to send a mail whose MimeMessage From is 
missing")
+        SetError(EmailSubmissionSetMethod.forbiddenFrom,
+          SetErrorDescription(s"Attempt to send a mail whose MimeMessage From 
is missing"), None)
       case _: MessageNotFoundException =>
         LOGGER.info(" EmailSubmission/set failed as the underlying email could 
not be found")
         SetError(SetError.invalidArgumentValue,
@@ -157,6 +161,7 @@ case class EmailSubmissionCreationParseException(setError: 
SetError) extends Exc
 case class NoRecipientException() extends Exception
 case class ForbiddenFromException(from: String) extends Exception
 case class ForbiddenMailFromException(from: List[String]) extends Exception
+case class ForbiddenHeaderFromException() extends Exception
 
 case class MessageMimeMessageSource(id: String, message: MessageResult) 
extends MimeMessageSource {
   override def getSourceId: String = id
@@ -354,15 +359,21 @@ class EmailSubmissionSetMethod @Inject()(serializer: 
EmailSubmissionSetSerialize
 
   def validateMimeMessages(mimeMessage: MimeMessage) : SMono[MimeMessage] = 
validateMailAddressHeaderMimeMessage(mimeMessage)
   private def validateMailAddressHeaderMimeMessage(mimeMessage: MimeMessage): 
SMono[MimeMessage] =
-    SFlux.fromIterable(Map("to" -> 
Option(mimeMessage.getRecipients(RecipientType.TO)).toList.flatten,
-        "cc" -> 
Option(mimeMessage.getRecipients(RecipientType.CC)).toList.flatten,
-        "bcc" -> 
Option(mimeMessage.getRecipients(RecipientType.BCC)).toList.flatten,
-        "from" -> Option(mimeMessage.getFrom).toList.flatten,
-        "sender" -> Option(mimeMessage.getSender).toList,
-        "replyTo" -> Option(mimeMessage.getReplyTo).toList.flatten))
-      .doOnNext { case (headerName, addresses) => (headerName, 
addresses.foreach(address => validateMailAddress(headerName, address))) }
-      .`then`()
-      .`then`(SMono.just(mimeMessage))
+    Option(mimeMessage.getFrom) match {
+      case Some(from) if from.nonEmpty => SFlux.fromIterable(Map(
+          "to" -> 
Option(mimeMessage.getRecipients(RecipientType.TO)).toList.flatten,
+          "cc" -> 
Option(mimeMessage.getRecipients(RecipientType.CC)).toList.flatten,
+          "bcc" -> 
Option(mimeMessage.getRecipients(RecipientType.BCC)).toList.flatten,
+          "from" -> from.toList,
+          "sender" -> Option(mimeMessage.getSender).toList,
+          "replyTo" -> Option(mimeMessage.getReplyTo).toList.flatten))
+        .doOnNext { case (headerName, addresses) => (headerName, 
addresses.foreach(address => validateMailAddress(headerName, address))) }
+        .`then`()
+        .`then`(SMono.just(mimeMessage))
+
+      case _ => SMono.error(ForbiddenHeaderFromException())
+    }
+
 
   private def validateMailAddress(headName: String, address: Address): 
MailAddress =
     Try(new MailAddress(asString(address))) match {


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org
For additional commands, e-mail: notifications-h...@james.apache.org

Reply via email to