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
commit 403f8d63b0907581037c1b22ec98064318d6098e Author: Tung Van TRAN <[email protected]> AuthorDate: Wed Jul 27 07:37:33 2022 +0700 JAMES-3775 Mailet for RSpamD --- .../docs/modules/ROOT/partials/IsMarkedAsSpam.adoc | 5 +- .../transport/matchers/IsMarkedAsSpamTest.java | 4 +- third-party/rspamd/pom.xml | 10 +- .../org/apache/james/rspamd/RSpamDScanner.java | 83 +++++++++++ .../apache/james/rspamd/DockerRSpamDExtension.java | 8 +- .../org/apache/james/rspamd/RSpamDScannerTest.java | 151 +++++++++++++++++++++ 6 files changed, 254 insertions(+), 7 deletions(-) diff --git a/server/apps/distributed-app/docs/modules/ROOT/partials/IsMarkedAsSpam.adoc b/server/apps/distributed-app/docs/modules/ROOT/partials/IsMarkedAsSpam.adoc index 1f2bf1ee1a..1c743eab22 100644 --- a/server/apps/distributed-app/docs/modules/ROOT/partials/IsMarkedAsSpam.adoc +++ b/server/apps/distributed-app/docs/modules/ROOT/partials/IsMarkedAsSpam.adoc @@ -16,12 +16,13 @@ As an example, here is a part of a mailet pipeline which can be used in your Loc <!-- End of SpamAssassing mailets pipeline --> .... -In order to use this with `rspamd`, we need to declare a condition for the matcher. +In order to use this with `rspamd`, we need to declare a condition for the matcher +and drop the RSpamD jar (*third-party/rspamd*) in the James extensions-jars folder. Eg: With the recipient header for RSpamD being *org.apache.james.rspamd.status*, then the configuration would be: .... -<!-- SpamAssassing mailets pipeline --> +<!-- RSpamD mailets pipeline --> <mailet match="IsMarkedAsSpam=org.apache.james.rspamd.status" class="WithStorageDirective"> <targetFolderName>Spam</targetFolderName> </mailet> diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/IsMarkedAsSpamTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/IsMarkedAsSpamTest.java index 5841ee54c9..3ea229ad1f 100644 --- a/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/IsMarkedAsSpamTest.java +++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/IsMarkedAsSpamTest.java @@ -168,10 +168,10 @@ class IsMarkedAsSpamTest { .recipient("[email protected]") .addHeaderForRecipient(PerRecipientHeaders.Header.builder() .name("custom.package") - .value("Yes, hits=6.8 required=5.0") + .value("Yes, actions=reject score=14.225 requiredScore=14.0 desiredRewriteSubject=") .build(), new MailAddress("[email protected]")) - .attribute(Attribute.convertToAttribute("custom.package", "Yes, hits=6.8 required=5.0")) + .attribute(Attribute.convertToAttribute("custom.package", "Yes, actions=reject score=14.225 requiredScore=14.0 desiredRewriteSubject=")) .build(); Collection<MailAddress> matches = matcher.match(mail); diff --git a/third-party/rspamd/pom.xml b/third-party/rspamd/pom.xml index a8a3beb13a..51142d3bf6 100644 --- a/third-party/rspamd/pom.xml +++ b/third-party/rspamd/pom.xml @@ -58,6 +58,10 @@ <groupId>${james.groupId}</groupId> <artifactId>apache-mailet-api</artifactId> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>apache-mailet-base</artifactId> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>event-bus-api</artifactId> @@ -75,6 +79,10 @@ <type>test-jar</type> <scope>test</scope> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-core</artifactId> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>james-server-data-api</artifactId> @@ -143,4 +151,4 @@ <scope>test</scope> </dependency> </dependencies> -</project> \ No newline at end of file +</project> diff --git a/third-party/rspamd/src/main/java/org/apache/james/rspamd/RSpamDScanner.java b/third-party/rspamd/src/main/java/org/apache/james/rspamd/RSpamDScanner.java new file mode 100644 index 0000000000..4f25e8f497 --- /dev/null +++ b/third-party/rspamd/src/main/java/org/apache/james/rspamd/RSpamDScanner.java @@ -0,0 +1,83 @@ +/**************************************************************** + * 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.rspamd; + + +import java.util.List; + +import javax.inject.Inject; +import javax.mail.MessagingException; + +import org.apache.james.core.MailAddress; +import org.apache.james.rspamd.client.RSpamDHttpClient; +import org.apache.james.rspamd.model.AnalysisResult; +import org.apache.james.server.core.MimeMessageInputStream; +import org.apache.mailet.Attribute; +import org.apache.mailet.AttributeName; +import org.apache.mailet.AttributeValue; +import org.apache.mailet.Mail; +import org.apache.mailet.PerRecipientHeaders; +import org.apache.mailet.base.GenericMailet; + +import com.google.common.collect.ImmutableList; + +public class RSpamDScanner extends GenericMailet { + public static final AttributeName FLAG_MAIL = AttributeName.of("org.apache.james.rspamd.flag"); + public static final AttributeName STATUS_MAIL = AttributeName.of("org.apache.james.rspamd.status"); + + private final RSpamDHttpClient rSpamDHttpClient; + + @Inject + public RSpamDScanner(RSpamDHttpClient rSpamDHttpClient) { + this.rSpamDHttpClient = rSpamDHttpClient; + } + + @Override + public void service(Mail mail) throws MessagingException { + AnalysisResult rSpamDResult = rSpamDHttpClient.checkV2(new MimeMessageInputStream(mail.getMessage())).block(); + + mail.getRecipients() + .forEach(recipient -> appendRSpamDResultHeader(mail, recipient, rSpamDResult)); + } + + private void appendRSpamDResultHeader(Mail mail, MailAddress recipient, AnalysisResult rSpamDResult) { + for (Attribute attribute : getHeadersAsAttributes(rSpamDResult)) { + mail.addSpecificHeaderForRecipient(PerRecipientHeaders.Header.builder() + .name(attribute.getName().asString()) + .value((String) attribute.getValue().value()) + .build(), recipient); + } + } + + private List<Attribute> getHeadersAsAttributes(AnalysisResult rSpamDResult) { + String defaultFlagMailAttributeValue = "NO"; + String defaultStatusMailAttributeValue = "No"; + if (rSpamDResult.getAction().equals(AnalysisResult.Action.REJECT)) { + defaultFlagMailAttributeValue = "YES"; + defaultStatusMailAttributeValue = "Yes"; + } + + return ImmutableList.of(new Attribute(FLAG_MAIL, AttributeValue.of(defaultFlagMailAttributeValue)), + new Attribute(STATUS_MAIL, AttributeValue.of(defaultStatusMailAttributeValue + "," + + " actions=" + rSpamDResult.getAction().getDescription() + + " score=" + rSpamDResult.getScore() + + " requiredScore=" + rSpamDResult.getRequiredScore()))); + } +} diff --git a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java index 0dc22cccd8..47fe2b74b5 100644 --- a/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java +++ b/third-party/rspamd/src/test/java/org/apache/james/rspamd/DockerRSpamDExtension.java @@ -59,7 +59,11 @@ public class DockerRSpamDExtension implements GuiceModuleTestExtension { return dockerRSpamD(); } - public URL getBaseUrl() throws MalformedURLException { - return new URL("http://127.0.0.1:" + dockerRSpamD().getPort()); + public URL getBaseUrl() { + try { + return new URL("http://127.0.0.1:" + dockerRSpamD().getPort()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } } } diff --git a/third-party/rspamd/src/test/java/org/apache/james/rspamd/RSpamDScannerTest.java b/third-party/rspamd/src/test/java/org/apache/james/rspamd/RSpamDScannerTest.java new file mode 100644 index 0000000000..3079a84116 --- /dev/null +++ b/third-party/rspamd/src/test/java/org/apache/james/rspamd/RSpamDScannerTest.java @@ -0,0 +1,151 @@ +/**************************************************************** + * 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.rspamd; + + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collection; +import java.util.Optional; + +import javax.mail.internet.MimeMessage; + +import org.apache.james.core.MailAddress; +import org.apache.james.core.builder.MimeMessageBuilder; +import org.apache.james.rspamd.client.RSpamDClientConfiguration; +import org.apache.james.rspamd.client.RSpamDHttpClient; +import org.apache.james.util.MimeMessageUtil; +import org.apache.mailet.Mail; +import org.apache.mailet.PerRecipientHeaders; +import org.apache.mailet.base.test.FakeMail; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.google.common.collect.ImmutableList; + +class RSpamDScannerTest { + + @RegisterExtension + static DockerRSpamDExtension rSpamDExtension = new DockerRSpamDExtension(); + static final String rSpamDPassword = "admin"; + + private RSpamDScanner mailet; + + @BeforeEach + void setup() { + RSpamDClientConfiguration configuration = new RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), rSpamDPassword, Optional.empty()); + RSpamDHttpClient client = new RSpamDHttpClient(configuration); + mailet = new RSpamDScanner(client); + } + + @Test + void serviceShouldWriteSpamAttributeOnMail() throws Exception { + Mail mail = FakeMail.builder() + .name("name") + .recipient("[email protected]") + .mimeMessage(MimeMessageBuilder.mimeMessageBuilder() + .addToRecipient("[email protected]") + .addFrom("[email protected]") + .setSubject("testing") + .setText("Please!") + .build()) + .build(); + + mailet.service(mail); + + assertThat( + mail.getPerRecipientSpecificHeaders() + .getHeadersByRecipient() + .get(new MailAddress("[email protected]")) + .stream() + .map(PerRecipientHeaders.Header::getName) + .collect(ImmutableList.toImmutableList())) + .contains(RSpamDScanner.FLAG_MAIL.asString(), RSpamDScanner.STATUS_MAIL.asString()); + } + + @Test + void serviceShouldWriteMessageAsNotSpamWhenNotSpam() throws Exception { + Mail mail = FakeMail.builder() + .name("name") + .recipient("[email protected]") + .mimeMessage(MimeMessageBuilder.mimeMessageBuilder() + .addToRecipient("[email protected]") + .addFrom("[email protected]") + .setSubject("testing") + .setText("Please!") + .build()) + .build(); + + mailet.service(mail); + + Collection<PerRecipientHeaders.Header> headersForRecipient = mail.getPerRecipientSpecificHeaders() + .getHeadersForRecipient(new MailAddress("[email protected]")); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(headersForRecipient.stream() + .filter(header -> header.getName().equals(RSpamDScanner.FLAG_MAIL.asString())) + .filter(header -> header.getValue().startsWith("NO")) + .findAny()) + .isPresent(); + + softly.assertThat(headersForRecipient.stream() + .filter(header -> header.getName().equals(RSpamDScanner.STATUS_MAIL.asString())) + .filter(header -> header.getValue().startsWith("No, actions=no action")) + .findAny()) + .isPresent(); + + }); + } + + @Test + void serviceShouldWriteMessageAsSpamWhenSpam() throws Exception { + MimeMessage mimeMessage = MimeMessageUtil.mimeMessageFromStream( + ClassLoader.getSystemResourceAsStream("mail/spam/spam8.eml")); + + Mail mail = FakeMail.builder() + .name("name") + .recipient("[email protected]") + .mimeMessage(mimeMessage) + .build(); + + mailet.service(mail); + + + Collection<PerRecipientHeaders.Header> headersForRecipient = mail.getPerRecipientSpecificHeaders() + .getHeadersForRecipient(new MailAddress("[email protected]")); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(headersForRecipient.stream() + .filter(header -> header.getName().equals(RSpamDScanner.FLAG_MAIL.asString())) + .filter(header -> header.getValue().startsWith("YES")) + .findAny()) + .isPresent(); + + softly.assertThat(headersForRecipient.stream() + .filter(header -> header.getName().equals(RSpamDScanner.STATUS_MAIL.asString())) + .filter(header -> header.getValue().startsWith("Yes, actions=reject")) + .findAny()) + .isPresent(); + + }); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
