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 440f7546405b81d99f239cbbcfab473e64122c2b Author: Quan Tran <[email protected]> AuthorDate: Tue Jun 30 09:39:46 2026 +0700 JAMES-4210 Preserve legacy SMTP AuthHook behavior with SASL LOGIN Apply the legacy AuthHook SASL adapter to the effective PLAIN mechanism and to LOGIN's delegated PLAIN mechanism, preserving legacy AuthHook behavior for both AUTH PLAIN and AUTH LOGIN without leaking AuthHook-specific startup logic into AuthCmdHandler. --- .../smtp/core/esmtp/AuthHookSaslMechanism.java | 30 ++++++++++++++---- .../smtp/core/esmtp/AuthHookSaslMechanismTest.java | 36 ++++++++++++++-------- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanism.java b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanism.java index 0c75392b88..7cc03f8554 100644 --- a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanism.java +++ b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanism.java @@ -53,17 +53,39 @@ class AuthHookSaslMechanism implements SaslMechanism { if (exchange instanceof AuthHookSaslMechanism.Exchange authHookExchange) { return authHookExchange.terminalResponse(); } - return Optional.empty(); + return LoginSaslMechanism.delegatedExchange(exchange) + .flatMap(AuthHookSaslMechanism::terminalResponse); + } + + static ImmutableList<SaslMechanism> withLegacyAuthHooks(ImmutableList<SaslMechanism> mechanisms, List<AuthHook> authHooks, + List<HookResultHook> hookResultHooks, SMTPSession session) { + return mechanisms.stream() + .map(mechanism -> adaptMechanism(mechanism, authHooks, hookResultHooks, session)) + .collect(ImmutableList.toImmutableList()); + } + + private static SaslMechanism adaptMechanism(SaslMechanism mechanism, List<AuthHook> authHooks, + List<HookResultHook> hookResultHooks, SMTPSession session) { + if (mechanism instanceof LoginSaslMechanism loginSaslMechanism) { + return new LoginSaslMechanism(new AuthHookSaslMechanism( + loginSaslMechanism.plainMechanism(), authHooks, hookResultHooks, session)); + } + if (mechanism.name().equalsIgnoreCase(SaslMechanismNames.PLAIN)) { + return new AuthHookSaslMechanism(mechanism, authHooks, hookResultHooks, session); + } + return mechanism; } private final SaslMechanism plainMechanism; private final ImmutableList<AuthHook> authHooks; private final ImmutableList<HookResultHook> hookResultHooks; + private final SMTPSession session; - AuthHookSaslMechanism(SaslMechanism plainMechanism, List<AuthHook> authHooks, List<HookResultHook> hookResultHooks) { + AuthHookSaslMechanism(SaslMechanism plainMechanism, List<AuthHook> authHooks, List<HookResultHook> hookResultHooks, SMTPSession session) { this.plainMechanism = plainMechanism; this.authHooks = ImmutableList.copyOf(authHooks); this.hookResultHooks = ImmutableList.copyOf(hookResultHooks); + this.session = session; } @Override @@ -78,10 +100,6 @@ class AuthHookSaslMechanism implements SaslMechanism { @Override public SaslExchange start(SaslInitialRequest request, SaslAuthenticator authenticator) { - throw new IllegalStateException("Legacy SMTP AuthHook adapter requires an SMTP session"); - } - - Exchange start(SaslInitialRequest request, SMTPSession session) { return new Exchange(request.initialResponse(), session); } diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanismTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanismTest.java index 5d8265031e..22472544b5 100644 --- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanismTest.java +++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/AuthHookSaslMechanismTest.java @@ -31,6 +31,7 @@ import java.util.Optional; import org.apache.james.core.Username; import org.apache.james.protocols.api.Response; +import org.apache.james.protocols.api.sasl.SaslAuthenticator; import org.apache.james.protocols.api.sasl.SaslFailure; import org.apache.james.protocols.api.sasl.SaslIdentity; import org.apache.james.protocols.api.sasl.SaslInitialRequest; @@ -50,6 +51,7 @@ class AuthHookSaslMechanismTest { private static final Username USERNAME = Username.of("[email protected]"); private final SMTPSession session = mock(SMTPSession.class); + private final SaslAuthenticator authenticator = mock(SaslAuthenticator.class); @Test void shouldDelegatePlainAvailabilityToWrappedMechanism() { @@ -57,7 +59,8 @@ class AuthHookSaslMechanismTest { AuthHookSaslMechanism mechanism = new AuthHookSaslMechanism( new PlainSaslMechanism(false, true), ImmutableList.of(mock(AuthHook.class)), - ImmutableList.of()); + ImmutableList.of(), + session); // WHEN checking whether it is available on either transport // THEN the legacy adapter preserves the configured policy @@ -70,14 +73,16 @@ class AuthHookSaslMechanismTest { // GIVEN a legacy hook declining the provided credentials AuthHook authHook = mock(AuthHook.class); when(authHook.doAuth(any(SMTPSession.class), eq(USERNAME), eq("password"))).thenReturn(HookResult.DECLINED); - SMTPSession session = mock(SMTPSession.class); AuthHookSaslMechanism mechanism = new AuthHookSaslMechanism( new PlainSaslMechanism(), ImmutableList.of(authHook), - ImmutableList.of()); + ImmutableList.of(), + session); // WHEN authenticating through the adapter - SaslStep step = mechanism.start(initialRequest("\[email protected]\0password"), session).firstStep(); + SaslStep step = mechanism + .start(initialRequest("\[email protected]\0password"), authenticator) + .firstStep(); // THEN the legacy terminal failure is preserved without a fallback login assertThat(step).isEqualTo(new SaslStep.Failure(SaslFailure.invalidCredentials( @@ -96,10 +101,13 @@ class AuthHookSaslMechanismTest { AuthHookSaslMechanism mechanism = new AuthHookSaslMechanism( new PlainSaslMechanism(), ImmutableList.of(authHook), - ImmutableList.of()); + ImmutableList.of(), + session); // WHEN authenticating through the adapter - SaslStep step = mechanism.start(initialRequest("\[email protected]\0"), session).firstStep(); + SaslStep step = mechanism + .start(initialRequest("\[email protected]\0"), authenticator) + .firstStep(); // THEN the legacy hook receives the delegation call assertThat(step).isEqualTo(new SaslStep.Success( @@ -120,15 +128,17 @@ class AuthHookSaslMechanismTest { AuthHookSaslMechanism mechanism = new AuthHookSaslMechanism( new PlainSaslMechanism(), ImmutableList.of(authHook), - ImmutableList.of()); + ImmutableList.of(), + session); // WHEN the adapter processes the PLAIN credentials - AuthHookSaslMechanism.Exchange exchange = mechanism.start(initialRequest("\[email protected]\0password"), session); + AuthHookSaslMechanism.Exchange exchange = (AuthHookSaslMechanism.Exchange) mechanism + .start(initialRequest("\[email protected]\0password"), authenticator); SaslStep step = exchange.firstStep(); // THEN the SASL failure keeps the legacy SMTP response for the protocol driver assertThat(step).isInstanceOf(SaslStep.Failure.class); - Response response = AuthHookSaslMechanism.terminalResponse(exchange).orElseThrow(); + Response response = exchange.terminalResponse().orElseThrow(); assertThat(response.getRetCode()).isEqualTo("421"); assertThat(response.getLines()).containsExactly("421 Too many authentication attempts"); assertThat(response.isEndSession()).isTrue(); @@ -146,15 +156,17 @@ class AuthHookSaslMechanismTest { AuthHookSaslMechanism mechanism = new AuthHookSaslMechanism( new PlainSaslMechanism(), ImmutableList.of(authHook), - ImmutableList.of()); + ImmutableList.of(), + session); // WHEN the adapter processes the PLAIN credentials - AuthHookSaslMechanism.Exchange exchange = mechanism.start(initialRequest("\[email protected]\0password"), session); + AuthHookSaslMechanism.Exchange exchange = (AuthHookSaslMechanism.Exchange) mechanism + .start(initialRequest("\[email protected]\0password"), authenticator); SaslStep step = exchange.firstStep(); // THEN SMTP success keeps the connection open assertThat(step).isInstanceOf(SaslStep.Success.class); - assertThat(AuthHookSaslMechanism.terminalResponse(exchange).orElseThrow().isEndSession()).isFalse(); + assertThat(exchange.terminalResponse().orElseThrow().isEndSession()).isFalse(); } private SaslInitialRequest initialRequest(String value) { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
