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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]