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

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


The following commit(s) were added to refs/heads/master by this push:
     new 47d443c  JAMES-3680 SMTP enable OAUTHBEARER authentication (#810)
47d443c is described below

commit 47d443c62ddfcbd8e7cbf91b2953bf0a72be7a7a
Author: Trần Hồng Quân <[email protected]>
AuthorDate: Mon Jan 10 10:52:45 2022 +0700

    JAMES-3680 SMTP enable OAUTHBEARER authentication (#810)
    
    
    Co-authored-by: Tung Van TRAN <[email protected]>
---
 .../protocols/lmtp/LMTPConfigurationImpl.java      |   9 +
 protocols/smtp/pom.xml                             |   4 +
 .../james/protocols/smtp/SMTPConfiguration.java    |   5 +
 .../protocols/smtp/SMTPConfigurationImpl.java      |   8 +
 .../apache/james/protocols/smtp/SMTPSession.java   |   4 +-
 .../james/protocols/smtp/SMTPSessionImpl.java      |   5 +
 .../protocols/smtp/core/esmtp/AuthCmdHandler.java  | 119 +++++--
 .../apache/james/protocols/smtp/hook/AuthHook.java |   3 +
 .../protocols/smtp/utils/BaseFakeSMTPSession.java  |   5 +
 .../sample-configuration/smtpserver.xml            |  14 +-
 .../src/test/resources/smtpserver.xml              |  12 +
 .../docs/modules/ROOT/pages/configure/smtp.adoc    |  12 +
 .../sample-configuration/smtpserver.xml            |  14 +-
 .../sample-configuration/smtpserver.xml            |  14 +-
 .../jpa-app/sample-configuration/smtpserver.xml    |  14 +-
 .../sample-configuration/smtpserver.xml            |  14 +-
 .../memory-app/sample-configuration/smtpserver.xml |  14 +-
 .../src/main/resources/smtpserver.xml              |  10 +
 .../james/jwt/DefaultPublicKeyProviderTest.java    |   1 -
 .../apache/james/lmtpserver/netty/LMTPServer.java  |   8 +
 server/protocols/protocols-smtp/pom.xml            |  15 +
 .../james/smtpserver/UsersRepositoryAuthHook.java  |  38 ++
 .../apache/james/smtpserver/netty/SMTPServer.java  |  34 +-
 .../org/apache/james/smtpserver/SMTPSaslTest.java  | 383 +++++++++++++++++++++
 .../protocols-smtp/src/test/resources/keystore     | Bin 0 -> 2245 bytes
 .../test/resources/smtpserver-advancedSecurity.xml |  20 ++
 src/site/xdoc/server/config-smtp-lmtp.xml          |   8 +
 27 files changed, 742 insertions(+), 45 deletions(-)

diff --git 
a/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java
 
b/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java
index 099b71c..9202c26 100644
--- 
a/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java
+++ 
b/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java
@@ -18,6 +18,10 @@
  ****************************************************************/
 package org.apache.james.protocols.lmtp;
 
+import java.util.Optional;
+
+import org.apache.james.protocols.api.OidcSASLConfiguration;
+
 public class LMTPConfigurationImpl extends LMTPConfiguration {
 
     private long maxMessageSize = 0;    
@@ -39,4 +43,9 @@ public class LMTPConfigurationImpl extends LMTPConfiguration {
     public boolean isPlainAuthEnabled() {
         return false;
     }
+
+    @Override
+    public Optional<OidcSASLConfiguration> saslConfiguration() {
+        return Optional.empty();
+    }
 }
diff --git a/protocols/smtp/pom.xml b/protocols/smtp/pom.xml
index d50cc37..d4592dc 100644
--- a/protocols/smtp/pom.xml
+++ b/protocols/smtp/pom.xml
@@ -90,6 +90,10 @@
             <groupId>javax.inject</groupId>
             <artifactId>javax.inject</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-jwt</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
index 65f526b..acbf2dc 100644
--- 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
+++ 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
@@ -21,6 +21,9 @@
 
 package org.apache.james.protocols.smtp;
 
+import java.util.Optional;
+
+import org.apache.james.protocols.api.OidcSASLConfiguration;
 import org.apache.james.protocols.api.ProtocolConfiguration;
 
 
@@ -72,4 +75,6 @@ public interface SMTPConfiguration extends 
ProtocolConfiguration {
 
     boolean isPlainAuthEnabled();
 
+    Optional<OidcSASLConfiguration> saslConfiguration();
+
 }
diff --git 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java
 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java
index 39871f6..5ee7990 100644
--- 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java
+++ 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java
@@ -20,6 +20,9 @@
 
 package org.apache.james.protocols.smtp;
 
+import java.util.Optional;
+
+import org.apache.james.protocols.api.OidcSASLConfiguration;
 import org.apache.james.protocols.api.ProtocolConfigurationImpl;
 
 /**
@@ -83,4 +86,9 @@ public class SMTPConfigurationImpl extends 
ProtocolConfigurationImpl implements
     public boolean isPlainAuthEnabled() {
         return true;
     }
+
+    @Override
+    public Optional<OidcSASLConfiguration> saslConfiguration() {
+        return Optional.empty();
+    }
 }
diff --git 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSession.java 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSession.java
index 0be25fb..3bfda32 100644
--- 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSession.java
+++ 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSession.java
@@ -77,7 +77,9 @@ public interface SMTPSession extends ProtocolSession {
      * @return recipient count
      */
     int getRcptCount();
-    
+
+
+    boolean supportsOAuth();
 
 }
 
diff --git 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSessionImpl.java
 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSessionImpl.java
index c583cf6..814c492 100644
--- 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSessionImpl.java
+++ 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPSessionImpl.java
@@ -65,6 +65,11 @@ public class SMTPSessionImpl extends ProtocolSessionImpl 
implements SMTPSession
     }
 
     @Override
+    public boolean supportsOAuth() {
+        return getConfiguration().saslConfiguration().isPresent() && 
isAuthAnnounced();
+    }
+
+    @Override
     public boolean isAuthAnnounced() {
         return 
getConfiguration().isAuthAnnounced(getRemoteAddress().getAddress().getHostAddress(),
 isTLSStarted());
     }
diff --git 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
index d1bc5c8..210c6a8 100644
--- 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
+++ 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
@@ -32,9 +32,11 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.StringTokenizer;
+import java.util.function.Function;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.james.core.Username;
+import org.apache.james.protocols.api.OidcSASLConfiguration;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.CommandHandler;
@@ -53,13 +55,14 @@ import 
org.apache.james.protocols.smtp.hook.MailParametersHook;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 
 /**
  * handles AUTH command
- * 
+ *
  * Note: we could extend this to use java5 sasl standard libraries and provide 
client
  * support against a server implemented via non-james specific hooks.
  * This would allow us to reuse hooks between imap4/pop3/smtp and eventually 
different
@@ -70,8 +73,8 @@ public class AuthCmdHandler
     private static final Collection<String> COMMANDS = ImmutableSet.of("AUTH");
     private static final Logger LOGGER = 
LoggerFactory.getLogger(CommandHandler.class);
     private static final String[] MAIL_PARAMS = { "AUTH" };
-    private static final List<String> ESMTP_FEATURES = ImmutableList.of("AUTH 
LOGIN PLAIN", "AUTH=LOGIN PLAIN");
-    
+    private static final String AUTH_TYPES_DELIMITER = " ";
+
     private static final Response AUTH_ABORTED = new 
SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS, 
DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.SECURITY_AUTH) + " 
Authentication aborted").immutable();
     private static final Response ALREADY_AUTH = new 
SMTPResponse(SMTPRetCode.BAD_SEQUENCE, 
DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER) + " User has 
previously authenticated. "
             + " Further authentication is not required!").immutable();
@@ -99,12 +102,12 @@ public class AuthCmdHandler
             } catch (UnsupportedEncodingException e) {
                 throw new RuntimeException("No " + charset + " support!");
             }
-           
+
         }
 
         private Response handleCommand(SMTPSession session, String line) {
             // See JAMES-939
-            
+
             // According to RFC2554:
             // "If the client wishes to cancel an authentication exchange, it 
issues a line with a single "*".
             // If the server receives such an answer, it MUST reject the AUTH
@@ -132,10 +135,16 @@ public class AuthCmdHandler
     protected static final String AUTH_TYPE_LOGIN = "LOGIN";
 
     /**
+     * The text string for the SMTP AUTH type OAUTHBEARER.
+     */
+    protected static final String AUTH_TYPE_OAUTHBEARER = "OAUTHBEARER";
+    protected static final String AUTH_TYPE_XOAUTH2 = "XOAUTH2";
+
+    /**
      * The AuthHooks
      */
     private List<AuthHook> hooks;
-    
+
     private List<HookResultHook> rHooks;
 
     /**
@@ -183,7 +192,7 @@ public class AuthCmdHandler
                     return doPlainAuthPass(session, userpass);
                 }
             } else if (authType.equals(AUTH_TYPE_LOGIN) && 
session.getConfiguration().isPlainAuthEnabled()) {
-                
+
                 if (initialResponse == null) {
                     session.pushLineHandler(new AbstractSMTPLineHandler() {
                         @Override
@@ -196,12 +205,42 @@ public class AuthCmdHandler
                     String user = initialResponse.trim();
                     return doLoginAuthPass(session, user);
                 }
+            } else if ((authType.equals(AUTH_TYPE_OAUTHBEARER) || 
authType.equals(AUTH_TYPE_XOAUTH2))
+                && session.supportsOAuth()) {
+                return doSASLAuthentication(session, initialResponse);
             } else {
                 return doUnknownAuth(session, authType, initialResponse);
             }
         }
     }
 
+    private Response doSASLAuthentication(SMTPSession session, String 
initialResponseString) {
+        return session.getConfiguration().saslConfiguration()
+            .map(oidcSASLConfiguration -> hooks.stream()
+                .flatMap(hook -> Optional.ofNullable(executeHook(session, hook,
+                    hook2 -> hook2.doSasl(session, oidcSASLConfiguration, 
initialResponseString))).stream())
+                .filter(response -> 
!SMTPRetCode.AUTH_FAILED.equals(response.getRetCode()))
+                .findFirst()
+                .orElseGet(() -> failSasl(oidcSASLConfiguration, session)))
+            .orElse(doUnknownAuth(session, AUTH_TYPE_OAUTHBEARER, 
initialResponseString));
+    }
+
+    private Response failSasl(OidcSASLConfiguration saslConfiguration, 
SMTPSession session) {
+        String rawResponse = 
String.format("{\"status\":\"invalid_token\",\"scope\":\"%s\",\"schemes\":\"%s\"}",
+            saslConfiguration.getScope(),
+            saslConfiguration.getOidcConfigurationURL().toString());
+
+        session.pushLineHandler(new AbstractSMTPLineHandler() {
+            @Override
+            protected Response onCommand(SMTPSession session, String l) {
+                session.popLineHandler();
+
+                return AUTH_FAILED;
+            }
+        });
+        return new SMTPResponse("334", 
Base64.getEncoder().encodeToString(rawResponse.getBytes()));
+    }
+
     /**
      * Carries out the Plain AUTH SASL exchange.
      *
@@ -269,7 +308,7 @@ public class AuthCmdHandler
         }
         // Authenticate user
         Response response = doAuthTest(session, Username.of(user), pass, 
"PLAIN");
-        
+
         session.popLineHandler();
 
         return response;
@@ -299,9 +338,9 @@ public class AuthCmdHandler
                 user = null;
             }
         }
-        
+
         session.popLineHandler();
-        
+
         session.pushLineHandler(new AbstractSMTPLineHandler() {
 
             private Username username;
@@ -318,7 +357,7 @@ public class AuthCmdHandler
         }.setUsername(Username.of(user)));
         return AUTH_READY_PASSWORD_LOGIN;
     }
-    
+
     private Response doLoginAuthPassCheck(SMTPSession session, Username 
username, String pass) {
         if (pass != null) {
             try {
@@ -329,7 +368,7 @@ public class AuthCmdHandler
                 pass = null;
             }
         }
-       
+
         session.popLineHandler();
 
         // Authenticate user
@@ -342,25 +381,12 @@ public class AuthCmdHandler
         }
 
         Response res = null;
-        
+
         List<AuthHook> hooks = getHooks();
-        
+
         if (hooks != null) {
             for (AuthHook rawHook : hooks) {
-                LOGGER.debug("executing  hook {}", rawHook);
-
-                long start = System.currentTimeMillis();
-                HookResult hRes = rawHook.doAuth(session, username, pass);
-                long executionTime = System.currentTimeMillis() - start;
-
-                if (rHooks != null) {
-                    for (HookResultHook rHook : rHooks) {
-                        LOGGER.debug("executing  hook {}", rHook);
-                        hRes = rHook.onHookResult(session, hRes, 
executionTime, rawHook);
-                    }
-                }
-
-                res = calcDefaultSMTPResponse(hRes);
+                res = executeHook(session, rawHook, hook -> 
hook.doAuth(session, username, pass));
 
                 if (res != null) {
                     if (SMTPRetCode.AUTH_FAILED.equals(res.getRetCode())) {
@@ -379,10 +405,26 @@ public class AuthCmdHandler
         return res;
     }
 
+    private Response executeHook(SMTPSession session, AuthHook rawHook, 
Function<AuthHook, HookResult> tc) {
+        LOGGER.debug("executing  hook {}", rawHook);
+
+        long start = System.currentTimeMillis();
+        HookResult hRes = tc.apply(rawHook);
+        long executionTime = System.currentTimeMillis() - start;
+
+        HookResult finalHookResult = Optional.ofNullable(rHooks)
+                .orElse(ImmutableList.of()).stream()
+                .peek(rHook -> LOGGER.debug("executing  hook {}", rHook))
+                .reduce(hRes, (a, b) -> b.onHookResult(session, a, 
executionTime, rawHook), (a, b) -> {
+                    throw new UnsupportedOperationException();
+                });
+
+        return calcDefaultSMTPResponse(finalHookResult);
+    }
 
     /**
      * Calculate the SMTPResponse for the given result
-     * 
+     *
      * @param result the HookResult which should converted to SMTPResponse
      * @return the calculated SMTPResponse for the given HookReslut
      */
@@ -397,7 +439,7 @@ public class AuthCmdHandler
             String smtpDescription = 
Optional.ofNullable(result.getSmtpDescription())
                 .or(() -> retrieveDefaultSmtpDescription(returnCode))
                 .orElse(null);
-    
+
             if 
(HookReturnCode.Action.ACTIVE_ACTIONS.contains(returnCode.getAction())) {
                 SMTPResponse response =  new SMTPResponse(smtpReturnCode, 
smtpDescription);
 
@@ -465,9 +507,20 @@ public class AuthCmdHandler
     @Override
     public List<String> getImplementedEsmtpFeatures(SMTPSession session) {
         if (session.isAuthAnnounced()) {
+            ImmutableList.Builder<String> authTypesBuilder = 
ImmutableList.builder();
             if (session.getConfiguration().isPlainAuthEnabled()) {
-                return ESMTP_FEATURES;
+                authTypesBuilder.add(AUTH_TYPE_LOGIN, AUTH_TYPE_PLAIN);
+            }
+            if (session.getConfiguration().saslConfiguration().isPresent()) {
+                authTypesBuilder.add(AUTH_TYPE_OAUTHBEARER);
+                authTypesBuilder.add(AUTH_TYPE_XOAUTH2);
+            }
+            ImmutableList<String> authTypes = authTypesBuilder.build();
+            if (authTypes.isEmpty()) {
+                return Collections.emptyList();
             }
+            String joined = Joiner.on(AUTH_TYPES_DELIMITER).join(authTypes);
+            return ImmutableList.of("AUTH " + joined, "AUTH=" + joined);
         }
         return Collections.emptyList();
     }
@@ -493,11 +546,11 @@ public class AuthCmdHandler
             this.rHooks = (List<HookResultHook>) extension;
         }
     }
