This is an automated email from the ASF dual-hosted git repository.

chibenwa pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit ba6abe993d9829fc05c0beee1d48d8e829eb7a67
Author: Quan Tran <[email protected]>
AuthorDate: Wed Jun 24 14:20:21 2026 +0700

    JAMES-4210 Wire SMTP SASL support for Spring and scaling Pulsar apps
    
    Adapt non-Guice and Spring SMTP server creation to provide default SASL 
mechanisms and authenticator wiring. Add a UsersRepository-backed authenticator 
for the scaling Pulsar SMTP app.
---
 .../src/main/java/org/apache/james/Main.java       |  2 +
 .../james/UsersRepositoryBackedAuthenticator.java  | 48 +++++++++++++
 server/protocols/protocols-smtp/pom.xml            |  4 ++
 .../apache/james/smtpserver/netty/SMTPServer.java  | 83 ++++++++--------------
 .../james/smtpserver/netty/SMTPServerFactory.java  | 55 +++++++++++++-
 .../META-INF/spring/smtpserver-context.xml         | 10 +++
 6 files changed, 146 insertions(+), 56 deletions(-)

diff --git 
a/server/apps/scaling-pulsar-smtp/src/main/java/org/apache/james/Main.java 
b/server/apps/scaling-pulsar-smtp/src/main/java/org/apache/james/Main.java
index 7fc3a55792..8c08ae690a 100644
--- a/server/apps/scaling-pulsar-smtp/src/main/java/org/apache/james/Main.java
+++ b/server/apps/scaling-pulsar-smtp/src/main/java/org/apache/james/Main.java
@@ -25,6 +25,7 @@ import org.apache.james.blob.api.BlobStoreDAO;
 import org.apache.james.blob.api.MetricableBlobStore;
 import org.apache.james.blob.objectstorage.aws.S3BlobStoreDAO;
 import org.apache.james.blob.objectstorage.aws.S3RequestOption;
+import org.apache.james.mailbox.Authenticator;
 import org.apache.james.mailrepository.api.MailRepositoryFactory;
 import org.apache.james.mailrepository.api.MailRepositoryUrlStore;
 import org.apache.james.mailrepository.postgres.PostgresMailRepositoryFactory;
@@ -109,6 +110,7 @@ public class Main implements JamesServerMain {
                 Multibinder.newSetBinder(binder, PostgresDataDefinition.class)
                         
.addBinding().toInstance(org.apache.james.mailrepository.postgres.PostgresMailRepositoryDataDefinition.MODULE);
                 
binder.bind(MailRepositoryUrlStore.class).to(PostgresMailRepositoryUrlStore.class).in(Scopes.SINGLETON);
+                
binder.bind(Authenticator.class).to(UsersRepositoryBackedAuthenticator.class).in(Scopes.SINGLETON);
             },
             new CoreDataModule(),
             new MemoryDelegationStoreModule(),
diff --git 
a/server/apps/scaling-pulsar-smtp/src/main/java/org/apache/james/UsersRepositoryBackedAuthenticator.java
 
b/server/apps/scaling-pulsar-smtp/src/main/java/org/apache/james/UsersRepositoryBackedAuthenticator.java
new file mode 100644
index 0000000000..f980601e08
--- /dev/null
+++ 
b/server/apps/scaling-pulsar-smtp/src/main/java/org/apache/james/UsersRepositoryBackedAuthenticator.java
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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;
+
+import java.util.Optional;
+
+import jakarta.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.Authenticator;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.UsersRepositoryException;
+
+class UsersRepositoryBackedAuthenticator implements Authenticator {
+    private final UsersRepository usersRepository;
+
+    @Inject
+    UsersRepositoryBackedAuthenticator(UsersRepository usersRepository) {
+        this.usersRepository = usersRepository;
+    }
+
+    @Override
+    public Optional<Username> isAuthentic(Username userid, CharSequence 
passwd) throws MailboxException {
+        try {
+            return usersRepository.test(userid, passwd.toString());
+        } catch (UsersRepositoryException e) {
+            throw new MailboxException("Unable to access UsersRepository", e);
+        }
+    }
+}
diff --git a/server/protocols/protocols-smtp/pom.xml 
b/server/protocols/protocols-smtp/pom.xml
index 5351f7b5ac..d8d790af5d 100644
--- a/server/protocols/protocols-smtp/pom.xml
+++ b/server/protocols/protocols-smtp/pom.xml
@@ -142,6 +142,10 @@
             <groupId>${james.protocols.groupId}</groupId>
             <artifactId>protocols-netty</artifactId>
         </dependency>
