Move SASL authentication state into SASLMechanism

This commit is contained in:
Florian Schmaus 2019-09-24 10:19:12 +02:00
parent 1d03cfdc79
commit 3ee4dd7b3a
2 changed files with 77 additions and 44 deletions

View File

@ -154,17 +154,6 @@ public final class SASLAuthentication {
private final ConnectionConfiguration configuration; private final ConnectionConfiguration configuration;
private SASLMechanism currentMechanism = null; private SASLMechanism currentMechanism = null;
/**
* Boolean indicating if SASL negotiation has finished and was successful.
*/
private boolean authenticationSuccessful;
/**
* Either of type {@link SmackSaslException},{@link SASLErrorException}, {@link NotConnectedException} or
* {@link InterruptedException}.
*/
private Exception saslException;
SASLAuthentication(AbstractXMPPConnection connection, ConnectionConfiguration configuration) { SASLAuthentication(AbstractXMPPConnection connection, ConnectionConfiguration configuration) {
this.configuration = configuration; this.configuration = configuration;
this.connection = connection; this.connection = connection;
@ -194,22 +183,23 @@ public final class SASLAuthentication {
public SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession) public SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession)
throws XMPPErrorException, SASLErrorException, IOException, throws XMPPErrorException, SASLErrorException, IOException,
InterruptedException, SmackSaslException, NotConnectedException, NoResponseException { InterruptedException, SmackSaslException, NotConnectedException, NoResponseException {
currentMechanism = selectMechanism(authzid); final SASLMechanism mechanism = selectMechanism(authzid);
final CallbackHandler callbackHandler = configuration.getCallbackHandler(); final CallbackHandler callbackHandler = configuration.getCallbackHandler();
final String host = connection.getHost(); final String host = connection.getHost();
final DomainBareJid xmppServiceDomain = connection.getXMPPServiceDomain(); final DomainBareJid xmppServiceDomain = connection.getXMPPServiceDomain();
authenticationSuccessful = false;
synchronized (this) { synchronized (this) {
currentMechanism = mechanism;
if (callbackHandler != null) { if (callbackHandler != null) {
currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid, sslSession); currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid, sslSession);
} }
else { else {
currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid, sslSession); currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid, sslSession);
} }
final long deadline = System.currentTimeMillis() + connection.getReplyTimeout(); final long deadline = System.currentTimeMillis() + connection.getReplyTimeout();
while (!authenticationSuccessful && saslException == null) { while (!mechanism.isFinished()) {
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
if (now >= deadline) break; if (now >= deadline) break;
// Wait until SASL negotiation finishes // Wait until SASL negotiation finishes
@ -217,30 +207,9 @@ public final class SASLAuthentication {
} }
} }
if (saslException != null) { mechanism.throwExceptionIfRequired();
Exception saslException = this.saslException;
// Clear the saslException class field, so that this exception is not thrown after a new authenticate()
// invocation (with different credentials).
this.saslException = null;
if (saslException instanceof SmackSaslException) { return mechanism;
throw (SmackSaslException) saslException;
} else if (saslException instanceof SASLErrorException) {
throw (SASLErrorException) saslException;
} else if (saslException instanceof NotConnectedException) {
throw (NotConnectedException) saslException;
} else if (saslException instanceof InterruptedException) {
throw (InterruptedException) saslException;
} else {
throw new IllegalStateException("Unexpected exception type" , saslException);
}
}
if (!authenticationSuccessful) {
throw NoResponseException.newWith(connection, "successful SASL authentication");
}
return currentMechanism;
} }
/** /**
@ -269,7 +238,9 @@ public final class SASLAuthentication {
*/ */
public void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException { public void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException {
try { try {
currentMechanism.challengeReceived(challenge, finalChallenge); synchronized (this) {
currentMechanism.challengeReceived(challenge, finalChallenge);
}
} catch (InterruptedException | SmackSaslException | NotConnectedException e) { } catch (InterruptedException | SmackSaslException | NotConnectedException e) {
authenticationFailed(e); authenticationFailed(e);
throw e; throw e;
@ -292,10 +263,11 @@ public final class SASLAuthentication {
if (success.getData() != null) { if (success.getData() != null) {
challengeReceived(success.getData(), true); challengeReceived(success.getData(), true);
} }
currentMechanism.checkIfSuccessfulOrThrow();
authenticationSuccessful = true;
// Wake up the thread that is waiting in the #authenticate method // Wake up the thread that is waiting in the #authenticate method
synchronized (this) { synchronized (this) {
currentMechanism.afterFinalSaslChallenge();
notify(); notify();
} }
} }
@ -312,15 +284,20 @@ public final class SASLAuthentication {
} }
private void authenticationFailed(Exception exception) { private void authenticationFailed(Exception exception) {
saslException = exception;
// Wake up the thread that is waiting in the #authenticate method // Wake up the thread that is waiting in the #authenticate method
synchronized (this) { synchronized (this) {
currentMechanism.setException(exception);
notify(); notify();
} }
} }
public boolean authenticationSuccessful() { public boolean authenticationSuccessful() {
return authenticationSuccessful; synchronized (this) {
if (currentMechanism == null) {
return false;
}
return currentMechanism.isAuthenticationSuccessful();
}
} }
String getNameOfLastUsedSaslMechansism() { String getNameOfLastUsedSaslMechansism() {

View File

@ -23,6 +23,7 @@ import javax.net.ssl.SSLSession;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackSaslException; import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
@ -56,6 +57,17 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
public static final String GSSAPI = "GSSAPI"; public static final String GSSAPI = "GSSAPI";
public static final String PLAIN = "PLAIN"; public static final String PLAIN = "PLAIN";
/**
* Boolean indicating if SASL negotiation has finished and was successful.
*/
private boolean authenticationSuccessful;
/**
* Either of type {@link SmackSaslException},{@link SASLErrorException}, {@link NotConnectedException} or
* {@link InterruptedException}.
*/
private Exception exception;
protected XMPPConnection connection; protected XMPPConnection connection;
protected ConnectionConfiguration connectionConfiguration; protected ConnectionConfiguration connectionConfiguration;
@ -275,7 +287,18 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
*/ */
public abstract int getPriority(); public abstract int getPriority();
public abstract void checkIfSuccessfulOrThrow() throws SmackSaslException; /**
* Check if the SASL mechanism was successful and if it was, then mark it so.
*
* @throws SmackSaslException in case of an SASL error.
*/
public final void afterFinalSaslChallenge() throws SmackSaslException {
checkIfSuccessfulOrThrow();
authenticationSuccessful = true;
}
protected abstract void checkIfSuccessfulOrThrow() throws SmackSaslException;
public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) { public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) {
SASLMechanism saslMechansim = newInstance(); SASLMechanism saslMechansim = newInstance();
@ -288,6 +311,39 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
return false; return false;
} }
public boolean isAuthenticationSuccessful() {
return authenticationSuccessful;
}
public boolean isFinished() {
return isAuthenticationSuccessful() || exception != null;
}
public void throwExceptionIfRequired() throws SmackSaslException, SASLErrorException, NotConnectedException,
InterruptedException, NoResponseException {
if (exception != null) {
if (exception instanceof SmackSaslException) {
throw (SmackSaslException) exception;
} else if (exception instanceof SASLErrorException) {
throw (SASLErrorException) exception;
} else if (exception instanceof NotConnectedException) {
throw (NotConnectedException) exception;
} else if (exception instanceof InterruptedException) {
throw (InterruptedException) exception;
} else {
throw new IllegalStateException("Unexpected exception type", exception);
}
}
if (!authenticationSuccessful) {
throw NoResponseException.newWith(connection, "successful SASL authentication");
}
}
public void setException(Exception exception) {
this.exception = exception;
}
protected abstract SASLMechanism newInstance(); protected abstract SASLMechanism newInstance();
protected static byte[] toBytes(String string) { protected static byte[] toBytes(String string) {