-    
+
 
     /**
      * Return a list which holds all hooks for the cmdHandler
-     * 
+     *
      * @return list containing all hooks for the cmd handler
      */
     protected List<AuthHook> getHooks() {
diff --git 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/hook/AuthHook.java
 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/hook/AuthHook.java
index 60cc5a3..f9e7f76 100644
--- 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/hook/AuthHook.java
+++ 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/hook/AuthHook.java
@@ -19,6 +19,7 @@
 package org.apache.james.protocols.smtp.hook;
 
 import org.apache.james.core.Username;
+import org.apache.james.protocols.api.OidcSASLConfiguration;
 import org.apache.james.protocols.smtp.SMTPSession;
 
 /**
@@ -35,4 +36,6 @@ public interface AuthHook extends Hook {
      * @return HockResult
      */
     HookResult doAuth(SMTPSession session, Username username, String password);
+
+    HookResult doSasl(SMTPSession session, OidcSASLConfiguration 
saslConfiguration, String initialResponse);
 }
diff --git 
a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java
 
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java
index e8c5b06..0954de6 100644
--- 
a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java
+++ 
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java
@@ -63,6 +63,11 @@ public class BaseFakeSMTPSession implements SMTPSession {
     }
 
     @Override
+    public boolean supportsOAuth() {
+        return false;
+    }
+
+    @Override
     public String getSessionID() {
         throw new UnsupportedOperationException("Unimplemented Stub Method");
     }
