Skip to content

Commit

Permalink
Move SASL logic into AbstractXMPPConnection
Browse files Browse the repository at this point in the history
Besides the way the transport handles the stream after SASL
<success/>, the SASL logic is independend from the underlying
transport (BOSH, TCP, …). Hence move it up into
AbstractXMPPConnection.

This also has the benefit that we can make some more methods private
or package-private.

Also introduce XmlStringBuilder.optTextChild(), which causes some
associated changes.
  • Loading branch information
Flowdalic committed Sep 25, 2019
1 parent c3247ef commit eeb6c52
Show file tree
Hide file tree
Showing 23 changed files with 489 additions and 328 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
Expand All @@ -39,8 +40,6 @@
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.CloseableUtil;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
Expand Down Expand Up @@ -218,7 +217,7 @@ public boolean isUsingCompression() {
protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
SmackException, IOException, InterruptedException {
// Authenticate using SASL
saslAuthentication.authenticate(username, password, config.getAuthzid(), null);
authenticate(username, password, config.getAuthzid(), null);

bindResourceAndEstablishSession(resource);

Expand Down Expand Up @@ -395,6 +394,26 @@ public void run() {
readerConsumer.start();
}

@Override
protected void afterSaslAuthenticationSuccess()
throws NotConnectedException, InterruptedException, SmackWrappedException {
// XMPP over BOSH is unusual when it comes to SASL authentication: Instead of sending a new stream open, it
// requires a special XML element ot be send after successful SASL authentication.
// See XEP-0206 § 5., especially the following is example 5 of XEP-0206.
ComposableBody composeableBody = ComposableBody.builder().setNamespaceDefinition("xmpp",
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
"xmpp"), "true").setAttribute(
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build();

try {
send(composeableBody);
} catch (BOSHException e) {
// jbosh's exception API does not really match the one of Smack.
throw new SmackException.SmackWrappedException(e);
}
}

/**
* A listener class which listen for a successfully established connection
* and connection errors and notifies the BOSHConnection.
Expand Down Expand Up @@ -490,30 +509,9 @@ public void responseReceived(BOSHMessageEvent event) {
case Presence.ELEMENT:
parseAndProcessStanza(parser);
break;
case "challenge":
// The server is challenging the SASL authentication
// made by the client
final String challengeData = parser.nextText();
getSASLAuthentication().challengeReceived(challengeData);
break;
case "success":
send(ComposableBody.builder().setNamespaceDefinition("xmpp",
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
"xmpp"), "true").setAttribute(
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build());
Success success = new Success(parser.nextText());
getSASLAuthentication().authenticated(success);
break;
case "features":
parseFeatures(parser);
break;
case "failure":
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
getSASLAuthentication().authenticationFailed(failure);
}
break;
case "error":
// Some BOSH error isn't stream error.
if ("urn:ietf:params:xml:ns:xmpp-streams".equals(parser.getNamespace(null))) {
Expand All @@ -522,6 +520,9 @@ public void responseReceived(BOSHMessageEvent event) {
StanzaError.Builder builder = PacketParserUtils.parseError(parser);
throw new XMPPException.XMPPErrorException(null, builder.build());
}
default:
parseAndProcessNonza(parser);
break;
}
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.callback.Callback;
Expand All @@ -78,6 +79,7 @@
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
Expand Down Expand Up @@ -113,9 +115,14 @@
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils;
Expand All @@ -127,6 +134,7 @@
import org.jivesoftware.smack.xml.XmlPullParserException;

import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
Expand Down Expand Up @@ -237,7 +245,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {

protected XmlEnvironment outgoingStreamXmlEnvironment;

final Map<QName, NonzaCallback> nonzaCallbacks = new HashMap<>();
final MultiMap<QName, NonzaCallback> nonzaCallbacksMap = new MultiMap<>();

protected final Lock connectionLock = new ReentrantLock();

Expand Down Expand Up @@ -308,7 +316,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
/**
* The SASLAuthentication manager that is responsible for authenticating with the server.
*/
protected final SASLAuthentication saslAuthentication;
private final SASLAuthentication saslAuthentication;

/**
* A number to uniquely identify connections that are created. This is distinct from the
Expand Down Expand Up @@ -402,6 +410,26 @@ public void execute(Runnable runnable) {
protected AbstractXMPPConnection(ConnectionConfiguration configuration) {
saslAuthentication = new SASLAuthentication(this, configuration);
config = configuration;

// Install the SASL Nonza callbacks.
buildNonzaCallback()
.listenFor(SaslNonza.Challenge.class, c -> {
try {
saslAuthentication.challengeReceived(c);
} catch (SmackException | InterruptedException e) {
saslAuthentication.authenticationFailed(e);
}
})
.listenFor(SaslNonza.Success.class, s -> {
try {
saslAuthentication.authenticated(s);
} catch (SmackSaslException | NotConnectedException | InterruptedException e) {
saslAuthentication.authenticationFailed(e);
}
})
.listenFor(SaslNonza.SASLFailure.class, f -> saslAuthentication.authenticationFailed(f))
.install();

SmackDebuggerFactory debuggerFactory = configuration.getDebuggerFactory();
if (debuggerFactory != null) {
debugger = debuggerFactory.create(this);
Expand Down Expand Up @@ -810,14 +838,50 @@ public final void sendStanza(Stanza stanza) throws NotConnectedException, Interr
}

/**
* Returns the SASLAuthentication manager that is responsible for authenticating with
* the server.
* Authenticate a connection.
*
* @param username the username that is authenticating with the server.
* @param password the password to send to the server.
* @param authzid the authorization identifier (typically null).
* @param sslSession the optional SSL/TLS session (if one was established)
* @return the used SASLMechanism.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws SASLErrorException if a SASL protocol error was returned.
* @throws IOException if an I/O error occured.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackSaslException if a SASL specific error occured.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws NoResponseException if there was no response from the remote entity.
* @throws SmackWrappedException in case of an exception.
* @see SASLAuthentication#authenticate(String, String, EntityBareJid, SSLSession)
*/
protected final SASLMechanism authenticate(String username, String password, EntityBareJid authzid,
SSLSession sslSession) throws XMPPErrorException, SASLErrorException, SmackSaslException,
NotConnectedException, NoResponseException, IOException, InterruptedException, SmackWrappedException {
SASLMechanism saslMechanism = saslAuthentication.authenticate(username, password, authzid, sslSession);
afterSaslAuthenticationSuccess();
return saslMechanism;
}

/**
* Hook for subclasses right after successful SASL authentication. RFC 6120 § 6.4.6. specifies a that the initiating
* entity, needs to initiate a new stream in this case. But some transports, like BOSH, requires a special handling.
* <p>
* Note that we can not reset XMPPTCPConnection's parser here, because this method is invoked by the thread calling
* {@link #login()}, but the parser reset has to be done within the reader thread.
* </p>
*
* @return the SASLAuthentication manager that is responsible for authenticating with
* the server.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackWrappedException in case of an exception.
*/
protected SASLAuthentication getSASLAuthentication() {
return saslAuthentication;
protected void afterSaslAuthenticationSuccess()
throws NotConnectedException, InterruptedException, SmackWrappedException {
sendStreamOpen();
}

protected final boolean isSaslAuthenticated() {
return saslAuthentication.authenticationSuccessful();
}

/**
Expand Down Expand Up @@ -1225,28 +1289,36 @@ protected <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza n
}

protected final void parseAndProcessNonza(XmlPullParser parser) throws IOException, XmlPullParserException, SmackParsingException {
ParserUtils.assertAtStartTag(parser);

final int initialDepth = parser.getDepth();
final String element = parser.getName();
final String namespace = parser.getNamespace();
final QName key = new QName(namespace, element);

NonzaProvider<? extends Nonza> nonzaProvider = ProviderManager.getNonzaProvider(key);
if (nonzaProvider == null) {
LOGGER.severe("Unknown nonza: " + key);
ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
return;
}

NonzaCallback nonzaCallback;
synchronized (nonzaCallbacks) {
nonzaCallback = nonzaCallbacks.get(key);
List<NonzaCallback> nonzaCallbacks;
synchronized (nonzaCallbacksMap) {
nonzaCallbacks = nonzaCallbacksMap.getAll(key);
nonzaCallbacks = CollectionUtil.newListWith(nonzaCallbacks);
}
if (nonzaCallback == null) {
if (nonzaCallbacks == null) {
LOGGER.info("No nonza callback for " + key);
ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
return;
}

Nonza nonza = nonzaProvider.parse(parser, incomingStreamXmlEnvironment);

nonzaCallback.onNonzaReceived(nonza);
for (NonzaCallback nonzaCallback : nonzaCallbacks) {
nonzaCallback.onNonzaReceived(nonza);
}
}

protected void parseAndProcessStanza(XmlPullParser parser)
Expand Down

This file was deleted.

0 comments on commit eeb6c52

Please sign in to comment.