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 883f25db2c8093825cd71859a53dcda917891615
Author: Quan Tran <[email protected]>
AuthorDate: Fri Jul 17 11:52:07 2026 +0700

    JAMES-4210 Wire configurable POP3 SASL mechanisms
    
    Load built-in and custom SASL factories with Guice and wire Spring to the 
default PLAIN and OAuth mechanisms.
---
 server/container/guice/protocols/pop/pom.xml       |  4 ++
 .../james/modules/protocols/POP3ServerModule.java  | 57 ++++++++++++++++++++++
 .../Pop3DefaultSaslMechanismFactories.java         | 31 ++++++++++++
 .../james/pop3server/netty/POP3ServerFactory.java  | 47 ++++++++++++++++++
 .../META-INF/spring/pop3server-context.xml         |  4 ++
 5 files changed, 143 insertions(+)

diff --git a/server/container/guice/protocols/pop/pom.xml 
b/server/container/guice/protocols/pop/pom.xml
index 7d72e0fa82..8c848c0a49 100644
--- a/server/container/guice/protocols/pop/pom.xml
+++ b/server/container/guice/protocols/pop/pom.xml
@@ -49,6 +49,10 @@
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.protocols.groupId}</groupId>
+            <artifactId>protocols-sasl</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.google.inject</groupId>
             <artifactId>guice</artifactId>
diff --git 
a/server/container/guice/protocols/pop/src/main/java/org/apache/james/modules/protocols/POP3ServerModule.java
 
b/server/container/guice/protocols/pop/src/main/java/org/apache/james/modules/protocols/POP3ServerModule.java
index 445fb50b03..662782365a 100644
--- 
a/server/container/guice/protocols/pop/src/main/java/org/apache/james/modules/protocols/POP3ServerModule.java
+++ 
b/server/container/guice/protocols/pop/src/main/java/org/apache/james/modules/protocols/POP3ServerModule.java
@@ -19,6 +19,12 @@
 
 package org.apache.james.modules.protocols;
 
+import java.util.Arrays;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.james.ProtocolConfigurationSanitizer;
 import org.apache.james.RunArguments;
 import org.apache.james.filesystem.api.FileSystem;
@@ -26,15 +32,26 @@ import 
org.apache.james.lifecycle.api.ConfigurationSanitizer;
 import org.apache.james.pop3server.mailbox.DefaultMailboxAdapterFactory;
 import org.apache.james.pop3server.mailbox.MailboxAdapterFactory;
 import org.apache.james.pop3server.netty.POP3ServerFactory;
+import 
org.apache.james.pop3server.netty.POP3ServerFactory.Pop3SaslMechanismLoader;
+import org.apache.james.protocols.api.sasl.SaslMechanism;
+import org.apache.james.protocols.api.sasl.SaslMechanismFactory;
 import org.apache.james.protocols.lib.netty.CertificateReloadable;
+import org.apache.james.protocols.sasl.BuiltInSaslMechanismFactories;
+import org.apache.james.protocols.sasl.OauthBearerSaslMechanismFactory;
+import org.apache.james.protocols.sasl.PlainSaslMechanismFactory;
+import org.apache.james.protocols.sasl.XOauth2SaslMechanismFactory;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.GuiceProbe;
+import org.apache.james.utils.GuiceSaslMechanismResolver;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
 import org.apache.james.utils.KeystoreCreator;
 
+import com.google.common.collect.ImmutableList;
 import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
 import com.google.inject.Scopes;
+import com.google.inject.Singleton;
 import com.google.inject.multibindings.Multibinder;
 import com.google.inject.multibindings.ProvidesIntoSet;
 
@@ -51,6 +68,46 @@ public class POP3ServerModule extends AbstractModule {
         Multibinder.newSetBinder(binder(), 
CertificateReloadable.Factory.class).addBinding().to(POP3ServerFactory.class);
     }
 
