/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.riotfamily.core.security.auth;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.riotfamily.common.beans.property.PropertyUtils;
import org.riotfamily.common.util.HashUtils;
import org.riotfamily.core.dao.hibernate.HqlDao;
import org.riotfamily.core.screen.list.ListParamsImpl;
import org.springframework.util.Assert;
/**
* RiotUserDao that performs look-ups via Hibernate.
*
* @author Felix Gnass [fgnass at neteye dot de]
* @since 6.5
*/
public class HibernateUserDao extends HqlDao
implements RiotUserDao {
public static final String DEFAULT_USERNAME = "admin";
public static final String DEFAULT_PASSWORD = "admin";
private String usernameProperty = "id";
private String passwordProperty = "password";
private String newPasswordProperty = "newPassword";
private boolean hashPasswords = true;
private RiotUser initialUser;
public HibernateUserDao(SessionFactory sessionFactory) {
super(sessionFactory);
}
/**
* Sets the user class.
* @throws IllegalArgumentException if the given class does not implement
* the {@link RiotUser} interface.
*/
@Override
public void setEntityClass(Class<?> entityClass) {
Assert.isAssignable(RiotUser.class, entityClass);
super.setEntityClass(entityClass);
}
/**
* Sets the name of the property that holds the username. This property is
* used by {@link #findUserByCredentials(String, String)} to look up
* a user upon login.
*/
public void setUsernameProperty(String usernameProperty) {
Assert.notNull(usernameProperty);
this.usernameProperty = usernameProperty;
}
/**
* Sets the name of the property that holds the (possibly hashed) password.
* This property is used by {@link #findUserByCredentials(String, String)}
* to look up a user upon login.
*/
public void setPasswordProperty(String passwordProperty) {
Assert.notNull(passwordProperty);
this.passwordProperty = passwordProperty;
}
/**
* Sets whether hashed passwords should be used instead of plain text.
* Default is <code>true</code>.
* @see #hashPassword(String)
*/
public void setHashPasswords(boolean hashPasswords) {
this.hashPasswords = hashPasswords;
}
/**
* Sets the name of the (transient) property that holds the new plain text
* password. When {@link #setHashPasswords(boolean) hashed passwords} are
* used, this property is checked upon updates. If the property contains a
* non null value, this value is used to create a new password hash.
*/
public void setNewPasswordProperty(String newPasswordProperty) {
this.newPasswordProperty = newPasswordProperty;
}
/**
* Sets the initial user object that is persisted when no other user exists.
* If set to <code>null</code> (default), a new instance of the
* {@link #setEntityClass(Class) entity class} is created via reflection.
*/
public void setInitialUser(RiotUser initialUser) {
this.initialUser = initialUser;
}
/**
* Creates (or validates) the initial user.
* <p>
* Note: The user is not saved to the database at this point, as this
* method is not invoked within a transaction. The user will be persisted
* when {@link #findUserByCredentials(String, String)} or
* {@link #findUserById(String)} is called and the database does not
* contain any user objects.
* </p>
*/
@Override
protected void initDao() throws Exception {
if (initialUser != null) {
Assert.isInstanceOf(getEntityClass(), initialUser);
}
else {
initialUser = (RiotUser) getEntityClass().newInstance();
PropertyUtils.setProperty(initialUser, usernameProperty,
DEFAULT_USERNAME);
String password = hashPasswords
? HashUtils.md5(DEFAULT_PASSWORD)
: DEFAULT_PASSWORD;
PropertyUtils.setProperty(initialUser, passwordProperty, password);
}
super.initDao();
}
/**
* Hashes the given password. The default implementation creates a MD5
* sum. Subclasses may overwrite this method to use a different algorithm
* or add a salt.
*/
protected String hashPassword(String plainText) {
return HashUtils.md5(plainText);
}
public void updatePassword(RiotUser user, String newPassword) {
PropertyUtils.setProperty(user, newPasswordProperty, newPassword);
hashNewPassword(user);
getSession().update(user);
}
/**
* Performs a database lookup with the given credentials. If no matching
* user is found, {@link #findInitialUser(String, String)} is called.
*/
public RiotUser findUserByCredentials(String username, String password) {
if (hashPasswords) {
password = hashPassword(password);
}
Criteria c = getSession().createCriteria(getEntityClass())
.add(Restrictions.eq(usernameProperty, username))
.add(Restrictions.eq(passwordProperty, password));
RiotUser user = (RiotUser) c.uniqueResult();
if (user == null) {
user = findInitialUser(username, password);
}
return user;
}
/**
* If no user exists, the given credentials are compared with the ones of
* the initial user. If username and password match, the initial user is
* persisted and returned.
*/
protected RiotUser findInitialUser(String username, String password) {
if (!anyUserExists()) {
save(initialUser, null);
String initialUsername = PropertyUtils.getPropertyAsString(initialUser, usernameProperty);
Assert.notNull(initialUsername, "The initial user's '" + usernameProperty + "' property must not be null.");
String initialPassword = PropertyUtils.getPropertyAsString(initialUser, passwordProperty);
Assert.notNull(initialPassword, "The initial user's '" + passwordProperty + "' property must not be null.");
if (initialUsername.equals(username) && initialPassword.equals(password)) {
return initialUser;
}
}
return null;
}
/**
* Performs a database lookup with the given userId. If no matching
* user is found, {@link #findInitialUser(String)} is called.
*/
public RiotUser findUserById(String userId) {
RiotUser user = (RiotUser) load(userId);
if (user == null) {
user = findInitialUser(userId);
}
return user;
}
/**
* If no user exists, the given userId is compared with the one of the
* initial user. If the id matches, the initial user is persisted and
* returned.
*/
protected RiotUser findInitialUser(String userId) {
if (!anyUserExists() && userId.equals(initialUser.getUserId())) {
save(initialUser, null);
return initialUser;
}
return null;
}
/**
* Returns whether any user exists in the database.
*/
protected boolean anyUserExists() {
return list(null, new ListParamsImpl()).size() > 0;
}
/**
* If {@link #setHashPasswords(boolean) hashed passwords} are enabled,
* this method checks whether the {@link #setNewPasswordProperty(String)
* newPassword} property contains a non-null value and sets the value of
* the {@link #setPasswordProperty(String) password} property to the
* {@link #hashPassword(String) hashed value}.
*/
private void hashNewPassword(Object user) {
if (hashPasswords) {
String newPassword = PropertyUtils.getPropertyAsString(user, newPasswordProperty);
if (newPassword != null) {
String hash = hashPassword(newPassword);
PropertyUtils.setProperty(user, passwordProperty, hash);
PropertyUtils.setProperty(user, newPasswordProperty, null);
}
}
}
/**
* Invokes {@link #hashNewPassword(Object)} and delegates the call to the
* super method.
*/
public void save(Object entity, Object parent) {
hashNewPassword(entity);
super.save(entity, parent);
}
/**
* Invokes {@link #hashNewPassword(Object)} and delegates the call to the
* super method.
*/
public Object update(Object entity) {
hashNewPassword(entity);
return super.update(entity);
}
}