import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.securityfilter.realm.SecurityRealmInterface;
/**
* A JNDI DataSource realm for use with SecurityFilter.
*
* @author Chris Schultz (chris@christopherschultz.net)
*/
public class DataSourceRealm
implements SecurityRealmInterface
{
/**
* The logger for this class.
*/
private static final Logger logger = Logger.getLogger(DataSourceRealm.class);
/**
* Authenticate a user.
*
* @param username The username of the user to authenticate.
* @param credentials The plaintext password, as entered by the user.
*
* @return A Principal object representing the user if successful,
* null otherwise.
*/
public Principal authenticate(String username, String credentials)
{
Connection conn = null;
try
{
// Ensure that we have an open database connection
conn = getConnection();
if (null == conn)
{
logger.error("Could not open JDBC connection");
// If the db connection open fails, return "not authenticated"
return null;
}
// Acquire a Principal object for this user
return authenticate(conn,
username,
credentials);
}
catch (NamingException ne)
{
logger.error("Cannot authenticate", ne);
return null;
}
catch (SQLException sqle)
{
logger.error("Cannot authenticate", sqle);
return null;
}
catch (NoSuchAlgorithmException nsae)
{
logger.error("Bad hash algorithm '" + digest + "'", nsae);
return null;
}
finally
{
if (conn != null)
try { conn.close(); } catch(SQLException sqle)
{ logger.error("Cannot close Connection", sqle); }
}
}
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return null
.
*
* @param dbConnection The database connection to be used
* @param username Username of the Principal to look up
* @param credentials Password or other credentials to use in
* authenticating this username
*
* @exception SQLException if a database error occurs
*/
private Principal authenticate(Connection conn,
String username,
String credentials)
throws SQLException, NoSuchAlgorithmException
{
PreparedStatement ps = null;
ResultSet rs = null;
// Perform a message digest if necessary
if(null != this.digest)
{
if(logger.isDebugEnabled())
logger.debug("Digesting credentials using " + this.digest);
credentials = Digester.digest(credentials, this.digest);
}
try
{
// First, get the user record.
ps = conn.prepareStatement(getUserSelectStatement());
ps.setString(1, username);
ps.setString(2, credentials);
rs = ps.executeQuery();
if(!rs.next())
return null; // No such user
rs.close(); rs = null;
ps.close(); ps = null;
// Now, get the user's roles
ArrayList roles = new ArrayList();
ps = conn.prepareStatement(getRoleSelect());
ps.setString(1, username);
rs = ps.executeQuery();
while(rs.next())
roles.add(rs.getString("rolename"));
return new SimplePrincipal(username, roles);
}
finally
{
if(null != rs)
try { rs.close(); } catch (SQLException sqle)
{ logger.error("Could not close ResultSet", sqle); }
if(null != ps)
try { ps.close(); } catch (SQLException sqle)
{ logger.error("Could not close PreparedStatement", sqle); }
}
}
/**
* Return true
if the specified Principal has the specified
* security role, within the context of this Realm; otherwise return
* false
. This method can be overridden by Realm
* implementations, but the default is adequate when an instance of
* GenericPrincipal
is used to represent authenticated
* Principals from this Realm.
*
* @param principal Principal for whom the role is to be checked
* @param role Security role to be checked
*/
/**
* Test for role membership.
*
* Use Principal.getName() to get the username from the principal object.
*
* @param principal Principal object representing a user
* @param rolename name of a role to test for membership
*
* @return true if the user is in the role, false otherwise
*/
public boolean isUserInRole(Principal principal, String rolename)
{
if ((principal == null) || (rolename == null) ||
!(principal instanceof SimplePrincipal))
return (false);
SimplePrincipal p = (SimplePrincipal)principal;
return p.isInRole(rolename);
}
/**
* The name of the JNDI JDBC DataSource
*/
protected String dataSourceName;
/**
* The table that holds user data.
*/
protected String userTable;
/**
* The column in the user table that holds the user's name
*/
protected String userNameCol;
/**
* The column in the user table that holds the user's credentials
* (i.e. password).
*/
protected String userCredCol;
/**
* The table that holds the relation between user's and roles
*/
protected String userRoleTable;
/**
* The column in the user role table that names a role
*/
protected String roleNameCol;
/**
* The column in the user table that identifies the user when
* JOINing to the userRoleTable.
*/
protected String userTableUserIdColumn;
/**
* The column in the userRole table that identifies the user when
* JOINing from the userTable.
*/
protected String userRoleTableUserIdColumn;
/**
* The name of the message digest (eg. SHA-256) to be used on
* user credentials.
*/
private String digest;
/**
* Return the name of the JNDI JDBC DataSource.
*
*/
public String getDataSourceName() {
return dataSourceName;
}
/**
* Set the name of the JNDI JDBC DataSource.
*
* @param dataSourceName the name of the JNDI JDBC DataSource
*/
public void setDataSourceName(String dataSourceName) {
this.dataSourceName = dataSourceName;
}
/**
* Return the column in the user role table that names a role.
*
*/
public String getRoleNameCol() {
return roleNameCol;
}
/**
* Set the column in the user role table that names a role.
*
* @param roleNameCol The column name
*/
public void setRoleNameCol(String roleNameCol) {
this.roleNameCol = roleNameCol;
}
/**
* Return the column in the user table that holds the user's credentials.
*
*/
public String getUserCredCol() {
return userCredCol;
}
/**
* Set the column in the user table that holds the user's credentials.
*
* @param userCredCol The column name
*/
public void setUserCredCol(String userCredCol) {
this.userCredCol = userCredCol;
}
/**
* Return the column in the user table that holds the user's name.
*
*/
public String getUserNameCol() {
return userNameCol;
}
/**
* Set the column in the user table that holds the user's name.
*
* @param userNameCol The column name
*/
public void setUserNameCol(String userNameCol) {
this.userNameCol = userNameCol;
}
/**
* Return the table that holds the relation between user's and roles.
*
*/
public String getUserRoleTable() {
return userRoleTable;
}
/**
* Set the table that holds the relation between user's and roles.
*
* @param userRoleTable The table name
*/
public void setUserRoleTable(String userRoleTable) {
this.userRoleTable = userRoleTable;
}
/**
* Return the table that holds user data..
*
*/
public String getUserTable() {
return userTable;
}
/**
* Set the table that holds user data.
*
* @param userTable The table name
*/
public void setUserTable(String userTable) {
this.userTable = userTable;
}
/**
* Sets the column in the user table that identifies the user when
* JOINing to the userTable.
*/
public void setUserTableUserIdColumn(String userTableUserIdColumn) {
this.userTableUserIdColumn = userTableUserIdColumn;
}
/**
* Gets the column in the user table that identifies the user when
* JOINing to the userTable.
*/
public String getUserTableUserIdColumn() {
return userTableUserIdColumn;
}
/**
* Sets the column in the userRole table that identifies the user when
* JOINing from the userTable.
*/
public void setUserRoleTableUserIdColumn(String userRoleTableUserIdColumn) {
this.userRoleTableUserIdColumn = userRoleTableUserIdColumn;
}
/**
* Gets the column in the userRole table that identifies the user when
* JOINing from the userTable.
*/
public String getUserRoleTableUserIdColumn() {
return userRoleTableUserIdColumn;
}
/**
* Sets the message digest (e.g. SHA-256) used for hashing credentials.
*
* @param digest The message digest to use, or null
for no
* hashing (plaintext passwords -- not recommended).
*/
public void setDigest(String digest)
{
this.digest = digest;
}
/**
* Gets the message digest (e.g. SHA-256) used for hashing credentials.
*
* @return The message digest to use, or null
for no
* hashing (plaintext passwords -- not recommended).
*/
public String getDigest()
{
return this.digest;
}
/**
* Open the specified database connection.
*
* @return Connection to the database
*/
protected Connection getConnection()
throws NamingException, SQLException
{
Context context = new InitialContext();
DataSource ds = (DataSource)context.lookup(dataSourceName);
if(null == ds)
throw new NamingException("Found no DataSource for '"
+ dataSourceName + "'");
return ds.getConnection();
}
private String _userSelect;
private String _roleSelect;
private synchronized String getUserSelectStatement()
{
if(null == _userSelect)
_userSelect = "SELECT " + userNameCol
+ " FROM " + userTable
+ " WHERE "
+ userNameCol + "=? AND "
+ userCredCol + "=?";
return _userSelect;
}
private synchronized String getRoleSelect()
{
if(null == _roleSelect)
{
// User userNameCol as the default for userTableUserIdColumn
// and userRoleTableUserIdColumn
if(null == userTableUserIdColumn)
userTableUserIdColumn = userNameCol;
if(null == userRoleTableUserIdColumn)
userRoleTableUserIdColumn = userNameCol;
_roleSelect = "SELECT " + userRoleTable + "." + roleNameCol
+ " FROM " + userTable
+ " JOIN " + userRoleTable
+ " ON " + userTable + "." + userTableUserIdColumn
+ "=" + userRoleTable + "." + userRoleTableUserIdColumn
+ " WHERE "
+ userTable + "." + userNameCol + "=?";
}
return _roleSelect;
}
}