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]
