Use strategy pattern for XMPP login (Patch by Stefan Sieber)

cusax-fix
Ingo Bauersachs 13 years ago
parent febee35ee1
commit cf2f1e21b1

Binary file not shown.

Binary file not shown.

@ -0,0 +1,73 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.jabber;
import net.java.sip.communicator.service.certificate.*;
import net.java.sip.communicator.service.protocol.*;
import org.jivesoftware.smack.*;
import javax.net.ssl.*;
import java.security.*;
/**
* Is responsible to configure the login mechanism for smack
* and later login to the XMPP server.
*
* @author Stefan Sieber
*/
public interface JabberLoginStrategy
{
/**
* Prepare the login by e.g. asking the user for his password.
*
* @param authority SecurityAuthority to obtain the password
* @param reasonCode reason why we're preparing for login
* @return UserCredentials in case they need to be cached for this session
* (i.e. password is not persistent)
*
* @see SecurityAuthority
*/
public UserCredentials prepareLogin(SecurityAuthority authority,
int reasonCode);
/**
* Determines whether the login preparation was successful and the strategy
* is ready to start connecting.
*
* @return true if prepareLogin was successful.
*/
public boolean loginPreparationSuccessful();
/**
* Performs the login for the specified connection.
*
* @param connection Connection to login
* @param userName userName to be used for the login.
* @param resource the XMPP resource
*/
public void login(XMPPConnection connection, String userName,
String resource)
throws XMPPException;
/**
* Is TLS required for this login strategy / account?
* @return true if TLS is required
*/
public boolean isTlsRequired();
/**
* Creates an SSLContext to use for the login strategy.
* @param certificateService certificate service to retrieve the
* ssl context
* @param trustManager Trust manager to use for the context
*
* @return the SSLContext
*/
public SSLContext createSslContext(CertificateService certificateService,
X509TrustManager trustManager)
throws GeneralSecurityException;
}

@ -0,0 +1,198 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.jabber;
import net.java.sip.communicator.impl.protocol.jabber.sasl.*;
import net.java.sip.communicator.service.certificate.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import org.jivesoftware.smack.*;
import javax.net.ssl.*;
import java.security.*;
/**
* Login to Jabber using username & password.
*
* @author Stefan Sieber
*/
public class LoginByPasswordStrategy
implements JabberLoginStrategy
{
private final AbstractProtocolProviderService protocolProvider;
private final AccountID accountID;
private String password;
/**
* Create a login strategy that logs in using user credentials (username
* and password)
* @param protocolProvider protocol provider service to fire registration
* change events.
* @param accountID The accountID to use for the login.
*/
public LoginByPasswordStrategy(
AbstractProtocolProviderService protocolProvider,
AccountID accountID)
{
this.protocolProvider = protocolProvider;
this.accountID = accountID;
}
/**
* Loads the account passwords as preparation for the login.
*
* @param authority SecurityAuthority to obtain the password
* @param reasonCode reason why we're preparing for login
* @return UserCredentials in case they need to be cached for this session
* (i.e. password is not persistent)
*/
public UserCredentials prepareLogin(SecurityAuthority authority,
int reasonCode)
{
return loadPassword(authority, reasonCode);
}
/**
* Determines whether the strategy is ready to perform the login.
*
* @return True when the password was sucessfully loaded.
*/
public boolean loginPreparationSuccessful()
{
return password != null;
}
/**
* Performs the login on an XMPP connection using SASL PLAIN.
*
* @param connection The connection on which the login is performed.
* @param userName The username for the login.
* @param resource The XMPP resource.
* @throws XMPPException
*/
public void login(XMPPConnection connection, String userName,
String resource)
throws XMPPException
{
SASLAuthentication.supportSASLMechanism("PLAIN", 0);
// Insert our sasl mechanism implementation
// in order to support some incompatible servers
boolean disableCustomDigestMD5
= accountID.getAccountPropertyBoolean(
"DISABLE_CUSTOM_DIGEST_MD5",
false);
if(!disableCustomDigestMD5)
{
SASLAuthentication.unregisterSASLMechanism("DIGEST-MD5");
SASLAuthentication.registerSASLMechanism("DIGEST-MD5",
SASLDigestMD5Mechanism.class);
SASLAuthentication.supportSASLMechanism("DIGEST-MD5");
}
connection.login(userName, password, resource);
}
/*
* (non-Javadoc)
*
* @see net.java.sip.communicator.impl.protocol.jabber.JabberLoginStrategy#
* isTlsRequired()
*/
public boolean isTlsRequired()
{
// requires TLS by default (i.e. it will not connect to a non-TLS server
// and will not fallback to cleartext)
return !accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.IS_ALLOW_NON_SECURE, false);
}
/**
* Prepares an SSL Context that is customized SSL context.
*
* @param cs The certificate service that provides the context.
* @param trustManager The TrustManager to use within the context.
* @return An initialized context for the current provider.
* @throws GeneralSecurityException
*/
public SSLContext createSslContext(CertificateService cs,
X509TrustManager trustManager)
throws GeneralSecurityException
{
return cs.getSSLContext(trustManager);
}
/**
* Load the password from the account configuration or ask the user.
*
* @param authority SecurityAuthority
* @param reasonCode the authentication reason code. Indicates the reason of
* this authentication.
* @return The UserCredentials in case they should be cached for this
* session (i.e. are not persistent)
*/
private UserCredentials loadPassword(SecurityAuthority authority,
int reasonCode)
{
UserCredentials cachedCredentials = null;
//verify whether a password has already been stored for this account
password = JabberActivator.
getProtocolProviderFactory().loadPassword(accountID);
//decode
if (password == null)
{
//create a default credentials object
UserCredentials credentials = new UserCredentials();
credentials.setUserName(accountID.getUserID());
//request a password from the user
credentials = authority.obtainCredentials(
accountID.getDisplayName(),
credentials,
reasonCode);
// in case user has canceled the login window
if(credentials == null)
{
protocolProvider.fireRegistrationStateChanged(
protocolProvider.getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"No credentials provided");
return null;
}
//extract the password the user passed us.
char[] pass = credentials.getPassword();
// the user didn't provide us a password (canceled the operation)
if(pass == null)
{
protocolProvider.fireRegistrationStateChanged(
protocolProvider.getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"No password entered");
return null;
}
password = new String(pass);
if (credentials.isPasswordPersistent())
{
JabberActivator.getProtocolProviderFactory()
.storePassword(accountID, password);
}
else
cachedCredentials = credentials;
}
return cachedCredentials;
}
}

