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; } }