Skip to content

Commit

Permalink
[ENHANCEMENT] Integration tests for MaxRcptHandler (#2448)
Browse files Browse the repository at this point in the history
  • Loading branch information
florentos17 authored Oct 15, 2024
1 parent 816c9f8 commit 5a62ece
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class MaxRcptHandler implements RcptHook {


/**
* Set the max rcpt for wich should be accepted
* Set the max rcpt for which a mail should be accepted
*
* @param maxRcpt
* The max rcpt count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.io.IOUtils;
Expand All @@ -44,9 +46,23 @@ public class SmtpConfiguration implements SerializableAsXml {

static class HookConfigurationEntry {
String hookFqcn;
Map<String, String> hookConfig;

HookConfigurationEntry(String hookFqcn, Map<String, String> hookConfig) {
this.hookFqcn = hookFqcn;
this.hookConfig = hookConfig;
}

HookConfigurationEntry(String hookFqcn) {
this.hookFqcn = hookFqcn;
this.hookConfig = new HashMap<>();
}

private static Map<String, Object> asMustacheScopes(HookConfigurationEntry hook) {
Map<String, Object> hookScope = new HashMap<>();
hookScope.put("hookFqcn", hook.hookFqcn);
hookScope.put("hookConfigAsXML", hook.hookConfig.entrySet());
return hookScope;
}
}

Expand All @@ -58,15 +74,15 @@ public static class Builder {
private Optional<SMTPConfiguration.SenderVerificationMode> verifyIndentity;
private Optional<Boolean> bracketEnforcement;
private Optional<String> authorizedAddresses;
private ImmutableList.Builder<HookConfigurationEntry> addittionalHooks;
private ImmutableList.Builder<HookConfigurationEntry> additionalHooks;

public Builder() {
authorizedAddresses = Optional.empty();
authRequired = Optional.empty();
verifyIndentity = Optional.empty();
maxMessageSize = Optional.empty();
bracketEnforcement = Optional.empty();
addittionalHooks = ImmutableList.builder();
additionalHooks = ImmutableList.builder();
}

public Builder withAutorizedAddresses(String authorizedAddresses) {
Expand Down Expand Up @@ -111,7 +127,12 @@ public Builder relaxedIdentityVerification() {
}

public Builder addHook(String hookFQCN) {
this.addittionalHooks.add(new HookConfigurationEntry(hookFQCN));
this.additionalHooks.add(new HookConfigurationEntry(hookFQCN));
return this;
}

public Builder addHook(String hookFQCN, Map<String, String> hookConfig) {
this.additionalHooks.add(new HookConfigurationEntry(hookFQCN, hookConfig));
return this;
}

Expand All @@ -121,7 +142,7 @@ public SmtpConfiguration build() {
bracketEnforcement.orElse(true),
verifyIndentity.orElse(SMTPConfiguration.SenderVerificationMode.DISABLED),
maxMessageSize.orElse(DEFAULT_DISABLED),
addittionalHooks.build());
additionalHooks.build());
}
}

Expand All @@ -134,16 +155,20 @@ public static Builder builder() {
private final boolean bracketEnforcement;
private final SMTPConfiguration.SenderVerificationMode verifyIndentity;
private final String maxMessageSize;
private final ImmutableList<HookConfigurationEntry> addittionalHooks;

private SmtpConfiguration(Optional<String> authorizedAddresses, boolean authRequired, boolean bracketEnforcement,
SMTPConfiguration.SenderVerificationMode verifyIndentity, String maxMessageSize, ImmutableList<HookConfigurationEntry> addittionalHooks) {
private final ImmutableList<HookConfigurationEntry> additionalHooks;

private SmtpConfiguration(Optional<String> authorizedAddresses,
boolean authRequired,
boolean bracketEnforcement,
SMTPConfiguration.SenderVerificationMode verifyIndentity,
String maxMessageSize,
ImmutableList<HookConfigurationEntry> additionalHooks) {
this.authorizedAddresses = authorizedAddresses;
this.authRequired = authRequired;
this.bracketEnforcement = bracketEnforcement;
this.verifyIndentity = verifyIndentity;
this.maxMessageSize = maxMessageSize;
this.addittionalHooks = addittionalHooks;
this.additionalHooks = additionalHooks;
}

@Override
Expand All @@ -155,7 +180,12 @@ public String serializeAsXml() throws IOException {
scopes.put("verifyIdentity", verifyIndentity.toString());
scopes.put("maxmessagesize", maxMessageSize);
scopes.put("bracketEnforcement", bracketEnforcement);
scopes.put("hooks", addittionalHooks);

List<Map<String, Object>> additionalHooksWithConfig = additionalHooks.stream()
.map(HookConfigurationEntry::asMustacheScopes)
.collect(ImmutableList.toImmutableList());

scopes.put("hooks", additionalHooksWithConfig);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(byteArrayOutputStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@
<handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/>
<handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
{{#hooks}}
<handler class="{{hookFqcn}}"/>
<handler class="{{hookFqcn}}">
{{#hookConfigAsXML}}
<{{key}}>{{value}}</{{key}}>
{{/hookConfigAsXML}}
</handler>
{{/hooks}}
</handlerchain>
<gracefulShutdown>false</gracefulShutdown>
Expand Down Expand Up @@ -87,7 +91,11 @@
<handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/>
<handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
{{#hooks}}
<handler class="{{hookFqcn}}"/>
<handler class="{{hookFqcn}}">
{{#hookConfigAsXML}}
<{{key}}>{{value}}</{{key}}>
{{/hookConfigAsXML}}
</handler>
{{/hooks}}
</handlerchain>
<gracefulShutdown>false</gracefulShutdown>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath;

import java.io.IOException;
import java.util.Map;

import javax.xml.transform.Source;

Expand Down Expand Up @@ -180,4 +181,33 @@ public void verifyIdentityShouldBeDisabledByDefault() throws IOException {
hasXPath("/smtpservers/smtpserver/verifyIdentity/text()",
is("DISABLED")));
}

@Test
public void addHookShouldRegisterHookWithoutConfig() throws IOException {
String hookFqcn = "com.example.hooks.MyCustomHook";

SmtpConfiguration configuration = SmtpConfiguration.builder()
.addHook(hookFqcn)
.build();

assertThat(configuration.serializeAsXml(),
hasXPath("/smtpservers/smtpserver/handlerchain/handler[@class='" + hookFqcn + "']/@class", is(hookFqcn)));
}

@Test
public void addHookShouldRegisterHookWithConfig() throws IOException {
String hookFqcn = "com.example.hooks.MyCustomHook";
Map<String, String> hookConfig = Map.of("param1", "value1", "param2", "value2");

SmtpConfiguration configuration = SmtpConfiguration.builder()
.addHook(hookFqcn, hookConfig)
.build();

String xmlOutput = configuration.serializeAsXml();
assertThat(xmlOutput, hasXPath("/smtpservers/smtpserver/handlerchain/handler[@class='" + hookFqcn + "']/@class", is(hookFqcn)));
assertThat(xmlOutput, hasXPath("/smtpservers/smtpserver/handlerchain/handler[@class='" + hookFqcn + "']/param1/text()", is("value1")));
assertThat(xmlOutput, hasXPath("/smtpservers/smtpserver/handlerchain/handler[@class='" + hookFqcn + "']/param2/text()", is("value2")));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/****************************************************************
* 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.smtp;

import static org.apache.james.mailets.configuration.Constants.DEFAULT_DOMAIN;
import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP;
import static org.apache.james.mailets.configuration.Constants.PASSWORD;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

import org.apache.james.mailets.TemporaryJamesServer;
import org.apache.james.mailets.configuration.SmtpConfiguration;
import org.apache.james.modules.protocols.SmtpGuiceProbe;
import org.apache.james.probe.DataProbe;
import org.apache.james.smtpserver.fastfail.MaxRcptHandler;
import org.apache.james.utils.DataProbeImpl;
import org.apache.james.utils.SMTPMessageSender;
import org.apache.james.utils.SMTPSendingException;
import org.apache.james.utils.SmtpSendingStep;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;

import com.github.fge.lambdas.Throwing;
import com.google.common.collect.ImmutableList;

class SmtpMaxRcptHandlerTest {
private static final String USER = "user@" + DEFAULT_DOMAIN;
private static final Integer DEFAULT_MAX_RCPT = 50;

@RegisterExtension
public SMTPMessageSender messageSender = new SMTPMessageSender(DEFAULT_DOMAIN);

private TemporaryJamesServer jamesServer;

@BeforeEach
public void createJamesServer(@TempDir File temporaryFolder) throws Exception {
SmtpConfiguration.Builder smtpConfiguration = SmtpConfiguration.builder()
.doNotVerifyIdentity()
.addHook(MaxRcptHandler.class.getName(),
Map.of("maxRcpt", DEFAULT_MAX_RCPT.toString()));

jamesServer = TemporaryJamesServer.builder()
.withSmtpConfiguration(smtpConfiguration)
.build(temporaryFolder);
jamesServer.start();

DataProbe dataProbe = jamesServer.getProbe(DataProbeImpl.class);
dataProbe.addDomain(DEFAULT_DOMAIN);
dataProbe.addUser(USER, PASSWORD);
IntStream.range(0, DEFAULT_MAX_RCPT + 1).forEach(Throwing.intConsumer((
i -> dataProbe.addUser("recipient" + i + "@" + DEFAULT_DOMAIN, PASSWORD))));
}

@AfterEach
void tearDown() {
if (jamesServer != null) {
jamesServer.shutdown();
}
}

@Test
void messageShouldNotBeAcceptedWhenMaxRcptHandlerExceeded() throws Exception {
assertThatThrownBy(() ->
messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
.authenticate(USER, PASSWORD)
.sendMessageWithHeaders(USER, getRecipients(DEFAULT_MAX_RCPT + 1), "message"))
.isEqualTo(new SMTPSendingException(SmtpSendingStep.RCPT, "452 4.5.3 Requested action not taken: max recipients reached\n"));
}

@Test
void messageShouldBeAcceptedWhenMaxRcptHandlerWithinLimit() throws Exception {
assertDoesNotThrow(() ->
messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
.authenticate(USER, PASSWORD)
.sendMessageWithHeaders(USER, getRecipients(DEFAULT_MAX_RCPT), "message"));
}

private List<String> getRecipients(Integer n) {
return IntStream.range(0, n)
.mapToObj(i -> "recipient" + i + "@" + DEFAULT_DOMAIN)
.collect(ImmutableList.toImmutableList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public class MaxRcptHandler extends org.apache.james.protocols.smtp.core.fastfai
@Override
public void init(Configuration config) throws ConfigurationException {
int maxRcpt = config.getInt("maxRcpt", 0);
setMaxRcpt(maxRcpt);
setMaxRcpt(maxRcpt);
}
}

0 comments on commit 5a62ece

Please sign in to comment.