+        <dependency>
+            <groupId>${james.protocols.groupId}</groupId>
+            <artifactId>protocols-sasl</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>${james.protocols.groupId}</groupId>
diff --git 
a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
index e509ac384c..b60abaa58c 100644
--- 
a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
+++ 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
@@ -24,9 +24,7 @@ import static 
org.apache.james.smtpserver.netty.SMTPServer.AuthenticationAnnounc
 import static 
org.apache.james.smtpserver.netty.SMTPServer.AuthenticationAnnounceMode.NEVER;
 
 import java.net.InetSocketAddress;
-import java.net.MalformedURLException;
 import java.net.SocketAddress;
-import java.net.URISyntaxException;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.Set;
@@ -44,9 +42,10 @@ import org.apache.james.core.Disconnector;
 import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.dnsservice.library.netmatcher.NetMatcher;
-import org.apache.james.jwt.OidcSASLConfiguration;
 import org.apache.james.protocols.api.ProtocolSession;
 import org.apache.james.protocols.api.ProtocolTransport;
+import org.apache.james.protocols.api.sasl.SaslAuthenticator;
+import org.apache.james.protocols.api.sasl.SaslMechanism;
 import org.apache.james.protocols.lib.handler.HandlersPackage;
 import org.apache.james.protocols.lib.netty.AbstractProtocolAsyncServer;
 import org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
@@ -55,6 +54,7 @@ import org.apache.james.protocols.netty.ChannelHandlerFactory;
 import org.apache.james.protocols.smtp.SMTPConfiguration;
 import org.apache.james.protocols.smtp.SMTPProtocol;
 import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.core.esmtp.AuthCmdHandler;
 import org.apache.james.smtpserver.CoreCmdHandlerLoader;
 import org.apache.james.smtpserver.ExtendedSMTPSession;
 import org.apache.james.smtpserver.jmx.JMXHandlersLoader;
@@ -62,6 +62,7 @@ import org.apache.james.util.Size;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
@@ -110,30 +111,15 @@ public class SMTPServer extends 
AbstractProtocolAsyncServer implements SMTPServe
         }
     }
 
