This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 816c9f80126c6767bcf81e81db827311b39b3de7 Author: Rene Cordier <rcord...@linagora.com> AuthorDate: Fri Oct 11 17:17:30 2024 +0700 JAMES-4032 Add validatedEntities field for DKIMHook --- .../servers/partials/configure/smtp-hooks.adoc | 16 ++++--- .../java/org/apache/james/smtpserver/DKIMHook.java | 53 ++++++++++++++++++---- .../org/apache/james/smtpserver/DKIMHookTest.java | 10 ++-- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/docs/modules/servers/partials/configure/smtp-hooks.adoc b/docs/modules/servers/partials/configure/smtp-hooks.adoc index 83b821402d..afb1ce3e53 100644 --- a/docs/modules/servers/partials/configure/smtp-hooks.adoc +++ b/docs/modules/servers/partials/configure/smtp-hooks.adoc @@ -360,6 +360,7 @@ Supported configuration elements: - *forceCRLF*: Should CRLF be forced when computing body hashes. - *onlyForSenderDomain*: If specified, the DKIM checks are applied just for the emails whose MAIL FROM or from header specifies this domain. If unspecified, all emails are checked (default). +- *validatedEntities*: If specified, comma separated values allowing granular checks on emails whose MAIL FROM (`envelope` option) or from header (`headers` option) specifies the domain defined in `onlyForSenderDomain`. If unspecified, defaults to `envelope,headers`. - *signatureRequired*: If DKIM signature is checked, the absence of signature will generate failure. Defaults to false. - *expectedDToken*: If DKIM signature is checked, the body should contain at least one DKIM signature with this d token. If unspecified, all d tokens are considered valid (default). @@ -368,13 +369,14 @@ Example handlerchain configuration for `smtpserver.xml`: [source,xml] .... <handlerchain> - <handler class="org.apache.james.smtpserver.DKIMHook"> - <forceCLRF>true</forceCLRF> - <onlyForSenderDomain>apache.org</onlyForSenderDomain> - <signatureRequired>true</signatureRequired> - <expectedDToken>apache.org</expectedDToken> - </handler> - <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/> + <handler class="org.apache.james.smtpserver.DKIMHook"> + <forceCLRF>true</forceCLRF> + <onlyForSenderDomain>apache.org</onlyForSenderDomain> + <validatedEntities>envelope,headers</validatedEntities> + <signatureRequired>true</signatureRequired> + <expectedDToken>apache.org</expectedDToken> + </handler> + <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/> </handlerchain> .... diff --git a/server/protocols/protocols-smtp-dkim/src/main/java/org/apache/james/smtpserver/DKIMHook.java b/server/protocols/protocols-smtp-dkim/src/main/java/org/apache/james/smtpserver/DKIMHook.java index cc8817ed53..cda884e4bd 100644 --- a/server/protocols/protocols-smtp-dkim/src/main/java/org/apache/james/smtpserver/DKIMHook.java +++ b/server/protocols/protocols-smtp-dkim/src/main/java/org/apache/james/smtpserver/DKIMHook.java @@ -22,7 +22,6 @@ package org.apache.james.smtpserver; import static org.apache.james.protocols.smtp.SMTPRetCode.AUTH_REQUIRED; import static org.apache.james.protocols.smtp.SMTPRetCode.LOCAL_ERROR; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -52,6 +51,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; /** @@ -90,8 +91,8 @@ public class DKIMHook implements JamesMessageHook { @FunctionalInterface interface DKIMCheckNeeded extends Predicate<Mail> { - static DKIMCheckNeeded or(DKIMCheckNeeded... checkNeededs) { - return mail -> Arrays.stream(checkNeededs) + static DKIMCheckNeeded or(ImmutableList<DKIMCheckNeeded> checkNeededs) { + return mail -> checkNeededs.stream() .anyMatch(predicate -> predicate.test(mail)); } @@ -186,32 +187,54 @@ public class DKIMHook implements JamesMessageHook { } public static class Config { + public static final ImmutableList<ValidatedEntity> DEFAULT_VALIDATED_ENTITIES = ImmutableList.of(ValidatedEntity.envelope, ValidatedEntity.headers); + public static Config parse(Configuration config) { return new Config( config.getBoolean("forceCRLF", true), config.getBoolean("signatureRequired", true), Optional.ofNullable(config.getString("onlyForSenderDomain", null)) .map(Domain::of), + Optional.ofNullable(config.getString("validatedEntities", null)) + .map(entities -> Stream.of(entities.split(",")) + .map(ValidatedEntity::from) + .collect(ImmutableList.toImmutableList())) + .orElse(DEFAULT_VALIDATED_ENTITIES), Optional.ofNullable(config.getString("expectedDToken", null))); } + public enum ValidatedEntity { + envelope, + headers; + + static ValidatedEntity from(String rawValue) { + Preconditions.checkNotNull(rawValue); + + return Stream.of(values()) + .filter(entity -> entity.name().equalsIgnoreCase(rawValue)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(String.format("invalid validated entity '%s'", rawValue))); + } + } + private final boolean forceCRLF; private final boolean signatureRequired; private final Optional<Domain> onlyForSenderDomain; + private final ImmutableList<ValidatedEntity> validatedEntities; private final Optional<String> expectedDToken; - public Config(boolean forceCRLF, boolean signatureRequired, Optional<Domain> onlyForSenderDomain, Optional<String> expectedDToken) { + public Config(boolean forceCRLF, boolean signatureRequired, Optional<Domain> onlyForSenderDomain, + ImmutableList<ValidatedEntity> validatedEntities, Optional<String> expectedDToken) { this.forceCRLF = forceCRLF; this.signatureRequired = signatureRequired; this.onlyForSenderDomain = onlyForSenderDomain; + this.validatedEntities = validatedEntities; this.expectedDToken = expectedDToken; } DKIMCheckNeeded dkimCheckNeeded() { return onlyForSenderDomain - .map(domain -> DKIMCheckNeeded.or( - DKIMCheckNeeded.onlyForSenderDomain(domain), - DKIMCheckNeeded.onlyForHeaderFromDomain(domain))) + .map(domain -> DKIMCheckNeeded.or(computeDKIMChecksNeeded(domain))) .orElse(DKIMCheckNeeded.ALL); } @@ -222,12 +245,26 @@ public class DKIMHook implements JamesMessageHook { .orElse(SignatureRecordValidation.ALLOW_ALL)); } + private ImmutableList<DKIMCheckNeeded> computeDKIMChecksNeeded(Domain domain) { + return validatedEntities.stream() + .map(entity -> toDKIMCheck(entity, domain)) + .collect(ImmutableList.toImmutableList()); + } + + private DKIMCheckNeeded toDKIMCheck(ValidatedEntity entity, Domain domain) { + return switch (entity) { + case envelope -> DKIMCheckNeeded.onlyForSenderDomain(domain); + case headers -> DKIMCheckNeeded.onlyForHeaderFromDomain(domain); + }; + } + @Override public final boolean equals(Object o) { if (o instanceof Config config) { return forceCRLF == config.forceCRLF && signatureRequired == config.signatureRequired && Objects.equals(onlyForSenderDomain, config.onlyForSenderDomain) + && Objects.equals(validatedEntities, config.validatedEntities) && Objects.equals(expectedDToken, config.expectedDToken); } return false; @@ -235,7 +272,7 @@ public class DKIMHook implements JamesMessageHook { @Override public final int hashCode() { - return Objects.hash(forceCRLF, signatureRequired, onlyForSenderDomain, expectedDToken); + return Objects.hash(forceCRLF, signatureRequired, onlyForSenderDomain, validatedEntities, expectedDToken); } } diff --git a/server/protocols/protocols-smtp-dkim/src/test/java/org/apache/james/smtpserver/DKIMHookTest.java b/server/protocols/protocols-smtp-dkim/src/test/java/org/apache/james/smtpserver/DKIMHookTest.java index fdad0bf7aa..dbb1184f9a 100644 --- a/server/protocols/protocols-smtp-dkim/src/test/java/org/apache/james/smtpserver/DKIMHookTest.java +++ b/server/protocols/protocols-smtp-dkim/src/test/java/org/apache/james/smtpserver/DKIMHookTest.java @@ -19,6 +19,7 @@ package org.apache.james.smtpserver; +import static org.apache.james.smtpserver.DKIMHook.Config.DEFAULT_VALIDATED_ENTITIES; import static org.assertj.core.api.Assertions.assertThat; import java.util.Optional; @@ -29,6 +30,7 @@ import org.apache.james.core.MaybeSender; import org.apache.james.core.builder.MimeMessageBuilder; import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; import org.apache.james.protocols.smtp.hook.HookReturnCode; +import org.apache.james.smtpserver.DKIMHook.Config.ValidatedEntity; import org.apache.mailet.base.test.FakeMail; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -234,7 +236,8 @@ class DKIMHookTest { BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); assertThat(DKIMHook.Config.parse(configuration)) - .isEqualTo(new DKIMHook.Config(true, true, Optional.empty(), Optional.empty())); + .isEqualTo(new DKIMHook.Config(true, true, Optional.empty(), + DEFAULT_VALIDATED_ENTITIES, Optional.empty())); } @Test @@ -243,11 +246,12 @@ class DKIMHookTest { configuration.addProperty("forceCRLF", false); configuration.addProperty("signatureRequired", false); configuration.addProperty("onlyForSenderDomain", "linagora.com"); + configuration.addProperty("validatedEntities", "envelope"); configuration.addProperty("expectedDToken", "apache.org"); assertThat(DKIMHook.Config.parse(configuration)) - .isEqualTo(new DKIMHook.Config(false, false, - Optional.of(Domain.of("linagora.com")), Optional.of("apache.org"))); + .isEqualTo(new DKIMHook.Config(false, false, Optional.of(Domain.of("linagora.com")), + ImmutableList.of(ValidatedEntity.envelope), Optional.of("apache.org"))); } } } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org