package sync.server.system;
import java.security.acl.Group;
import java.security.Principal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.login.FailedLoginException;
import javax.sql.DataSource;
import sync.server.util.SyncUtils;
import sync.util.Logger;
import sync.util.Constants;
public class SyncServerLoginModule implements LoginModule
{
private static final long DEFAULT_CACHE_DURATION = 0; // No built-in caching
protected boolean loginOk;
protected Subject subject;
private char[] credential;
protected CallbackHandler callbackHandler;
protected Map sharedState;
protected Map options;
private SimpleGroup roles = new SimpleGroup("Roles");
private Principal identity;
private String dsJndiName;
private long validCacheTime = DEFAULT_CACHE_DURATION;
private String roleQuery = "select r.user_role_desc as Role from SCC_USER_ROLE r, SCC_USER u where r.user_role_id=u.user_role_id and u.user_name=? and u.password=?";
private static HashMap cachedMap = new HashMap();
private static Object lock = new Object();
/**
* Initialize this LoginModule.
*/
public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)
{
SyncUtils.log("SyncServerLoginModule.initialize", Logger.LOG_MIN_INFO, 0, 0," init sync-login", null, null);
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
dsJndiName = (String) options.get("dsJndiName");
if( dsJndiName == null )
dsJndiName = Constants.JDBC_DATA_SOURCE;
String tmp = (String)options.get("roleQuery");
if( tmp != null )
roleQuery = tmp.toString();
tmp = (String)options.get("cacheDurationMS");
if( tmp != null )
{
try
{
validCacheTime = Long.parseLong(tmp);
}
catch (NumberFormatException nfe)
{
SyncUtils.log("SSLM.initialize", Logger.LOG_ERROR, 0, 0,
"Invalid cache duration (ms): " + tmp, null, null);
}
}
}
protected String getRole(String username, String password) throws LoginException
{
SyncUtils.log("SyncServerLoginModule.initialize", Logger.LOG_MIN_INFO, 0, 0," getrole sync-login", null, null);
Connection conn = null;
String role = null;
PreparedStatement ps = null;
InitialContext ctx = null;
ResultSet rs = null;
try
{
ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(dsJndiName);
conn = ds.getConnection();
// Get the user role names
ps = conn.prepareStatement(roleQuery);
try
{
ps.setString(1, username);
ps.setString(2, password);
}
catch(ArrayIndexOutOfBoundsException ignore)
{
// The query may not have any parameters so just try it
}
SyncUtils.log("SyncServerLoginModule.getRole", Logger.LOG_DEBUG_INFO, 0, 0,
"Retrieving user role from database for user " + username, null, null);
rs = ps.executeQuery();
if( rs.next() == false )
{
throw new FailedLoginException("No matching username and password found");
}
else
{
role = rs.getString(1);
}
}
catch(NamingException ex)
{
throw new LoginException(ex.toString(true));
}
catch(SQLException ex)
{
throw new LoginException(ex.toString());
}
finally
{
SyncUtils.closeConnection(rs, ps, conn);
SyncUtils.closeContext(ctx);
ctx = null;
}
return role;
}
/** Called by login() to acquire the username and password strings for
authentication. This method does no validation of either.
@return String[], [0] = username, [1] = password
@exception LoginException thrown if CallbackHandler is not set or fails.
*/
protected String[] getUsernameAndPassword() throws LoginException
{
SyncUtils.log("SyncServerLoginModule.initialize", Logger.LOG_MIN_INFO, 0, 0," getUsernameAndPassword1 sync-login", null, null);
String[] info = {null, null};
// prompt for a username and password
if( callbackHandler == null )
{
throw new LoginException("Error: no CallbackHandler available " +
"to collect authentication information");
}
SyncUtils.log("SyncServerLoginModule.initialize", Logger.LOG_MIN_INFO, 0, 0," getUsernameAndPassword2 sync-login", null, null);
NameCallback nc = new NameCallback("User name: ", "guest");
PasswordCallback pc = new PasswordCallback("Password: ", false);
Callback[] callbacks = {nc, pc};
String username = null;
String password = null;
SyncUtils.log("SyncServerLoginModule.initialize", Logger.LOG_MIN_INFO, 0, 0," getUsernameAndPassword3 sync-login", null, null);
try
{
callbackHandler.handle(callbacks);
username = nc.getName();
char[] tmpPassword = pc.getPassword();
if( tmpPassword != null )
{
credential = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, credential, 0, tmpPassword.length);
pc.clearPassword();
password = new String(credential);
}
}
catch(java.io.IOException ioe)
{
throw new LoginException(ioe.toString());
}
catch(UnsupportedCallbackException uce)
{
throw new LoginException("CallbackHandler does not support: " + uce.getCallback());
}
info[0] = username;
info[1] = password;
return info;
}
/** Perform the authentication of the username and password.
*/
public boolean login() throws LoginException
{
SyncUtils.log("SyncServerLoginModule.initialize", Logger.LOG_MIN_INFO, 0, 0," login sync-login", null, null);
loginOk = false;
String info[] = getUsernameAndPassword();
String username = info[0];
String password = info[1];
if( username == null || password == null )
{
throw new FailedLoginException("Username and Password Required");
}
String role = null;
if (validCacheTime == 0)
{
role = getRole(username, password);
}
else
{
synchronized (lock)
{
CachedData cachedData = (CachedData)cachedMap.get(username.toLowerCase());
if ((cachedData != null) && !cachedData.isExpired() && cachedData.isPasswordValid(password))
{
role = cachedData.getRole();
}
else
{
role = getRole(username, password);
cachedMap.put(username.toLowerCase(), new CachedData(username, password, role));
}
}
}
identity = new SimplePrincipal(username);
roles.addMember(new SimplePrincipal(role));
loginOk = true;
return true;
}
/** Method to commit the authentication process (phase 2). If the login
method completed successfully as indicated by loginOk == true, this
method adds the getIdentity() value to the subject getPrincipals() Set.
It also adds the members of each Group returned by getRoleSets()
to the subject getPrincipals() Set.
@see javax.security.auth.Subject;
@see java.security.acl.Group;
@return true always.
*/
public boolean commit() throws LoginException
{
if( loginOk == false )
return false;
Set principals = subject.getPrincipals();
principals.add(identity);
Group subjectGroup = findGroup("Roles", principals);
if (subjectGroup == null)
{
principals.add(roles);
}
return true;
}
/** Method to abort the authentication process (phase 2).
@return true alaways
*/
public boolean abort() throws LoginException
{
return true;
}
/** Remove the user identity and roles added to the Subject during commit.
@return true always.
*/
public boolean logout() throws LoginException
{
Set principals = subject.getPrincipals();
principals.remove(identity);
return true;
}
/**
* Find a Group with the given name. Subclasses should use this
* method to locate the 'Roles' group or create additional types of groups.
*
* @return A named Group from the principals set.
*/
protected Group findGroup(String name, Set principals)
{
Group roles = null;
Iterator iter = principals.iterator();
while( iter.hasNext() )
{
Object next = iter.next();
if( (next instanceof Group) == false )
continue;
Group grp = (Group) next;
if( grp.getName().equals(name) )
{
roles = grp;
break;
}
}
return roles;
}
public class SimplePrincipal
implements Principal
{
private String username = null;
public SimplePrincipal(String name)
{
username = name;
}
public boolean equals(Object another)
{
if ((another != null) && (another instanceof String))
{
String s = (String)another;
return s.equalsIgnoreCase(username);
}
return false;
}
public String getName()
{
return username;
}
public int hashCode()
{
return username.hashCode();
}
public String toString()
{
return getName();
}
}
public class SimpleGroup extends SimplePrincipal
implements Group
{
private HashSet members = new HashSet();
public SimpleGroup(String name)
{
super(name);
}
public boolean addMember(Principal user)
{
return members.add(user);
}
public boolean isMember(Principal user)
{
return members.contains(user);
}
public Enumeration members()
{
return new SimpleGroupEnumeration(members.iterator());
}
public boolean removeMember(Principal user)
{
return members.remove(user);
}
}
public class SimpleGroupEnumeration
implements Enumeration
{
private Iterator iter = null;
public SimpleGroupEnumeration(Iterator iter)
{
this.iter = iter;
}
public boolean hasMoreElements()
{
return iter.hasNext();
}
public Object nextElement()
{
return iter.next();
}
}
protected class CachedData
{
private long cacheTime = 0;
private String username = null;
private String password = null;
private String role = null;
public CachedData(String username, String password, String role)
{
this.username = username;
this.password = password;
this.role = role;
this.cacheTime = System.currentTimeMillis();
}
public boolean isExpired()
{
return ((System.currentTimeMillis() - cacheTime) > validCacheTime);
}
public String getUsername()
{
return username;
}
public boolean isPasswordValid(String newPw)
{
if (password != null)
{
return password.equalsIgnoreCase(newPw);
}
else if (newPw == null)
{
return true;
}
return false;
}
public String getRole()
{
return role;
}
}
}