JAMES-4210 SASL modurlarization adoption for SMTP#3072
JAMES-4210 SASL modurlarization adoption for SMTP#3072quantranhong1999 wants to merge 10 commits into
Conversation
Introduce an SMTP SASL bridge for decoding initial responses and continuation lines, encoding challenges as SMTP 334 responses, detecting aborts, and supporting final SASL server data through the RFC 4422 additional-challenge flow.
Assert the base64 OAuth error challenge and dummy client response required by RFC 7628 before terminal IMAP authentication failure, aligning IMAP behavior with the existing SMTP OIDC error flow.
Add shared SASL behavior needed by SMTP: transport availability on factories and mechanisms, OAuth invalid-token challenge flow, server-error convenience failure, and legacy PLAIN trailing-NUL compatibility.
Replace embedded SMTP PLAIN/OIDC authentication logic with the shared SaslMechanism exchange flow, including LOGIN framing, final server data acknowledgement, SMTP session authentication state, and SASL-focused regression tests.
Keep legacy SMTP AuthHook registrations usable by wrapping them as a PLAIN SASL mechanism, add post-auth result hook support, deprecate AuthHook, and document migration expectations.
Wire SMTP Guice support for per-server SASL mechanism resolution, default SMTP SASL factories, probe cleanup, and module tests.
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.
Remove LMTP test and server implementations of SMTP authentication configuration methods that no longer exist after SMTP AUTH moved to SASL mechanisms.
Stop the custom SMTP command example from explicitly registering UsersRepositoryAuthHook; default SMTP authentication is now handled by the SASL AUTH command path.
chibenwa
left a comment
There was a problem hiding this comment.
Minor remarks, this looks overall good to me.
| private final String name; | ||
| private final OidcJwtTokenVerifier verifier; | ||
| private final boolean requiresSsl; | ||
| private final byte[] invalidTokenResponse; |
There was a problem hiding this comment.
Can't we supply a default / static value ?
I'd try if possible to get a similar invalidToken response in SMTP and IMAP in order to allign IMAP with SMTP
| try { | ||
| SaslInitialRequest request = SASL_BRIDGE.initialRequest(authType, initialResponse); | ||
| exchange = startExchange(session, mechanism, request); | ||
| return handleFirstSaslStep(session, authType, exchange); |
There was a problem hiding this comment.
try {
SaslInitialRequest request = ;
188 +
SaslExchange exchange = startExchange(session, maybeMechanism.get(), SASL_BRIDGE.initialRequest(authType, initialResponse));
189 +
return handleFirstSaslStep(session, authType, exchange)
| return switch (step) { | ||
| case SaslStep.Challenge challenge -> SASL_BRIDGE.challenge(challenge); | ||
| case SaslStep.Success success -> { | ||
| session.popLineHandler(); | ||
| yield handleSaslSuccess(session, authType, exchange, success); | ||
| } | ||
| case SaslStep.Failure failure -> { | ||
| session.popLineHandler(); | ||
| yield handleSaslFailure(session, authType, exchange, failure.failure()); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Extract this block, mutualize it use a Runnable to either () -> {} or session::popLineHandler
| if (line != null) { | ||
| String lineWithoutTrailingCrLf = StringUtils.replace(line, "\r\n", ""); | ||
| return new String(Base64.getDecoder().decode(lineWithoutTrailingCrLf), StandardCharsets.UTF_8); | ||
| private Response handleLoginFraming(SMTPSession session, Optional<String> initialResponse) { |
There was a problem hiding this comment.
Maybe it would be easier to handle login as a dedicated Sasl mechanism registered only for SMTP
Then we won't have this specific handling here.
| private SaslExchange startExchange(SMTPSession session, SaslMechanism mechanism, SaslInitialRequest request) { | ||
| if (mechanism instanceof AuthHookSaslMechanism authHookSaslMechanism) { |
There was a problem hiding this comment.
Why sasl mechanism specific internals leaks in here ?
| import org.apache.james.user.api.UsersRepository; | ||
| import org.apache.james.user.api.UsersRepositoryException; | ||
|
|
||
| class UsersRepositoryBackedAuthenticator implements Authenticator { |
| @PreDestroy | ||
| void destroy() { | ||
| // SMTPServerFactory is provided through a factory method; dispose it explicitly on Guice shutdown. | ||
| smtpServerFactory.destroy(); | ||
| } |
| 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; | ||
| } |
There was a problem hiding this comment.
The interface is the same as for IMAP stack, no need to duplicate it IMO, likely idem for default mechanisms
Follow previous IMAP work #3059