mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 12:02:05 +01:00
Rework and improve authentication API and internals
Add - performSaslAnonymousAuthentication() - performSaslExternalAuthentication(SSLContext) - addEnabledSaslMechanism(String) - addEnabledSaslMechanisms(Collection<String>) to ConnectionConfiguration.Builder. Instead of providing a special API call for anonymous authentication, Smack now has a configuration builder method to set anonymous/external authentication. This also removes a lot of duplicate code within Smack. Also move SASLAnonymous into o.j.smack.sasl.core. Fixes SMACK-629.
This commit is contained in:
parent
51700400bc
commit
9354e4fb74
10 changed files with 240 additions and 214 deletions
|
@ -210,42 +210,16 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void loginNonAnonymously(String username, String password, String resource)
|
protected void loginInternal(String username, String password, String resource) throws XMPPException,
|
||||||
throws XMPPException, SmackException, IOException, InterruptedException {
|
SmackException, IOException, InterruptedException {
|
||||||
if (saslAuthentication.hasNonAnonymousAuthentication()) {
|
|
||||||
// Authenticate using SASL
|
// Authenticate using SASL
|
||||||
if (password != null) {
|
saslAuthentication.authenticate(username, password);
|
||||||
saslAuthentication.authenticate(username, password, resource);
|
|
||||||
} else {
|
|
||||||
saslAuthentication.authenticate(resource, config.getCallbackHandler());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new SmackException("No non-anonymous SASL authentication mechanism available");
|
|
||||||
}
|
|
||||||
|
|
||||||
bindResourceAndEstablishSession(resource);
|
bindResourceAndEstablishSession(resource);
|
||||||
|
|
||||||
afterSuccessfulLogin(false);
|
afterSuccessfulLogin(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void loginAnonymously() throws XMPPException, SmackException, IOException, InterruptedException {
|
|
||||||
// Wait with SASL auth until the SASL mechanisms have been received
|
|
||||||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
|
||||||
|
|
||||||
if (saslAuthentication.hasAnonymousAuthentication()) {
|
|
||||||
saslAuthentication.authenticateAnonymously();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Authenticate using Non-SASL
|
|
||||||
throw new SmackException("No anonymous SASL authentication mechanism available");
|
|
||||||
}
|
|
||||||
|
|
||||||
bindResourceAndEstablishSession(null);
|
|
||||||
|
|
||||||
afterSuccessfulLogin(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(PlainStreamElement element) throws NotConnectedException {
|
public void send(PlainStreamElement element) throws NotConnectedException {
|
||||||
if (done) {
|
if (done) {
|
||||||
|
|
|
@ -204,7 +204,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
/**
|
/**
|
||||||
* The SASLAuthentication manager that is responsible for authenticating with the server.
|
* The SASLAuthentication manager that is responsible for authenticating with the server.
|
||||||
*/
|
*/
|
||||||
protected SASLAuthentication saslAuthentication = new SASLAuthentication(this);
|
protected final SASLAuthentication saslAuthentication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A number to uniquely identify connections that are created. This is distinct from the
|
* A number to uniquely identify connections that are created. This is distinct from the
|
||||||
|
@ -291,6 +291,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
* @param configuration The configuration which is used to establish the connection.
|
* @param configuration The configuration which is used to establish the connection.
|
||||||
*/
|
*/
|
||||||
protected AbstractXMPPConnection(ConnectionConfiguration configuration) {
|
protected AbstractXMPPConnection(ConnectionConfiguration configuration) {
|
||||||
|
saslAuthentication = new SASLAuthentication(this, configuration);
|
||||||
config = configuration;
|
config = configuration;
|
||||||
// Notify listeners that a new connection has been established
|
// Notify listeners that a new connection has been established
|
||||||
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
|
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
|
||||||
|
@ -397,11 +398,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public synchronized void login() throws XMPPException, SmackException, IOException, InterruptedException {
|
public synchronized void login() throws XMPPException, SmackException, IOException, InterruptedException {
|
||||||
if (isAnonymous()) {
|
|
||||||
throwNotConnectedExceptionIfAppropriate();
|
|
||||||
throwAlreadyLoggedInExceptionIfAppropriate();
|
|
||||||
loginAnonymously();
|
|
||||||
} else {
|
|
||||||
// The previously used username, password and resource take over precedence over the
|
// The previously used username, password and resource take over precedence over the
|
||||||
// ones from the connection configuration
|
// ones from the connection configuration
|
||||||
CharSequence username = usedUsername != null ? usedUsername : config.getUsername();
|
CharSequence username = usedUsername != null ? usedUsername : config.getUsername();
|
||||||
|
@ -409,7 +405,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
String resource = usedResource != null ? usedResource : config.getResource();
|
String resource = usedResource != null ? usedResource : config.getResource();
|
||||||
login(username, password, resource);
|
login(username, password, resource);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as {@link #login(CharSequence, String, String)}, but takes the resource from the connection
|
* Same as {@link #login(CharSequence, String, String)}, but takes the resource from the connection
|
||||||
|
@ -451,14 +446,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
usedUsername = username != null ? username.toString() : null;
|
usedUsername = username != null ? username.toString() : null;
|
||||||
usedPassword = password;
|
usedPassword = password;
|
||||||
usedResource = resource;
|
usedResource = resource;
|
||||||
loginNonAnonymously(usedUsername, usedPassword, usedResource);
|
loginInternal(usedUsername, usedPassword, usedResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void loginNonAnonymously(String username, String password, String resource)
|
protected abstract void loginInternal(String username, String password, String resource)
|
||||||
throws XMPPException, SmackException, IOException, InterruptedException;
|
throws XMPPException, SmackException, IOException, InterruptedException;
|
||||||
|
|
||||||
protected abstract void loginAnonymously() throws XMPPException, SmackException, IOException, InterruptedException;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isConnected() {
|
public final boolean isConnected() {
|
||||||
return connected;
|
return connected;
|
||||||
|
|
|
@ -17,8 +17,19 @@
|
||||||
|
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Session;
|
import org.jivesoftware.smack.packet.Session;
|
||||||
import org.jivesoftware.smack.proxy.ProxyInfo;
|
import org.jivesoftware.smack.proxy.ProxyInfo;
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
|
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
|
||||||
|
import org.jivesoftware.smack.util.CollectionUtil;
|
||||||
|
import org.jivesoftware.smack.util.Objects;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
@ -93,6 +104,8 @@ public abstract class ConnectionConfiguration {
|
||||||
|
|
||||||
protected final boolean allowNullOrEmptyUsername;
|
protected final boolean allowNullOrEmptyUsername;
|
||||||
|
|
||||||
|
private final Set<String> enabledSaslMechanisms;
|
||||||
|
|
||||||
protected ConnectionConfiguration(Builder<?,?> builder) {
|
protected ConnectionConfiguration(Builder<?,?> builder) {
|
||||||
username = builder.username;
|
username = builder.username;
|
||||||
password = builder.password;
|
password = builder.password;
|
||||||
|
@ -130,6 +143,10 @@ public abstract class ConnectionConfiguration {
|
||||||
legacySessionDisabled = builder.legacySessionDisabled;
|
legacySessionDisabled = builder.legacySessionDisabled;
|
||||||
debuggerEnabled = builder.debuggerEnabled;
|
debuggerEnabled = builder.debuggerEnabled;
|
||||||
allowNullOrEmptyUsername = builder.allowEmptyOrNullUsername;
|
allowNullOrEmptyUsername = builder.allowEmptyOrNullUsername;
|
||||||
|
enabledSaslMechanisms = builder.enabledSaslMechanisms;
|
||||||
|
|
||||||
|
// If the enabledSaslmechanisms are set, then they must not be empty
|
||||||
|
assert(enabledSaslMechanisms != null ? !enabledSaslMechanisms.isEmpty() : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -350,6 +367,24 @@ public abstract class ConnectionConfiguration {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given SASL mechansism is enabled in this connection configuration.
|
||||||
|
*
|
||||||
|
* @param saslMechanism
|
||||||
|
* @return true if the given SASL mechanism is enabled, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isEnabledSaslMechanism(String saslMechanism) {
|
||||||
|
// If enabledSaslMechanisms is not set, then all mechanisms are enabled per default
|
||||||
|
if (enabledSaslMechanisms == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return enabledSaslMechanisms.contains(saslMechanism);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getEnabledSaslMechanisms() {
|
||||||
|
return Collections.unmodifiableSet(enabledSaslMechanisms);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder for XMPP connection configurations.
|
* A builder for XMPP connection configurations.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -387,6 +422,8 @@ public abstract class ConnectionConfiguration {
|
||||||
private String host;
|
private String host;
|
||||||
private int port = 5222;
|
private int port = 5222;
|
||||||
private boolean allowEmptyOrNullUsername = false;
|
private boolean allowEmptyOrNullUsername = false;
|
||||||
|
private boolean saslMechanismsSealed;
|
||||||
|
private Set<String> enabledSaslMechanisms;
|
||||||
|
|
||||||
protected Builder() {
|
protected Builder() {
|
||||||
}
|
}
|
||||||
|
@ -518,7 +555,7 @@ public abstract class ConnectionConfiguration {
|
||||||
* @return a reference to this builder.
|
* @return a reference to this builder.
|
||||||
*/
|
*/
|
||||||
public B setCustomSSLContext(SSLContext context) {
|
public B setCustomSSLContext(SSLContext context) {
|
||||||
this.customSSLContext = context;
|
this.customSSLContext = Objects.requireNonNull(context, "The SSLContext must not be null");
|
||||||
return getThis();
|
return getThis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,6 +665,92 @@ public abstract class ConnectionConfiguration {
|
||||||
return getThis();
|
return getThis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform anonymous authentication using SASL ANONYMOUS. Your XMPP service must support this authentication
|
||||||
|
* mechanism. This method also calls {@link #addEnabledSaslMechanism(String)} with "ANONYMOUS" as argument.
|
||||||
|
*
|
||||||
|
* @return a reference to this builder.
|
||||||
|
*/
|
||||||
|
public B performSaslAnonymousAuthentication() {
|
||||||
|
if (!SASLAuthentication.isSaslMechanismRegistered(SASLAnonymous.NAME)) {
|
||||||
|
throw new IllegalArgumentException("SASL " + SASLAnonymous.NAME + " is not registered");
|
||||||
|
}
|
||||||
|
throwIfEnabledSaslMechanismsSet();
|
||||||
|
|
||||||
|
allowEmptyOrNullUsernames();
|
||||||
|
addEnabledSaslMechanism(SASLAnonymous.NAME);
|
||||||
|
saslMechanismsSealed = true;
|
||||||
|
return getThis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform authentication using SASL EXTERNAL. Your XMPP service must support this
|
||||||
|
* authentication mechanism. This method also calls {@link #addEnabledSaslMechanism(String)} with "EXTERNAL" as
|
||||||
|
* argument. It also calls {@link #allowEmptyOrNullUsernames()} and {@link #setSecurityMode(SecurityMode)} to
|
||||||
|
* {@link SecurityMode#required}.
|
||||||
|
*
|
||||||
|
* @return a reference to this builder.
|
||||||
|
*/
|
||||||
|
public B performSaslExternalAuthentication(SSLContext sslContext) {
|
||||||
|
if (!SASLAuthentication.isSaslMechanismRegistered(SASLMechanism.EXTERNAL)) {
|
||||||
|
throw new IllegalArgumentException("SASL " + SASLMechanism.EXTERNAL + " is not registered");
|
||||||
|
}
|
||||||
|
setCustomSSLContext(sslContext);
|
||||||
|
throwIfEnabledSaslMechanismsSet();
|
||||||
|
|
||||||
|
allowEmptyOrNullUsernames();
|
||||||
|
setSecurityMode(SecurityMode.required);
|
||||||
|
addEnabledSaslMechanism(SASLMechanism.EXTERNAL);
|
||||||
|
saslMechanismsSealed = true;
|
||||||
|
return getThis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwIfEnabledSaslMechanismsSet() {
|
||||||
|
if (enabledSaslMechanisms != null) {
|
||||||
|
throw new IllegalStateException("Enabled SASL mechanisms found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the given mechanism to the enabled ones. See {@link #addEnabledSaslMechanism(Collection)} for a discussion about enabled SASL mechanisms.
|
||||||
|
*
|
||||||
|
* @param saslMechanism the name of the mechanism to enable.
|
||||||
|
* @return a reference to this builder.
|
||||||
|
*/
|
||||||
|
public B addEnabledSaslMechanism(String saslMechanism) {
|
||||||
|
return addEnabledSaslMechanism(Arrays.asList(StringUtils.requireNotNullOrEmpty(saslMechanism,
|
||||||
|
"saslMechanism must not be null or empty")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the given SASL mechanisms. If you never add a mechanism to the set of enabled ones, <b>all mechanisms
|
||||||
|
* known to Smack</b> will be enabled. Only explicitly enable particular SASL mechanisms if you want to limit
|
||||||
|
* the used mechanisms to the enabled ones.
|
||||||
|
*
|
||||||
|
* @param saslMechanisms a collection of names of mechanisms to enable.
|
||||||
|
* @return a reference to this builder.
|
||||||
|
*/
|
||||||
|
public B addEnabledSaslMechanism(Collection<String> saslMechanisms) {
|
||||||
|
if (saslMechanismsSealed) {
|
||||||
|
throw new IllegalStateException("The enabled SASL mechanisms are sealed, you can not add new ones");
|
||||||
|
}
|
||||||
|
CollectionUtil.requireNotEmpty(saslMechanisms, "saslMechanisms");
|
||||||
|
Set<String> blacklistedMechanisms = SASLAuthentication.getBlacklistedSASLMechanisms();
|
||||||
|
for (String mechanism : saslMechanisms) {
|
||||||
|
if (!SASLAuthentication.isSaslMechanismRegistered(mechanism)) {
|
||||||
|
throw new IllegalArgumentException("SASL " + mechanism + " is not avaiable. Consider registering it with Smack");
|
||||||
|
}
|
||||||
|
if (blacklistedMechanisms.contains(mechanism)) {
|
||||||
|
throw new IllegalArgumentException("SALS " + mechanism + " is blacklisted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enabledSaslMechanisms == null) {
|
||||||
|
enabledSaslMechanisms = new HashSet<>(saslMechanisms.size());
|
||||||
|
}
|
||||||
|
enabledSaslMechanisms.addAll(saslMechanisms);
|
||||||
|
return getThis();
|
||||||
|
}
|
||||||
|
|
||||||
public abstract C build();
|
public abstract C build();
|
||||||
|
|
||||||
protected abstract B getThis();
|
protected abstract B getThis();
|
||||||
|
|
|
@ -20,11 +20,11 @@ package org.jivesoftware.smack;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.packet.Mechanisms;
|
import org.jivesoftware.smack.packet.Mechanisms;
|
||||||
import org.jivesoftware.smack.sasl.SASLAnonymous;
|
|
||||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
|
||||||
|
@ -91,6 +91,17 @@ public class SASLAuthentication {
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isSaslMechanismRegistered(String saslMechanism) {
|
||||||
|
synchronized (REGISTERED_MECHANISMS) {
|
||||||
|
for (SASLMechanism mechanism : REGISTERED_MECHANISMS) {
|
||||||
|
if (mechanism.getName().equals(saslMechanism)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister a SASLMechanism by it's full class name. For example
|
* Unregister a SASLMechanism by it's full class name. For example
|
||||||
* "org.jivesoftware.smack.sasl.javax.SASLCramMD5Mechanism".
|
* "org.jivesoftware.smack.sasl.javax.SASLCramMD5Mechanism".
|
||||||
|
@ -131,6 +142,7 @@ public class SASLAuthentication {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AbstractXMPPConnection connection;
|
private final AbstractXMPPConnection connection;
|
||||||
|
private final ConnectionConfiguration configuration;
|
||||||
private SASLMechanism currentMechanism = null;
|
private SASLMechanism currentMechanism = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,68 +155,12 @@ public class SASLAuthentication {
|
||||||
*/
|
*/
|
||||||
private Exception saslException;
|
private Exception saslException;
|
||||||
|
|
||||||
SASLAuthentication(AbstractXMPPConnection connection) {
|
SASLAuthentication(AbstractXMPPConnection connection, ConnectionConfiguration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the server offered ANONYMOUS SASL as a way to authenticate users.
|
|
||||||
*
|
|
||||||
* @return true if the server offered ANONYMOUS SASL as a way to authenticate users.
|
|
||||||
*/
|
|
||||||
public boolean hasAnonymousAuthentication() {
|
|
||||||
return serverMechanisms().contains("ANONYMOUS");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the server offered SASL authentication besides ANONYMOUS SASL.
|
|
||||||
*
|
|
||||||
* @return true if the server offered SASL authentication besides ANONYMOUS SASL.
|
|
||||||
*/
|
|
||||||
public boolean hasNonAnonymousAuthentication() {
|
|
||||||
return !serverMechanisms().isEmpty() && (serverMechanisms().size() != 1 || !hasAnonymousAuthentication());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs SASL authentication of the specified user. If SASL authentication was successful
|
|
||||||
* then resource binding and session establishment will be performed. This method will return
|
|
||||||
* the full JID provided by the server while binding a resource to the connection.<p>
|
|
||||||
*
|
|
||||||
* The server may assign a full JID with a username or resource different than the requested
|
|
||||||
* by this method.
|
|
||||||
*
|
|
||||||
* @param resource the desired resource.
|
|
||||||
* @param cbh the CallbackHandler used to get information from the user
|
|
||||||
* @throws IOException
|
|
||||||
* @throws XMPPErrorException
|
|
||||||
* @throws SASLErrorException
|
|
||||||
* @throws SmackException
|
|
||||||
* @throws InterruptedException
|
|
||||||
*/
|
|
||||||
public void authenticate(String resource, CallbackHandler cbh) throws IOException,
|
|
||||||
XMPPErrorException, SASLErrorException, SmackException, InterruptedException {
|
|
||||||
SASLMechanism selectedMechanism = selectMechanism();
|
|
||||||
if (selectedMechanism != null) {
|
|
||||||
currentMechanism = selectedMechanism;
|
|
||||||
synchronized (this) {
|
|
||||||
currentMechanism.authenticate(connection.getHost(), connection.getServiceName(), cbh);
|
|
||||||
// Wait until SASL negotiation finishes
|
|
||||||
wait(connection.getPacketReplyTimeout());
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeThrowException();
|
|
||||||
|
|
||||||
if (!authenticationSuccessful) {
|
|
||||||
throw NoResponseException.newWith(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new SmackException(
|
|
||||||
"SASL Authentication failed. No known authentication mechanisims.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs SASL authentication of the specified user. If SASL authentication was successful
|
* Performs SASL authentication of the specified user. If SASL authentication was successful
|
||||||
* then resource binding and session establishment will be performed. This method will return
|
* then resource binding and session establishment will be performed. This method will return
|
||||||
|
@ -215,70 +171,31 @@ public class SASLAuthentication {
|
||||||
*
|
*
|
||||||
* @param username the username that is authenticating with the server.
|
* @param username the username that is authenticating with the server.
|
||||||
* @param password the password to send to the server.
|
* @param password the password to send to the server.
|
||||||
* @param resource the desired resource.
|
|
||||||
* @throws XMPPErrorException
|
* @throws XMPPErrorException
|
||||||
* @throws SASLErrorException
|
* @throws SASLErrorException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws SmackException
|
* @throws SmackException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public void authenticate(String username, String password, String resource)
|
public void authenticate(String username, String password)
|
||||||
throws XMPPErrorException, SASLErrorException, IOException,
|
throws XMPPErrorException, SASLErrorException, IOException,
|
||||||
SmackException, InterruptedException {
|
SmackException, InterruptedException {
|
||||||
SASLMechanism selectedMechanism = selectMechanism();
|
currentMechanism = selectMechanism();
|
||||||
if (selectedMechanism != null) {
|
final CallbackHandler callbackHandler = configuration.getCallbackHandler();
|
||||||
currentMechanism = selectedMechanism;
|
final String host = connection.getHost();
|
||||||
|
final DomainBareJid xmppDomain = connection.getServiceName();
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
currentMechanism.authenticate(username, connection.getHost(),
|
if (callbackHandler != null) {
|
||||||
connection.getServiceName(), password);
|
currentMechanism.authenticate(host, xmppDomain, callbackHandler);
|
||||||
// Wait until SASL negotiation finishes
|
|
||||||
wait(connection.getPacketReplyTimeout());
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeThrowException();
|
|
||||||
|
|
||||||
if (!authenticationSuccessful) {
|
|
||||||
throw NoResponseException.newWith(connection);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new SmackException(
|
currentMechanism.authenticate(username, host, xmppDomain, password);
|
||||||
"SASL Authentication failed. No known authentication mechanisims.");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs ANONYMOUS SASL authentication. If SASL authentication was successful
|
|
||||||
* then resource binding and session establishment will be performed. This method will return
|
|
||||||
* the full JID provided by the server while binding a resource to the connection.<p>
|
|
||||||
*
|
|
||||||
* The server will assign a full JID with a randomly generated resource and possibly with
|
|
||||||
* no username.
|
|
||||||
*
|
|
||||||
* @throws SASLErrorException
|
|
||||||
* @throws XMPPErrorException if an error occures while authenticating.
|
|
||||||
* @throws SmackException if there was no response from the server.
|
|
||||||
* @throws InterruptedException
|
|
||||||
*/
|
|
||||||
public void authenticateAnonymously() throws SASLErrorException,
|
|
||||||
SmackException, XMPPErrorException, InterruptedException {
|
|
||||||
currentMechanism = (new SASLAnonymous()).instanceForAuthentication(connection);
|
|
||||||
|
|
||||||
// Wait until SASL negotiation finishes
|
// Wait until SASL negotiation finishes
|
||||||
synchronized (this) {
|
|
||||||
currentMechanism.authenticate(null, null, null, "");
|
|
||||||
wait(connection.getPacketReplyTimeout());
|
wait(connection.getPacketReplyTimeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeThrowException();
|
|
||||||
|
|
||||||
if (!authenticationSuccessful) {
|
|
||||||
throw NoResponseException.newWith(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeThrowException() throws SmackException, SASLErrorException {
|
|
||||||
if (saslException != null){
|
if (saslException != null){
|
||||||
if (saslException instanceof SmackException) {
|
if (saslException instanceof SmackException) {
|
||||||
throw (SmackException) saslException;
|
throw (SmackException) saslException;
|
||||||
|
@ -288,6 +205,10 @@ public class SASLAuthentication {
|
||||||
throw new IllegalStateException("Unexpected exception type" , saslException);
|
throw new IllegalStateException("Unexpected exception type" , saslException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!authenticationSuccessful) {
|
||||||
|
throw NoResponseException.newWith(connection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -378,10 +299,12 @@ public class SASLAuthentication {
|
||||||
saslException = null;
|
saslException = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SASLMechanism selectMechanism() {
|
private SASLMechanism selectMechanism() throws SmackException {
|
||||||
// Locate the SASLMechanism to use
|
|
||||||
SASLMechanism selectedMechanism = null;
|
|
||||||
Iterator<SASLMechanism> it = REGISTERED_MECHANISMS.iterator();
|
Iterator<SASLMechanism> it = REGISTERED_MECHANISMS.iterator();
|
||||||
|
final List<String> serverMechanisms = getServerMechanisms();
|
||||||
|
if (serverMechanisms.isEmpty()) {
|
||||||
|
LOGGER.warning("Server did not report any SASL mechanisms");
|
||||||
|
}
|
||||||
// Iterate in SASL Priority order over registered mechanisms
|
// Iterate in SASL Priority order over registered mechanisms
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
SASLMechanism mechanism = it.next();
|
SASLMechanism mechanism = it.next();
|
||||||
|
@ -391,19 +314,31 @@ public class SASLAuthentication {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (serverMechanisms().contains(mechanismName)) {
|
if (!configuration.isEnabledSaslMechanism(mechanismName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (serverMechanisms.contains(mechanismName)) {
|
||||||
// Create a new instance of the SASLMechanism for every authentication attempt.
|
// Create a new instance of the SASLMechanism for every authentication attempt.
|
||||||
selectedMechanism = mechanism.instanceForAuthentication(connection);
|
return mechanism.instanceForAuthentication(connection);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return selectedMechanism;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> serverMechanisms() {
|
synchronized (BLACKLISTED_MECHANISMS) {
|
||||||
|
// @formatter:off
|
||||||
|
throw new SmackException(
|
||||||
|
"No supported and enabled SASL Mechanism provided by server. " +
|
||||||
|
"Server announced mechanisms: " + serverMechanisms + ". " +
|
||||||
|
"Registerd SASL mechanisms with Smack: " + REGISTERED_MECHANISMS + ". " +
|
||||||
|
"Enabled SASL mechansisms for this connection: " + configuration.getEnabledSaslMechanisms() + ". " +
|
||||||
|
"Blacklisted SASL mechanisms: " + BLACKLISTED_MECHANISMS + '.'
|
||||||
|
);
|
||||||
|
// @formatter;on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getServerMechanisms() {
|
||||||
Mechanisms mechanisms = connection.getFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE);
|
Mechanisms mechanisms = connection.getFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE);
|
||||||
if (mechanisms == null) {
|
if (mechanisms == null) {
|
||||||
LOGGER.warning("Server did not report any SASL mechanisms");
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return mechanisms.getMechanisms();
|
return mechanisms.getMechanisms();
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.jivesoftware.smack.initializer.SmackInitializer;
|
||||||
import org.jivesoftware.smack.packet.Bind;
|
import org.jivesoftware.smack.packet.Bind;
|
||||||
import org.jivesoftware.smack.provider.BindIQProvider;
|
import org.jivesoftware.smack.provider.BindIQProvider;
|
||||||
import org.jivesoftware.smack.provider.ProviderManager;
|
import org.jivesoftware.smack.provider.ProviderManager;
|
||||||
|
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
|
||||||
import org.jivesoftware.smack.sasl.core.SASLXOauth2Mechanism;
|
import org.jivesoftware.smack.sasl.core.SASLXOauth2Mechanism;
|
||||||
import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism;
|
import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism;
|
||||||
import org.jivesoftware.smack.util.FileUtils;
|
import org.jivesoftware.smack.util.FileUtils;
|
||||||
|
@ -137,6 +138,7 @@ public final class SmackInitialization {
|
||||||
|
|
||||||
SASLAuthentication.registerSASLMechanism(new SCRAMSHA1Mechanism());
|
SASLAuthentication.registerSASLMechanism(new SCRAMSHA1Mechanism());
|
||||||
SASLAuthentication.registerSASLMechanism(new SASLXOauth2Mechanism());
|
SASLAuthentication.registerSASLMechanism(new SASLXOauth2Mechanism());
|
||||||
|
SASLAuthentication.registerSASLMechanism(new SASLAnonymous());
|
||||||
|
|
||||||
ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider());
|
ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider());
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,9 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
authenticate();
|
authenticate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SmackException
|
||||||
|
*/
|
||||||
protected void authenticateInternal() throws SmackException {
|
protected void authenticateInternal() throws SmackException {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +251,9 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
connection.send(responseStanza);
|
connection.send(responseStanza);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SmackException
|
||||||
|
*/
|
||||||
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,10 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl.core;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2015 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.util;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class CollectionUtil {
|
||||||
|
|
||||||
|
public static <T> Collection<T> requireNotEmpty(Collection<T> collection, String collectionName) {
|
||||||
|
if (collection == null) {
|
||||||
|
throw new NullPointerException(collectionName + " must not be null.");
|
||||||
|
}
|
||||||
|
if (collection.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(collectionName + " must not be empty.");
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -116,23 +116,12 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void loginNonAnonymously(String username, String password, String resource)
|
protected void loginInternal(String username, String password, String resource)
|
||||||
throws XMPPException {
|
throws XMPPException {
|
||||||
user = getUserJid();
|
user = getUserJid();
|
||||||
authenticated = true;
|
authenticated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loginAnonymously() throws XMPPException {
|
|
||||||
if (!isConnected()) {
|
|
||||||
throw new IllegalStateException("Not connected to server.");
|
|
||||||
}
|
|
||||||
if (isAuthenticated()) {
|
|
||||||
throw new IllegalStateException("Already logged in to server.");
|
|
||||||
}
|
|
||||||
authenticated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(PlainStreamElement element) {
|
public void send(PlainStreamElement element) {
|
||||||
queue.add(element);
|
queue.add(element);
|
||||||
|
|
|
@ -361,18 +361,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void loginNonAnonymously(String username, String password, String resource) throws XMPPException, SmackException, IOException, InterruptedException {
|
protected synchronized void loginInternal(String username, String password, String resource) throws XMPPException,
|
||||||
if (saslAuthentication.hasNonAnonymousAuthentication()) {
|
SmackException, IOException, InterruptedException {
|
||||||
// Authenticate using SASL
|
// Authenticate using SASL
|
||||||
if (password != null) {
|
saslAuthentication.authenticate(username, password);
|
||||||
saslAuthentication.authenticate(username, password, resource);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
saslAuthentication.authenticate(resource, config.getCallbackHandler());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new SmackException("No non-anonymous SASL authentication mechanism available");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If compression is enabled then request the server to use stream compression. XEP-170
|
// If compression is enabled then request the server to use stream compression. XEP-170
|
||||||
// recommends to perform stream compression before resource binding.
|
// recommends to perform stream compression before resource binding.
|
||||||
|
@ -430,28 +422,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
afterSuccessfulLogin(false);
|
afterSuccessfulLogin(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void loginAnonymously() throws XMPPException, SmackException, IOException, InterruptedException {
|
|
||||||
// Wait with SASL auth until the SASL mechanisms have been received
|
|
||||||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
|
||||||
|
|
||||||
if (saslAuthentication.hasAnonymousAuthentication()) {
|
|
||||||
saslAuthentication.authenticateAnonymously();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new SmackException("No anonymous SASL authentication mechanism available");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If compression is enabled then request the server to use stream compression
|
|
||||||
if (config.isCompressionEnabled()) {
|
|
||||||
useCompression();
|
|
||||||
}
|
|
||||||
|
|
||||||
bindResourceAndEstablishSession(null);
|
|
||||||
|
|
||||||
afterSuccessfulLogin(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecureConnection() {
|
public boolean isSecureConnection() {
|
||||||
return usingTLS;
|
return usingTLS;
|
||||||
|
|
Loading…
Reference in a new issue