/**
* TNTConcept Easy Enterprise Management by Autentia Real Bussiness Solution S.L. Copyright (C) 2007 Autentia Real Bussiness
* Solution S.L. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation, either version 3 of the License. This program is distributed
* in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of
* the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.autentia.tnt.manager.security;
import static org.acegisecurity.context.SecurityContextHolder.getContext;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Locale;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.ldap.InitialDirContextFactory;
import org.acegisecurity.ldap.LdapCallback;
import org.acegisecurity.ldap.LdapTemplate;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DataRetrievalFailureException;
import com.autentia.tnt.businessobject.Setting;
import com.autentia.tnt.businessobject.User;
import com.autentia.tnt.dao.DataAccException;
import com.autentia.tnt.dao.DataIntegrityException;
import com.autentia.tnt.dao.DataNotFoundException;
import com.autentia.tnt.dao.hibernate.SettingDAO;
import com.autentia.tnt.dao.hibernate.UserDAO;
import com.autentia.tnt.dao.search.SettingSearch;
import com.autentia.tnt.manager.security.exception.SecException;
import com.autentia.tnt.util.ConfigurationUtil;
import com.autentia.tnt.util.HibernateUtil;
import com.autentia.tnt.util.SettingPath;
import com.autentia.tnt.util.SpringUtils;
import com.autentia.tnt.version.Version;
/**
* @author ivan
*/
public abstract class AuthenticationManager implements UserDetailsService {
protected static final Log log = LogFactory.getLog(AuthenticationManager.class);
/** Algorithm used to store passwords in database */
public static final String HASH_ALGORITHM = "SHA-1";
/** Administrator role id */
private int administratorRoleId;
/** User DAO */
private UserDAO userDAO = new UserDAO();
/** Settings manager */
private final SettingDAO settings = new SettingDAO();
/** IUserRolesService implementation */
private IUserRolesService userRolesService;
/**
* @deprecated the preferred way to get the default manager is using Spring injection
*/
public static AuthenticationManager getDefault() {
return (AuthenticationManager)SpringUtils.getSpringBean("userDetailsService");
}
/**
* Constructor
*
* @param cfg configuration object
* @param userRolesService delegate for getting user's roles
*/
public AuthenticationManager(ConfigurationUtil cfg, IUserRolesService userRolesService) {
this.userRolesService = userRolesService;
this.administratorRoleId = cfg.getRoleAdminId();
}
/**
* Get currently logged-in principal
*
* @return the active Principal
*/
public Principal getCurrentPrincipal() {
Object ret = getContext().getAuthentication().getPrincipal();
return (ret instanceof Principal) ? (Principal)ret : null;
}
public abstract boolean checkPassword(User user, String passwd);
public abstract void changePassword(User user, String password);
/**
* Load a User for ACEGI given its user name
*
* @param username user name
* @throws org.acegisecurity.userdetails.UsernameNotFoundException
* @throws org.springframework.dao.DataAccessException
* @return the user object description as specified by ACEGI
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
try {
Version db = Version.getDatabaseVersion();
Version code = Version.getApplicationVersion();
if (db.compareTo(code, Version.MINOR) == 0) {
log.info("loadUserByUsername - getting user " + username + " using Hibernate");
User user = userDAO.searchByLogin(username);
GrantedAuthority[] auths = userRolesService.getAuthorities(user);
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
for (GrantedAuthority auth : auths) {
sb.append(auth);
sb.append(" ");
}
log.debug("loadUserByUsername - user roles: " + sb);
}
final Principal principal = new Principal(user, auths);
// setting user preferred Locale
final SettingSearch s = new SettingSearch();
s.setName(SettingPath.GENERAL_PREFERRED_LOCALE);
s.setOwnerId(user.getId());
final List<Setting> vals = settings.search(s, null);
final Setting val = (vals != null && vals.size() > 0) ? vals.get(0) : null;
if (val != null) {
final Locale local = new Locale(val.getValue());
principal.setLocale(local);
}
return principal;
} else {
log.info("loadUserByUsername - getting user " + username + " using JDBC");
return jdbcSearchByLogin(username);
}
} catch (SecException e) {
log.warn("loadUserByUsername - exception", e);
throw new DataRetrievalFailureException("Error getting roles for user: " + username, e);
} catch (DataIntegrityException e) {
log.warn("loadUserByUsername - exception", e);
throw new DataIntegrityViolationException("Inconsistent user name: " + username, e);
} catch (DataNotFoundException e) {
log.warn("loadUserByUsername - exception", e);
throw new UsernameNotFoundException("User not found: " + username, e);
} catch (DataAccException e) {
log.warn("loadUserByUsername - exception", e);
throw new DataRetrievalFailureException("Error getting user: " + username, e);
} catch (SQLException e) {
log.warn("loadUserByUsername - exception", e);
throw new DataRetrievalFailureException("Error getting user: " + username, e);
}
}
/**
* Reset user password.
*
* @param user the user
* @return the new password
*/
public abstract String resetPassword(User user, String[] rnd0, String[] rnd1, String[] rnd2, String[] rnd3,
String[] rnd4);
/**
* Generate a new random password
*
* @return a new random password
*/
protected String generateRandomPassword(String[] rnd0, String[] rnd1, String[] rnd2, String[] rnd3, String[] rnd4) {
StringBuilder ret = new StringBuilder();
// Get lists of random words. We could cache these, but this method is
// rarely called and caching would
// depend on user locale, so we prefer to waste CPU better than memory.
// Get a true random number generator
SecureRandom rnd;
try {
rnd = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException ex) {
rnd = new SecureRandom();
}
// Generate random numbers
int i0 = rnd.nextInt(rnd0.length);
int i1 = rnd.nextInt(rnd1.length);
int i2 = rnd.nextInt(rnd2.length);
int i3 = rnd.nextInt(rnd3.length);
int i4 = rnd.nextInt(rnd4.length);
// Compose password
ret.append(rnd0[i0]);
ret.append(rnd1[i1]);
ret.append(rnd2[i2]);
ret.append(rnd3[i3]);
ret.append(rnd4[i4]);
return ret.toString();
}
/**
* <p>
* Get user details given her login. This method accesses tables using JDBC. Thus, is should only be used if we need to
* log into the single-user console to migrate tables (in that case, we cannot rely on Hibernate to get the users until
* we have really finished migrating tables and data).
* </p>
*
* @return a MiniUser object (minimal representation of a user)
* @param login user login
* @throws com.autentia.tnt.dao.DataNotFoundException if user does not exist
* @throws com.autentia.tnt.dao.DataIntegrityException if user is duplicated in DB
* @throws com.autentia.tnt.dao.DataAccException if something fails accessing DB
*/
private Principal jdbcSearchByLogin(String login)
throws DataAccException, DataNotFoundException, DataIntegrityException {
Session ses = HibernateUtil.getSessionFactory().openSession();
Connection con = ses.connection();
Principal ret;
try {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = con.prepareStatement("select u.id,u.login,u.password,u.active,u.name,r.id roleId "
+ "from User u,Role r " + "where u.roleId=r.id " + "and u.login=?");
stmt.setString(1, login);
rs = stmt.executeQuery();
if (rs.next()) {
int roleId = rs.getInt("roleId");
ret = new Principal(rs.getInt("id"), 0, // In console mode we won't need departmentId so
// we leave it blank. We can't get
// it from the database because in versions prior to
// 0.4 the field didn't exist
rs.getString("login"), rs.getString("password"), rs.getBoolean("active"),
rs.getString("name"), roleId, (roleId == administratorRoleId)
? new GrantedAuthority[] { Permission.Action_Console } : new GrantedAuthority[] {});
if (rs.next()) {
throw new DataIntegrityException(User.class, "Duplicated user login: " + login);
}
} else {
throw new DataNotFoundException(User.class, login);
}
} catch (SQLException e) {
throw new DataAccException("Error loading user: " + login, e);
} finally {
if (rs != null) try {
rs.close();
} catch (SQLException e) {
log.error("jdbcSearchByLogin - Error al liberar el resultset", e);
}
;
if (stmt != null) try {
stmt.close();
} catch (SQLException e) {
log.error("jdbcSearchByLogin - Error al liberar el statement", e);
}
;
}
} finally {
ses.close();
}
return ret;
}
}