diff --git a/server/apps/cassandra-app/sample-configuration/smtpserver.xml 
b/server/apps/cassandra-app/sample-configuration/smtpserver.xml
index e994912..1de4a16 100644
--- a/server/apps/cassandra-app/sample-configuration/smtpserver.xml
+++ b/server/apps/cassandra-app/sample-configuration/smtpserver.xml
@@ -47,7 +47,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <auth>
             <announce>never</announce>
-            <requireSSL>true</requireSSL>
+            <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
@@ -90,6 +90,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
@@ -132,6 +138,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
diff --git 
a/server/apps/cli-integration-tests/src/test/resources/smtpserver.xml 
b/server/apps/cli-integration-tests/src/test/resources/smtpserver.xml
index adc3a72..0fef5ec 100644
--- a/server/apps/cli-integration-tests/src/test/resources/smtpserver.xml
+++ b/server/apps/cli-integration-tests/src/test/resources/smtpserver.xml
@@ -65,6 +65,12 @@
             <announce>always</announce>
             <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>0.0.0.0/0</authorizedAddresses>
         <!-- Trust authenticated users -->
@@ -94,6 +100,12 @@
             <announce>always</announce>
             <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>0.0.0.0/0</authorizedAddresses>
         <!-- Trust authenticated users -->
