package com.hwlcn.security.realm.jdbc;
import com.hwlcn.security.authc.*;
import com.hwlcn.security.authz.AuthorizationException;
import com.hwlcn.security.authz.SimpleAuthorizationInfo;
import com.hwlcn.security.authz.AuthorizationInfo;
import com.hwlcn.security.config.ConfigurationException;
import com.hwlcn.security.realm.AuthorizingRealm;
import com.hwlcn.security.subject.PrincipalCollection;
import com.hwlcn.security.util.ByteSource;
import com.hwlcn.security.util.JdbcUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
public class JdbcRealm extends AuthorizingRealm {
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
/**
* The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
*/
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";
/**
* The default query used to retrieve the roles that apply to a user.
*/
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
/**
* The default query used to retrieve permissions that apply to a particular role.
*/
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
/**
* Password hash salt configuration. <ul>
* <li>NO_SALT - password hashes are not salted.</li>
* <li>CRYPT - password hashes are stored in unix crypt format.</li>
* <li>COLUMN - salt is in a separate column in the database.</li>
* <li>EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(String)} will be called
* to get the salt</li></ul>
*/
public enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL};
/*--------------------------------------------
| I N S T A N C E V A R I A B L E S |
============================================*/
protected DataSource dataSource;
protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;
protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;
protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
protected boolean permissionsLookupEnabled = false;
protected SaltStyle saltStyle = SaltStyle.NO_SALT;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setAuthenticationQuery(String authenticationQuery) {
this.authenticationQuery = authenticationQuery;
}
public void setUserRolesQuery(String userRolesQuery) {
this.userRolesQuery = userRolesQuery;
}
public void setPermissionsQuery(String permissionsQuery) {
this.permissionsQuery = permissionsQuery;
}
public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) {
this.permissionsLookupEnabled = permissionsLookupEnabled;
}
public void setSaltStyle(SaltStyle saltStyle) {
this.saltStyle = saltStyle;
if (saltStyle == SaltStyle.COLUMN && authenticationQuery.equals(DEFAULT_AUTHENTICATION_QUERY)) {
authenticationQuery = DEFAULT_SALTED_AUTHENTICATION_QUERY;
}
}
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
if (username == null) {
throw new AccountException("Null usernames are not allowed by this realm.");
}
Connection conn = null;
SimpleAuthenticationInfo info = null;
try {
conn = dataSource.getConnection();
String password = null;
String salt = null;
switch (saltStyle) {
case NO_SALT:
password = getPasswordForUser(conn, username)[0];
break;
case CRYPT:
throw new ConfigurationException("Not implemented yet");
case COLUMN:
String[] queryResults = getPasswordForUser(conn, username);
password = queryResults[0];
salt = queryResults[1];
break;
case EXTERNAL:
password = getPasswordForUser(conn, username)[0];
salt = getSaltForUser(username);
}
if (password == null) {
throw new UnknownAccountException("No account found for user [" + username + "]");
}
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
if (salt != null) {
info.setCredentialsSalt(ByteSource.Util.bytes(salt));
}
} catch (SQLException e) {
final String message = "There was a SQL error while authenticating user [" + username + "]";
if (log.isErrorEnabled()) {
log.error(message, e);
}
throw new AuthenticationException(message, e);
} finally {
JdbcUtils.closeConnection(conn);
}
return info;
}
private String[] getPasswordForUser(Connection conn, String username) throws SQLException {
String[] result;
boolean returningSeparatedSalt = false;
switch (saltStyle) {
case NO_SALT:
case CRYPT:
case EXTERNAL:
result = new String[1];
break;
default:
result = new String[2];
returningSeparatedSalt = true;
}
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(authenticationQuery);
ps.setString(1, username);
rs = ps.executeQuery();
boolean foundResult = false;
while (rs.next()) {
if (foundResult) {
throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
}
result[0] = rs.getString(1);
if (returningSeparatedSalt) {
result[1] = rs.getString(2);
}
foundResult = true;
}
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return result;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//null usernames are invalid
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
String username = (String) getAvailablePrincipal(principals);
Connection conn = null;
Set<String> roleNames = null;
Set<String> permissions = null;
try {
conn = dataSource.getConnection();
// Retrieve roles and permissions from database
roleNames = getRoleNamesForUser(conn, username);
if (permissionsLookupEnabled) {
permissions = getPermissions(conn, username, roleNames);
}
} catch (SQLException e) {
final String message = "There was a SQL error while authorizing user [" + username + "]";
if (log.isErrorEnabled()) {
log.error(message, e);
}
// Rethrow any SQL errors as an authorization exception
throw new AuthorizationException(message, e);
} finally {
JdbcUtils.closeConnection(conn);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
info.setStringPermissions(permissions);
return info;
}
protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
Set<String> roleNames = new LinkedHashSet<String>();
try {
ps = conn.prepareStatement(userRolesQuery);
ps.setString(1, username);
rs = ps.executeQuery();
while (rs.next()) {
String roleName = rs.getString(1);
if (roleName != null) {
roleNames.add(roleName);
} else {
if (log.isWarnEnabled()) {
log.warn("Null role name found while retrieving role names for user [" + username + "]");
}
}
}
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return roleNames;
}
protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
PreparedStatement ps = null;
Set<String> permissions = new LinkedHashSet<String>();
try {
ps = conn.prepareStatement(permissionsQuery);
for (String roleName : roleNames) {
ps.setString(1, roleName);
ResultSet rs = null;
try {
rs = ps.executeQuery();
while (rs.next()) {
String permissionString = rs.getString(1);
permissions.add(permissionString);
}
} finally {
JdbcUtils.closeResultSet(rs);
}
}
} finally {
JdbcUtils.closeStatement(ps);
}
return permissions;
}
protected String getSaltForUser(String username) {
return username;
}
}