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 1bd737f22..3855b0995 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -158,7 +158,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { protected final Map streamFeatures = new HashMap(); /** - * The full JID of the authenticated user. + * The full JID of the authenticated user, as returned by the resource binding response of the server. + *

+ * It is important that we don't infer the user from the login() arguments and the configurations service name, as, + * for example, when SASL External is used, the username is not given to login but taken from the 'external' + * certificate. + *

*/ protected String user; @@ -431,7 +436,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { */ public void login(String username, String password, String resource) throws XMPPException, SmackException, IOException { - if (StringUtils.isNullOrEmpty(username)) { + if (!config.allowNullOrEmptyUsername && StringUtils.isNullOrEmpty(username)) { throw new IllegalArgumentException("Username must not be null or empty"); } usedUsername = username; @@ -482,6 +487,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { Bind bindResource = Bind.newSet(resource); PacketCollector packetCollector = createPacketCollectorAndSend(new PacketIDFilter(bindResource), bindResource); Bind response = packetCollector.nextResultOrThrow(); + // Set the connections user to the result of resource binding. It is important that we don't infer the user + // from the login() arguments and the configurations service name, as, for example, when SASL External is used, + // the username is not given to login but taken from the 'external' certificate. user = response.getJid(); serviceName = XmppStringUtils.parseDomain(user); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java index 6790bc4bc..c0699c819 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java @@ -93,6 +93,8 @@ public abstract class ConnectionConfiguration { // Holds the proxy information (such as proxyhost, proxyport, username, password etc) protected final ProxyInfo proxy; + protected final boolean allowNullOrEmptyUsername; + protected ConnectionConfiguration(Builder builder) { if (builder.username != null) { // Do partial version of nameprep on the username. @@ -136,6 +138,7 @@ public abstract class ConnectionConfiguration { legacySessionDisabled = builder.legacySessionDisabled; rosterStore = builder.rosterStore; debuggerEnabled = builder.debuggerEnabled; + allowNullOrEmptyUsername = builder.allowEmptyOrNullUsername; } /** @@ -405,6 +408,7 @@ public abstract class ConnectionConfiguration { private String serviceName; private String host; private int port = 5222; + private boolean allowEmptyOrNullUsername = false; protected Builder() { } @@ -647,6 +651,19 @@ public abstract class ConnectionConfiguration { return getThis(); } + /** + * Allow null or the empty String as username. + * + * Some SASL mechanisms (e.g. SASL External) may also signal the username (as "authorization identity"), in + * which case Smack should not throw an IllegalArgumentException when the username is not set. + * + * @return a reference to this builder. + */ + public B allowEmptyOrNullUsernames() { + allowEmptyOrNullUsername = true; + return getThis(); + } + public abstract C build(); protected abstract B getThis(); 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 39416a210..735c817f5 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 @@ -91,7 +91,10 @@ public abstract class SASLMechanism implements Comparable { protected XMPPConnection connection; /** - * authcid + * authcid, i.e. the username (localpart) of the XMPP connection trying to authenticated. + *

+ * Not to be confused with the authzid. + *

*/ protected String authenticationId; diff --git a/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLExternalMechanism.java b/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLExternalMechanism.java index 01bdadfe2..1bfd92494 100644 --- a/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLExternalMechanism.java +++ b/smack-sasl-provided/src/main/java/org/jivesoftware/smack/sasl/provided/SASLExternalMechanism.java @@ -20,7 +20,7 @@ import javax.security.auth.callback.CallbackHandler; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.sasl.SASLMechanism; - +import org.jivesoftware.smack.util.StringUtils; import org.jxmpp.util.XmppStringUtils; /** @@ -39,7 +39,7 @@ public class SASLExternalMechanism extends SASLMechanism { @Override protected byte[] getAuthenticationText() throws SmackException { - if (authenticationId == null) { + if (StringUtils.isNullOrEmpty(authenticationId)) { return null; }