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 b988d2cfc8430239ff66cd14861dc31a30f7f3a3 Author: Quan Tran <[email protected]> AuthorDate: Fri Jul 17 11:52:32 2026 +0700 JAMES-4210 Test POP3 AUTH and TLS behavior Cover PLAIN and OAuth flows, STARTTLS capability changes, line limits, cancellation, cleanup, and TLS defaults. --- .../apache/james/pop3server/POP3ServerTest.java | 499 +++++++++++++++++++++ .../pop3server/netty/POP3ServerFactoryTest.java | 79 ++++ 2 files changed, 578 insertions(+) diff --git a/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java index 9f30037270..932f907a71 100644 --- a/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java +++ b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java @@ -22,20 +22,29 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.io.Reader; import java.net.InetSocketAddress; +import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Arrays; +import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import jakarta.mail.util.SharedByteArrayInputStream; import org.apache.commons.configuration2.XMLConfiguration; +import org.apache.commons.configuration2.ex.ConfigurationException; import org.apache.commons.net.pop3.POP3Client; import org.apache.commons.net.pop3.POP3MessageInfo; import org.apache.commons.net.pop3.POP3Reply; @@ -44,6 +53,7 @@ import org.apache.james.UserEntityValidator; import org.apache.james.core.Username; import org.apache.james.domainlist.api.DomainList; import org.apache.james.filesystem.api.FileSystem; +import org.apache.james.jwt.OidcJwtTokenVerifier; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageManager; @@ -58,27 +68,103 @@ import org.apache.james.mime4j.dom.Message; import org.apache.james.pop3server.mailbox.DefaultMailboxAdapterFactory; import org.apache.james.pop3server.mailbox.MailboxAdapterFactory; import org.apache.james.pop3server.netty.POP3Server; +import org.apache.james.pop3server.netty.POP3ServerFactory; +import org.apache.james.protocols.api.OIDCSASLHelper; +import org.apache.james.protocols.api.sasl.SaslAuthenticator; +import org.apache.james.protocols.api.sasl.SaslExchange; +import org.apache.james.protocols.api.sasl.SaslIdentity; +import org.apache.james.protocols.api.sasl.SaslInitialRequest; +import org.apache.james.protocols.api.sasl.SaslMechanism; +import org.apache.james.protocols.api.sasl.SaslMechanismNames; +import org.apache.james.protocols.api.sasl.SaslStep; +import org.apache.james.protocols.api.utils.BogusSslContextFactory; +import org.apache.james.protocols.api.utils.BogusTrustManagerFactory; import org.apache.james.protocols.api.utils.ProtocolServerUtils; import org.apache.james.protocols.lib.LegacyJavaEncryptionFactory; import org.apache.james.protocols.lib.POP3BeforeSMTPHelper; import org.apache.james.protocols.lib.mock.ConfigLoader; import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader; +import org.apache.james.protocols.netty.AbstractChannelPipelineFactory; +import org.apache.james.protocols.sasl.oidc.OAuthSaslMechanism; import org.apache.james.server.core.filesystem.FileSystemImpl; import org.apache.james.user.api.UsersRepository; import org.apache.james.user.api.UsersRepositoryException; import org.apache.james.user.memory.MemoryUsersRepository; +import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import com.google.common.collect.ImmutableList; import com.google.inject.name.Names; import reactor.core.publisher.Flux; public class POP3ServerTest { private static final DomainList NO_DOMAIN_LIST = null; + private static final String OAUTH_TOKEN = "token"; + private static final byte[] INVALID_TOKEN_RESPONSE = "{\"status\":\"invalid_token\"}".getBytes(StandardCharsets.UTF_8); + private static final int OVERSIZED_SASL_CONTINUATION_LENGTH = 65537; + + private static class TestOidcJwtTokenVerifier extends OidcJwtTokenVerifier { + private final Optional<Username> validationResult; + + private TestOidcJwtTokenVerifier(Optional<Username> validationResult) { + super(null); + this.validationResult = validationResult; + } + + @Override + public Optional<Username> validateToken(String token) { + return validationResult; + } + } + + private static class ServerDataSaslMechanism implements SaslMechanism { + private static final byte[] CHALLENGE = "challenge".getBytes(StandardCharsets.US_ASCII); + private static final byte[] SERVER_DATA = "server-data".getBytes(StandardCharsets.US_ASCII); + + private final Username username; + private final AtomicBoolean closed; + private final String name; + + private ServerDataSaslMechanism(Username username, AtomicBoolean closed) { + this("TEST", username, closed); + } + + private ServerDataSaslMechanism(String name, Username username, AtomicBoolean closed) { + this.name = name; + this.username = username; + this.closed = closed; + } + + @Override + public String name() { + return name; + } + + @Override + public SaslExchange start(SaslInitialRequest request, SaslAuthenticator authenticator) { + return new SaslExchange() { + @Override + public SaslStep firstStep() { + return new SaslStep.Challenge(Optional.of(CHALLENGE)); + } + + @Override + public SaslStep onResponse(byte[] clientResponse) { + return new SaslStep.Success(new SaslIdentity(username, username), Optional.of(SERVER_DATA)); + } + + @Override + public void close() { + closed.set(true); + } + }; + } + } private XMLConfiguration pop3Configuration; protected final MemoryUsersRepository usersRepository = MemoryUsersRepository.withoutVirtualHosting(NO_DOMAIN_LIST); @@ -97,6 +183,11 @@ public class POP3ServerTest { setUpServiceManager(); setUpPOP3Server(); pop3Configuration = ConfigLoader.getConfig(fileSystem.getResource("classpath://pop3server.xml")); + configureSaslMechanismsFromConfiguration(); + } + + private void configureSaslMechanismsFromConfiguration() throws ConfigurationException { + pop3Server.setSaslMechanisms(POP3ServerFactory.Pop3SaslMechanismLoader.defaultLoader().load(pop3Configuration)); } @AfterEach @@ -852,6 +943,371 @@ public class POP3ServerTest { } + @Test + void authPlainWithInitialResponseShouldAuthenticate() throws Exception { + finishSetUp(pop3Configuration); + usersRepository.addUser(Username.of("auth-user"), "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH PLAIN " + plainInitialResponse("auth-user", "secret")); + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + + send(writer, "STAT"); + assertThat(reader.readLine()).startsWith("+OK"); + } + } + + @Test + void authPlainShouldSupportContinuation() throws Exception { + finishSetUp(pop3Configuration); + usersRepository.addUser(Username.of("auth-user"), "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH PLAIN"); + assertThat(reader.readLine()).isEqualTo("+ "); + + send(writer, plainInitialResponse("auth-user", "secret")); + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + } + } + + @Test + void malformedAuthShouldPreserveUserPassAuthentication() throws Exception { + finishSetUp(pop3Configuration); + usersRepository.addUser(Username.of("auth-user"), "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "USER auth-user"); + assertThat(reader.readLine()).startsWith("+OK"); + send(writer, "AUTH PLAIN " + encoded("malformed")); + assertThat(reader.readLine()).isEqualTo("-ERR Invalid AUTH request."); + + send(writer, "PASS secret"); + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + } + } + + @Test + void capaShouldAdvertiseAvailableSaslMechanisms() throws Exception { + finishSetUp(pop3Configuration); + + pop3Client = new POP3Client(); + InetSocketAddress bindedAddress = new ProtocolServerUtils(pop3Server).retrieveBindedAddress(); + pop3Client.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort()); + + assertThat(pop3Client.sendCommand("CAPA")).isEqualTo(POP3Reply.OK); + pop3Client.getAdditionalReply(); + + assertThat(pop3Client.getReplyStrings()).contains("SASL PLAIN"); + } + + @Test + void capaShouldAdvertisePlainOnlyAfterStartTlsWhenRequireSSL() throws Exception { + pop3Configuration.setProperty("auth.requireSSL", true); + configureSaslMechanismsFromConfiguration(); + finishSetUp(pop3Configuration); + + POP3SClient client = new POP3SClient(false, BogusSslContextFactory.getClientContext()); + client.setTrustManager(BogusTrustManagerFactory.getTrustManagers()[0]); + pop3Client = client; + InetSocketAddress bindedAddress = new ProtocolServerUtils(pop3Server).retrieveBindedAddress(); + client.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort()); + + assertThat(client.sendCommand("CAPA")).isEqualTo(POP3Reply.OK); + client.getAdditionalReply(); + assertThat(client.getReplyStrings()).doesNotContain("SASL PLAIN"); + + assertThat(client.execTLS()).isTrue(); + assertThat(client.sendCommand("CAPA")).isEqualTo(POP3Reply.OK); + client.getAdditionalReply(); + assertThat(client.getReplyStrings()).contains("SASL PLAIN"); + } + + @Test + void unknownAuthMechanismShouldBeRejectedAsUnsupported() throws Exception { + finishSetUp(pop3Configuration); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH UNKNOWN"); + assertThat(reader.readLine()).isEqualTo("-ERR Unsupported authentication mechanism."); + } + } + + @Test + void clearTextPassAndAuthPlainShouldBeRejectedByDefault() throws Exception { + pop3Configuration.clearProperty("auth.requireSSL"); // absent "auth.requireSSL" defaults to true + configureSaslMechanismsFromConfiguration(); + finishSetUp(pop3Configuration); + usersRepository.addUser(Username.of("auth-user"), "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH PLAIN " + plainInitialResponse("auth-user", "secret")); + assertThat(reader.readLine()).isEqualTo("-ERR Authentication requires TLS."); + + send(writer, "USER auth-user"); + assertThat(reader.readLine()).startsWith("+OK"); + send(writer, "PASS secret"); + assertThat(reader.readLine()).isEqualTo("-ERR Authentication requires TLS."); + } + } + + @Test + void userPassShouldBeAvailableAfterStartTlsWhenRequireSSL() throws Exception { + pop3Configuration.setProperty("auth.requireSSL", true); + finishSetUp(pop3Configuration); + usersRepository.addUser(Username.of("auth-user"), "secret"); + + POP3SClient client = new POP3SClient(false, BogusSslContextFactory.getClientContext()); + client.setTrustManager(BogusTrustManagerFactory.getTrustManagers()[0]); + pop3Client = client; + InetSocketAddress bindedAddress = new ProtocolServerUtils(pop3Server).retrieveBindedAddress(); + client.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort()); + + assertThat(client.execTLS()).isTrue(); + assertThat(client.login("auth-user", "secret")).isTrue(); + } + + @Test + void authPlainShouldBeAvailableAfterStartTlsWhenRequireSSL() throws Exception { + pop3Configuration.setProperty("auth.requireSSL", true); + configureSaslMechanismsFromConfiguration(); + finishSetUp(pop3Configuration); + usersRepository.addUser(Username.of("auth-user"), "secret"); + + POP3SClient client = new POP3SClient(false, BogusSslContextFactory.getClientContext()); + client.setTrustManager(BogusTrustManagerFactory.getTrustManagers()[0]); + pop3Client = client; + InetSocketAddress bindedAddress = new ProtocolServerUtils(pop3Server).retrieveBindedAddress(); + client.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort()); + + assertThat(client.execTLS()).isTrue(); + assertThat(client.sendCommand("AUTH PLAIN " + plainInitialResponse("auth-user", "secret"))).isEqualTo(POP3Reply.OK); + assertThat(client.getReplyString()).contains("+OK Welcome auth-user"); + assertThat(client.sendCommand("STAT")).isEqualTo(POP3Reply.OK); + } + + @Test + void clearTextPassAndAuthPlainShouldBeAvailableWhenRequireSSLIsFalse() throws Exception { + pop3Configuration.setProperty("auth.requireSSL", false); + configureSaslMechanismsFromConfiguration(); + finishSetUp(pop3Configuration); + usersRepository.addUser(Username.of("auth-user"), "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH PLAIN " + plainInitialResponse("auth-user", "secret")); + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + } + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "USER auth-user"); + assertThat(reader.readLine()).startsWith("+OK"); + send(writer, "PASS secret"); + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + } + } + + @Test + void authShouldSupportFinalServerDataAndCloseExchange() throws Exception { + Username username = Username.of("auth-user"); + AtomicBoolean closed = new AtomicBoolean(); + pop3Server.setSaslMechanisms(ImmutableList.of(new ServerDataSaslMechanism(username, closed))); + finishSetUp(pop3Configuration); + usersRepository.addUser(username, "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH TEST"); + assertThat(reader.readLine()).isEqualTo("+ " + encoded("challenge")); + // RFC 5034 allows SASL continuations to exceed normal POP3 command line limits. + send(writer, encoded("x".repeat(AbstractChannelPipelineFactory.MAX_LINE_LENGTH))); + assertThat(reader.readLine()).isEqualTo("+ " + encoded("server-data")); + send(writer, ""); + + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + assertThat(closed).isTrue(); + + send(writer, "AUTH TEST"); + assertThat(reader.readLine()).startsWith("-ERR"); + send(writer, "CAPA"); + assertThat(reader.readLine()).startsWith("+OK"); + assertThat(readMultilineResponse(reader)).contains("SASL TEST"); + } + } + + @Test + void authCancellationShouldCloseExchangeAndPreserveUserPassAuthentication() throws Exception { + Username username = Username.of("auth-user"); + AtomicBoolean closed = new AtomicBoolean(); + pop3Configuration.setProperty("auth.requireSSL", false); + pop3Server.setSaslMechanisms(ImmutableList.of(new ServerDataSaslMechanism(username, closed))); + finishSetUp(pop3Configuration); + usersRepository.addUser(username, "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "USER auth-user"); + assertThat(reader.readLine()).startsWith("+OK"); + send(writer, "AUTH TEST"); + assertThat(reader.readLine()).isEqualTo("+ " + encoded("challenge")); + send(writer, "*"); + assertThat(reader.readLine()).startsWith("-ERR"); + assertThat(closed).isTrue(); + + send(writer, "PASS secret"); + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + } + } + + @Test + void oversizedSaslContinuationShouldCloseExchangeAndConnection() throws Exception { + Username username = Username.of("auth-user"); + AtomicBoolean closed = new AtomicBoolean(); + pop3Server.setSaslMechanisms(ImmutableList.of(new ServerDataSaslMechanism(username, closed))); + finishSetUp(pop3Configuration); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + socket.setSoTimeout(5_000); + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH TEST"); + assertThat(reader.readLine()).isEqualTo("+ " + encoded("challenge")); + send(writer, "A".repeat(OVERSIZED_SASL_CONTINUATION_LENGTH)); + + assertThat(reader.readLine()).isEqualTo("-ERR Exceed maximal line length"); + assertThat(reader.readLine()).isNull(); + Awaitility.await().atMost(Duration.ofSeconds(2)).untilTrue(closed); + } + } + + @Test + void authShouldEnforceInitialCommandLength() throws Exception { + Username username = Username.of("auth-user"); + AtomicBoolean closed = new AtomicBoolean(); + pop3Server.setSaslMechanisms(ImmutableList.of(new ServerDataSaslMechanism("TST", username, closed))); + finishSetUp(pop3Configuration); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + String maximumLengthInitialResponse = "A".repeat(244); + send(writer, "AUTH TST " + maximumLengthInitialResponse); + assertThat(reader.readLine()).isEqualTo("+ " + encoded("challenge")); + send(writer, "*"); + assertThat(reader.readLine()).startsWith("-ERR"); + + send(writer, "AUTH TST " + maximumLengthInitialResponse + "A"); + assertThat(reader.readLine()).startsWith("-ERR"); + + // RFC 5034 applies the 255-octet limit to the original command, before parser whitespace normalization. + send(writer, "AUTH TST" + " ".repeat(246)); + assertThat(reader.readLine()).startsWith("-ERR"); + } + } + + @Test + void authOauthBearerShouldAuthenticateWithValidToken() throws Exception { + Username username = Username.of("auth-user"); + pop3Server.setSaslMechanisms(ImmutableList.of(oauthBearerMechanism(Optional.of(username)))); + finishSetUp(pop3Configuration); + usersRepository.addUser(username, "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + String initialResponse = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(username.asString(), OAUTH_TOKEN); + send(writer, "AUTH OAUTHBEARER " + initialResponse); + + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + } + } + + @Test + void authOauthBearerShouldAuthenticateLargeTokenThroughContinuation() throws Exception { + Username username = Username.of("auth-user"); + pop3Server.setSaslMechanisms(ImmutableList.of(oauthBearerMechanism(Optional.of(username)))); + finishSetUp(pop3Configuration); + usersRepository.addUser(username, "secret"); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + send(writer, "AUTH OAUTHBEARER"); + assertThat(reader.readLine()).isEqualTo("+ "); + + // RFC 5034 permits SASL continuations to exceed regular POP3 command limits. A large token + // must therefore be sent after the empty challenge instead of as an initial AUTH response. + String clientResponse = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse( + username.asString(), "x".repeat(AbstractChannelPipelineFactory.MAX_LINE_LENGTH)); + assertThat(clientResponse.length()).isGreaterThan(AbstractChannelPipelineFactory.MAX_LINE_LENGTH); + send(writer, clientResponse); + + assertThat(reader.readLine()).isEqualTo("+OK Welcome auth-user"); + } + } + + @Test + void authOauthBearerShouldSendErrorChallengeBeforeRejectingInvalidToken() throws Exception { + Username username = Username.of("auth-user"); + pop3Server.setSaslMechanisms(ImmutableList.of(oauthBearerMechanism(Optional.empty()))); + finishSetUp(pop3Configuration); + + try (Socket socket = connectToPop3Server(); + BufferedReader reader = reader(socket); + BufferedWriter writer = writer(socket)) { + assertThat(reader.readLine()).startsWith("+OK"); + + String initialResponse = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(username.asString(), OAUTH_TOKEN); + send(writer, "AUTH OAUTHBEARER " + initialResponse); + assertThat(reader.readLine()).isEqualTo("+ " + Base64.getEncoder().encodeToString(INVALID_TOKEN_RESPONSE)); + + // RFC 7628 section 3.2.3 requires a dummy client response before the server completes the failure. + send(writer, "AQ=="); + assertThat(reader.readLine()).startsWith("-ERR"); + } + } + /* * See JAMES-649 The same happens when using RETR * @@ -968,6 +1424,49 @@ public class POP3ServerTest { initPOP3Server(pop3Configuration); } + private Socket connectToPop3Server() throws IOException { + InetSocketAddress bindedAddress = new ProtocolServerUtils(pop3Server).retrieveBindedAddress(); + return new Socket(bindedAddress.getAddress(), bindedAddress.getPort()); + } + + private BufferedReader reader(Socket socket) throws IOException { + return new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.US_ASCII)); + } + + private BufferedWriter writer(Socket socket) throws IOException { + return new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.US_ASCII)); + } + + private void send(BufferedWriter writer, String command) throws IOException { + writer.write(command); + writer.write("\r\n"); + writer.flush(); + } + + private String plainInitialResponse(String username, String password) { + return Base64.getEncoder().encodeToString(("\0" + username + "\0" + password).getBytes(StandardCharsets.UTF_8)); + } + + private String encoded(String value) { + return Base64.getEncoder().encodeToString(value.getBytes(StandardCharsets.US_ASCII)); + } + + private OAuthSaslMechanism oauthBearerMechanism(Optional<Username> validationResult) { + return new OAuthSaslMechanism(SaslMechanismNames.OAUTHBEARER, + new TestOidcJwtTokenVerifier(validationResult), false, INVALID_TOKEN_RESPONSE); + } + + private ImmutableList<String> readMultilineResponse(BufferedReader reader) throws IOException { + ImmutableList.Builder<String> lines = ImmutableList.builder(); + while (true) { + String line = reader.readLine(); + if (line.equals(".")) { + return lines.build(); + } + lines.add(line); + } + } + protected void setUpServiceManager() { mailboxManager = InMemoryIntegrationResources.builder() .authenticator((userid, passwd) -> { diff --git a/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/netty/POP3ServerFactoryTest.java b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/netty/POP3ServerFactoryTest.java new file mode 100644 index 0000000000..53278d2216 --- /dev/null +++ b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/netty/POP3ServerFactoryTest.java @@ -0,0 +1,79 @@ +/**************************************************************** + * 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.pop3server.netty; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.commons.configuration2.BaseHierarchicalConfiguration; +import org.apache.james.protocols.api.sasl.SaslMechanism; +import org.apache.james.protocols.api.sasl.SaslMechanismNames; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; + +class POP3ServerFactoryTest { + private static final boolean CLEAR_TEXT_TRANSPORT = false; + private static final boolean ENCRYPTED_TRANSPORT = true; + + @Test + void defaultLoaderShouldEnableOnlyPlainWithoutOidcConfiguration() throws Exception { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + + ImmutableList<SaslMechanism> mechanisms = POP3ServerFactory.Pop3SaslMechanismLoader.defaultLoader().load(configuration); + + assertThat(mechanisms) + .extracting(SaslMechanism::name) + .containsExactly(SaslMechanismNames.PLAIN); + } + + @Test + void defaultLoaderShouldEnableOidcMechanismsWithOidcConfiguration() throws Exception { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + configuration.addProperty("auth.oidc.jwksURL", "https://example.com/jwks"); + configuration.addProperty("auth.oidc.claim", "email"); + configuration.addProperty("auth.oidc.oidcConfigurationURL", "https://example.com/.well-known/openid-configuration"); + configuration.addProperty("auth.oidc.scope", "openid email"); + configuration.addProperty("auth.oidc.aud", "james"); + + ImmutableList<SaslMechanism> mechanisms = POP3ServerFactory.Pop3SaslMechanismLoader.defaultLoader().load(configuration); + + assertThat(mechanisms) + .extracting(SaslMechanism::name) + .containsExactly(SaslMechanismNames.PLAIN, SaslMechanismNames.OAUTHBEARER, SaslMechanismNames.XOAUTH2); + } + + @Test + void defaultPlainMechanismShouldRequireTls() throws Exception { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + SaslMechanism plain = POP3ServerFactory.Pop3SaslMechanismLoader.defaultLoader().load(configuration).getFirst(); + + assertThat(plain.isAvailableOnTransport(CLEAR_TEXT_TRANSPORT)).isFalse(); + assertThat(plain.isAvailableOnTransport(ENCRYPTED_TRANSPORT)).isTrue(); + } + + @Test + void defaultPlainMechanismShouldHonorRequireSslFalse() throws Exception { + BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration(); + configuration.addProperty("auth.requireSSL", false); + SaslMechanism plain = POP3ServerFactory.Pop3SaslMechanismLoader.defaultLoader().load(configuration).getFirst(); + + assertThat(plain.isAvailableOnTransport(CLEAR_TEXT_TRANSPORT)).isTrue(); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