-    public static class AuthenticationConfiguration {
-        private static final String OIDC_PATH = "auth.oidc";
+    public static class AuthAnnouncementConfiguration {
+        public static final boolean REQUIRE_SSL_DEFAULT = false;
 
-        public static AuthenticationConfiguration 
parse(HierarchicalConfiguration<ImmutableNode> configuration) throws 
ConfigurationException {
-            return new AuthenticationConfiguration(
+        public static AuthAnnouncementConfiguration 
parse(HierarchicalConfiguration<ImmutableNode> configuration) throws 
ConfigurationException {
+            return new AuthAnnouncementConfiguration(
                 Optional.ofNullable(configuration.getString("auth.announce", 
null))
                     .map(AuthenticationAnnounceMode::parse)
                     .orElseGet(() -> 
fallbackAuthenticationAnnounceMode(configuration)),
-                configuration.getBoolean("auth.requireSSL", false),
-                configuration.getBoolean("auth.plainAuthEnabled", true),
-                parseSASLConfiguration(configuration));
-        }
-
-        private static Optional<OidcSASLConfiguration> 
parseSASLConfiguration(HierarchicalConfiguration<ImmutableNode> configuration) 
throws ConfigurationException {
-            boolean haveOidcProperties = 
configuration.getKeys(OIDC_PATH).hasNext();
-            if (haveOidcProperties) {
-                try {
-                    return 
Optional.of(OidcSASLConfiguration.parse(configuration.configurationAt(OIDC_PATH)));
-                } catch (MalformedURLException | URISyntaxException exception) 
{
-                   throw new ConfigurationException("Failed to retrieve oauth 
component", exception);
-                }
-            } else {
-                return Optional.empty();
-            }
+                configuration.getBoolean("auth.requireSSL", 
REQUIRE_SSL_DEFAULT));
         }
 
         private static AuthenticationAnnounceMode 
fallbackAuthenticationAnnounceMode(HierarchicalConfiguration<ImmutableNode> 
configuration) {
@@ -142,37 +128,22 @@ public class SMTPServer extends 
AbstractProtocolAsyncServer implements SMTPServe
 
         private final AuthenticationAnnounceMode authenticationAnnounceMode;
         private final boolean requireSSL;
-        private final boolean plainAuthEnabled;
-        private final Optional<OidcSASLConfiguration> saslConfiguration;
 
-        public AuthenticationConfiguration(AuthenticationAnnounceMode 
authenticationAnnounceMode, boolean requireSSL, boolean plainAuthEnabled, 
Optional<OidcSASLConfiguration> saslConfiguration) {
+        public AuthAnnouncementConfiguration(AuthenticationAnnounceMode 
authenticationAnnounceMode, boolean requireSSL) {
             this.authenticationAnnounceMode = authenticationAnnounceMode;
             this.requireSSL = requireSSL;
-            this.plainAuthEnabled = plainAuthEnabled;
-            this.saslConfiguration = saslConfiguration;
         }
 
         public AuthenticationAnnounceMode getAuthenticationAnnounceMode() {
             return authenticationAnnounceMode;
         }
 
-        public boolean isRequireSSL() {
-            return requireSSL;
-        }
-
-        public boolean isPlainAuthEnabled() {
-            return plainAuthEnabled;
-        }
-
-        public Optional<OidcSASLConfiguration> getSaslConfiguration() {
-            return saslConfiguration;
-        }
     }
 
     /**
      * Whether authentication is required to use this SMTP server.
      */
-    private AuthenticationConfiguration authenticationConfiguration;
+    private AuthAnnouncementConfiguration authAnnouncementConfiguration;
     
     /**
      * Whether the server needs helo to be send first
@@ -203,6 +174,8 @@ public class SMTPServer extends AbstractProtocolAsyncServer 
implements SMTPServe
     private final SmtpMetrics smtpMetrics;
     private final DefaultChannelGroup smtpChannelGroup;
     private Set<String> disabledFeatures = ImmutableSet.of();
+    private ImmutableList<SaslMechanism> saslMechanisms = ImmutableList.of();
+    private Optional<SaslAuthenticator> saslAuthenticator = Optional.empty();
 
     private boolean addressBracketsEnforcement = true;
 
@@ -216,6 +189,14 @@ public class SMTPServer extends 
AbstractProtocolAsyncServer implements SMTPServe
         this.smtpChannelGroup = new 
DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
     }
 
+    public void setSaslMechanisms(ImmutableList<SaslMechanism> saslMechanisms) 
{
+        this.saslMechanisms = saslMechanisms;
+    }
+
+    public void setSaslAuthenticator(Optional<SaslAuthenticator> 
saslAuthenticator) {
+        this.saslAuthenticator = saslAuthenticator;
+    }
+
     @Inject
     public void setDnsService(DNSService dns) {
         this.dns = dns;
@@ -224,6 +205,9 @@ public class SMTPServer extends AbstractProtocolAsyncServer 
implements SMTPServe
     @Override
     protected void preInit() throws Exception {
         super.preInit();
+        saslAuthenticator.ifPresent(authenticator -> getProtocolHandlerChain()
+            .getHandlers(AuthCmdHandler.class)
+            .forEach(handler -> 
handler.configureSaslMechanisms(saslMechanisms, authenticator)));
         if (authorizedAddresses != null) {
             java.util.StringTokenizer st = new 
java.util.StringTokenizer(authorizedAddresses, ", ", false);
             java.util.Collection<String> networks = new 
java.util.ArrayList<>();
@@ -246,7 +230,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer 
implements SMTPServe
     public void doConfigure(HierarchicalConfiguration<ImmutableNode> 
configuration) throws ConfigurationException {
         super.doConfigure(configuration);
         if (isEnabled()) {
-            authenticationConfiguration = 
AuthenticationConfiguration.parse(configuration);
+            authAnnouncementConfiguration = 
AuthAnnouncementConfiguration.parse(configuration);
 
             authorizedAddresses = 
configuration.getString("authorizedAddresses", null);
 
@@ -322,19 +306,15 @@ public class SMTPServer extends 
AbstractProtocolAsyncServer implements SMTPServe
             return SMTPServer.this.addressBracketsEnforcement;
         }
 
-        public boolean isPlainAuthEnabled() {
-            return authenticationConfiguration.isPlainAuthEnabled();
-        }
-
         @Override
         public boolean isAuthAnnounced(String remoteIP, boolean tlsStarted) {
-            if (authenticationConfiguration.requireSSL && !tlsStarted) {
+            if (authAnnouncementConfiguration.requireSSL && !tlsStarted) {
                 return false;
             }
-            if (authenticationConfiguration.getAuthenticationAnnounceMode() == 
ALWAYS) {
+            if (authAnnouncementConfiguration.getAuthenticationAnnounceMode() 
== ALWAYS) {
                 return true;
             }
-            if (authenticationConfiguration.getAuthenticationAnnounceMode() == 
NEVER) {
+            if (authAnnouncementConfiguration.getAuthenticationAnnounceMode() 
== NEVER) {
                 return false;
             }
             return Optional.ofNullable(authorizedNetworks)
@@ -356,11 +336,6 @@ public class SMTPServer extends 
AbstractProtocolAsyncServer implements SMTPServe
             return "JAMES SMTP Server ";
         }
 
-        @Override
-        public Optional<OidcSASLConfiguration> saslConfiguration() {
-            return authenticationConfiguration.getSaslConfiguration();
-        }
-
         @Override
         public Set<String> disabledFeatures() {
             return disabledFeatures;
@@ -428,7 +403,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer 
implements SMTPServe
     }
 
     public AuthenticationAnnounceMode getAuthRequired() {
-        return authenticationConfiguration.getAuthenticationAnnounceMode();
+        return authAnnouncementConfiguration.getAuthenticationAnnounceMode();
     }
 
     @Override
diff --git 
a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
index 5d4bbc08f4..c04861508e 100644
--- 
a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
+++ 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
@@ -21,12 +21,14 @@ package org.apache.james.smtpserver.netty;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 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.core.ConnectionDescription;
 import org.apache.james.core.ConnectionDescriptionSupplier;
@@ -35,26 +37,73 @@ import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.protocols.api.sasl.SaslAuthenticator;
+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 
org.apache.james.smtpserver.netty.SMTPServer.AuthAnnouncementConfiguration;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
 
 public class SMTPServerFactory extends AbstractServerFactory implements 
Disconnector, ConnectionDescriptionSupplier {
+    @FunctionalInterface
+    public interface SmtpSaslMechanismLoader {
+        static SmtpSaslMechanismLoader defaultLoader() {
+            ImmutableList<SaslMechanismFactory> defaultFactories = 
ImmutableList.of(
+                new 
PlainSaslMechanismFactory(AuthAnnouncementConfiguration.REQUIRE_SSL_DEFAULT),
+                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;
+        }
+    }
 
     protected final DNSService dns;
     protected final ProtocolHandlerLoader loader;
     protected final FileSystem fileSystem;
     protected final SmtpMetricsImpl smtpMetrics;
+    protected final SmtpSaslMechanismLoader saslMechanismLoader;
+    protected final Optional<SaslAuthenticator> saslAuthenticator;
     protected Encryption.Factory encryptionFactory;
 
-    @Inject
     public SMTPServerFactory(DNSService dns, ProtocolHandlerLoader loader, 
FileSystem fileSystem,
-                             MetricFactory metricFactory) {
+                             MetricFactory metricFactory, 
SmtpSaslMechanismLoader saslMechanismLoader, SaslAuthenticator 
saslAuthenticator) {
+        this(dns, loader, fileSystem, metricFactory, saslMechanismLoader, 
Optional.of(saslAuthenticator));
+    }
+
+    private SMTPServerFactory(DNSService dns, ProtocolHandlerLoader loader, 
FileSystem fileSystem,
+                              MetricFactory metricFactory, 
SmtpSaslMechanismLoader saslMechanismLoader,
+                              Optional<SaslAuthenticator> saslAuthenticator) {
         this.dns = dns;
         this.loader = loader;
         this.fileSystem = fileSystem;
         this.smtpMetrics = new SmtpMetricsImpl(metricFactory);
+        this.saslMechanismLoader = saslMechanismLoader;
+        this.saslAuthenticator = saslAuthenticator;
     }
 
     @Inject
@@ -78,6 +127,8 @@ public class SMTPServerFactory extends AbstractServerFactory 
implements Disconne
             server.setProtocolHandlerLoader(loader);
             server.setFileSystem(fileSystem);
             server.setEncryptionFactory(encryptionFactory);
+            server.setSaslMechanisms(saslMechanismLoader.load(serverConfig));
+            server.setSaslAuthenticator(saslAuthenticator);
             server.configure(serverConfig);
             servers.add(server);
         }
diff --git 
a/server/protocols/protocols-smtp/src/main/resources/META-INF/spring/smtpserver-context.xml
 
b/server/protocols/protocols-smtp/src/main/resources/META-INF/spring/smtpserver-context.xml
index 68fedd9e1c..1ac134715b 100644
--- 
a/server/protocols/protocols-smtp/src/main/resources/META-INF/spring/smtpserver-context.xml
+++ 
b/server/protocols/protocols-smtp/src/main/resources/META-INF/spring/smtpserver-context.xml
@@ -23,6 +23,16 @@
         <constructor-arg index="1" ref="protocolhandlerloader"/>
         <constructor-arg index="2" ref="filesystem"/>
         <constructor-arg index="3" ref="metricFactory"/>
+        <constructor-arg index="4" ref="smtpSaslMechanismLoader"/>
+        <constructor-arg index="5" ref="smtpSaslAuthenticator"/>
+    </bean>
+
+    <bean id="smtpSaslMechanismLoader" 
class="org.apache.james.smtpserver.netty.SMTPServerFactory$SmtpSaslMechanismLoader"
+          factory-method="defaultLoader"/>
+
+    <bean id="smtpSaslAuthenticator" 
class="org.apache.james.protocols.sasl.JamesSaslAuthenticator">
+        <constructor-arg index="0" ref="authenticator"/>
+        <constructor-arg index="1" ref="authorizator"/>
     </bean>
     
 </beans>


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

Reply via email to