+    @Provides
+    @Singleton
+    @Pop3DefaultSaslMechanismFactories
+    ImmutableList<SaslMechanismFactory> 
provideDefaultPop3SaslMechanismFactories(OauthBearerSaslMechanismFactory 
oauthBearer,
+                                                                               
  XOauth2SaslMechanismFactory xoauth2) {
+        return ImmutableList.of(new PlainSaslMechanismFactory(), oauthBearer, 
xoauth2);
+    }
+
+    @Provides
+    @Singleton
+    Pop3SaslMechanismLoader 
providePop3SaslMechanismLoader(GuiceSaslMechanismResolver saslMechanismResolver,
+                                                           
@Pop3DefaultSaslMechanismFactories ImmutableList<SaslMechanismFactory> 
defaultSaslMechanismFactories) {
+        return configuration -> retrieveSaslMechanisms(saslMechanismResolver, 
defaultSaslMechanismFactories, configuration);
+    }
+
+    private ImmutableList<SaslMechanism> 
retrieveSaslMechanisms(GuiceSaslMechanismResolver saslMechanismResolver,
+                                                                
ImmutableList<SaslMechanismFactory> defaultSaslMechanismFactories,
+                                                                
HierarchicalConfiguration<ImmutableNode> configuration) throws 
ConfigurationException {
+        ImmutableList<String> mechanismFactoryClassNames = 
retrieveSaslMechanismFactoryClassNames(configuration);
+        ImmutableList<SaslMechanismFactory> enabledDefaultFactories =
+            
BuiltInSaslMechanismFactories.enabledForServer(defaultSaslMechanismFactories, 
configuration);
+        return saslMechanismResolver.resolve(mechanismFactoryClassNames, 
enabledDefaultFactories, configuration);
+    }
+
+    ImmutableList<String> 
retrieveSaslMechanismFactoryClassNames(HierarchicalConfiguration<ImmutableNode> 
configuration) throws ConfigurationException {
+        if (!configuration.containsKey("auth.saslMechanisms")) {
+            return ImmutableList.of();
+        }
+
+        ImmutableList<String> mechanismFactoryClassNames = 
Arrays.stream(configuration.getStringArray("auth.saslMechanisms"))
+            .flatMap(value -> Arrays.stream(value.split(",")))
+            .map(String::trim)
+            .collect(ImmutableList.toImmutableList());
+
+        if (mechanismFactoryClassNames.isEmpty() || 
mechanismFactoryClassNames.stream().anyMatch(StringUtils::isBlank)) {
+            throw new ConfigurationException("auth.saslMechanisms must not be 
blank when configured");
+        }
+        return mechanismFactoryClassNames;
+    }
+
     @ProvidesIntoSet
     InitializationOperation configurePop3(ConfigurationProvider 
configurationProvider, POP3ServerFactory pop3ServerFactory) {
         return InitilizationOperationBuilder
diff --git 
a/server/container/guice/protocols/pop/src/main/java/org/apache/james/modules/protocols/Pop3DefaultSaslMechanismFactories.java
 
b/server/container/guice/protocols/pop/src/main/java/org/apache/james/modules/protocols/Pop3DefaultSaslMechanismFactories.java
new file mode 100644
index 0000000000..042a217a85
--- /dev/null
+++ 
b/server/container/guice/protocols/pop/src/main/java/org/apache/james/modules/protocols/Pop3DefaultSaslMechanismFactories.java
@@ -0,0 +1,31 @@
+/****************************************************************
+ * 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 java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+
+import com.google.inject.BindingAnnotation;
+
+@BindingAnnotation
+@Retention(RUNTIME)
+public @interface Pop3DefaultSaslMechanismFactories {
+}
diff --git 
a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3ServerFactory.java
 
b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3ServerFactory.java
index 5b0ae64ee2..7b604d1aed 100644
--- 
a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3ServerFactory.java
+++ 
b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3ServerFactory.java
@@ -25,18 +25,64 @@ import java.util.List;
 import jakarta.inject.Inject;
 
 import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.protocols.api.sasl.SaslMechanism;
+import org.apache.james.protocols.api.sasl.SaslMechanismFactory;
 import org.apache.james.protocols.lib.handler.ProtocolHandlerLoader;
 import org.apache.james.protocols.lib.netty.AbstractConfigurableAsyncServer;
 import org.apache.james.protocols.lib.netty.AbstractServerFactory;
 import org.apache.james.protocols.netty.Encryption;
+import org.apache.james.protocols.sasl.BuiltInSaslMechanismFactories;
+import org.apache.james.protocols.sasl.OauthBearerSaslMechanismFactory;
+import org.apache.james.protocols.sasl.PlainSaslMechanismFactory;
+import org.apache.james.protocols.sasl.XOauth2SaslMechanismFactory;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
 
 public class POP3ServerFactory extends AbstractServerFactory {
+    @FunctionalInterface
+    public interface Pop3SaslMechanismLoader {
+        static Pop3SaslMechanismLoader defaultLoader() {
+            ImmutableList<SaslMechanismFactory> defaultFactories = 
ImmutableList.of(
+                new PlainSaslMechanismFactory(),
+                new OauthBearerSaslMechanismFactory(),
+                new XOauth2SaslMechanismFactory());
+            return configuration -> loadBuiltInMechanisms(defaultFactories, 
configuration);
+        }
+
+        ImmutableList<SaslMechanism> 
load(HierarchicalConfiguration<ImmutableNode> configuration) throws 
ConfigurationException;
+    }
+
+    private static ImmutableList<SaslMechanism> 
loadBuiltInMechanisms(ImmutableList<SaslMechanismFactory> defaultFactories,
+                                                                       
HierarchicalConfiguration<ImmutableNode> configuration) throws 
ConfigurationException {
+        try {
+            return 
BuiltInSaslMechanismFactories.enabledForServer(defaultFactories, configuration)
+                .stream()
+                .map(Throwing.function(factory -> 
factory.create(configuration)))
+                .collect(ImmutableList.toImmutableList());
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof ConfigurationException 
configurationException) {
+                throw configurationException;
+            }
+            throw e;
+        }
+    }
 
     private ProtocolHandlerLoader loader;
     private FileSystem fileSystem;
     private Encryption.Factory encryptionFactory;
+    private Pop3SaslMechanismLoader saslMechanismLoader = 
Pop3SaslMechanismLoader.defaultLoader();
+
+    public POP3ServerFactory() {
+    }
+
+    @Inject
+    public POP3ServerFactory(Pop3SaslMechanismLoader saslMechanismLoader) {
+        this.saslMechanismLoader = saslMechanismLoader;
+    }
 
     @Inject
     public void setProtocolHandlerLoader(ProtocolHandlerLoader loader) {
@@ -68,6 +114,7 @@ public class POP3ServerFactory extends AbstractServerFactory 
{
             server.setProtocolHandlerLoader(loader);
             server.setFileSystem(fileSystem);
             server.setEncryptionFactory(encryptionFactory);
+            server.setSaslMechanisms(saslMechanismLoader.load(serverConfig));
             server.configure(serverConfig);
             servers.add(server);
         }
diff --git 
a/server/protocols/protocols-pop3/src/main/resources/META-INF/spring/pop3server-context.xml
 
b/server/protocols/protocols-pop3/src/main/resources/META-INF/spring/pop3server-context.xml
index 93d795b835..0c39e15ee0 100644
--- 
a/server/protocols/protocols-pop3/src/main/resources/META-INF/spring/pop3server-context.xml
+++ 
b/server/protocols/protocols-pop3/src/main/resources/META-INF/spring/pop3server-context.xml
@@ -19,10 +19,14 @@
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd";>
 
     <bean id="pop3server" 
class="org.apache.james.pop3server.netty.POP3ServerFactory">
+        <constructor-arg index="0" ref="pop3SaslMechanismLoader"/>
         <property name="protocolHandlerLoader" ref="protocolhandlerloader"/>
         <property name="fileSystem" ref="filesystem"/>
     </bean>
 
+    <bean id="pop3SaslMechanismLoader" 
class="org.apache.james.pop3server.netty.POP3ServerFactory$Pop3SaslMechanismLoader"
+          factory-method="defaultLoader"/>
+
     <bean id="mailboxAdapter" 
class="org.apache.james.pop3server.mailbox.DefaultMailboxAdapterFactory">
         <constructor-arg index="0" ref="mailboxmanager"/>
     </bean>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to