Skip to content

Commit

Permalink
JAMES-1930 Implement AUTH PLAIN delegation as part of IMAP
Browse files Browse the repository at this point in the history
  • Loading branch information
chibenwa authored and aduprat committed Feb 14, 2017
1 parent 0175e31 commit 157be0d
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 80 deletions.
Expand Up @@ -113,6 +113,10 @@ public static final HumanReadableText permanentFlags(Flags flags) {

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.");
Expand Down
Expand Up @@ -31,9 +31,14 @@
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";
Expand All @@ -45,62 +50,152 @@ public AbstractAuthProcessor(Class<M> acceptableClass, ImapProcessor next, Mailb
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);
}
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;
}
}
}
Expand Up @@ -37,8 +37,6 @@
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.
*
Expand Down Expand Up @@ -103,12 +101,15 @@ public void onLine(ImapSession session, byte[] data) {
* @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 {

Expand Down Expand Up @@ -161,40 +162,4 @@ public List<String> getImplementedCapabilities(ImapSession session) {
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;
}
}

}
Expand Up @@ -48,13 +48,12 @@ public LoginProcessor(ImapProcessor next, MailboxManager mailboxManager, StatusR
* 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);
}
}

Expand Down

0 comments on commit 157be0d

Please sign in to comment.