This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 31bc950aa9b839ef5a669241b13773ee3bf93486 Author: Quan Tran <[email protected]> AuthorDate: Fri Jul 17 11:52:41 2026 +0700 JAMES-4210 Test POP3 SASL production wiring Cover Guice mechanism configuration and distributed CAPA and AUTH PLAIN behavior. --- .../java/org/apache/james/Pop3ServerContract.java | 71 +++++++++++++++++++++ .../modules/protocols/POP3ServerModuleTest.java | 72 ++++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/server/apps/distributed-pop3-app/src/test/java/org/apache/james/Pop3ServerContract.java b/server/apps/distributed-pop3-app/src/test/java/org/apache/james/Pop3ServerContract.java index cce24282ba..5b5032f5fc 100644 --- a/server/apps/distributed-pop3-app/src/test/java/org/apache/james/Pop3ServerContract.java +++ b/server/apps/distributed-pop3-app/src/test/java/org/apache/james/Pop3ServerContract.java @@ -21,12 +21,21 @@ package org.apache.james; import static org.assertj.core.api.Assertions.assertThat; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.io.Reader; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.List; import java.util.stream.IntStream; import org.apache.commons.net.pop3.POP3Client; import org.apache.commons.net.pop3.POP3MessageInfo; +import org.apache.commons.net.pop3.POP3Reply; import org.apache.james.backends.cassandra.TestingSession; import org.apache.james.backends.cassandra.init.SessionWithInitializedTablesFactory; import org.apache.james.core.Username; @@ -102,6 +111,68 @@ public interface Pop3ServerContract { String PASSWORD = "123456"; String DOMAIN = "examplebis.local"; + private static String plainInitialResponse(String username, String password) { + return Base64.getEncoder() + .encodeToString(("\0" + username + "\0" + password).getBytes(StandardCharsets.UTF_8)); + } + + private static void send(BufferedWriter writer, String command) throws IOException { + writer.write(command + "\r\n"); + writer.flush(); + } + + @Test + default void capaShouldAdvertisePlainSaslMechanism(GuiceJamesServer server) throws Exception { + POP3Client pop3Client = new POP3Client(); + pop3Client.connect("127.0.0.1", server.getProbe(Pop3GuiceProbe.class).getPop3Port()); + + assertThat(pop3Client.sendCommand("CAPA")).isEqualTo(POP3Reply.OK); + pop3Client.getAdditionalReply(); + assertThat(pop3Client.getReplyStrings()).contains("SASL PLAIN"); + + pop3Client.disconnect(); + } + + @Test + default void authPlainShouldSucceed(GuiceJamesServer server) throws Exception { + server.getProbe(DataProbeImpl.class).fluent() + .addDomain(DOMAIN) + .addUser(USER, PASSWORD); + + try (Socket socket = new Socket("127.0.0.1", server.getProbe(Pop3GuiceProbe.class).getPop3Port()); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.US_ASCII)); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.US_ASCII))) { + assertThat(reader.readLine()).startsWith("+OK"); + + // WHEN authenticating with an RFC 5034 PLAIN initial response + send(writer, "AUTH PLAIN " + plainInitialResponse(USER, PASSWORD)); + + // THEN authentication succeeds and POP3 transaction commands are available + assertThat(reader.readLine()).isEqualTo("+OK Welcome " + USER); + send(writer, "STAT"); + assertThat(reader.readLine()).matches("\\+OK \\d+ \\d+"); + } + } + + @Test + default void authPlainShouldRejectInvalidCredentials(GuiceJamesServer server) throws Exception { + server.getProbe(DataProbeImpl.class).fluent() + .addDomain(DOMAIN) + .addUser(USER, PASSWORD); + + try (Socket socket = new Socket("127.0.0.1", server.getProbe(Pop3GuiceProbe.class).getPop3Port()); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.US_ASCII)); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.US_ASCII))) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH PLAIN " + plainInitialResponse(USER, "invalid-password")); + assertThat(reader.readLine()).isEqualTo("-ERR Authentication failed."); + + send(writer, "STAT"); + assertThat(reader.readLine()).isEqualTo("-ERR"); + } + } + @Test default void mailsCanBeReadInPop3(GuiceJamesServer server) throws Exception { server.getProbe(DataProbeImpl.class).fluent() diff --git a/server/container/guice/protocols/pop/src/test/java/org/apache/james/modules/protocols/POP3ServerModuleTest.java b/server/container/guice/protocols/pop/src/test/java/org/apache/james/modules/protocols/POP3ServerModuleTest.java new file mode 100644 index 0000000000..8a011cd9e3 --- /dev/null +++ b/server/container/guice/protocols/pop/src/test/java/org/apache/james/modules/protocols/POP3ServerModuleTest.java @@ -0,0 +1,72 @@ +/**************************************************************** + * 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.modules.protocols; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.apache.commons.configuration2.BaseHierarchicalConfiguration; +import org.apache.commons.configuration2.ex.ConfigurationException; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; + +class POP3ServerModuleTest { + private final POP3ServerModule testee = new POP3ServerModule(); + + @Test + void retrieveSaslMechanismFactoryClassNamesShouldReturnEmptyWhenAbsent() throws Exception { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + + ImmutableList<String> mechanismFactoryClassNames = testee.retrieveSaslMechanismFactoryClassNames(configuration); + + assertThat(mechanismFactoryClassNames).isEmpty(); + } + + @Test + void retrieveSaslMechanismFactoryClassNamesShouldPreserveConfiguredOrder() throws Exception { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + configuration.addProperty("auth.saslMechanisms", + "XOauth2SaslMechanismFactory,PlainSaslMechanismFactory,com.example.CustomSaslMechanismFactory"); + + ImmutableList<String> mechanismFactoryClassNames = testee.retrieveSaslMechanismFactoryClassNames(configuration); + + assertThat(mechanismFactoryClassNames) + .containsExactly("XOauth2SaslMechanismFactory", "PlainSaslMechanismFactory", "com.example.CustomSaslMechanismFactory"); + } + + @Test + void retrieveSaslMechanismFactoryClassNamesShouldRejectBlankConfiguredList() { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + configuration.addProperty("auth.saslMechanisms", " "); + + assertThatThrownBy(() -> testee.retrieveSaslMechanismFactoryClassNames(configuration)) + .isInstanceOf(ConfigurationException.class); + } + + @Test + void retrieveSaslMechanismFactoryClassNamesShouldRejectBlankEntry() { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + configuration.addProperty("auth.saslMechanisms", "PlainSaslMechanismFactory,,XOauth2SaslMechanismFactory"); + + assertThatThrownBy(() -> testee.retrieveSaslMechanismFactoryClassNames(configuration)) + .isInstanceOf(ConfigurationException.class); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
