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;