JAMES-1930 Implement AUTH PLAIN delegation as part of IMAP
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/157be0db Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/157be0db Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/157be0db Branch: refs/heads/master Commit: 157be0db425f59e3751ea4ebdcefe3cf4daf7ec2 Parents: 0175e31 Author: Benoit Tellier <[email protected]> Authored: Fri Feb 10 11:51:24 2017 +0700 Committer: Antoine Duprat <[email protected]> Committed: Tue Feb 14 11:29:30 2017 +0100 ---------------------------------------------------------------------- .../imap/api/display/HumanReadableText.java | 4 + .../imap/processor/AbstractAuthProcessor.java | 165 +++++++++++++++---- .../imap/processor/AuthenticateProcessor.java | 49 +----- .../james/imap/processor/LoginProcessor.java | 5 +- 4 files changed, 143 insertions(+), 80 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/157be0db/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java b/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java index 42dba98..57a98e0 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java @@ -113,6 +113,10 @@ public class HumanReadableText { public static final HumanReadableText COMSUME_UID_FAILED = new HumanReadableText("org.apache.james.imap.COMSUME_UID_FAILED", "failed. Failed to acquire UID."); + public static final HumanReadableText USER_DOES_NOT_EXIST = new HumanReadableText("org.apache.james.imap.GENERIC_FAILURE_DURING_PROCESSING", "User does not exist"); + + public static final HumanReadableText NOT_AN_ADMIN = new HumanReadableText("org.apache.james.imap.GENERIC_FAILURE_DURING_PROCESSING", "Not an admin"); + public static final HumanReadableText GENERIC_FAILURE_DURING_PROCESSING = new HumanReadableText("org.apache.james.imap.GENERIC_FAILURE_DURING_PROCESSING", "processing failed."); public static final HumanReadableText FAILURE_MAILBOX_EXISTS = new HumanReadableText("org.apache.james.imap.FAILURE_NO_SUCH_MAILBOX", "failed. Mailbox already exists."); http://git-wip-us.apache.org/repos/asf/james-project/blob/157be0db/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractAuthProcessor.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractAuthProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractAuthProcessor.java index be5460a..5e10aa6 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractAuthProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractAuthProcessor.java @@ -31,9 +31,14 @@ import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.BadCredentialsException; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.MailboxExistsException; +import org.apache.james.mailbox.exception.NotAdminException; +import org.apache.james.mailbox.exception.UserDoesNotExistException; import org.apache.james.mailbox.model.MailboxConstants; import org.apache.james.mailbox.model.MailboxPath; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + public abstract class AbstractAuthProcessor<M extends ImapRequest> extends AbstractMailboxProcessor<M>{ private static final String ATTRIBUTE_NUMBER_OF_FAILURES = "org.apache.james.imap.processor.imap4rev1.NUMBER_OF_FAILURES"; @@ -45,57 +50,73 @@ public abstract class AbstractAuthProcessor<M extends ImapRequest> extends Abstr super(acceptableClass, next, mailboxManager, factory); } - protected void doAuth(String userid, String passwd, ImapSession session, String tag, ImapCommand command, Responder responder, HumanReadableText failed) { + protected void doAuth(AuthenticationAttempt authenticationAttempt, ImapSession session, String tag, ImapCommand command, Responder responder, HumanReadableText failed) { + Preconditions.checkArgument(!authenticationAttempt.isDelegation()); try { boolean authFailure = false; - if (userid == null) { + if (authenticationAttempt.getAuthenticationId() == null) { authFailure = true; } - if (authFailure == false) { + if (!authFailure) { final MailboxManager mailboxManager = getMailboxManager(); try { - final MailboxSession mailboxSession = mailboxManager.login(userid, passwd, session.getLog()); + final MailboxSession mailboxSession = mailboxManager.login(authenticationAttempt.getAuthenticationId(), + authenticationAttempt.getPassword(), + session.getLog()); session.authenticated(); session.setAttribute(ImapSessionUtils.MAILBOX_SESSION_ATTRIBUTE_SESSION_KEY, mailboxSession); - final MailboxPath inboxPath = PathConverter.forSession(session).buildFullPath(MailboxConstants.INBOX); - if (mailboxManager.mailboxExists(inboxPath, mailboxSession)) { - if (session.getLog().isDebugEnabled()) { - session.getLog().debug("INBOX exists. No need to create it."); - } - } else { - try { - session.getLog().debug("INBOX does not exist. Creating it."); - mailboxManager.createMailbox(inboxPath, mailboxSession); - } catch (MailboxExistsException e) { - if (session.getLog().isDebugEnabled()) { - session.getLog().debug("Mailbox created by concurrent call. Safe to ignore this exception."); - } - } - } + provisionInbox(session, mailboxManager, mailboxSession); okComplete(command, tag, responder); } catch (BadCredentialsException e) { authFailure = true; } } if (authFailure) { - final Integer currentNumberOfFailures = (Integer) session.getAttribute(ATTRIBUTE_NUMBER_OF_FAILURES); - final int failures; - if (currentNumberOfFailures == null) { - failures = 1; - } else { - failures = currentNumberOfFailures.intValue() + 1; - } - if (failures < MAX_FAILURES) { - session.setAttribute(ATTRIBUTE_NUMBER_OF_FAILURES, failures); - no(command, tag, responder, failed); - } else { - if (session.getLog().isInfoEnabled()) { - session.getLog().info("Too many authentication failures. Closing connection."); - } - bye(responder, HumanReadableText.TOO_MANY_FAILURES); - session.logout(); + manageFailureCount(session, tag, command, responder, failed); + } + } catch (MailboxException e) { + if (session.getLog().isInfoEnabled()) { + session.getLog().info("Login failed", e); + } + no(command, tag, responder, HumanReadableText.GENERIC_FAILURE_DURING_PROCESSING); + } + } + + protected void doAuthWithDelegation(AuthenticationAttempt authenticationAttempt, ImapSession session, String tag, ImapCommand command, Responder responder, HumanReadableText failed) { + Preconditions.checkArgument(authenticationAttempt.isDelegation()); + try { + boolean authFailure = false; + if (authenticationAttempt.getAuthenticationId() == null) { + authFailure = true; + } + if (!authFailure) { + final MailboxManager mailboxManager = getMailboxManager(); + try { + final MailboxSession mailboxSession = mailboxManager.loginAsOtherUser(authenticationAttempt.getAuthenticationId(), + authenticationAttempt.getPassword(), + authenticationAttempt.getDelegateUserName().get(), + session.getLog()); + session.authenticated(); + session.setAttribute(ImapSessionUtils.MAILBOX_SESSION_ATTRIBUTE_SESSION_KEY, mailboxSession); + provisionInbox(session, mailboxManager, mailboxSession); + okComplete(command, tag, responder); + } catch (BadCredentialsException e) { + authFailure = true; } } + if (authFailure) { + manageFailureCount(session, tag, command, responder, failed); + } + } catch (UserDoesNotExistException e) { + if (session.getLog().isInfoEnabled()) { + session.getLog().info("User " + authenticationAttempt.getAuthenticationId() + " does not exist", e); + } + no(command, tag, responder, HumanReadableText.USER_DOES_NOT_EXIST); + } catch (NotAdminException e) { + if (session.getLog().isInfoEnabled()) { + session.getLog().info("User " + authenticationAttempt.getDelegateUserName() + " is not an admin", e); + } + no(command, tag, responder, HumanReadableText.NOT_AN_ADMIN); } catch (MailboxException e) { if (session.getLog().isInfoEnabled()) { session.getLog().info("Login failed", e); @@ -103,4 +124,78 @@ public abstract class AbstractAuthProcessor<M extends ImapRequest> extends Abstr no(command, tag, responder, HumanReadableText.GENERIC_FAILURE_DURING_PROCESSING); } } + + private void provisionInbox(ImapSession session, MailboxManager mailboxManager, MailboxSession mailboxSession) throws MailboxException { + final MailboxPath inboxPath = PathConverter.forSession(session).buildFullPath(MailboxConstants.INBOX); + if (mailboxManager.mailboxExists(inboxPath, mailboxSession)) { + if (session.getLog().isDebugEnabled()) { + session.getLog().debug("INBOX exists. No need to create it."); + } + } else { + try { + session.getLog().debug("INBOX does not exist. Creating it."); + mailboxManager.createMailbox(inboxPath, mailboxSession); + } catch (MailboxExistsException e) { + if (session.getLog().isDebugEnabled()) { + session.getLog().debug("Mailbox created by concurrent call. Safe to ignore this exception."); + } + } + } + } + + protected void manageFailureCount(ImapSession session, String tag, ImapCommand command, Responder responder, HumanReadableText failed) { + final Integer currentNumberOfFailures = (Integer) session.getAttribute(ATTRIBUTE_NUMBER_OF_FAILURES); + final int failures; + if (currentNumberOfFailures == null) { + failures = 1; + } else { + failures = currentNumberOfFailures + 1; + } + if (failures < MAX_FAILURES) { + session.setAttribute(ATTRIBUTE_NUMBER_OF_FAILURES, failures); + no(command, tag, responder, failed); + } else { + if (session.getLog().isInfoEnabled()) { + session.getLog().info("Too many authentication failures. Closing connection."); + } + bye(responder, HumanReadableText.TOO_MANY_FAILURES); + session.logout(); + } + } + + protected static AuthenticationAttempt delegation(String authorizeId, String authenticationId, String password) { + return new AuthenticationAttempt(Optional.of(authorizeId), authenticationId, password); + } + + protected static AuthenticationAttempt noDelegation(String authenticationId, String password) { + return new AuthenticationAttempt(Optional.<String>absent(), authenticationId, password); + } + + protected static class AuthenticationAttempt { + private final Optional<String> delegateUserName; + private final String authenticationId; + private final String password; + + public AuthenticationAttempt(Optional<String> delegateUserName, String authenticationId, String password) { + this.delegateUserName = delegateUserName; + this.authenticationId = authenticationId; + this.password = password; + } + + public boolean isDelegation() { + return delegateUserName.isPresent(); + } + + public Optional<String> getDelegateUserName() { + return delegateUserName; + } + + public String getAuthenticationId() { + return authenticationId; + } + + public String getPassword() { + return password; + } + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/157be0db/protocols/imap/src/main/java/org/apache/james/imap/processor/AuthenticateProcessor.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AuthenticateProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AuthenticateProcessor.java index 1abc69d..ea8324a 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AuthenticateProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AuthenticateProcessor.java @@ -37,8 +37,6 @@ import org.apache.james.imap.message.request.IRAuthenticateRequest; import org.apache.james.imap.message.response.AuthenticateResponse; import org.apache.james.mailbox.MailboxManager; -import com.google.common.base.Optional; - /** * Processor which handles the AUTHENTICATE command. Only authtype of PLAIN is supported ATM. * @@ -103,12 +101,15 @@ public class AuthenticateProcessor extends AbstractAuthProcessor<AuthenticateReq * @param responder */ protected void doPlainAuth(String initialClientResponse, ImapSession session, String tag, ImapCommand command, Responder responder) { - AuthPlainAttempt authPlainAttempt = parseDelegationAttempt(initialClientResponse); - // Authenticate user - doAuth(authPlainAttempt.getAuthenticationId(), authPlainAttempt.getPassword(), session, tag, command, responder, HumanReadableText.AUTHENTICATION_FAILED); + AuthenticationAttempt authenticationAttempt = parseDelegationAttempt(initialClientResponse); + if (authenticationAttempt.isDelegation()) { + doAuthWithDelegation(authenticationAttempt, session, tag, command, responder, HumanReadableText.AUTHENTICATION_FAILED); + } else { + doAuth(authenticationAttempt, session, tag, command, responder, HumanReadableText.AUTHENTICATION_FAILED); + } } - private AuthPlainAttempt parseDelegationAttempt(String initialClientResponse) { + private AuthenticationAttempt parseDelegationAttempt(String initialClientResponse) { String token2; try { @@ -161,40 +162,4 @@ public class AuthenticateProcessor extends AbstractAuthProcessor<AuthenticateReq return Collections.unmodifiableList(caps); } - private static AuthPlainAttempt delegation(String authorizeId, String authenticationId, String password) { - return new AuthPlainAttempt(Optional.of(authorizeId), authenticationId, password); - } - - private static AuthPlainAttempt noDelegation(String authenticationId, String password) { - return new AuthPlainAttempt(Optional.<String>absent(), authenticationId, password); - } - - private static class AuthPlainAttempt { - private final Optional<String> authorizeId; - private final String authenticationId; - private final String password; - - private AuthPlainAttempt(Optional<String> authorizeId, String authenticationId, String password) { - this.authorizeId = authorizeId; - this.authenticationId = authenticationId; - this.password = password; - } - - public boolean isDelegation() { - return authorizeId.isPresent(); - } - - public Optional<String> getAuthorizeId() { - return authorizeId; - } - - public String getAuthenticationId() { - return authenticationId; - } - - public String getPassword() { - return password; - } - } - } http://git-wip-us.apache.org/repos/asf/james-project/blob/157be0db/protocols/imap/src/main/java/org/apache/james/imap/processor/LoginProcessor.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/LoginProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/LoginProcessor.java index 3e824e1..4837446 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/LoginProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/LoginProcessor.java @@ -48,13 +48,12 @@ public class LoginProcessor extends AbstractAuthProcessor<LoginRequest> implemen * org.apache.james.imap.api.ImapCommand, org.apache.james.imap.api.process.ImapProcessor.Responder) */ protected void doProcess(LoginRequest request, ImapSession session, String tag, ImapCommand command, Responder responder) { - final String userid = request.getUserid(); - final String passwd = request.getPassword(); // check if the login is allowed with LOGIN command. See IMAP-304 if (session.isPlainAuthDisallowed() && session.isTLSActive() == false) { no(command, tag, responder, HumanReadableText.DISABLED_LOGIN); } else { - doAuth(userid, passwd, session, tag, command, responder, HumanReadableText.INVALID_LOGIN); + doAuth(noDelegation(request.getUserid(), request.getPassword()), + session, tag, command, responder, HumanReadableText.INVALID_LOGIN); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