diff --git 
a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc 
b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc
index e4cff82..2207ba7 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc
@@ -91,6 +91,18 @@ channels.
 | This is an optional tag, defaults to true. If false, AUTH PLAIN and AUTH 
LOGIN will not be exposed. This setting
 can be used to enforce strong authentication mechanisms.
 
+| auth.oidc.oidcConfigurationURL
+| Provide OIDC url address for information to user. Only configure this when 
you want to authenticate SMTP server using a OIDC provider.
+
+| auth.oidc.jwksURL
+| Provide url to get OIDC's JSON Web Key Set to validate user token. Only 
configure this when you want to authenticate SMTP server using a OIDC provider.
+
+| auth.oidc.claim
+| Claim string uses to identify user. E.g: "email_address". Only configure 
this when you want to authenticate SMTP server using a OIDC provider.
+
+| auth.oidc.scope
+| An OAuth scope that is valid to access the service (RF: RFC7628). Only 
configure this when you want to authenticate SMTP server using a OIDC provider.
+
 | authorizedAddresses
 | Authorize specific addresses/networks.
 
diff --git a/server/apps/distributed-app/sample-configuration/smtpserver.xml 
b/server/apps/distributed-app/sample-configuration/smtpserver.xml
index 71ebb29..e0c5c68 100644
--- a/server/apps/distributed-app/sample-configuration/smtpserver.xml
+++ b/server/apps/distributed-app/sample-configuration/smtpserver.xml
@@ -47,7 +47,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <auth>
             <announce>never</announce>
-            <requireSSL>true</requireSSL>
+            <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
@@ -90,6 +90,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
@@ -129,6 +135,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
diff --git 
a/server/apps/distributed-pop3-app/sample-configuration/smtpserver.xml 
b/server/apps/distributed-pop3-app/sample-configuration/smtpserver.xml
index 17478a0..b7472f5 100644
--- a/server/apps/distributed-pop3-app/sample-configuration/smtpserver.xml
+++ b/server/apps/distributed-pop3-app/sample-configuration/smtpserver.xml
@@ -37,7 +37,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <auth>
             <announce>never</announce>
-            <requireSSL>true</requireSSL>
+            <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
@@ -67,6 +67,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
@@ -96,6 +102,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
diff --git a/server/apps/jpa-app/sample-configuration/smtpserver.xml 
b/server/apps/jpa-app/sample-configuration/smtpserver.xml
index 6445e6a..26f4189 100644
--- a/server/apps/jpa-app/sample-configuration/smtpserver.xml
+++ b/server/apps/jpa-app/sample-configuration/smtpserver.xml
@@ -47,7 +47,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <auth>
             <announce>never</announce>
-            <requireSSL>true</requireSSL>
+            <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
@@ -87,6 +87,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
@@ -126,6 +132,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
diff --git a/server/apps/jpa-smtp-app/sample-configuration/smtpserver.xml 
b/server/apps/jpa-smtp-app/sample-configuration/smtpserver.xml
index 6445e6a..26f4189 100644
--- a/server/apps/jpa-smtp-app/sample-configuration/smtpserver.xml
+++ b/server/apps/jpa-smtp-app/sample-configuration/smtpserver.xml
@@ -47,7 +47,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <auth>
             <announce>never</announce>
