Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token-based reconnection implementation #85

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
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](tokenreconnection.md) | [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
50 changes: 50 additions & 0 deletions documentation/extensions/tokenreconnection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Token-based reconnection
========================

Allows to manage communications blocking.

* Login with token
* Get tokens
* Get last refresh token from "success" nonza
* Avoid reconnection using token


**XEP related:** [XEP-xxxx](http://www.xmpp.org/extensions/inbox/token-reconnection.html)


Login with token
----------------

```
xmppConnection.login(token, resourcepart);
```
*token* is a `String`

*resourcepart* is a `Resourcepart`


Get tokens
----------

```
TBRManager tbrManager = TBRManager.getInstanceFor(xmppConnection);
TBRTokens tbrTokens = tbrManager.getTokens();
String accessToken = tbrTokens.getAccessToken();
String refreshToken = tbrTokens.getRefreshToken();
```


Get last refresh token from "success" nonza
-------------------------------------------

```
String refreshToken = xmppConnection.getXOAUTHLastRefreshToken();
```


Avoid reconnection using token
------------------------------

```
xmppConnection.avoidTokenReconnection();
```
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,31 @@
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.packet.XMPPError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
Expand All @@ -46,17 +58,6 @@
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 @@ -222,6 +223,20 @@ protected void loginInternal(String username, String password, Resourcepart reso
// Authenticate using SASL
saslAuthentication.authenticate(username, password, config.getAuthzid(), null);

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 @@ -59,15 +59,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 @@ -291,6 +291,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 @@ -409,13 +414,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 @@ -445,7 +457,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 @@ -491,9 +508,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 @@ -580,15 +623,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
Loading