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 9f6f43b1b3f01a706238bd739e7bd045d18ebcc4
Author: Benoit TELLIER <[email protected]>
AuthorDate: Thu Sep 10 14:18:09 2026 +0200

    JAMES-4228 Allow user to supply his own validations for EmailSubmission/set
---
 docs/modules/servers/partials/configure/jmap.adoc  |   7 +
 .../sample-configuration/jmap.properties           |   3 +
 .../sample-configuration/jmap.properties           |   3 +
 .../sample-configuration/jmap.properties           |   3 +
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |  20 ++-
 .../AcceptAllEmailSubmissionSetValidation.scala    |  29 ++++
 ...EmailSubmissionSetExtraValidationContract.scala | 176 +++++++++++++++++++++
 .../RejectAllEmailSubmissionSetValidation.scala    |  38 +++++
 ...emoryEmailSubmissionSetExtraValidationTest.java |  45 ++++++
 .../james/jmap/core/JmapRfc8621Configuration.scala |   7 +
 .../jmap/core/JmapRfc8621ConfigurationTest.scala   |  73 +++++++++
 11 files changed, 400 insertions(+), 4 deletions(-)

diff --git a/docs/modules/servers/partials/configure/jmap.adoc 
b/docs/modules/servers/partials/configure/jmap.adoc
index 17b605d755..2eadeaa05e 100644
--- a/docs/modules/servers/partials/configure/jmap.adoc
+++ b/docs/modules/servers/partials/configure/jmap.adoc
@@ -139,6 +139,13 @@ accepts any recipient having a mapping, `anyMappingValid` 
requires at least one
 `allMappingsValid` requires all of them to. Only applies when 
`send.validate.rcpt` is enabled. Mirrors the
 `smtpserver.xml` `ValidRcptHandler` option of the same name.
 
+| send.extra.validations
+| Optional, defaults to empty. Coma separated list of 
`EmailSubmissionSetValidation` implementations to run
+against every mail submitted through `EmailSubmission/set`, before it reaches 
the queue. Each entry is a fully
+qualified class name (the `org.apache.james.jmap.method` package can be 
omitted), instantiated through Guice, and
+returning a SetError turns the creation into a `notCreated` entry. Validations 
run until the first rejection, in
+an unspecified order.
+
 | disabled.capabilities
 | Optional, defaults to empty. Coma separated list of JMAP capabilities to 
reject.
 This allows to prevent users from using some specific JMAP extensions.
diff --git a/server/apps/distributed-app/sample-configuration/jmap.properties 
b/server/apps/distributed-app/sample-configuration/jmap.properties
index b6af586348..fc37f1499c 100644
--- a/server/apps/distributed-app/sample-configuration/jmap.properties
+++ b/server/apps/distributed-app/sample-configuration/jmap.properties
@@ -15,6 +15,9 @@ tls.secret=james72laBalle
 #send.validate.rcpt.enableRecipientRewriteTable=true
 #send.validate.rcpt.recipientRewriteTableCheck=mappingExists
 
+# Extra EmailSubmission/set validations, run before the mail reaches the queue.
+#send.extra.validations=com.exemple.CustomEmailSubmissionSetValidation
+
 # Alternatively TLS keys can be supplied via PEM files
 # tls.privateKey=file://conf/private.nopass.key
 # tls.certificates=file://conf/certs.self-signed.csr
diff --git a/server/apps/memory-app/sample-configuration/jmap.properties 
b/server/apps/memory-app/sample-configuration/jmap.properties
index b6f198ac0b..6a8e20d6bf 100644
--- a/server/apps/memory-app/sample-configuration/jmap.properties
+++ b/server/apps/memory-app/sample-configuration/jmap.properties
@@ -13,6 +13,9 @@ delay.sends.enabled=true
 #send.validate.rcpt.enableRecipientRewriteTable=true
 #send.validate.rcpt.recipientRewriteTableCheck=mappingExists
 
+# Extra EmailSubmission/set validations, run before the mail reaches the queue.
+#send.extra.validations=com.exemple.CustomEmailSubmissionSetValidation
+
 # Alternatively TLS keys can be supplied via PEM files
 # tls.privateKey=file://conf/private.nopass.key
 # tls.certificates=file://conf/certs.self-signed.csr