-            <requireSSL>true</requireSSL>
+            <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
@@ -87,6 +87,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
@@ -126,6 +132,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
diff --git a/server/apps/memory-app/sample-configuration/smtpserver.xml 
b/server/apps/memory-app/sample-configuration/smtpserver.xml
index 6445e6a..26f4189 100644
--- a/server/apps/memory-app/sample-configuration/smtpserver.xml
+++ b/server/apps/memory-app/sample-configuration/smtpserver.xml
@@ -47,7 +47,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <auth>
             <announce>never</announce>
-            <requireSSL>true</requireSSL>
+            <requireSSL>false</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
@@ -87,6 +87,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
@@ -126,6 +132,12 @@
             <announce>forUnauthorizedAddresses</announce>
             <requireSSL>true</requireSSL>
             <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
         </auth>
         <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
         <!-- Trust authenticated users -->
diff --git 
a/server/mailet/integration-testing/src/main/resources/smtpserver.xml 
b/server/mailet/integration-testing/src/main/resources/smtpserver.xml
index 641c9c4..8af96e3 100644
--- a/server/mailet/integration-testing/src/main/resources/smtpserver.xml
+++ b/server/mailet/integration-testing/src/main/resources/smtpserver.xml
@@ -73,6 +73,16 @@
         {{#hasAuthorizedAddresses}}
         <authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses>
         {{/hasAuthorizedAddresses}}
+        <auth>
+            <requireSSL>true</requireSSL>
+            <plainAuthEnabled>true</plainAuthEnabled>
+            <oidc>
+                
<oidcConfigurationURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/.well-known/openid-configuration</oidcConfigurationURL>
+                
<jwksURL>https://auth.upn.integration-open-paas.org/auth/realms/upn/protocol/openid-connect/certs</jwksURL>
+                <claim>email</claim>
+                <scope>openid profile email</scope>
+            </oidc>
+        </auth>
         <!-- Trust authenticated users -->
         <verifyIdentity>false</verifyIdentity>
         <maxmessagesize>{{maxmessagesize}}</maxmessagesize>
diff --git 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/DefaultPublicKeyProviderTest.java
 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/DefaultPublicKeyProviderTest.java
index c4778ba..4c59934 100644
--- 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/DefaultPublicKeyProviderTest.java
+++ 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/DefaultPublicKeyProviderTest.java
@@ -19,7 +19,6 @@
 package org.apache.james.jwt;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.security.Security;
 import java.security.interfaces.RSAPublicKey;
diff --git 
a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
 
b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
index 42c38b8..e1375fc 100644
--- 
a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
+++ 
b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
@@ -18,11 +18,14 @@
  ****************************************************************/
 package org.apache.james.lmtpserver.netty;
 
+import java.util.Optional;
+
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 import org.apache.james.lmtpserver.CoreCmdHandlerLoader;
 import org.apache.james.lmtpserver.jmx.JMXHandlersLoader;
+import org.apache.james.protocols.api.OidcSASLConfiguration;
 import org.apache.james.protocols.lib.handler.HandlersPackage;
 import org.apache.james.protocols.lib.netty.AbstractProtocolAsyncServer;
 import org.apache.james.protocols.lmtp.LMTPConfiguration;
@@ -108,6 +111,11 @@ public class LMTPServer extends 
AbstractProtocolAsyncServer implements LMTPServe
         public boolean isPlainAuthEnabled() {
             return false;
         }
+
+        @Override
+        public Optional<OidcSASLConfiguration> saslConfiguration() {
+            return Optional.empty();
+        }
     }
 
     @Override
diff --git a/server/protocols/protocols-smtp/pom.xml 
b/server/protocols/protocols-smtp/pom.xml
index 936450b..0b916ca 100644
--- a/server/protocols/protocols-smtp/pom.xml
+++ b/server/protocols/protocols-smtp/pom.xml
@@ -79,6 +79,16 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-jwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-jwt</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-lifecycle-api</artifactId>
         </dependency>
         <dependency>
@@ -189,6 +199,11 @@
             <artifactId>apache-jspf-resolver</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.mock-server</groupId>
+            <artifactId>mockserver-netty</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
diff --git 
a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/UsersRepositoryAuthHook.java
 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/UsersRepositoryAuthHook.java
index bf88b00..695025a 100644
--- 
a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/UsersRepositoryAuthHook.java
+++ 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/UsersRepositoryAuthHook.java
@@ -18,9 +18,16 @@
  ****************************************************************/
 package org.apache.james.smtpserver;
 
+import java.util.Optional;
+
 import javax.inject.Inject;
+import javax.mail.internet.AddressException;
 
+import org.apache.james.core.MailAddress;
 import org.apache.james.core.Username;
+import org.apache.james.jwt.OidcJwtTokenVerifier;
+import org.apache.james.protocols.api.OIDCSASLParser;
+import org.apache.james.protocols.api.OidcSASLConfiguration;
 import org.apache.james.protocols.smtp.SMTPSession;
 import org.apache.james.protocols.smtp.hook.AuthHook;
 import org.apache.james.protocols.smtp.hook.HookResult;
@@ -59,4 +66,35 @@ public class UsersRepositoryAuthHook implements AuthHook {
         }
         return HookResult.DECLINED;
     }
