JAMES-2246 Allow customizable SMTP configuration
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/b64f2b2c Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/b64f2b2c Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/b64f2b2c Branch: refs/heads/master Commit: b64f2b2cbd1201060ff06e9743b1734678630a35 Parents: 4311b7d Author: benwa <btell...@linagora.com> Authored: Mon Dec 4 11:15:07 2017 +0700 Committer: benwa <btell...@linagora.com> Committed: Fri Dec 8 17:34:45 2017 +0700 ---------------------------------------------------------------------- server/mailet/integration-testing/pom.xml | 15 ++ .../mailets/TemporaryFilesystemModule.java | 3 +- .../james/mailets/TemporaryJamesServer.java | 24 ++- .../configuration/SerializableAsXml.java | 4 +- .../configuration/SmtpConfiguration.java | 149 ++++++++++++++++++ .../configuration/SmtpConfigurationTest.java | 155 +++++++++++++++++++ .../src/test/resources/smtpserver.xml | 28 ++-- 7 files changed, 363 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/b64f2b2c/server/mailet/integration-testing/pom.xml ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/pom.xml b/server/mailet/integration-testing/pom.xml index 336e5b6..12352da 100644 --- a/server/mailet/integration-testing/pom.xml +++ b/server/mailet/integration-testing/pom.xml @@ -106,6 +106,11 @@ <scope>test</scope> </dependency> <dependency> + <groupId>com.github.spullara.mustache.java</groupId> + <artifactId>compiler</artifactId> + <version>0.9.5</version> + </dependency> + <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> @@ -147,6 +152,16 @@ <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> </dependency> + <dependency> + <groupId>org.xmlunit</groupId> + <artifactId>xmlunit-core</artifactId> + <version>2.5.1</version> + </dependency> + <dependency> + <groupId>org.xmlunit</groupId> + <artifactId>xmlunit-matchers</artifactId> + <version>2.5.1</version> + </dependency> </dependencies> <profiles> http://git-wip-us.apache.org/repos/asf/james-project/blob/b64f2b2c/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryFilesystemModule.java ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryFilesystemModule.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryFilesystemModule.java index 22eaac4..7770d1a 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryFilesystemModule.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryFilesystemModule.java @@ -30,10 +30,10 @@ import java.util.List; import java.util.function.Supplier; import org.apache.commons.io.IOUtils; -import org.apache.james.server.core.JamesServerResourceLoader; import org.apache.james.filesystem.api.FileSystem; import org.apache.james.filesystem.api.JamesDirectoriesProvider; import org.apache.james.modules.CommonServicesModule; +import org.apache.james.server.core.JamesServerResourceLoader; import org.junit.rules.TemporaryFolder; import com.google.common.base.Throwables; @@ -52,7 +52,6 @@ public class TemporaryFilesystemModule extends AbstractModule { "managesieveserver.xml", "pop3server.xml", "recipientrewritetable.xml", - "smtpserver.xml", "usersrepository.xml", "smime.p12"); http://git-wip-us.apache.org/repos/asf/james-project/blob/b64f2b2c/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryJamesServer.java ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryJamesServer.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryJamesServer.java index 0d32c46..ebbe397 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryJamesServer.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/TemporaryJamesServer.java @@ -36,6 +36,7 @@ import org.apache.commons.io.IOUtils; import org.apache.james.GuiceJamesServer; import org.apache.james.MemoryJamesServerMain; import org.apache.james.mailets.configuration.MailetContainer; +import org.apache.james.mailets.configuration.SmtpConfiguration; import org.apache.james.modules.TestJMAPServerModule; import org.apache.james.utils.GuiceProbe; import org.apache.james.webadmin.WebAdminConfiguration; @@ -50,10 +51,12 @@ public class TemporaryJamesServer { public static class Builder { private ImmutableList.Builder<Module> overrideModules; private Optional<Module> module; + private Optional<SmtpConfiguration> smtpConfiguration; private Builder() { overrideModules = ImmutableList.builder(); module = Optional.empty(); + smtpConfiguration = Optional.empty(); } public Builder withBase(Module module) { @@ -61,6 +64,11 @@ public class TemporaryJamesServer { return this; } + public Builder withSmtpConfiguration(SmtpConfiguration smtpConfiguration) { + this.smtpConfiguration = Optional.of(smtpConfiguration); + return this; + } + public Builder withOverrides(Module... modules) { this.overrideModules.addAll(Arrays.asList(modules)); return this; @@ -70,6 +78,7 @@ public class TemporaryJamesServer { return new TemporaryJamesServer( temporaryFolder, mailetContainer, + smtpConfiguration.orElse(SmtpConfiguration.DEFAULT), module.orElse(MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE), overrideModules.build()); } @@ -80,14 +89,16 @@ public class TemporaryJamesServer { } private static final String MAILETCONTAINER_CONFIGURATION_FILENAME = "mailetcontainer.xml"; + private static final String SMTP_CONFIGURATION_FILENAME = "smtpserver.xml"; private static final int LIMIT_TO_3_MESSAGES = 3; private final GuiceJamesServer jamesServer; - private TemporaryJamesServer(TemporaryFolder temporaryFolder, MailetContainer mailetContainer, + private TemporaryJamesServer(TemporaryFolder temporaryFolder, MailetContainer mailetContainer, SmtpConfiguration smtpConfiguration, Module serverBaseModule, List<Module> additionalModules) throws Exception { appendMailetConfigurations(temporaryFolder, mailetContainer); + appendSmtpConfigurations(temporaryFolder, smtpConfiguration); jamesServer = new GuiceJamesServer() .combineWith(serverBaseModule) @@ -106,11 +117,22 @@ public class TemporaryJamesServer { } } + private void appendSmtpConfigurations(TemporaryFolder temporaryFolder, SmtpConfiguration smtpConfiguration) throws ConfigurationException, IOException { + try (OutputStream outputStream = createSmtpConfigurationFile(temporaryFolder)) { + IOUtils.write(smtpConfiguration.serializeAsXml(), outputStream, StandardCharsets.UTF_8); + } + } + private FileOutputStream createMailetConfigurationFile(TemporaryFolder temporaryFolder) throws IOException { File configurationFolder = temporaryFolder.newFolder("conf"); return new FileOutputStream(Paths.get(configurationFolder.getAbsolutePath(), MAILETCONTAINER_CONFIGURATION_FILENAME).toFile()); } + private FileOutputStream createSmtpConfigurationFile(TemporaryFolder temporaryFolder) throws IOException { + File configurationFolder = temporaryFolder.getRoot().listFiles()[0]; + return new FileOutputStream(Paths.get(configurationFolder.getAbsolutePath(), SMTP_CONFIGURATION_FILENAME).toFile()); + } + public void shutdown() { jamesServer.stop(); } http://git-wip-us.apache.org/repos/asf/james-project/blob/b64f2b2c/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SerializableAsXml.java ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SerializableAsXml.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SerializableAsXml.java index fcc9248..22dda03 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SerializableAsXml.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SerializableAsXml.java @@ -20,7 +20,9 @@ package org.apache.james.mailets.configuration; +import java.io.IOException; + public interface SerializableAsXml { - String serializeAsXml(); + String serializeAsXml() throws IOException; } http://git-wip-us.apache.org/repos/asf/james-project/blob/b64f2b2c/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfiguration.java ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfiguration.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfiguration.java new file mode 100644 index 0000000..de9a23b --- /dev/null +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfiguration.java @@ -0,0 +1,149 @@ +/**************************************************************** + * 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.mailets.configuration; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.StringReader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; +import com.google.common.base.Preconditions; + +public class SmtpConfiguration implements SerializableAsXml { + public static final boolean AUTH_REQUIRED = true; + public static final SmtpConfiguration DEFAULT = SmtpConfiguration.builder().build(); + + public static class Builder { + private static final int DEFAULT_DISABLED = 0; + + private Optional<Boolean> authRequired; + private Optional<Integer> maxMessageSizeInKb; + private Optional<Boolean> verifyIndentity; + private Optional<Boolean> bracketEnforcement; + private Optional<String> authorizedAddresses; + + public Builder() { + authorizedAddresses = Optional.empty(); + authRequired = Optional.empty(); + verifyIndentity = Optional.empty(); + maxMessageSizeInKb = Optional.empty(); + bracketEnforcement = Optional.empty(); + } + + public Builder withAutorizedAddresses(String authorizedAddresses) { + Preconditions.checkNotNull(authorizedAddresses); + this.authorizedAddresses = Optional.of(authorizedAddresses); + return this; + } + + public Builder withMaxMessageSizeInKb(int sizeInKb) { + Preconditions.checkArgument(sizeInKb > 0); + this.maxMessageSizeInKb = Optional.of(sizeInKb); + return this; + } + + public Builder requireAuthentication() { + this.authRequired = Optional.of(AUTH_REQUIRED); + return this; + } + + public Builder requireBracketEnforcement() { + this.bracketEnforcement = Optional.of(true); + return this; + } + + public Builder doNotRequireBracketEnforcement() { + this.bracketEnforcement = Optional.of(false); + return this; + } + + public Builder verifyIdentity() { + this.verifyIndentity = Optional.of(true); + return this; + } + + public Builder doNotVerifyIdentity() { + this.verifyIndentity = Optional.of(false); + return this; + } + + public SmtpConfiguration build() { + return new SmtpConfiguration(authorizedAddresses, + authRequired.orElse(!AUTH_REQUIRED), + bracketEnforcement.orElse(true), + verifyIndentity.orElse(true), + maxMessageSizeInKb.orElse(DEFAULT_DISABLED)); + } + } + + public static Builder builder() { + return new Builder(); + } + + private final Optional<String> authorizedAddresses; + private final boolean authRequired; + private final boolean bracketEnforcement; + private final boolean verifyIndentity; + private final int maxMessageSizeInKb; + + public SmtpConfiguration(Optional<String> authorizedAddresses, boolean authRequired, boolean bracketEnforcement, boolean verifyIndentity, int maxMessageSizeInKb) { + this.authorizedAddresses = authorizedAddresses; + this.authRequired = authRequired; + this.bracketEnforcement = bracketEnforcement; + this.verifyIndentity = verifyIndentity; + this.maxMessageSizeInKb = maxMessageSizeInKb; + } + + public String serializeAsXml() throws IOException { + HashMap<String, Object> scopes = new HashMap<>(); + scopes.put("hasAuthorizedAddresses", authorizedAddresses.isPresent()); + authorizedAddresses.ifPresent(value -> scopes.put("authorizedAddresses", value)); + scopes.put("authRequired", authRequired); + scopes.put("verifyIdentity", verifyIndentity); + scopes.put("maxmessagesize", maxMessageSizeInKb); + scopes.put("bracketEnforcement", bracketEnforcement); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Writer writer = new OutputStreamWriter(byteArrayOutputStream); + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile(getPatternReader(), "example"); + mustache.execute(writer, scopes); + writer.flush(); + return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8); + } + + private StringReader getPatternReader() throws IOException { + InputStream patternStream = ClassLoader.getSystemResourceAsStream("smtpserver.xml"); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + IOUtils.copy(patternStream, byteArrayOutputStream); + String pattern = new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8); + return new StringReader(pattern); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/b64f2b2c/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfigurationTest.java ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfigurationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfigurationTest.java new file mode 100644 index 0000000..abd0c3f --- /dev/null +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/configuration/SmtpConfigurationTest.java @@ -0,0 +1,155 @@ +/**************************************************************** + * 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.mailets.configuration; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.xmlunit.matchers.EvaluateXPathMatcher.hasXPath; + +import java.io.IOException; + +import javax.xml.transform.Source; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.w3c.dom.Node; +import org.xmlunit.builder.Input; +import org.xmlunit.xpath.JAXPXPathEngine; +import org.xmlunit.xpath.XPathEngine; + +public class SmtpConfigurationTest { + @Test + public void authenticationCanBeRequired() throws IOException { + assertThat(SmtpConfiguration.builder() + .requireAuthentication() + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/authRequired/text()", + is("true"))); + } + + @Test + public void maxMessageSizeCanBeCustomized() throws IOException { + assertThat(SmtpConfiguration.builder() + .withMaxMessageSizeInKb(36) + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/maxmessagesize/text()", + is("36"))); + } + + @Test + public void bracketEnforcementCanBeDisable() throws IOException { + assertThat(SmtpConfiguration.builder() + .doNotRequireBracketEnforcement() + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/addressBracketsEnforcement/text()", + is("false"))); + } + + @Test + public void verifyIdentityEnforcementCanBeDisabled() throws IOException { + assertThat(SmtpConfiguration.builder() + .doNotVerifyIdentity() + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/verifyIdentity/text()", + is("false"))); + } + + @Test + public void authenticationCanBeDisabled() throws IOException { + assertThat(SmtpConfiguration.builder() + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/authRequired/text()", + is("false"))); + } + + @Test + public void bracketEnforcementCanBeEnabled() throws IOException { + assertThat(SmtpConfiguration.builder() + .requireBracketEnforcement() + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/addressBracketsEnforcement/text()", + is("true"))); + } + + @Test + public void verifyIdentityEnforcementCanBeEnabled() throws IOException { + assertThat(SmtpConfiguration.builder() + .verifyIdentity() + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/verifyIdentity/text()", + is("true"))); + } + + @Test + public void specificNetworkCanBeAuthorized() throws IOException { + String network = "172.0.0.0/24"; + assertThat(SmtpConfiguration.builder() + .withAutorizedAddresses(network) + .build() + .serializeAsXml(), + hasXPath("/smtpservers/smtpserver/authorizedAddresses/text()", + is(network))); + } + + @Test + public void defaultSmtpConfigurationShouldNotHaveAuthorizedNetwork() throws IOException { + String xmlFile = SmtpConfiguration.DEFAULT.serializeAsXml(); + Source source = Input.fromString(xmlFile).build(); + XPathEngine xpath = new JAXPXPathEngine(); + Iterable<Node> allMatches = xpath.selectNodes("/smtpservers/smtpserver/authorizedAddresses", source); + + Assertions.assertThat(allMatches).isEmpty(); + } + + @Test + public void authenticationShouldNotBeRequiredByDefault() throws IOException { + assertThat(SmtpConfiguration.DEFAULT.serializeAsXml(), + hasXPath("/smtpservers/smtpserver/authRequired/text()", + is("false"))); + } + + @Test + public void maxMessageSizeShouldBeDisabledByDefault() throws IOException { + assertThat(SmtpConfiguration.DEFAULT.serializeAsXml(), + hasXPath("/smtpservers/smtpserver/maxmessagesize/text()", + is("0"))); + } + + @Test + public void addressBracketsEnforcementShouldBeEnforcedByDefault() throws IOException { + assertThat(SmtpConfiguration.DEFAULT.serializeAsXml(), + hasXPath("/smtpservers/smtpserver/addressBracketsEnforcement/text()", + is("true"))); + } + + @Test + public void verifyIdentityShouldBeEnforcedByDefault() throws IOException { + assertThat(SmtpConfiguration.DEFAULT.serializeAsXml(), + hasXPath("/smtpservers/smtpserver/verifyIdentity/text()", + is("true"))); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/b64f2b2c/server/mailet/integration-testing/src/test/resources/smtpserver.xml ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/resources/smtpserver.xml b/server/mailet/integration-testing/src/test/resources/smtpserver.xml index a3d4b8f..30c56f8 100644 --- a/server/mailet/integration-testing/src/test/resources/smtpserver.xml +++ b/server/mailet/integration-testing/src/test/resources/smtpserver.xml @@ -33,11 +33,13 @@ <connectiontimeout>360</connectiontimeout> <connectionLimit>0</connectionLimit> <connectionLimitPerIP>0</connectionLimitPerIP> - <authRequired>false</authRequired> - <authorizedAddresses>0.0.0.0/0</authorizedAddresses> - <verifyIdentity>true</verifyIdentity> - <maxmessagesize>0</maxmessagesize> - <addressBracketsEnforcement>true</addressBracketsEnforcement> + <authRequired>{{authRequired}}</authRequired> +{{#hasAuthorizedAddresses}} + <authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses> +{{/hasAuthorizedAddresses}} + <verifyIdentity>{{verifyIdentity}}</verifyIdentity> + <maxmessagesize>{{maxmessagesize}}</maxmessagesize> + <addressBracketsEnforcement>{{bracketEnforcement}}</addressBracketsEnforcement> <smtpGreeting>JAMES Linagora's SMTP awesome Server</smtpGreeting> <handlerchain> <handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/> @@ -61,11 +63,13 @@ Authorize only local users --> <authRequired>true</authRequired> - <authorizedAddresses>0.0.0.0/0</authorizedAddresses> + {{#hasAuthorizedAddresses}} + <authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses> + {{/hasAuthorizedAddresses}} <!-- Trust authenticated users --> <verifyIdentity>false</verifyIdentity> - <maxmessagesize>0</maxmessagesize> - <addressBracketsEnforcement>true</addressBracketsEnforcement> + <maxmessagesize>{{maxmessagesize}}</maxmessagesize> + <addressBracketsEnforcement>{{bracketEnforcement}}</addressBracketsEnforcement> <smtpGreeting>JAMES Linagora's SMTP awesome Server</smtpGreeting> <handlerchain> <handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/> @@ -89,11 +93,13 @@ Authorize only local users --> <authRequired>true</authRequired> - <authorizedAddresses>0.0.0.0/0</authorizedAddresses> + {{#hasAuthorizedAddresses}} + <authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses> + {{/hasAuthorizedAddresses}} <!-- Trust authenticated users --> <verifyIdentity>false</verifyIdentity> - <maxmessagesize>0</maxmessagesize> - <addressBracketsEnforcement>true</addressBracketsEnforcement> + <maxmessagesize>{{maxmessagesize}}</maxmessagesize> + <addressBracketsEnforcement>{{bracketEnforcement}}</addressBracketsEnforcement> <smtpGreeting>JAMES Linagora's SMTP awesome Server</smtpGreeting> <handlerchain> <handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/> --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org