diff --git a/server/apps/postgres-app/sample-configuration/jmap.properties 
b/server/apps/postgres-app/sample-configuration/jmap.properties
index b6af586348..fc37f1499c 100644
--- a/server/apps/postgres-app/sample-configuration/jmap.properties
+++ b/server/apps/postgres-app/sample-configuration/jmap.properties
@@ -15,6 +15,9 @@ tls.secret=james72laBalle
 #send.validate.rcpt.enableRecipientRewriteTable=true
 #send.validate.rcpt.recipientRewriteTableCheck=mappingExists
 
+# Extra EmailSubmission/set validations, run before the mail reaches the queue.
+#send.extra.validations=com.exemple.CustomEmailSubmissionSetValidation
+
 # Alternatively TLS keys can be supplied via PEM files
 # tls.privateKey=file://conf/private.nopass.key
 # tls.certificates=file://conf/certs.self-signed.csr
diff --git 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index a7243b01af..2d8e03491f 100644
--- 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -128,6 +128,7 @@ import com.google.inject.name.Named;
 public class RFC8621MethodsModule extends AbstractModule {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(RFC8621MethodsModule.class);
     private static PackageName IMPLICIT_AUTHENTICATION_STRATEGY_FQDN_PREFIX = 
PackageName.of("org.apache.james.jmap.http");
+    private static PackageName 
IMPLICIT_EMAIL_SUBMISSION_SET_VALIDATION_FQDN_PREFIX = 
PackageName.of("org.apache.james.jmap.method");
     private static List<String> DEFAULT_AUTHENTICATION_STRATEGIES = 
ImmutableList.of(
         BasicAuthenticationStrategy.class.getSimpleName(),
         JWTAuthenticationStrategy.class.getSimpleName());
@@ -248,11 +249,22 @@ public class RFC8621MethodsModule extends AbstractModule {
     @Provides
     @Singleton
     public Set<EmailSubmissionSetValidation> 
provideEmailSubmissionSetValidations(JmapRfc8621Configuration configuration,
-                                                                               
  Provider<RecipientValidator> recipientValidator) {
-        if (!configuration.validateRecipientsOnSend()) {
-            return ImmutableSet.of();
+                                                                               
  Provider<RecipientValidator> recipientValidator,
+                                                                               
  GuiceLoader guiceLoader) {
+        ImmutableSet.Builder<EmailSubmissionSetValidation> validations = 
ImmutableSet.builder();
+
+        if (configuration.validateRecipientsOnSend()) {
+            validations.add(new 
ValidRcptEmailSubmissionSetValidation(recipientValidator.get(), 
configuration.recipientValidationPolicy()));
         }
-        return ImmutableSet.of(new 
ValidRcptEmailSubmissionSetValidation(recipientValidator.get(), 
configuration.recipientValidationPolicy()));
+
+        configuration.extraEmailSubmissionValidations()
+            .stream()
+            .map(ClassName::new)
+            
.map(Throwing.function(guiceLoader.<EmailSubmissionSetValidation>withNamingSheme(
+                new 
NamingScheme.OptionalPackagePrefix(IMPLICIT_EMAIL_SUBMISSION_SET_VALIDATION_FQDN_PREFIX))::instantiate))
+            .forEach(validations::add);
+
+        return validations.build();
     }
 
     @Provides
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/custom/emailsubmission/AcceptAllEmailSubmissionSetValidation.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/custom/emailsubmission/AcceptAllEmailSubmissionSetValidation.scala
new file mode 100644
index 0000000000..8e7306fa8b
--- /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/custom/emailsubmission/AcceptAllEmailSubmissionSetValidation.scala
@@ -0,0 +1,29 @@
+/****************************************************************
+ * 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.custom.emailsubmission
+
+import org.apache.james.jmap.core.SetError
+import org.apache.james.jmap.method.EmailSubmissionSetValidation
+import org.apache.mailet.Mail
+import reactor.core.scala.publisher.SMono
+
+case class AcceptAllEmailSubmissionSetValidation() extends 
EmailSubmissionSetValidation {
+  override def validate(mail: Mail): SMono[Option[SetError]] = SMono.just(None)
+}
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/custom/emailsubmission/EmailSubmissionSetExtraValidationContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/custom/emailsubmission/EmailSubmissionSetExtraValidationContract.scala
new file mode 100644
index 0000000000..4ab949525b
--- /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/custom/emailsubmission/EmailSubmissionSetExtraValidationContract.scala
@@ -0,0 +1,176 @@
+/****************************************************************
+ * 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.custom.emailsubmission
+
+import java.nio.charset.StandardCharsets
+import java.util.concurrent.atomic.AtomicReference
+
+import com.google.common.collect.ImmutableList
+import com.google.inject.AbstractModule
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured
+import io.restassured.RestAssured.`given`
+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.{AfterEach, Test}
+
+/**
+ * `EmailSubmission/set` behaviour when third party validations are declared 
through
+ * `send.extra.validations`.
+ */
+object EmailSubmissionSetExtraValidationContract {
+  val currentServer: AtomicReference[GuiceJamesServer] = new 
AtomicReference[GuiceJamesServer]()
+}
+
+trait EmailSubmissionSetExtraValidationContract {
+  private def jmapServer: GuiceJamesServer = 
EmailSubmissionSetExtraValidationContract.currentServer.get()
+
+  @AfterEach
+  def tearDown(): Unit = jmapServer.stop()
+
+  private def startServerWith(basedServer: GuiceJamesServer, extraValidations: 
java.util.List[String]): Unit = {
+    
EmailSubmissionSetExtraValidationContract.currentServer.set(basedServer.overrideWith(new
 AbstractModule {
+      override def configure(): Unit =
+        bind(classOf[JmapRfc8621Configuration])
+          .toInstance(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION
+            .withExtraEmailSubmissionValidations(extraValidations))
+    }))
+    jmapServer.start()
+
+    jmapServer.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addUser(BOB.asString, BOB_PASSWORD)
+      .addUser(ANDRE.asString, ANDRE_PASSWORD)
+
+    jmapServer.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS))
+
+    RestAssured.requestSpecification = baseRequestSpecBuilder(jmapServer)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .build
+  }
+
+  @Test
+  def setShouldSucceedWhenNoExtraValidationIsDeclared(server: 
GuiceJamesServer): Unit = {
+    startServerWith(server, ImmutableList.of())
+
+    assertThatJson(submit())
+      .inPath("methodResponses[0][1].created")
+      .isObject
+      .containsKey("k1490")
+  }
+
+  @Test
+  def setShouldSucceedWhenTheExtraValidationAccepts(server: GuiceJamesServer): 
Unit = {
+    startServerWith(server, 
ImmutableList.of(classOf[AcceptAllEmailSubmissionSetValidation].getCanonicalName))
+
+    assertThatJson(submit())
+      .inPath("methodResponses[0][1].created")
+      .isObject
+      .containsKey("k1490")
+  }
+
+  @Test
+  def setShouldBeRejectedWhenTheExtraValidationRejects(server: 
GuiceJamesServer): Unit = {
+    startServerWith(server, 
ImmutableList.of(classOf[RejectAllEmailSubmissionSetValidation].getCanonicalName))
+
+    assertThatJson(submit())
+      .inPath("methodResponses[0][1].notCreated")
+      .isEqualTo(s"""{
+                    |  "k1490": {
+                    |    "type": 
"${RejectAllEmailSubmissionSetValidation.REJECTED.value}",
+                    |    "description": 
"${RejectAllEmailSubmissionSetValidation.DESCRIPTION}"
+                    |  }
+                    |}""".stripMargin)
+  }
+
+  @Test
+  def setShouldBeRejectedWhenASingleExtraValidationAmongSeveralRejects(server: 
GuiceJamesServer): Unit = {
+    startServerWith(server, ImmutableList.of(
+      classOf[AcceptAllEmailSubmissionSetValidation].getCanonicalName,
+      classOf[RejectAllEmailSubmissionSetValidation].getCanonicalName))
+
+    assertThatJson(submit())
+      .inPath("methodResponses[0][1].notCreated")
+      .isEqualTo(s"""{
+                    |  "k1490": {
+                    |    "type": 
"${RejectAllEmailSubmissionSetValidation.REJECTED.value}",
+                    |    "description": 
"${RejectAllEmailSubmissionSetValidation.DESCRIPTION}"
+                    |  }
+                    |}""".stripMargin)
+  }
+
+  private def submit(): String = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(BOB.asString)
+      .setFrom(BOB.asString)
+      .setTo(ANDRE.asString)
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val messageId = jmapServer.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.forUser(BOB, 
DefaultMailboxes.DRAFTS), AppendCommand.builder().build(message))
+      .getMessageId
+
+    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": [{"email": "${ANDRE.asString}"}]
+         |           }
+         |         }
+         |    }
+         |  }, "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/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/custom/emailsubmission/RejectAllEmailSubmissionSetValidation.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/custom/emailsubmission/RejectAllEmailSubmissionSetValidation.scala
new file mode 100644
index 0000000000..32f2efce74
--- /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/custom/emailsubmission/RejectAllEmailSubmissionSetValidation.scala
@@ -0,0 +1,38 @@
+/****************************************************************
+ * 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.custom.emailsubmission
+
+import eu.timepit.refined.auto._
+import org.apache.james.jmap.core.SetError
+import org.apache.james.jmap.core.SetError.{SetErrorDescription, SetErrorType}
+import org.apache.james.jmap.method.EmailSubmissionSetValidation
+import org.apache.mailet.Mail
+import reactor.core.scala.publisher.SMono
+
+object RejectAllEmailSubmissionSetValidation {
+  val REJECTED: SetErrorType = "customRejection"
+  val DESCRIPTION: String = "Rejected by the custom validation"
+}
+
+case class RejectAllEmailSubmissionSetValidation() extends 
EmailSubmissionSetValidation {
+  override def validate(mail: Mail): SMono[Option[SetError]] =
+    SMono.just(Some(SetError(RejectAllEmailSubmissionSetValidation.REJECTED,
+      SetErrorDescription(RejectAllEmailSubmissionSetValidation.DESCRIPTION), 
None)))
+}
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/MemoryEmailSubmissionSetExtraValidationTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSubmissionSetExtraValidationTest.java
new file mode 100644
index 0000000000..9a32527127
--- /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/MemoryEmailSubmissionSetExtraValidationTest.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.rfc8621.contract.custom.emailsubmission.EmailSubmissionSetExtraValidationContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryEmailSubmissionSetExtraValidationTest implements 
EmailSubmissionSetExtraValidationContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new 
JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
+        MemoryJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .enableJMAP()
+            .build())
+        .server(configuration -> 
MemoryJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .disableAutoStart()
+        .build();
+}
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
index 0931640308..346e0a63c3 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
@@ -56,6 +56,7 @@ object JmapConfigProperties {
   val JMAP_GET_MAX_SIZE_PROPERTY: String = "get.max.size"
   val JMAP_SET_MAX_SIZE_PROPERTY: String = "set.max.size"
   val SEND_VALIDATE_RCPT_PROPERTY: String = "send.validate.rcpt"
+  val SEND_EXTRA_VALIDATIONS_PROPERTY: String = "send.extra.validations"
 }
 
 object JmapRfc8621Configuration {
@@ -109,6 +110,8 @@ object JmapRfc8621Configuration {
         .getOrElse(JMAP_MAX_OBJECT_IN_SET),
       validateRecipientsOnSend = 
configuration.getBoolean(SEND_VALIDATE_RCPT_PROPERTY, false),
       recipientValidationPolicy = 
RecipientValidator.Policy.from(configuration.subset(SEND_VALIDATE_RCPT_PROPERTY)),
+      extraEmailSubmissionValidations = 
Optional.ofNullable(configuration.getList(classOf[String], 
SEND_EXTRA_VALIDATIONS_PROPERTY, null))
+        .orElse(ImmutableList.of()),
       webPushEnabled = configuration.getBoolean(WEB_PUSH_ENABLED_PROPERTY, 
true),
       maxTimeoutSeconds = 
Optional.ofNullable(configuration.getInteger(WEB_PUSH_MAX_TIMEOUT_SECONDS_PROPERTY,
 null)).map(Integer2int).toScala,
       maxConnections = 
Optional.ofNullable(configuration.getInteger(WEB_PUSH_MAX_CONNECTIONS_PROPERTY, 
null)).map(Integer2int).toScala,
@@ -135,6 +138,7 @@ case class JmapRfc8621Configuration(urlPrefixString: String,
                                     maxObjectsInSet: MaxObjectsInSet = 
JMAP_MAX_OBJECT_IN_SET,
                                     validateRecipientsOnSend: Boolean = false,
                                     recipientValidationPolicy: 
RecipientValidator.Policy = RecipientValidator.Policy.DEFAULT,
+                                    extraEmailSubmissionValidations: 
java.util.List[String] = ImmutableList.of(),
                                     webPushEnabled: Boolean = true,
                                     maxTimeoutSeconds: Option[Int] = None,
                                     maxConnections: Option[Int] = None,
@@ -153,4 +157,7 @@ case class JmapRfc8621Configuration(urlPrefixString: String,
 
   def withAuthenticationStrategies(list: Optional[java.util.List[String]]): 
JmapRfc8621Configuration =
     this.copy(authenticationStrategies = list.toScala)
+
+  def withExtraEmailSubmissionValidations(list: java.util.List[String]): 
JmapRfc8621Configuration =
+    this.copy(extraEmailSubmissionValidations = list)
 }
diff --git 
a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/core/JmapRfc8621ConfigurationTest.scala
 
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/core/JmapRfc8621ConfigurationTest.scala
new file mode 100644
index 0000000000..f8e6aac321
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/core/JmapRfc8621ConfigurationTest.scala
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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.core
+
+import com.google.common.collect.ImmutableList
+import org.apache.commons.configuration2.PropertiesConfiguration
+import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler
+import org.apache.james.rrt.api.RecipientValidator
+import org.apache.james.rrt.api.RecipientValidator.RecipientRewriteTableCheck
+import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
+import org.junit.jupiter.api.Test
+
+class JmapRfc8621ConfigurationTest {
+  private def configurationWith(properties: (String, String)*): 
JmapRfc8621Configuration = {
+    val underlying = new PropertiesConfiguration()
+    // James reads jmap.properties with a comma list delimiter, see 
PropertiesProvider
+    underlying.setListDelimiterHandler(new DefaultListDelimiterHandler(','))
+    properties.foreach { case (key, value) => underlying.addProperty(key, 
value) }
+    JmapRfc8621Configuration.from(underlying)
+  }
+
+  @Test
+  def recipientValidationShouldBeDisabledByDefault(): Unit =
+    assertThat(configurationWith().validateRecipientsOnSend).isFalse
+
+  @Test
+  def recipientValidationShouldBeEnabledWhenConfigured(): Unit =
+    assertThat(configurationWith("send.validate.rcpt" -> 
"true").validateRecipientsOnSend).isTrue
+
+  @Test
+  def recipientValidationPolicyShouldDefaultToMappingExists(): Unit =
+    assertThat(configurationWith("send.validate.rcpt" -> 
"true").recipientValidationPolicy)
+      .isEqualTo(RecipientValidator.Policy.DEFAULT)
+
+  @Test
+  def recipientValidationPolicyShouldBeReadFromItsSubKeys(): Unit =
+    assertThat(configurationWith(
+      "send.validate.rcpt" -> "true",
+      "send.validate.rcpt.enableRecipientRewriteTable" -> "false",
+      "send.validate.rcpt.recipientRewriteTableCheck" -> 
"allMappingsValid").recipientValidationPolicy)
+      .isEqualTo(new RecipientValidator.Policy(false, 
RecipientRewriteTableCheck.ALL_TARGETS_HAVE_LOCAL_MAILBOX))
+
+  @Test
+  def shouldThrowOnUnsupportedRecipientRewriteTableCheck(): Unit =
+    assertThatThrownBy(() => 
configurationWith("send.validate.rcpt.recipientRewriteTableCheck" -> "invalid"))
+      .hasMessageContaining("unsupported value 'invalid'")
+
+  @Test
+  def extraValidationsShouldBeEmptyByDefault(): Unit =
+    assertThat(configurationWith().extraEmailSubmissionValidations).isEmpty
+
+  @Test
+  def extraValidationsShouldBeReadAsAList(): Unit =
+    assertThat(configurationWith("send.extra.validations" -> 
"com.a.First,com.b.Second").extraEmailSubmissionValidations)
+      .isEqualTo(ImmutableList.of("com.a.First", "com.b.Second"))
+}


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

Reply via email to