package edu.amc.sakai.user;
import java.io.UnsupportedEncodingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.novell.ldap.LDAPConnection;
import com.novell.ldap.LDAPConstraints;
import com.novell.ldap.LDAPException;
import com.novell.ldap.LDAPTLSSocketFactory;
/**
* Allocates connected, constrained, and optionally
* bound and secure <code>LDAPConnections</code>
*
* @see LdapConnectionManagerConfig
* @author Dan McCallum, Unicon Inc
* @author John Lewis, Unicon Inc
*/
public class SimpleLdapConnectionManager implements LdapConnectionManager {
public static final String KEYSTORE_LOCATION_SYS_PROP_KEY =
"javax.net.ssl.trustStore";
public static final String KEYSTORE_PASSWORD_SYS_PROP_KEY =
"javax.net.ssl.trustStorePassword";
/** Class-specific logger */
private static Log M_log = LogFactory.getLog(SimpleLdapConnectionManager.class);
/** connection allocation configuration */
private LdapConnectionManagerConfig config;
/**
* {@inheritDoc}
*/
public void init() {
if ( M_log.isDebugEnabled() ) {
M_log.debug("init()");
}
if ( config.isSecureConnection() ) {
if ( M_log.isDebugEnabled() ) {
M_log.debug("init(): initializing secure socket factory");
}
initKeystoreLocation();
initKeystorePassword();
LDAPConnection.setSocketFactory(config.getSecureSocketFactory());
}
}
/**
* {@inheritDoc}
*/
public LDAPConnection getConnection() throws LDAPException {
if ( M_log.isDebugEnabled() ) {
M_log.debug("getConnection()");
}
LDAPConnection conn = new LDAPConnection();
applyConstraints(conn);
connect(conn);
if ( config.isAutoBind() ) {
if ( M_log.isDebugEnabled() ) {
M_log.debug("getConnection(): auto-binding");
}
try {
bind(conn, config.getLdapUser(), config.getLdapPassword());
} catch (LDAPException ldape) {
if (ldape.getResultCode() == LDAPException.INVALID_CREDENTIALS) {
M_log.warn("Failed to bind against: "+ conn.getHost()+ " with user: "+ config.getLdapUser()+ " password: "+ config.getLdapPassword().replaceAll(".", "*"));
}
throw ldape;
}
}
return conn;
}
/**
* {@inheritDoc}
*/
public LDAPConnection getBoundConnection(String dn, String pw) throws LDAPException {
if ( M_log.isDebugEnabled() ) {
M_log.debug("getBoundConnection(): [dn = " + dn + "]");
}
LDAPConnection conn = new LDAPConnection();
applyConstraints(conn);
connect(conn);
bind(conn, dn, pw);
return conn;
}
private void bind(LDAPConnection conn, String dn, String pw)
throws LDAPException {
if ( M_log.isDebugEnabled() ) {
M_log.debug("bind(): binding [dn = " + dn + "]");
}
try {
conn.bind(LDAPConnection.LDAP_V3, dn, pw.getBytes("UTF8"));
} catch ( UnsupportedEncodingException e ) {
throw new RuntimeException("Failed to encode user password", e);
}
}
/**
* {@inheritDoc}
*/
public void returnConnection(LDAPConnection conn) {
try {
if (conn != null)
conn.disconnect();
} catch (LDAPException e) {
M_log.error("returnConnection(): failed on disconnect: ", e);
}
}
/**
* {@inheritDoc}
*/
public void setConfig(LdapConnectionManagerConfig config) {
this.config = config;
}
/**
* {@inheritDoc}
*/
public LdapConnectionManagerConfig getConfig() {
return config;
}
/**
* Caches a keystore password as a system property. No-op
* if the {@link #KEYSTORE_PASSWORD_SYS_PROP_KEY} system property
* has a non-<code>null</code> value. Otherwise caches the
* keystore password from the currently assigned
* {@link LdapConnectionManagerConfig}.
*
* @param config
* @throws NullPointerException if a non-null keystore password
* cannot be resolved
*/
protected void initKeystorePassword() {
if ( M_log.isDebugEnabled() ) {
M_log.debug("initKeystorePassword()");
}
String sysKeystorePassword =
System.getProperty(KEYSTORE_PASSWORD_SYS_PROP_KEY);
if ( sysKeystorePassword == null ) {
String configuredKeystorePassword = config.getKeystorePassword();
if ( configuredKeystorePassword != null){
if ( M_log.isDebugEnabled() ) {
M_log.debug("initKeystorePassword(): setting system property");
}
System.setProperty(KEYSTORE_PASSWORD_SYS_PROP_KEY, configuredKeystorePassword);
}
} else {
if ( M_log.isDebugEnabled() ) {
M_log.debug("initKeystorePassword(): retained existing system property");
}
}
}
/**
* Caches a keystore location as a system property. No-op
* if the {@link #KEYSTORE_LOCATION_SYS_PROP_KEY} system property
* has a non-<code>null</code> value. Otherwise caches the
* keystore location from the currently assigned
* {@link LdapConnectionManagerConfig}.
*
* @param config
* @throws NullPointerException if a non-null keystore location
* cannot be resolved
*/
protected void initKeystoreLocation() {
if ( M_log.isDebugEnabled() ) {
M_log.debug("initKeystoreLocation()");
}
String sysKeystoreLocation =
System.getProperty(KEYSTORE_LOCATION_SYS_PROP_KEY);
if ( sysKeystoreLocation == null ) {
String configuredKeystoreLocation = config.getKeystoreLocation();
if ( configuredKeystoreLocation != null){
if ( M_log.isDebugEnabled() ) {
M_log.debug("initKeystoreLocation(): setting system property [location = " +
configuredKeystoreLocation + "]");
}
System.setProperty(KEYSTORE_LOCATION_SYS_PROP_KEY, configuredKeystoreLocation);
}
} else {
if ( M_log.isDebugEnabled() ) {
M_log.debug("initKeystoreLocation(): retained existing system property [location = " +
sysKeystoreLocation + "]");
}
}
}
/**
* Applies <code>LDAPConstraints</code>
* to the specified <code>LDAPConnection</code>.
* Implemented to assign <code>timeLimit</code> and
* <code>referralFollowing</code> constraint values
* retrieved from the currently assigned
* {@link LdapConnectionManagerConfig}.
*
* @param conn
*/
protected void applyConstraints(LDAPConnection conn) {
int timeout = config.getOperationTimeout();
boolean followReferrals = config.isFollowReferrals();
if ( M_log.isDebugEnabled() ) {
M_log.debug("applyConstraints(): values [timeout = " +
timeout + "][follow referrals = " + followReferrals + "]");
}
LDAPConstraints constraints = new LDAPConstraints();
constraints.setTimeLimit(timeout);
constraints.setReferralFollowing(followReferrals);
conn.setConstraints(constraints);
}
/**
* Connects the specified <code>LDAPConnection</code> to
* the currently configured host and port.
*
* @param conn an <code>LDAPConnection</code>
* @throws LDAPConnection if the connect attempt fails
*/
protected void connect(LDAPConnection conn) throws LDAPException {
if ( M_log.isDebugEnabled() ) {
M_log.debug("connect()");
}
conn.connect(config.getLdapHost(), config.getLdapPort());
try {
postConnect(conn);
} catch ( LDAPException e ) {
M_log.error("Failed to completely initialize a connection [host = " +
config.getLdapHost() + "][port = " +
config.getLdapPort() + "]", e);
try {
conn.disconnect();
} catch ( LDAPException ee ) {}
throw e;
} catch ( Throwable e ) {
M_log.error("Failed to completely initialize a connection [host = " +
config.getLdapHost() + "][port = " +
config.getLdapPort() + "]", e);
try {
conn.disconnect();
} catch ( LDAPException ee ) {}
if ( e instanceof Error ) {
throw (Error)e;
}
if ( e instanceof RuntimeException ) {
throw (RuntimeException)e;
}
throw new RuntimeException("LDAPConnection allocation failure", e);
}
}
protected void postConnect(LDAPConnection conn) throws LDAPException {
if ( M_log.isDebugEnabled() ) {
M_log.debug("postConnect()");
}
if ( config.isSecureConnection() && isTlsSocketFactory() ) {
if ( M_log.isDebugEnabled() ) {
M_log.debug("postConnect(): starting TLS");
}
conn.startTLS();
}
}
protected boolean isTlsSocketFactory() {
return config.getSecureSocketFactory() instanceof LDAPTLSSocketFactory;
}
/**
* {@inheritDoc}
*/
public void destroy() {
}
}