+
+    @Override
+    public HookResult doSasl(SMTPSession session, OidcSASLConfiguration 
configuration, String initialResponse) {
+        return OIDCSASLParser.parse(initialResponse)
+            .flatMap(value -> new OidcJwtTokenVerifier()
+                .verifyAndExtractClaim(value.getToken(), 
configuration.getJwksURL(), configuration.getClaim()))
+            .flatMap(this::extractUserFromClaim)
+            .map(username -> {
+                try {
+                    users.assertValid(username);
+                    session.setUsername(username);
+                    session.setRelayingAllowed(true);
+                    return HookResult.builder()
+                        .hookReturnCode(HookReturnCode.ok())
+                        .smtpDescription("Authentication successful.")
+                        .build();
+                } catch (UsersRepositoryException e) {
+                    LOGGER.warn("Invalid username", e);
+                    return HookResult.DECLINED;
+                }
+            })
+            .orElse(HookResult.DECLINED);
+    }
+
+    private Optional<Username> extractUserFromClaim(String claimValue) {
+        try {
+            return Optional.of(Username.fromMailAddress(new 
MailAddress(claimValue)));
+        } catch (AddressException e) {
+            return Optional.empty();
+        }
+    }
 }
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 633813e..e725db7 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
@@ -21,6 +21,7 @@ package org.apache.james.smtpserver.netty;
 import static 
org.apache.james.smtpserver.netty.SMTPServer.AuthenticationAnnounceMode.ALWAYS;
 import static 
org.apache.james.smtpserver.netty.SMTPServer.AuthenticationAnnounceMode.NEVER;
 
+import java.net.MalformedURLException;
 import java.util.Locale;
 import java.util.Optional;
 
@@ -31,6 +32,7 @@ import 
org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.dnsservice.library.netmatcher.NetMatcher;
+import org.apache.james.protocols.api.OidcSASLConfiguration;
 import org.apache.james.protocols.api.ProtocolSession;
 import org.apache.james.protocols.api.ProtocolTransport;
 import org.apache.james.protocols.lib.handler.HandlersPackage;
