This is an automated email from the ASF dual-hosted git repository. chibenwa pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 534980c8973ec3339cc66a54d25a835687a374a0 Author: Benoit TELLIER <[email protected]> AuthorDate: Thu Sep 10 14:00:29 2026 +0200 JAMES-4228 ValidRcptEmailSubmissionSetValidation integration test --- .../EmailSubmissionSetMethodContract.scala | 53 +++++++ .../EmailSubmissionSetValidateRcptContract.scala | 162 +++++++++++++++++++++ .../MemoryEmailSubmissionSetValidateRcptTest.java | 48 ++++++ 3 files changed, 263 insertions(+) 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 8cfe655dc8..879e41ffb4 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 @@ -2430,4 +2430,57 @@ trait EmailSubmissionSetMethodContract { | "hasAttachment": true |}]""".stripMargin) } + + @Test + def setShouldAcceptUnknownLocalRecipientWhenRecipientValidationIsDisabled(server: GuiceJamesServer): Unit = { + val message: Message = Message.Builder + .of + .setSubject("test") + .setSender(bobUsername.asString) + .setFrom(bobUsername.asString) + .setTo(s"unknown@${DOMAIN.asString}") + .setBody("testmail", StandardCharsets.UTF_8) + .build + + val bobDraftsPath = MailboxPath.forUser(bobUsername, DefaultMailboxes.DRAFTS) + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath) + val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(bobUsername.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": "$bobAccountId", + | "create": { + | "k1490": { + | "emailId": "${messageId.serialize}", + | "envelope": { + | "mailFrom": {"email": "${bobUsername.asString}"}, + | "rcptTo": [{"email": "unknown@${DOMAIN.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].created") + .isObject + .containsKey("k1490") + } } 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/EmailSubmissionSetValidateRcptContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetValidateRcptContract.scala new file mode 100644 index 0000000000..bf0067572c --- /dev/null +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetValidateRcptContract.scala @@ -0,0 +1,162 @@ +/**************************************************************** + * 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.rfc8621.contract + +import java.nio.charset.StandardCharsets + +import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT +import io.restassured.RestAssured.{`given`, requestSpecification} +import io.restassured.http.ContentType.JSON +import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson +import org.apache.http.HttpStatus.SC_OK +import org.apache.james.GuiceJamesServer +import org.apache.james.jmap.core.JmapRfc8621Configuration +import org.apache.james.jmap.http.UserCredential +import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder} +import org.apache.james.mailbox.DefaultMailboxes +import org.apache.james.mailbox.MessageManager.AppendCommand +import org.apache.james.mailbox.model.MailboxPath +import org.apache.james.mime4j.dom.Message +import org.apache.james.modules.MailboxProbeImpl +import org.apache.james.utils.DataProbeImpl +import org.junit.jupiter.api.{BeforeEach, Test} + +object EmailSubmissionSetValidateRcptContract { + val configuration: JmapRfc8621Configuration = JmapRfc8621Configuration( + urlPrefixString = "http://127.0.0.1", + websocketPrefixString = "ws://127.0.0.1", + validateRecipientsOnSend = true) +} + +/** + * `EmailSubmission/set` behaviour when `send.validate.rcpt` is turned on. + */ +trait EmailSubmissionSetValidateRcptContract { + @BeforeEach + def setUp(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[DataProbeImpl]) + .fluent + .addDomain(DOMAIN.asString) + .addUser(BOB.asString, BOB_PASSWORD) + .addUser(ANDRE.asString, ANDRE_PASSWORD) + + server.getProbe(classOf[MailboxProbeImpl]) + .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)) + + requestSpecification = baseRequestSpecBuilder(server) + .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD))) + .build + } + + @Test + def setShouldAcceptRecipientHavingALocalMailbox(server: GuiceJamesServer): Unit = + assertThatJson(submit(server, ANDRE.asString)) + .inPath("methodResponses[0][1].created") + .isObject + .containsKey("k1490") + + @Test + def setShouldAcceptRecipientOfARemoteDomain(server: GuiceJamesServer): Unit = + assertThatJson(submit(server, "[email protected]")) + .inPath("methodResponses[0][1].created") + .isObject + .containsKey("k1490") + + @Test + def setShouldAcceptRecipientResolvedByRecipientRewriteTable(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[DataProbeImpl]) + .addAddressMapping("alias", DOMAIN.asString, ANDRE.asString) + + assertThatJson(submit(server, s"alias@${DOMAIN.asString}")) + .inPath("methodResponses[0][1].created") + .isObject + .containsKey("k1490") + } + + @Test + def setShouldRejectRecipientOfALocalDomainWithoutMailboxNorMapping(server: GuiceJamesServer): Unit = + assertThatJson(submit(server, s"unknown@${DOMAIN.asString}")) + .inPath("methodResponses[0][1].notCreated") + .isEqualTo(s"""{ + | "k1490": { + | "type": "invalidRecipients", + | "description": "Invalid recipients: unknown@${DOMAIN.asString}", + | "properties": ["envelope.rcptTo"] + | } + |}""".stripMargin) + + @Test + def setShouldRejectTheWholeSubmissionWhenASingleRecipientIsInvalid(server: GuiceJamesServer): Unit = + assertThatJson(submit(server, ANDRE.asString, s"unknown@${DOMAIN.asString}")) + .inPath("methodResponses[0][1].notCreated") + .isEqualTo(s"""{ + | "k1490": { + | "type": "invalidRecipients", + | "description": "Invalid recipients: unknown@${DOMAIN.asString}", + | "properties": ["envelope.rcptTo"] + | } + |}""".stripMargin) + + private def submit(server: GuiceJamesServer, recipients: String*): String = { + val message: Message = Message.Builder + .of + .setSubject("test") + .setSender(BOB.asString) + .setFrom(BOB.asString) + .setTo(recipients: _*) + .setBody("testmail", StandardCharsets.UTF_8) + .build + + val messageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS), AppendCommand.builder().build(message)) + .getMessageId + + val rcptTo = recipients.map(recipient => s"""{"email": "$recipient"}""").mkString(", ") + val request = + 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": [$rcptTo] + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + } +} diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSubmissionSetValidateRcptTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSubmissionSetValidateRcptTest.java new file mode 100644 index 0000000000..9f24c1cc34 --- /dev/null +++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSubmissionSetValidateRcptTest.java @@ -0,0 +1,48 @@ +/**************************************************************** + * 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.rfc8621.memory; + +import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT; + +import org.apache.james.JamesServerBuilder; +import org.apache.james.JamesServerExtension; +import org.apache.james.MemoryJamesConfiguration; +import org.apache.james.MemoryJamesServerMain; +import org.apache.james.jmap.core.JmapRfc8621Configuration; +import org.apache.james.jmap.rfc8621.contract.EmailSubmissionSetValidateRcptContract; +import org.apache.james.modules.TestJMAPServerModule; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class MemoryEmailSubmissionSetValidateRcptTest implements EmailSubmissionSetValidateRcptContract { + @RegisterExtension + static JamesServerExtension testExtension = new JamesServerBuilder<MemoryJamesConfiguration>(tmpDir -> + MemoryJamesConfiguration.builder() + .workingDirectory(tmpDir) + .configurationFromClasspath() + .usersRepository(DEFAULT) + .enableJMAP() + .build()) + .server(configuration -> MemoryJamesServerMain.createServer(configuration) + .overrideWith(binder -> binder.bind(JmapRfc8621Configuration.class) + .toInstance(EmailSubmissionSetValidateRcptContract.configuration())) + .overrideWith(new TestJMAPServerModule())) + .lifeCycle(JamesServerExtension.Lifecycle.PER_TEST) + .build(); +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
