Skip to content

JAMES-4210 SASL modurlarization adoption for SMTP#3072

Open
quantranhong1999 wants to merge 10 commits into
apache:masterfrom
quantranhong1999:sasl-modularize-smtp
Open

JAMES-4210 SASL modurlarization adoption for SMTP#3072
quantranhong1999 wants to merge 10 commits into
apache:masterfrom
quantranhong1999:sasl-modularize-smtp

Conversation

@quantranhong1999

Copy link
Copy Markdown
Member

Follow previous IMAP work #3059

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 chibenwa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor remarks, this looks overall good to me.

private final String name;
private final OidcJwtTokenVerifier verifier;
private final boolean requiresSsl;
private final byte[] invalidTokenResponse;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        try {
            SaslInitialRequest request = ;
188	+
            SaslExchange exchange = startExchange(session, maybeMechanism.get(), SASL_BRIDGE.initialRequest(authType, initialResponse));
189	+
            return handleFirstSaslStep(session, authType, exchange)

Comment on lines +220 to +230
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());
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +412 to +413
private SaslExchange startExchange(SMTPSession session, SaslMechanism mechanism, SaslInitialRequest request) {
if (mechanism instanceof AuthHookSaslMechanism authHookSaslMechanism) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed ?

Comment on lines +60 to +64
@PreDestroy
void destroy() {
// SMTPServerFactory is provided through a factory method; dispose it explicitly on Guice shutdown.
smtpServerFactory.destroy();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed ?

Comment on lines +58 to +68
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface is the same as for IMAP stack, no need to duplicate it IMO, likely idem for default mechanisms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants