From eeb6c52f7e0907fa38a578c153461af5dffa459b Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 24 Sep 2019 23:32:08 +0200 Subject: [PATCH] Move SASL logic into AbstractXMPPConnection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Besides the way the transport handles the stream after SASL , 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. --- .../smack/bosh/XMPPBOSHConnection.java | 49 +++++----- .../smack/AbstractXMPPConnection.java | 98 ++++++++++++++++--- .../smack/GenericElementListener.java | 36 ------- .../org/jivesoftware/smack/NonzaCallback.java | 70 ++++++++----- .../smack/SASLAuthentication.java | 40 ++++---- .../smack/SmackInitialization.java | 6 ++ .../AbstractXmppStateMachineConnection.java | 17 +--- .../smack/provider/SaslChallengeProvider.java | 40 ++++++++ .../smack/provider/SaslFailureProvider.java | 62 ++++++++++++ .../smack/provider/SaslSuccessProvider.java | 40 ++++++++ .../smack/sasl/SASLErrorException.java | 2 +- .../smack/sasl/SASLMechanism.java | 4 +- ...SaslStreamElements.java => SaslNonza.java} | 90 +++++++---------- .../smack/util/CollectionUtil.java | 7 +- .../smack/util/PacketParserUtils.java | 39 -------- .../smack/util/XmlStringBuilder.java | 17 ++-- .../smack/provider/SaslProviderTest.java | 78 +++++++++++++++ .../sasl/core/SCRAMSHA1MechanismTest.java | 4 +- .../smack/util/PacketParserUtilsTest.java | 38 ------- .../hoxt/packet/AbstractHttpOverXmpp.java | 12 +-- .../smackx/mam/QueryArchiveTest.java | 2 +- .../smackx/delay/packet/DelayInformation.java | 4 +- .../smack/tcp/XMPPTCPConnection.java | 64 +++++------- 23 files changed, 490 insertions(+), 329 deletions(-) delete mode 100644 smack-core/src/main/java/org/jivesoftware/smack/GenericElementListener.java create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/provider/SaslChallengeProvider.java create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/provider/SaslFailureProvider.java create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/provider/SaslSuccessProvider.java rename smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/{SaslStreamElements.java => SaslNonza.java} (72%) create mode 100644 smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java index 85465c881..475bd1403 100644 --- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java +++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java @@ -29,6 +29,7 @@ import org.jivesoftware.smack.AbstractXMPPConnection; 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; @@ -39,8 +40,6 @@ import org.jivesoftware.smack.packet.Nonza; 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; @@ -218,7 +217,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { 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); @@ -395,6 +394,26 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { 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. @@ -490,30 +509,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { 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))) { @@ -522,6 +520,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { StanzaError.Builder builder = PacketParserUtils.parseError(parser); throw new XMPPException.XMPPErrorException(null, builder.build()); } + default: + parseAndProcessNonza(parser); + break; } break; default: diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 8e8725a3a..fca97f54c 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -60,6 +60,7 @@ import java.util.logging.Logger; 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; @@ -78,6 +79,7 @@ import org.jivesoftware.smack.SmackException.NotLoggedInException; 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; @@ -113,9 +115,14 @@ import org.jivesoftware.smack.parsing.SmackParsingException; 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; @@ -127,6 +134,7 @@ import org.jivesoftware.smack.xml.XmlPullParser; 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; @@ -237,7 +245,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { protected XmlEnvironment outgoingStreamXmlEnvironment; - final Map nonzaCallbacks = new HashMap<>(); + final MultiMap nonzaCallbacksMap = new MultiMap<>(); protected final Lock connectionLock = new ReentrantLock(); @@ -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 @@ -402,6 +410,26 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { 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); @@ -810,14 +838,50 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { } /** - * Returns the SASLAuthentication manager that is responsible for authenticating with - * the server. + * Authenticate a connection. * - * @return the SASLAuthentication manager that is responsible for authenticating with - * the server. + * @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 SASLAuthentication getSASLAuthentication() { - return saslAuthentication; + 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. + *

+ * 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. + *

+ * + * @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 void afterSaslAuthenticationSuccess() + throws NotConnectedException, InterruptedException, SmackWrappedException { + sendStreamOpen(); + } + + protected final boolean isSaslAuthenticated() { + return saslAuthentication.authenticationSuccessful(); } /** @@ -1225,6 +1289,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { } 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); @@ -1232,21 +1299,26 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { NonzaProvider 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 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) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/GenericElementListener.java b/smack-core/src/main/java/org/jivesoftware/smack/GenericElementListener.java deleted file mode 100644 index 959c21873..000000000 --- a/smack-core/src/main/java/org/jivesoftware/smack/GenericElementListener.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * - * Copyright 2018 Florian Schmaus - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jivesoftware.smack; - -import org.jivesoftware.smack.packet.Element; - -public abstract class GenericElementListener { - - private final Class elementClass; - - public GenericElementListener(Class elementClass) { - this.elementClass = elementClass; - } - - public abstract void process(E element); - - public final void processElement(Element element) { - E concreteEleement = elementClass.cast(element); - process(concreteEleement); - } - -} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/NonzaCallback.java b/smack-core/src/main/java/org/jivesoftware/smack/NonzaCallback.java index 3bdd04bfb..bfc1eddda 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/NonzaCallback.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/NonzaCallback.java @@ -16,6 +16,7 @@ */ package org.jivesoftware.smack; +import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -30,7 +31,7 @@ import org.jivesoftware.smack.util.XmppElementUtil; public class NonzaCallback { protected final AbstractXMPPConnection connection; - protected final Map> filterAndListeners; + protected final Map> filterAndListeners; private NonzaCallback(Builder builder) { this.connection = builder.connection; @@ -38,21 +39,18 @@ public class NonzaCallback { install(); } - void onNonzaReceived(Nonza nonza) { + void onNonzaReceived(Nonza nonza) throws IOException { QName key = nonza.getQName(); - GenericElementListener nonzaListener = filterAndListeners.get(key); + ClassAndConsumer classAndConsumer = filterAndListeners.get(key); - nonzaListener.processElement(nonza); + classAndConsumer.accept(nonza); } public void cancel() { - synchronized (connection.nonzaCallbacks) { - for (Map.Entry> entry : filterAndListeners.entrySet()) { - QName filterKey = entry.getKey(); - NonzaCallback installedCallback = connection.nonzaCallbacks.get(filterKey); - if (equals(installedCallback)) { - connection.nonzaCallbacks.remove(filterKey); - } + for (Map.Entry> entry : filterAndListeners.entrySet()) { + QName filterKey = entry.getKey(); + synchronized (connection.nonzaCallbacksMap) { + connection.nonzaCallbacksMap.removeOne(filterKey, this); } } } @@ -62,9 +60,9 @@ public class NonzaCallback { return; } - synchronized (connection.nonzaCallbacks) { - for (QName key : filterAndListeners.keySet()) { - connection.nonzaCallbacks.put(key, this); + for (QName key : filterAndListeners.keySet()) { + synchronized (connection.nonzaCallbacksMap) { + connection.nonzaCallbacksMap.put(key, this); } } } @@ -74,31 +72,35 @@ public class NonzaCallback { private SN successNonza; private FN failedNonza; - private NonzaResponseCallback(Class successNonzaClass, Class failedNonzaClass, + private NonzaResponseCallback(Class successNonzaClass, Class failedNonzaClass, Builder builder) { super(builder); final QName successNonzaKey = XmppElementUtil.getQNameFor(successNonzaClass); final QName failedNonzaKey = XmppElementUtil.getQNameFor(failedNonzaClass); - final GenericElementListener successListener = new GenericElementListener(successNonzaClass) { + final NonzaListener successListener = new NonzaListener() { @Override - public void process(SN successNonza) { + public void accept(SN successNonza) { NonzaResponseCallback.this.successNonza = successNonza; notifyResponse(); } }; + final ClassAndConsumer successClassAndConsumer = new ClassAndConsumer<>(successNonzaClass, + successListener); - final GenericElementListener failedListener = new GenericElementListener(failedNonzaClass) { + final NonzaListener failedListener = new NonzaListener() { @Override - public void process(FN failedNonza) { + public void accept(FN failedNonza) { NonzaResponseCallback.this.failedNonza = failedNonza; notifyResponse(); } }; + final ClassAndConsumer failedClassAndConsumer = new ClassAndConsumer<>(failedNonzaClass, + failedListener); - filterAndListeners.put(successNonzaKey, successListener); - filterAndListeners.put(failedNonzaKey, failedListener); + filterAndListeners.put(successNonzaKey, successClassAndConsumer); + filterAndListeners.put(failedNonzaKey, failedClassAndConsumer); install(); } @@ -139,15 +141,16 @@ public class NonzaCallback { public static final class Builder { private final AbstractXMPPConnection connection; - private Map> filterAndListeners = new HashMap<>(); + private Map> filterAndListeners = new HashMap<>(); Builder(AbstractXMPPConnection connection) { this.connection = connection; } - public Builder listenFor(Class nonza, GenericElementListener nonzaListener) { + public Builder listenFor(Class nonza, NonzaListener nonzaListener) { QName key = XmppElementUtil.getQNameFor(nonza); - filterAndListeners.put(key, nonzaListener); + ClassAndConsumer classAndConsumer = new ClassAndConsumer<>(nonza, nonzaListener); + filterAndListeners.put(key, classAndConsumer); return this; } @@ -156,6 +159,25 @@ public class NonzaCallback { } } + public interface NonzaListener { + void accept(N nonza) throws IOException; + } + + private static final class ClassAndConsumer { + private final Class clazz; + private final NonzaListener consumer; + + private ClassAndConsumer(Class clazz, NonzaListener consumer) { + this.clazz = clazz; + this.consumer = consumer; + } + + private void accept(Object object) throws IOException { + N nonza = clazz.cast(object); + consumer.accept(nonza); + } + } + static SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class successNonzaClass, Class failedNonzaClass) throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java index e9f087b4d..91018d6d8 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java @@ -39,8 +39,9 @@ import org.jivesoftware.smack.packet.Mechanisms; import org.jivesoftware.smack.sasl.SASLErrorException; import org.jivesoftware.smack.sasl.SASLMechanism; import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success; +import org.jivesoftware.smack.sasl.packet.SaslNonza; +import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure; +import org.jivesoftware.smack.sasl.packet.SaslNonza.Success; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; @@ -180,7 +181,7 @@ public final class SASLAuthentication { * @throws NotConnectedException if the XMPP connection is not connected. * @throws NoResponseException if there was no response from the remote entity. */ - public SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession) + SASLMechanism authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession) throws XMPPErrorException, SASLErrorException, IOException, InterruptedException, SmackSaslException, NotConnectedException, NoResponseException { final SASLMechanism mechanism = selectMechanism(authzid); @@ -216,12 +217,12 @@ public final class SASLAuthentication { * Wrapper for {@link #challengeReceived(String, boolean)}, with finalChallenge set * to false. * - * @param challenge a base64 encoded string representing the challenge. + * @param challenge the challenge Nonza. * @throws SmackException if Smack detected an exceptional situation. * @throws InterruptedException if the calling thread was interrupted. */ - public void challengeReceived(String challenge) throws SmackException, InterruptedException { - challengeReceived(challenge, false); + void challengeReceived(SaslNonza.Challenge challenge) throws SmackException, InterruptedException { + challengeReceived(challenge.getData(), false); } /** @@ -236,15 +237,12 @@ public final class SASLAuthentication { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException { - try { - synchronized (this) { - currentMechanism.challengeReceived(challenge, finalChallenge); - } - } catch (InterruptedException | SmackSaslException | NotConnectedException e) { - authenticationFailed(e); - throw e; + private void challengeReceived(String challenge, boolean finalChallenge) throws SmackSaslException, NotConnectedException, InterruptedException { + SASLMechanism mechanism; + synchronized (this) { + mechanism = currentMechanism; } + mechanism.challengeReceived(challenge, finalChallenge); } /** @@ -253,8 +251,10 @@ public final class SASLAuthentication { * @param success result of the authentication. * @throws SmackException if Smack detected an exceptional situation. * @throws InterruptedException if the calling thread was interrupted. + * @throws NotConnectedException if the XMPP connection is not connected. + * @throws SmackSaslException if a SASL specific error occured. */ - public void authenticated(Success success) throws SmackException, InterruptedException { + void authenticated(Success success) throws InterruptedException, SmackSaslException, NotConnectedException { // RFC6120 6.3.10 "At the end of the authentication exchange, the SASL server (the XMPP // "receiving entity") can include "additional data with success" if appropriate for the // SASL mechanism in use. In XMPP, this is done by including the additional data as the XML @@ -279,11 +279,15 @@ public final class SASLAuthentication { * @param saslFailure the SASL failure as reported by the server * @see RFC6120 6.5 */ - public void authenticationFailed(SASLFailure saslFailure) { - authenticationFailed(new SASLErrorException(currentMechanism.getName(), saslFailure)); + void authenticationFailed(SASLFailure saslFailure) { + SASLErrorException saslErrorException; + synchronized (this) { + saslErrorException = new SASLErrorException(currentMechanism.getName(), saslFailure); + } + authenticationFailed(saslErrorException); } - private void authenticationFailed(Exception exception) { + void authenticationFailed(Exception exception) { // Wake up the thread that is waiting in the #authenticate method synchronized (this) { currentMechanism.setException(exception); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java index 5bcf27b2f..cf3a646a2 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java @@ -37,6 +37,9 @@ import org.jivesoftware.smack.packet.Message.Body; import org.jivesoftware.smack.provider.BindIQProvider; import org.jivesoftware.smack.provider.BodyElementProvider; import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.provider.SaslChallengeProvider; +import org.jivesoftware.smack.provider.SaslFailureProvider; +import org.jivesoftware.smack.provider.SaslSuccessProvider; import org.jivesoftware.smack.provider.TlsFailureProvider; import org.jivesoftware.smack.provider.TlsProceedProvider; import org.jivesoftware.smack.sasl.core.SASLAnonymous; @@ -126,6 +129,9 @@ public final class SmackInitialization { ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider()); ProviderManager.addExtensionProvider(Body.ELEMENT, Body.NAMESPACE, new BodyElementProvider()); + ProviderManager.addNonzaProvider(SaslChallengeProvider.INSTANCE); + ProviderManager.addNonzaProvider(SaslSuccessProvider.INSTANCE); + ProviderManager.addNonzaProvider(SaslFailureProvider.INSTANCE); ProviderManager.addNonzaProvider(TlsProceedProvider.INSTANCE); ProviderManager.addNonzaProvider(TlsFailureProvider.INSTANCE); ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java index beed39d96..2c7168148 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java @@ -53,8 +53,6 @@ import org.jivesoftware.smack.packet.StreamError; import org.jivesoftware.smack.parsing.SmackParsingException; import org.jivesoftware.smack.sasl.SASLErrorException; import org.jivesoftware.smack.sasl.SASLMechanism; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; @@ -335,19 +333,6 @@ public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPCon parseFeatures(parser); afterFeaturesReceived(); break; - // SASL related top level stream elements - case Challenge.ELEMENT: - // The server is challenging the SASL authentication made by the client - String challengeData = parser.nextText(); - getSASLAuthentication().challengeReceived(challengeData); - break; - case Success.ELEMENT: - Success success = new Success(parser.nextText()); - // The SASL authentication with the server was successful. The next step - // will be to bind the resource - getSASLAuthentication().authenticated(success); - sendStreamOpen(); - break; default: parseAndProcessNonza(parser); break; @@ -613,7 +598,7 @@ public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPCon prepareToWaitForFeaturesReceived(); LoginContext loginContext = walkStateGraphContext.loginContext; - SASLMechanism usedSaslMechanism = saslAuthentication.authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession()); + SASLMechanism usedSaslMechanism = authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession()); // authenticate() will only return if the SASL authentication was successful, but we also need to wait for the next round of stream features. waitForFeaturesReceived("server stream features after SASL authentication"); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslChallengeProvider.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslChallengeProvider.java new file mode 100644 index 000000000..138733ac0 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslChallengeProvider.java @@ -0,0 +1,40 @@ +/** + * + * Copyright 2019 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.provider; + +import java.io.IOException; + +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.sasl.packet.SaslNonza; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; + +public final class SaslChallengeProvider extends NonzaProvider { + + public static final SaslChallengeProvider INSTANCE = new SaslChallengeProvider(); + + private SaslChallengeProvider() { + } + + @Override + public SaslNonza.Challenge parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws IOException, XmlPullParserException { + String data = parser.nextText(); + return new SaslNonza.Challenge(data); + } + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslFailureProvider.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslFailureProvider.java new file mode 100644 index 000000000..8b151da29 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslFailureProvider.java @@ -0,0 +1,62 @@ +/** + * + * Copyright 2019 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.provider; + +import java.io.IOException; +import java.util.Map; + +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; + +public final class SaslFailureProvider extends NonzaProvider { + + public static final SaslFailureProvider INSTANCE = new SaslFailureProvider(); + + private SaslFailureProvider() { + } + + @Override + public SASLFailure parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException { + String condition = null; + Map descriptiveTexts = null; + outerloop: while (true) { + XmlPullParser.TagEvent eventType = parser.nextTag(); + switch (eventType) { + case START_ELEMENT: + String name = parser.getName(); + if (name.equals("text")) { + descriptiveTexts = PacketParserUtils.parseDescriptiveTexts(parser, descriptiveTexts); + } + else { + assert condition == null; + condition = parser.getName(); + } + break; + case END_ELEMENT: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + return new SASLFailure(condition, descriptiveTexts); + } + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslSuccessProvider.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslSuccessProvider.java new file mode 100644 index 000000000..b63261158 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/SaslSuccessProvider.java @@ -0,0 +1,40 @@ +/** + * + * Copyright 2019 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.provider; + +import java.io.IOException; + +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.sasl.packet.SaslNonza; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; + +public final class SaslSuccessProvider extends NonzaProvider { + + public static final SaslSuccessProvider INSTANCE = new SaslSuccessProvider(); + + private SaslSuccessProvider() { + } + + @Override + public SaslNonza.Success parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws IOException, XmlPullParserException { + String data = parser.nextText(); + return new SaslNonza.Success(data); + } + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java index 11d9879a3..740aeca8f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLErrorException.java @@ -20,7 +20,7 @@ import java.util.HashMap; import java.util.Map; import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; +import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure; public class SASLErrorException extends XMPPException { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java index 236fce4d1..e2d674bc0 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java @@ -27,8 +27,8 @@ import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.SmackSaslException; import org.jivesoftware.smack.XMPPConnection; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response; +import org.jivesoftware.smack.sasl.packet.SaslNonza.AuthMechanism; +import org.jivesoftware.smack.sasl.packet.SaslNonza.Response; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.stringencoder.Base64; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslStreamElements.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslNonza.java similarity index 72% rename from smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslStreamElements.java rename to smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslNonza.java index f59205dbf..4c1272ed9 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslStreamElements.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/packet/SaslNonza.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014-2018 Florian Schmaus + * Copyright 2014-2019 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,21 +18,30 @@ package org.jivesoftware.smack.sasl.packet; import java.util.Map; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.packet.AbstractError; import org.jivesoftware.smack.packet.Nonza; +import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.sasl.SASLError; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; -public class SaslStreamElements { - public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl"; +public interface SaslNonza extends Nonza { + String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl"; + + @Override + default String getNamespace() { + return NAMESPACE; + } /** * Initiating SASL authentication by select a mechanism. */ - public static class AuthMechanism implements Nonza { + class AuthMechanism implements SaslNonza { public static final String ELEMENT = "auth"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private final String mechanism; private final String authenticationText; @@ -44,11 +53,11 @@ public class SaslStreamElements { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder(); - xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).attribute("mechanism", mechanism).rightAngleBracket(); - xml.optAppend(authenticationText); - xml.closeElement(ELEMENT); + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.attribute("mechanism", mechanism).rightAngleBracket(); + xml.escape(authenticationText); + xml.closeElement(this); return xml; } @@ -60,11 +69,6 @@ public class SaslStreamElements { return authenticationText; } - @Override - public String getNamespace() { - return NAMESPACE; - } - @Override public String getElementName() { return ELEMENT; @@ -74,8 +78,9 @@ public class SaslStreamElements { /** * A SASL challenge stream element. */ - public static class Challenge implements Nonza { + class Challenge implements SaslNonza { public static final String ELEMENT = "challenge"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private final String data; @@ -83,18 +88,15 @@ public class SaslStreamElements { this.data = StringUtils.returnIfNotEmptyTrimmed(data); } - @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder().halfOpenElement(ELEMENT).xmlnsAttribute( - NAMESPACE).rightAngleBracket(); - xml.optAppend(data); - xml.closeElement(ELEMENT); - return xml; + public String getData() { + return data; } @Override - public String getNamespace() { - return NAMESPACE; + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.optTextChild(data, this); + return xml; } @Override @@ -106,8 +108,9 @@ public class SaslStreamElements { /** * A SASL response stream element. */ - public static class Response implements Nonza { + class Response implements SaslNonza { public static final String ELEMENT = "response"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private final String authenticationText; @@ -120,11 +123,9 @@ public class SaslStreamElements { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder(); - xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket(); - xml.optAppend(authenticationText); - xml.closeElement(ELEMENT); + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.optTextChild(authenticationText, this); return xml; } @@ -132,11 +133,6 @@ public class SaslStreamElements { return authenticationText; } - @Override - public String getNamespace() { - return NAMESPACE; - } - @Override public String getElementName() { return ELEMENT; @@ -146,8 +142,9 @@ public class SaslStreamElements { /** * A SASL success stream element. */ - public static class Success implements Nonza { + class Success implements SaslNonza { public static final String ELEMENT = "success"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private final String data; @@ -171,19 +168,12 @@ public class SaslStreamElements { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder(); - xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket(); - xml.optAppend(data); - xml.closeElement(ELEMENT); + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.optTextChild(data, this); return xml; } - @Override - public String getNamespace() { - return NAMESPACE; - } - @Override public String getElementName() { return ELEMENT; @@ -194,8 +184,9 @@ public class SaslStreamElements { * A SASL failure stream element, also called "SASL Error". * @see RFC 6120 6.5 SASL Errors */ - public static class SASLFailure extends AbstractError implements Nonza { + class SASLFailure extends AbstractError implements SaslNonza { public static final String ELEMENT = "failure"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private final SASLError saslError; private final String saslErrorString; @@ -250,11 +241,6 @@ public class SaslStreamElements { return toXML().toString(); } - @Override - public String getNamespace() { - return NAMESPACE; - } - @Override public String getElementName() { return ELEMENT; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java index a5838083a..4170ce70d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java @@ -51,8 +51,9 @@ public class CollectionUtil { } public static ArrayList newListWith(Collection collection) { - ArrayList arrayList = new ArrayList<>(collection.size()); - arrayList.addAll(collection); - return arrayList; + if (collection == null) { + return null; + } + return new ArrayList<>(collection); } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java index a71361af1..665e90564 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java @@ -49,7 +49,6 @@ import org.jivesoftware.smack.parsing.StandardExtensionElementProvider; import org.jivesoftware.smack.provider.ExtensionElementProvider; import org.jivesoftware.smack.provider.IQProvider; import org.jivesoftware.smack.provider.ProviderManager; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; import org.jivesoftware.smack.xml.SmackXmlParser; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; @@ -686,44 +685,6 @@ public class PacketParserUtils { return descriptiveTexts; } - /** - * Parses SASL authentication error packets. - * - * @param parser the XML parser. - * @return a SASL Failure packet. - * @throws IOException if an I/O error occured. - * @throws XmlPullParserException if an error in the XML parser occured. - */ - public static SASLFailure parseSASLFailure(XmlPullParser parser) throws XmlPullParserException, IOException { - final int initialDepth = parser.getDepth(); - String condition = null; - Map descriptiveTexts = null; - outerloop: while (true) { - XmlPullParser.Event eventType = parser.next(); - switch (eventType) { - case START_ELEMENT: - String name = parser.getName(); - if (name.equals("text")) { - descriptiveTexts = parseDescriptiveTexts(parser, descriptiveTexts); - } - else { - assert condition == null; - condition = parser.getName(); - } - break; - case END_ELEMENT: - if (parser.getDepth() == initialDepth) { - break outerloop; - } - break; - default: - // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement. - break; - } - } - return new SASLFailure(condition, descriptiveTexts); - } - public static StreamError parseStreamError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException { return parseStreamError(parser, null); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index 28890b368..436f9e5ad 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -497,13 +497,6 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { return this; } - public XmlStringBuilder optAppend(CharSequence csq) { - if (csq != null) { - append(csq); - } - return this; - } - public XmlStringBuilder optAppend(Element element) { if (element != null) { append(element.toXML(effectiveXmlEnvironment)); @@ -511,6 +504,16 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { return this; } + public XmlStringBuilder optTextChild(CharSequence sqc, NamedElement parentElement) { + if (sqc == null) { + return closeEmptyElement(); + } + rightAngleBracket(); + escape(sqc); + closeElement(parentElement); + return this; + } + public XmlStringBuilder append(XmlStringBuilder xsb) { assert xsb != null; sb.append(xsb.sb); diff --git a/smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java b/smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java new file mode 100644 index 000000000..9252625ed --- /dev/null +++ b/smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java @@ -0,0 +1,78 @@ +/** + * + * Copyright (C) 2007 Jive Software, 2019 Florian Schmaus. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.provider; + +import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; + +import java.io.IOException; + +import javax.xml.parsers.FactoryConfigurationError; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.jivesoftware.smack.packet.StreamOpen; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.sasl.SASLError; +import org.jivesoftware.smack.sasl.packet.SaslNonza; +import org.jivesoftware.smack.sasl.packet.SaslNonza.SASLFailure; +import org.jivesoftware.smack.test.util.SmackTestUtil; +import org.jivesoftware.smack.xml.XmlPullParserException; + +import com.jamesmurty.utils.XMLBuilder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class SaslProviderTest { + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void parseSASLFailureSimple(SmackTestUtil.XmlPullParserKind parserKind) + throws TransformerException, ParserConfigurationException, FactoryConfigurationError, + XmlPullParserException, IOException, SmackParsingException { + // @formatter:off + final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslNonza.NAMESPACE) + .e(SASLError.account_disabled.toString()) + .asString(); + // @formatter:on + SASLFailure saslFailure = SmackTestUtil.parse(saslFailureString, SaslFailureProvider.class, parserKind); + assertXmlSimilar(saslFailureString, saslFailure.toString()); + } + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void parseSASLFailureExtended(SmackTestUtil.XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException, TransformerException, + ParserConfigurationException, FactoryConfigurationError { + // @formatter:off + final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslNonza.NAMESPACE) + .e(SASLError.account_disabled.toString()) + .up() + .e("text").a("xml:lang", "en") + .t("Call 212-555-1212 for assistance.") + .up() + .e("text").a("xml:lang", "de") + .t("Bitte wenden sie sich an (04321) 123-4444") + .up() + .e("text") + .t("Wusel dusel") + .asString(); + // @formatter:on + SASLFailure saslFailure = SmackTestUtil.parse(saslFailureString, SaslFailureProvider.class, parserKind); + assertXmlSimilar(saslFailureString, saslFailure.toXML(StreamOpen.CLIENT_NAMESPACE)); + } + +} diff --git a/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java b/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java index 32af99e7e..490a855d6 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java @@ -21,8 +21,8 @@ import static org.junit.Assert.assertEquals; import org.jivesoftware.smack.DummyConnection; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response; +import org.jivesoftware.smack.sasl.packet.SaslNonza.AuthMechanism; +import org.jivesoftware.smack.sasl.packet.SaslNonza.Response; import org.jivesoftware.smack.test.util.SmackTestSuite; import org.jivesoftware.smack.util.stringencoder.Base64; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java b/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java index 76f0d996a..90dd82b9d 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java @@ -39,9 +39,6 @@ import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.packet.StreamOpen; -import org.jivesoftware.smack.sasl.SASLError; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; import org.jivesoftware.smack.test.util.SmackTestUtil; import org.jivesoftware.smack.test.util.TestUtils; import org.jivesoftware.smack.xml.XmlPullParser; @@ -814,41 +811,6 @@ public class PacketParserUtilsTest { assertXmlSimilar(stanza, result.toString()); } - @Test - public void parseSASLFailureSimple() throws FactoryConfigurationError, SAXException, IOException, - TransformerException, ParserConfigurationException, XmlPullParserException { - // @formatter:off - final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslStreamElements.NAMESPACE) - .e(SASLError.account_disabled.toString()) - .asString(); - // @formatter:on - XmlPullParser parser = TestUtils.getParser(saslFailureString, SASLFailure.ELEMENT); - SASLFailure saslFailure = PacketParserUtils.parseSASLFailure(parser); - assertXmlSimilar(saslFailureString, saslFailure.toString()); - } - - @Test - public void parseSASLFailureExtended() throws FactoryConfigurationError, TransformerException, - ParserConfigurationException, XmlPullParserException, IOException, SAXException { - // @formatter:off - final String saslFailureString = XMLBuilder.create(SASLFailure.ELEMENT, SaslStreamElements.NAMESPACE) - .e(SASLError.account_disabled.toString()) - .up() - .e("text").a("xml:lang", "en") - .t("Call 212-555-1212 for assistance.") - .up() - .e("text").a("xml:lang", "de") - .t("Bitte wenden sie sich an (04321) 123-4444") - .up() - .e("text") - .t("Wusel dusel") - .asString(); - // @formatter:on - XmlPullParser parser = TestUtils.getParser(saslFailureString, SASLFailure.ELEMENT); - SASLFailure saslFailure = PacketParserUtils.parseSASLFailure(parser); - assertXmlSimilar(saslFailureString, saslFailure.toXML(StreamOpen.CLIENT_NAMESPACE)); - } - @SuppressWarnings("ReferenceEquality") private static String determineNonDefaultLanguage() { String otherLanguage = "jp"; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java index 735807f4b..08d273678 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java @@ -225,9 +225,7 @@ public abstract class AbstractHttpOverXmpp extends IQ { @Override public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); - xml.rightAngleBracket(); - xml.optAppend(text); - xml.closeElement(this); + xml.optTextChild(text, this); return xml; } @@ -269,9 +267,7 @@ public abstract class AbstractHttpOverXmpp extends IQ { @Override public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); - xml.rightAngleBracket(); - xml.optAppend(text); - xml.closeElement(this); + xml.optTextChild(text, this); return xml; } @@ -313,9 +309,7 @@ public abstract class AbstractHttpOverXmpp extends IQ { @Override public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); - xml.rightAngleBracket(); - xml.optAppend(text); - xml.closeElement(this); + xml.optTextChild(text, this); return xml; } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java index 44990f420..f523a8c3b 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java @@ -46,7 +46,7 @@ public class QueryArchiveTest extends MamTest { private static final String mamQueryResultExample = "" + "" + "" - + "" + "" + "" + "Thrice the brinded cat hath mew." + "" + "" + "" + ""; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInformation.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInformation.java index 24c85d1d8..d022900ee 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInformation.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInformation.java @@ -106,9 +106,7 @@ public class DelayInformation implements ExtensionElement { XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); xml.attribute("stamp", XmppDateTime.formatXEP0082Date(stamp)); xml.optAttribute("from", from); - xml.rightAngleBracket(); - xml.optAppend(reason); - xml.closeElement(this); + xml.optTextChild(reason, this); return xml; } diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index ed250eb65..a81d39fef 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -88,10 +88,7 @@ import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StartTls; import org.jivesoftware.smack.packet.StreamError; import org.jivesoftware.smack.proxy.ProxyInfo; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; -import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success; +import org.jivesoftware.smack.sasl.packet.SaslNonza; import org.jivesoftware.smack.sm.SMUtils; import org.jivesoftware.smack.sm.StreamManagementException; import org.jivesoftware.smack.sm.StreamManagementException.StreamIdDoesNotMatchException; @@ -301,6 +298,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } } }); + + // Re-init the reader and writer in case of SASL . This is done to reset the parser since a new stream + // is initiated. + buildNonzaCallback().listenFor(SaslNonza.Success.class, s -> resetParser()).install(); } /** @@ -370,7 +371,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { SmackException, IOException, InterruptedException { // Authenticate using SASL SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null; - saslAuthentication.authenticate(username, password, config.getAuthzid(), sslSession); + authenticate(username, password, config.getAuthzid(), sslSession); // Wait for stream features after the authentication. // TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be @@ -856,7 +857,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { tlsHandled.reportSuccess(); } - if (getSASLAuthentication().authenticationSuccessful()) { + if (isSaslAuthenticated()) { // If we have received features after the SASL has been successfully completed, then we // have also *maybe* received, as it is an optional feature, the compression feature // from the server. @@ -864,18 +865,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } } - /** - * Resets the parser using the latest connection's reader. Resetting the parser is necessary - * when the plain connection has been secured or when a new opening stream element is going - * to be sent by the server. - * - * @throws SmackException if the parser could not be reset. - * @throws InterruptedException if the calling thread was interrupted. - * @throws XmlPullParserException if an error in the XML parser occured. - */ - void openStream() throws SmackException, InterruptedException, XmlPullParserException { + private void resetParser() throws IOException { + try { + packetReader.parser = SmackXmlParser.newXmlParser(reader); + } catch (XmlPullParserException e) { + throw new IOException(e); + } + } + + private void openStreamAndResetParser() throws IOException, NotConnectedException, InterruptedException { sendStreamOpen(); - packetReader.parser = SmackXmlParser.newXmlParser(reader); + resetParser(); } protected class PacketReader { @@ -920,7 +920,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { private void parsePackets() { boolean initialStreamOpenSend = false; try { - openStream(); + openStreamAndResetParser(); initialStreamOpenSend = true; XmlPullParser.Event eventType = parser.getEventType(); while (!done) { @@ -956,7 +956,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // Secure the connection by negotiating TLS proceedTLSReceived(); // Send a new opening stream to the server - openStream(); + openStreamAndResetParser(); } catch (Exception e) { SmackException.SmackWrappedException smackException = new SmackException.SmackWrappedException(e); @@ -979,35 +979,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { compressSyncPoint.reportFailure(new SmackException.SmackMessageException( "Could not establish compression")); break; - case SaslStreamElements.NAMESPACE: - // SASL authentication has failed. The server may close the connection - // depending on the number of retries - final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser); - getSASLAuthentication().authenticationFailed(failure); - break; + default: + parseAndProcessNonza(parser); } break; - case Challenge.ELEMENT: - // The server is challenging the SASL authentication made by the client - String challengeData = parser.nextText(); - getSASLAuthentication().challengeReceived(challengeData); - break; - case Success.ELEMENT: - Success success = new Success(parser.nextText()); - // We now need to bind a resource for the connection - // Open a new stream and wait for the response - openStream(); - // The SASL authentication with the server was successful. The next step - // will be to bind the resource - getSASLAuthentication().authenticated(success); - break; case Compressed.ELEMENT: // Server confirmed that it's possible to use stream compression. Start // stream compression // Initialize the reader and writer with the new compressed version initReaderAndWriter(); // Send a new opening stream to the server - openStream(); + openStreamAndResetParser(); // Notify that compression is being used compressSyncPoint.reportSuccess(); break; @@ -1089,7 +1071,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } break; default: - LOGGER.warning("Unknown top level stream element: " + name); + parseAndProcessNonza(parser); break; } break;