@@ -86,15 +88,29 @@ public class SMTPServer extends AbstractProtocolAsyncServer 
implements SMTPServe
     }
 
     public static class AuthenticationConfiguration {
+        private static final String OIDC_PATH = "auth.oidc";
 
-
-        public static AuthenticationConfiguration 
parse(HierarchicalConfiguration<ImmutableNode> configuration) {
+        public static AuthenticationConfiguration 
parse(HierarchicalConfiguration<ImmutableNode> configuration) throws 
ConfigurationException {
             return new AuthenticationConfiguration(
                 Optional.ofNullable(configuration.getString("auth.announce", 
null))
                     .map(AuthenticationAnnounceMode::parse)
                     .orElseGet(() -> 
fallbackAuthenticationAnnounceMode(configuration)),
                 configuration.getBoolean("auth.requireSSL", false),
-                configuration.getBoolean("auth.plainAuthEnabled", true));
+                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 exception) {
+                   throw new ConfigurationException("Failed to retrieve oauth 
component", exception);
+                }
+            } else {
+                return Optional.empty();
+            }
         }
 
         private static AuthenticationAnnounceMode 
fallbackAuthenticationAnnounceMode(HierarchicalConfiguration<ImmutableNode> 
configuration) {
@@ -104,11 +120,13 @@ 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) {
+        public AuthenticationConfiguration(AuthenticationAnnounceMode 
authenticationAnnounceMode, boolean requireSSL, boolean plainAuthEnabled, 
Optional<OidcSASLConfiguration> saslConfiguration) {
             this.authenticationAnnounceMode = authenticationAnnounceMode;
             this.requireSSL = requireSSL;
             this.plainAuthEnabled = plainAuthEnabled;
+            this.saslConfiguration = saslConfiguration;
         }
 
         public AuthenticationAnnounceMode getAuthenticationAnnounceMode() {
@@ -122,6 +140,10 @@ public class SMTPServer extends 
AbstractProtocolAsyncServer implements SMTPServe
         public boolean isPlainAuthEnabled() {
             return plainAuthEnabled;
         }
+
+        public Optional<OidcSASLConfiguration> getSaslConfiguration() {
+            return saslConfiguration;
+        }
     }
 
     /**
@@ -318,6 +340,10 @@ public class SMTPServer extends 
AbstractProtocolAsyncServer implements SMTPServe
             return "JAMES SMTP Server ";
         }
 
+        @Override
+        public Optional<OidcSASLConfiguration> saslConfiguration() {
+            return authenticationConfiguration.getSaslConfiguration();
+        }
     }
 
     @Override
diff --git 
a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPSaslTest.java
 
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPSaslTest.java
new file mode 100644
index 0000000..0c07100
--- /dev/null
+++ 
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPSaslTest.java
@@ -0,0 +1,383 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.commons.net.smtp.SMTPSClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.jwt.OidcTokenFixture;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.OIDCSASLHelper;
+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.mock.ConfigLoader;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.james.util.ClassLoaderUtils;
+import org.assertj.core.api.SoftAssertions;
+import org.jboss.netty.util.HashedWheelTimer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.HttpRequest;
+import org.mockserver.model.HttpResponse;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+class SMTPSaslTest {
+    public static final String LOCAL_DOMAIN = "domain.org";
+    public static final Username USER = Username.of("[email protected]");
+    public static final String PASSWORD = "userpassword";
+    public static final String JWKS_URI_PATH = "/jwks";
+    public static final String OIDC_URL = "https://example.com/jwks";;
+    public static final String SCOPE = "scope";
+    public static final String FAIL_RESPONSE_TOKEN = 
Base64.getEncoder().encodeToString(
+        
String.format("{\"status\":\"invalid_token\",\"scope\":\"%s\",\"schemes\":\"%s\"}",
 SCOPE, OIDC_URL).getBytes(UTF_8));
+    public static final String VALID_TOKEN = 
OIDCSASLHelper.generateOauthBearer(USER.asString(), 
OidcTokenFixture.VALID_TOKEN);
+    public static final String INVALID_TOKEN = 
OIDCSASLHelper.generateOauthBearer(USER.asString(), 
OidcTokenFixture.INVALID_TOKEN);
+
+
+    protected HashedWheelTimer hashedWheelTimer;
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+    private ClientAndServer authServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(USER, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        hashedWheelTimer = new HashedWheelTimer();
+        setUpSMTPServer();
+
+        authServer = ClientAndServer.startClientAndServer(0);
+        authServer
+            .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+            .respond(HttpResponse.response().withStatusCode(200)
+                .withHeader("Content-Type", "application/json")
+                .withBody(OidcTokenFixture.JWKS_RESPONSE, 
StandardCharsets.UTF_8));
+
+        HierarchicalConfiguration<ImmutableNode> config = 
ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("smtpserver-advancedSecurity.xml"));
+        config.addProperty("auth.oidc.jwksURL", 
String.format("http://127.0.0.1:%s%s";, authServer.getLocalPort(), 
JWKS_URI_PATH));
+        config.addProperty("auth.oidc.claim", OidcTokenFixture.CLAIM);
+        config.addProperty("auth.oidc.oidcConfigurationURL", OIDC_URL);
+        config.addProperty("auth.oidc.scope", SCOPE);
+        smtpServer.configure(config);
+        smtpServer.init();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new 
MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = 
MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new 
SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setHashWheelTimer(hashedWheelTimer);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new 
MemoryRecipientRewriteTable();
+        
rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new 
AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, 
aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new 
RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> 
binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() 
{}).toInstance(queueFactory))
+            .put(binder -> 
binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> 
binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> 
binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> 
binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> 
binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> 
binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> 
binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .build();
+    }
+
+    private SMTPSClient initSMTPSClient() throws IOException {
+        SMTPSClient client = new SMTPSClient(false, 
BogusSslContextFactory.getClientContext());
+        client.setTrustManager(BogusTrustManagerFactory.getTrustManagers()[0]);
+        InetSocketAddress bindedAddress = new 
ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        client.connect(bindedAddress.getAddress().getHostAddress(), 
bindedAddress.getPort());
+        client.execTLS();
+        return client;
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+        hashedWheelTimer.stop();
+        authServer.stop();
+    }
+
+    @Test
+    void oauthShouldSuccessWhenValidToken() throws Exception {
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN);
+
+        assertThat(client.getReplyString()).contains("235 Authentication 
successful.");
+    }
+
+    @Test
+    void oauthShouldSupportXOAUTH2Type() throws Exception {
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("AUTH XOAUTH2 " + VALID_TOKEN);
+
+        assertThat(client.getReplyString()).contains("235 Authentication 
successful.");
+    }
+
+    @Test
+    void oauthWithNoTLSConnectShouldFail() throws Exception {
+        SMTPClient client = new SMTPClient();
+        InetSocketAddress bindedAddress = new 
ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        client.connect(bindedAddress.getAddress().getHostAddress(), 
bindedAddress.getPort());
+
+        client.sendCommand("EHLO localhost");
+        assertThat(client.getReplyString())
+            .as("Should not advertise OAUTHBEARER when no TLS connect.")
+            .doesNotContain("OAUTHBEARER");
+
+        client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN);
+        assertThat(client.getReplyString()).contains("504 Unrecognized 
Authentication Type");
+    }
+
+    @Test
+    void oauthShouldFailWhenInvalidToken() throws Exception {
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("AUTH OAUTHBEARER " + INVALID_TOKEN);
+        assertThat(client.getReplyString()).contains("334 " + 
FAIL_RESPONSE_TOKEN);
+
+        client.sendCommand("AQ==");
+        assertThat(client.getReplyString()).contains("535 Authentication 
Failed");
+    }
+
+    @Test
+    void sendMailShouldSuccessWhenAuthenticatedByOAuthBearer() throws 
Exception {
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("EHLO localhost");
+
+        client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN);
+
+        client.setSender(USER.asString());
+        client.addRecipient("[email protected]");
+        client.sendShortMessageData("Subject: test\r\n\r\nTest body 
testAuth\r\n");
+        client.quit();
+
+        assertThat(queue.getLastMail())
+            .as("mail received by mail server")
+            .isNotNull();
+    }
+
+    @Test
+    void sendMailShouldFailWhenNotAuthenticated() throws Exception {
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("EHLO localhost");
+
+        client.setSender(USER.asString());
+        client.addRecipient("[email protected]");
+        client.sendShortMessageData("Subject: test\r\n\r\nTest body 
testAuth\r\n");
+        client.quit();
+
+        assertThat(queue.getLastMail())
+            .as("mail received by mail server")
+            .isNull();
+    }
+
+    @Test
+    void shouldNotOauthWhenAlreadyAuthenticated() throws Exception {
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN);
+        client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN);
+
+        assertThat(client.getReplyString()).contains("503 5.5.0 User has 
previously authenticated.  Further authentication is not required!");
+    }
+
+    @Test
+    void oauthShouldFailWhenConfigIsNotProvided() throws Exception {
+        smtpServer.destroy();
+        HierarchicalConfiguration<ImmutableNode> config = 
ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("smtpserver-advancedSecurity.xml"));
+        smtpServer.configure(config);
+        smtpServer.init();
+
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN);
+        assertThat(client.getReplyString()).contains("504 Unrecognized 
Authentication Type");
+    }
+
+    @Test
+    void ehloShouldAdvertiseOAUTHBEARERWhenConfigIsProvided() throws Exception 
{
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(client.getReplyCode()).isEqualTo(250);
+            softly.assertThat(client.getReplyString())
+                .contains("250-AUTH OAUTHBEARER");
+        });
+    }
+
+    @Test
+    void ehloShouldAdvertiseXOAUTH2WhenConfigIsProvided() throws Exception {
+        SMTPSClient client = initSMTPSClient();
+
+        client.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(client.getReplyCode()).isEqualTo(250);
+            softly.assertThat(client.getReplyString())
+                .contains("XOAUTH2");
+        });
+    }
+
+    @Test
+    void ehloShouldNotAdvertiseOAUTHBEARERWhenConfigIsNotProvided() throws 
Exception {
+        smtpServer.destroy();
+        HierarchicalConfiguration<ImmutableNode> config = 
ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("smtpserver-advancedSecurity.xml"));
+        smtpServer.configure(config);
+        smtpServer.init();
+
+        SMTPSClient client = initSMTPSClient();
+        client.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(client.getReplyCode()).isEqualTo(250);
+            softly.assertThat(client.getReplyString())
+                .doesNotContain("OAUTHBEARER");
+        });
+    }
+
+    @Test
+    void ehloShouldNotAdvertiseXOAUTH2WhenConfigIsNotProvided() throws 
Exception {
+        smtpServer.destroy();
+        HierarchicalConfiguration<ImmutableNode> config = 
ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("smtpserver-advancedSecurity.xml"));
+        smtpServer.configure(config);
+        smtpServer.init();
+
+        SMTPSClient client = initSMTPSClient();
+        client.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(client.getReplyCode()).isEqualTo(250);
+            softly.assertThat(client.getReplyString())
+                .doesNotContain("XOAUTH2");
+        });
+    }
+
+}
diff --git a/server/protocols/protocols-smtp/src/test/resources/keystore 
b/server/protocols/protocols-smtp/src/test/resources/keystore
new file mode 100644
index 0000000..536a6c7
Binary files /dev/null and 
b/server/protocols/protocols-smtp/src/test/resources/keystore differ
diff --git 
a/server/protocols/protocols-smtp/src/test/resources/smtpserver-advancedSecurity.xml
 
b/server/protocols/protocols-smtp/src/test/resources/smtpserver-advancedSecurity.xml
new file mode 100644
index 0000000..57f0cdd
--- /dev/null
+++ 
b/server/protocols/protocols-smtp/src/test/resources/smtpserver-advancedSecurity.xml
@@ -0,0 +1,20 @@
+<smtpserver enabled="true">
+    <bind>0.0.0.0:0</bind>
+    <tls socketTLS="false" startTLS="true">
+        <keystore>classpath://keystore</keystore>
+        <secret>james72laBalle</secret>
+        <provider>org.bouncycastle.jce.provider.BouncyCastleProvider</provider>
+        <algorithm>SunX509</algorithm>
+    </tls>
+    <auth>
+        <announce>always</announce>
+        <requireSSL>true</requireSSL>
+        <plainAuthEnabled>false</plainAuthEnabled>
+        <!--OIDC path will be add dynamically in Tests-->
+    </auth>
+    <verifyIdentity>true</verifyIdentity>
+    <smtpGreeting>Apache JAMES awesome SMTP Server</smtpGreeting>
+    <handlerchain>
+        <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
+    </handlerchain>
+</smtpserver>
\ No newline at end of file
diff --git a/src/site/xdoc/server/config-smtp-lmtp.xml 
b/src/site/xdoc/server/config-smtp-lmtp.xml
index 35b682b..0a41142 100644
--- a/src/site/xdoc/server/config-smtp-lmtp.xml
+++ b/src/site/xdoc/server/config-smtp-lmtp.xml
@@ -103,6 +103,14 @@
         <dt><strong>auth.plainAuthEnabled</strong></dt>
         <dd>This is an optional tag, defaults to true. If false, AUTH PLAIN 
and AUTH LOGIN will not be exposed. This setting
             can be used to enforce strong authentication mechanisms.</dd>
+        <dt><strong>auth.oidc.oidcConfigurationURL</strong></dt>
+        <dd>Provide OIDC url address for information to user. Only configure 
this when you want to authenticate SMTP server using a OIDC provider.</dd>
+        <dt><strong>auth.oidc.jwksURL</strong></dt>
+        <dd>Provide url to get OIDC's JSON Web Key Set to validate user token. 
Only configure this when you want to authenticate SMTP server using a OIDC 
provider.</dd>
+        <dt><strong>auth.oidc.claim</strong></dt>
+        <dd>Claim string uses to identify user. E.g: "email_address". Only 
configure this when you want to authenticate SMTP server using a OIDC 
provider.</dd>
+        <dt><strong>auth.oidc.scope</strong></dt>
+        <dd>An OAuth scope that is valid to access the service (RF: RFC7628). 
Only configure this when you want to authenticate SMTP server using a OIDC 
provider.</dd>
       <dt><strong>handler.authorizedAddresses</strong></dt>
       <dd>Authorize specific addresses/networks.
                If you use SMTP AUTH, addresses that match those specified here 
will

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

Reply via email to