@ -27,7 +27,6 @@
import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*;
import net.java.sip.communicator.impl.protocol.jabber.sasl.*;
import net.java.sip.communicator.service.certificate.*;
import net.java.sip.communicator.service.dns.*;
import net.java.sip.communicator.service.protocol.*;
@ -638,11 +637,16 @@ private void connectAndLogin(SecurityAuthority authority,
synchronized(initializationLock)
{
// init the necessary objects
JabberLoginStrategy loginStrategy
= new LoginByPasswordStrategy(this, getAccountID());
userCredentials = loginStrategy.prepareLogin(authority, reasonCode);
if(!loginStrategy.loginPreparationSuccessful())
return;
String serviceName
= StringUtils.parseServer(getAccountID().getUserID());
String password = loadPassword(authority, reasonCode);
if (password == null)
return;
loadResource();
loadProxy();
Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
@ -658,8 +662,8 @@ private void connectAndLogin(SecurityAuthority authority,
if(!isServerOverriden)
{
state = connectUsingSRVRecords(serviceName, password,
serviceName, hadDnsSecException);
state = connectUsingSRVRecords(serviceName,
serviceName, hadDnsSecException, loginStrategy);
if(hadDnsSecException[0])
{
setDnssecLoginFailure();
@ -681,8 +685,8 @@ private void connectAndLogin(SecurityAuthority authority,
customXMPPDomain);
state = connectUsingSRVRecords(
customXMPPDomain, password, serviceName,
hadDnsSecException);
customXMPPDomain, serviceName,
hadDnsSecException, loginStrategy);
logger.info("state for connectUsingSRVRecords: " + state);
@ -740,8 +744,8 @@ private void connectAndLogin(SecurityAuthority authority,
{
try
{
state = connectAndLogin(isa, password,
serviceName);
state = connectAndLogin(isa, serviceName,
loginStrategy);
if(state == ConnectState.ABORT_CONNECTING
|| state == ConnectState.STOP_TRYING)
return;
@ -770,16 +774,16 @@ private void setDnssecLoginFailure()
/**
* Connects using the domain specified and its SRV records.
* @param domain the domain to use
* @param password the password of the user
* @param serviceName the domain name of the user's login
* @param dnssecState state of possible received DNSSEC exceptions
* @param loginStrategy the login strategy to use
* @return whether to continue trying or stop.
*/
private ConnectState connectUsingSRVRecords(
String domain,
String password,
String serviceName,
boolean[] dnssecState)
boolean[] dnssecState,
JabberLoginStrategy loginStrategy)
throws XMPPException
{
// check to see is there SRV records for this server domain
@ -847,7 +851,7 @@ private ConnectState connectUsingSRVRecords(
}
ConnectState state = connectAndLogin(
isa, password, serviceName);
isa, serviceName, loginStrategy);
return state;
}
catch(XMPPException ex)
@ -878,12 +882,13 @@ private ConnectState connectUsingSRVRecords(
* name fails, a second attempt including the service name is made.
*
* @param currentAddress the IP address to connect to
* @param password the password of the user
* @param serviceName the domain name of the user's login
* @param loginStrategy the login strategy to use
* @throws XMPPException when a failure occurs
*/
private ConnectState connectAndLogin(InetSocketAddress currentAddress,
String password, String serviceName)
String serviceName,
JabberLoginStrategy loginStrategy)
throws XMPPException
{
String userID = null;
@ -908,7 +913,7 @@ private ConnectState connectAndLogin(InetSocketAddress currentAddress,
{
return connectAndLogin(
currentAddress, serviceName,
userID, password, resource);
userID, resource, loginStrategy);
}
catch(XMPPException ex)
{
@ -939,7 +944,8 @@ private ConnectState connectAndLogin(InetSocketAddress currentAddress,
return connectAndLogin(
currentAddress, serviceName,
userID + "@" + serviceName,
password, resource);
resource,
loginStrategy);
}
catch(XMPPException ex2)
{
@ -982,71 +988,6 @@ private void loadResource()
}
}
/**
* Load the password from the account configuration or ask the user.
*
* @param authority SecurityAuthority
* @param reasonCode the authentication reason code. Indicates the reason of
* this authentication.
* @return The password for the account or null if no password could be
* obtained
*/
private String loadPassword(SecurityAuthority authority, int reasonCode)
{
//verify whether a password has already been stored for this account
String password = JabberActivator.
getProtocolProviderFactory().loadPassword(getAccountID());
//decode
if (password == null)
{
//create a default credentials object
UserCredentials credentials = new UserCredentials();
credentials.setUserName(getAccountID().getUserID());
//request a password from the user
credentials = authority.obtainCredentials(
getAccountID().getDisplayName(),
credentials,
reasonCode);
// in case user has canceled the login window
if(credentials == null)
{
fireRegistrationStateChanged(
getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"No credentials provided");
return null;
}
//extract the password the user passed us.
char[] pass = credentials.getPassword();
// the user didn't provide us a password (canceled the operation)
if(pass == null)
{
fireRegistrationStateChanged(
getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"No password entered");
return null;
}
password = new String(pass);
if (credentials.isPasswordPersistent())
{
JabberActivator.getProtocolProviderFactory()
.storePassword(getAccountID(), password);
}
else
userCredentials = credentials;
}
return password;
}
/**
* Sets the global proxy information based on the configuration
*
@ -1120,14 +1061,15 @@ private void loadProxy() throws OperationFailedException
* @param address the address to connect to
* @param serviceName the service name to use
* @param userName the username to use
* @param password the password to use
* @param resource and the resource.
* @param loginStrategy the login strategy to use
* @return return the state how to continue the connect process.
* @throws XMPPException if we cannot connect for some reason
*/
private ConnectState connectAndLogin(
InetSocketAddress address, String serviceName,
String userName, String password, String resource)
String userName, String resource,
JabberLoginStrategy loginStrategy)
throws XMPPException
{
ConnectionConfiguration confConn = new ConnectionConfiguration(
@ -1137,10 +1079,7 @@ private ConnectState connectAndLogin(
);
confConn.setReconnectionAllowed(false);
// requires TLS by default (i.e. it will not connect to a non-TLS server
// and will not fallback to cleartext)
boolean tlsRequired = !accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.IS_ALLOW_NON_SECURE, false);
boolean tlsRequired = loginStrategy.isTlsRequired();
// user have the possibility to disable TLS but in this case, it will
// not be able to connect to a server which requires TLS
@ -1165,16 +1104,9 @@ private ConnectState connectAndLogin(
getCertificateVerificationService();
if(cvs != null)
{
connection.setCustomTrustManager(
new HostTrustManager(
cvs.getTrustManager(
Arrays.asList(new String[]{
serviceName,
"_xmpp-client." + serviceName
})
)
)
);
SSLContext sslContext = loginStrategy.createSslContext(cvs,
getTrustManager(cvs, serviceName));
connection.setCustomSslContext(sslContext);
}
else if (tlsRequired)
throw new XMPPException(
@ -1255,24 +1187,7 @@ else if (tlsRequired)
, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
, null);
SASLAuthentication.supportSASLMechanism("PLAIN", 0);
// Insert our sasl mechanism implementation
// in order to support some incompatible servers
boolean disableCustomDigestMD5
= accountID.getAccountPropertyBoolean(
"DISABLE_CUSTOM_DIGEST_MD5",
false);
if(!disableCustomDigestMD5)
{
SASLAuthentication.unregisterSASLMechanism("DIGEST-MD5");
SASLAuthentication.registerSASLMechanism("DIGEST-MD5",
SASLDigestMD5Mechanism.class);
SASLAuthentication.supportSASLMechanism("DIGEST-MD5");
}
connection.login(userName, password, resource);
loginStrategy.login(connection, userName, resource);
if(connection.isAuthenticated())
{
@ -1316,6 +1231,28 @@ else if (tlsRequired)
}
}
/**
* Gets the TrustManager that should be used for the specified service
*
* @param serviceName the service name
* @param cvs The CertificateVerificationService to retrieve the
* trust manager
* @return the trust manager
*/
private X509TrustManager getTrustManager(CertificateService cvs,
String serviceName)
throws GeneralSecurityException
{
return new HostTrustManager(
cvs.getTrustManager(
Arrays.asList(new String[]{
serviceName,
"_xmpp-client." + serviceName
})
)
);
}
/**
* Registers our ServiceDiscoveryManager
*/

Loading…
Cancel
Save