Skip to content

Commit

Permalink
Token-based reconnection implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ramabit committed Jul 13, 2016
1 parent 08c93da commit 04b2517
Show file tree
Hide file tree
Showing 19 changed files with 871 additions and 105 deletions.
2 changes: 2 additions & 0 deletions documentation/extensions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Currently supported XEPs of Smack (all subprojects)
| Name | XEP | Description |
|---------------------------------------------|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| Nonzas | [XEP-0360](http://xmpp.org/extensions/xep-0360.html) | Defines the term "Nonza", describing every top level stream element that is not a Stanza. |
| Token-based reconnection | [XEP-xxxx](http://www.xmpp.org/extensions/inbox/token-reconnection.html) | Defines a token-based session authentication mechanism. |


Currently supported XEPs of smack-tcp
-------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,38 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import org.igniterealtime.jbosh.AbstractBody;
import org.igniterealtime.jbosh.BOSHClient;
import org.igniterealtime.jbosh.BOSHClientConfig;
import org.igniterealtime.jbosh.BOSHClientConnEvent;
import org.igniterealtime.jbosh.BOSHClientConnListener;
import org.igniterealtime.jbosh.BOSHClientRequestListener;
import org.igniterealtime.jbosh.BOSHClientResponseListener;
import org.igniterealtime.jbosh.BOSHException;
import org.igniterealtime.jbosh.BOSHMessageEvent;
import org.igniterealtime.jbosh.BodyQName;
import org.igniterealtime.jbosh.ComposableBody;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.parts.Resourcepart;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import org.igniterealtime.jbosh.AbstractBody;
import org.igniterealtime.jbosh.BOSHClient;
import org.igniterealtime.jbosh.BOSHClientConfig;
import org.igniterealtime.jbosh.BOSHClientConnEvent;
import org.igniterealtime.jbosh.BOSHClientConnListener;
import org.igniterealtime.jbosh.BOSHClientRequestListener;
import org.igniterealtime.jbosh.BOSHClientResponseListener;
import org.igniterealtime.jbosh.BOSHException;
import org.igniterealtime.jbosh.BOSHMessageEvent;
import org.igniterealtime.jbosh.BodyQName;
import org.igniterealtime.jbosh.ComposableBody;

/**
* Creates a connection to an XMPP server via HTTP binding.
Expand Down Expand Up @@ -221,6 +222,20 @@ protected void loginInternal(String username, String password, Resourcepart reso
// Authenticate using SASL
saslAuthentication.authenticate(username, password, config.getAuthzid());

afterAuth(resource);
}

@Override
protected void loginInternal(String token, Resourcepart resource)
throws XMPPException, SmackException, IOException, InterruptedException {
// Authenticate using SASL
saslAuthentication.authenticate(token);

afterAuth(resource);
}

private void afterAuth(Resourcepart resource)
throws XMPPErrorException, SmackException, InterruptedException, NotConnectedException {
bindResourceAndEstablishSession(resource);

afterSuccessfulLogin(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@
import org.jivesoftware.smack.iqrequest.IQRequestHandler;
import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.ErrorIQ;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Session;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
Expand Down Expand Up @@ -286,6 +286,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private final Map<String, IQRequestHandler> setIqRequestHandler = new HashMap<>();
private final Map<String, IQRequestHandler> getIqRequestHandler = new HashMap<>();

/**
* Indicates the last refresh token received via the X-OAUTH mechanism.
*/
protected String refreshTokenXOAUTH;

/**
* Create a new XMPPConnection to an XMPP server.
*
Expand Down Expand Up @@ -384,13 +389,20 @@ public synchronized AbstractXMPPConnection connect() throws SmackException, IOEx
*/
protected abstract void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException;

private String usedUsername, usedPassword;
private String usedUsername, usedPassword, usedToken;

/**
* The resourcepart used for this connection. May not be the resulting resourcepart if it's null or overridden by the XMPP service.
*/
private Resourcepart usedResource;

/**
* Method to avoid reconnection using token.
*/
public void avoidTokenReconnection() {
usedToken = null;
}

/**
* Logs in to the server using the strongest SASL mechanism supported by
* the server. If more than the connection's default stanza(/packet) timeout elapses in each step of the
Expand Down Expand Up @@ -420,7 +432,12 @@ public synchronized void login() throws XMPPException, SmackException, IOExcepti
CharSequence username = usedUsername != null ? usedUsername : config.getUsername();
String password = usedPassword != null ? usedPassword : config.getPassword();
Resourcepart resource = usedResource != null ? usedResource : config.getResource();
login(username, password, resource);

if (usedToken != null) {
login(usedToken, resource);
} else {
login(username, password, resource);
}
}

/**
Expand Down Expand Up @@ -466,9 +483,35 @@ public synchronized void login(CharSequence username, String password, Resourcep
loginInternal(usedUsername, usedPassword, usedResource);
}

/**
* Login with the given token. Calling this method Smack will try to login
* using the X-OAUTH mechanism.
*
* @param token
* @param resource
* @throws XMPPException
* @throws SmackException
* @throws IOException
* @throws InterruptedException
*/
public synchronized void login(String token, Resourcepart resource)
throws XMPPException, SmackException, IOException, InterruptedException {

StringUtils.requireNotNullOrEmpty(token, "Token must not be null or empty");

throwNotConnectedExceptionIfAppropriate("Did you call connect() before login()?");
throwAlreadyLoggedInExceptionIfAppropriate();
usedToken = token;
usedResource = resource;
loginInternal(token, resource);
}

protected abstract void loginInternal(String username, String password, Resourcepart resource)
throws XMPPException, SmackException, IOException, InterruptedException;

protected abstract void loginInternal(String token, Resourcepart resource)
throws XMPPException, SmackException, IOException, InterruptedException;

@Override
public final boolean isConnected() {
return connected;
Expand Down Expand Up @@ -555,15 +598,34 @@ protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedEx
}
}

/**
* Get the X-OAUTH last refresh token.
*
* @return the X-OAUTH last refresh token
*/
@Override
public String getXOAUTHLastRefreshToken() {
return refreshTokenXOAUTH;
}

/**
* Set the X-OAUTH last refresh token.
*/
@Override
public void setXOAUTHLastRefreshToken(String token) {
refreshTokenXOAUTH = token;
}

@Override
public final boolean isAnonymous() {
return isAuthenticated() && SASLAnonymous.NAME.equals(getUsedSaslMechansism());
}

/**
* Get the name of the SASL mechanism that was used to authenticate this connection. This returns the name of
* mechanism which was used the last time this conneciton was authenticated, and will return <code>null</code> if
* this connection was not authenticated before.
* Get the name of the SASL mechanism that was used to authenticate this
* connection. This returns the name of mechanism which was used the last
* time this connection was authenticated, and will return <code>null</code>
* if this connection was not authenticated before.
*
* @return the name of the used SASL mechanism.
* @since 4.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,6 @@

package org.jivesoftware.smack;

import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;

import javax.security.auth.callback.CallbackHandler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -40,6 +28,18 @@
import java.util.Set;
import java.util.logging.Logger;

import javax.security.auth.callback.CallbackHandler;

import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;

/**
* <p>This class is responsible authenticating the user using SASL, binding the resource
* to the connection and establishing a session with the server.</p>
Expand All @@ -63,6 +63,8 @@ public final class SASLAuthentication {

private static final List<SASLMechanism> REGISTERED_MECHANISMS = new ArrayList<SASLMechanism>();

private static SASLMechanism XOAUTH_REGISTERED_MECHANISM;

private static final Set<String> BLACKLISTED_MECHANISMS = new HashSet<String>();

/**
Expand All @@ -77,6 +79,15 @@ public static void registerSASLMechanism(SASLMechanism mechanism) {
}
}

/**
* Register a new X-OAUTH SASL mechanism.
*
* @param mechanism
*/
public static void registerXOAUTHSASLMechanism(SASLMechanism mechanism) {
XOAUTH_REGISTERED_MECHANISM = mechanism;
}

/**
* Returns the registered SASLMechanism sorted by the level of preference.
*
Expand Down Expand Up @@ -203,13 +214,53 @@ public void authenticate(String username, String password, EntityBareJid authzid
}
}

if (saslException != null){
afterAuthenticate();
}

/**
* Authenticate with a given token.
*
* @param token
* @throws XMPPErrorException
* @throws SASLErrorException
* @throws IOException
* @throws SmackException
* @throws InterruptedException
*/
public void authenticate(String token)
throws XMPPErrorException, SASLErrorException, IOException, SmackException, InterruptedException {
currentMechanism = selectXOAUTHMechanism();

if (currentMechanism == null) {
return;
}

final CallbackHandler callbackHandler = configuration.getCallbackHandler();

synchronized (this) {
currentMechanism.authenticate(token, callbackHandler);

final long deadline = System.currentTimeMillis() + connection.getPacketReplyTimeout();
while (!authenticationSuccessful && saslException == null) {
final long now = System.currentTimeMillis();
if (now > deadline)
break;
// Wait until SASL negotiation finishes
wait(deadline - now);
}
}

afterAuthenticate();
}

private void afterAuthenticate() throws SmackException, SASLErrorException, NoResponseException {
if (saslException != null) {
if (saslException instanceof SmackException) {
throw (SmackException) saslException;
} else if (saslException instanceof SASLErrorException) {
throw (SASLErrorException) saslException;
} else {
throw new IllegalStateException("Unexpected exception type" , saslException);
throw new IllegalStateException("Unexpected exception type", saslException);
}
}

Expand All @@ -219,12 +270,13 @@ public void authenticate(String username, String password, EntityBareJid authzid
}

/**
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
* to <code>false</code>.
* Wrapper for {@link #challengeReceived(String, boolean)}, with
* <code>finalChallenge</code> set to <code>false</code>.
*
* @param challenge a base64 encoded string representing the challenge.
* @param challenge
* a base64 encoded string representing the challenge.
* @throws SmackException
* @throws InterruptedException
* @throws InterruptedException
*/
public void challengeReceived(String challenge) throws SmackException, InterruptedException {
challengeReceived(challenge, false);
Expand Down Expand Up @@ -314,6 +366,24 @@ String getNameOfLastUsedSaslMechansism() {
return lastUsedMech.getName();
}

private SASLMechanism selectXOAUTHMechanism() throws SmackException {
SASLMechanism mechanism = XOAUTH_REGISTERED_MECHANISM;

final List<String> serverMechanisms = getServerMechanisms();
if (serverMechanisms.isEmpty()) {
LOGGER.warning("Server did not report any SASL mechanisms");
}

String mechanismName = mechanism.getName();

if (serverMechanisms.contains(mechanismName)) {
return mechanism.instanceForAuthentication(connection);
} else {
return null;
}

}

private SASLMechanism selectMechanism(EntityBareJid authzid) throws SmackException {
Iterator<SASLMechanism> it = REGISTERED_MECHANISMS.iterator();
final List<String> serverMechanisms = getServerMechanisms();
Expand Down Expand Up @@ -345,6 +415,8 @@ private SASLMechanism selectMechanism(EntityBareJid authzid) throws SmackExcepti
}

synchronized (BLACKLISTED_MECHANISMS) {
List<SASLMechanism> mechanisms = REGISTERED_MECHANISMS;
mechanisms.add(XOAUTH_REGISTERED_MECHANISM);
// @formatter:off
throw new SmackException(
"No supported and enabled SASL Mechanism provided by server. " +
Expand Down
Loading

0 comments on commit 04b2517

Please sign in to comment.