/* * JBoss, Home of Professional Open Source * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.seam.wiki.core.action; import org.jboss.seam.Component; import org.jboss.seam.ScopeType; import org.jboss.seam.international.StatusMessages; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.faces.Renderer; import org.jboss.seam.log.Log; import org.jboss.seam.wiki.core.action.prefs.UserManagementPreferences; import org.jboss.seam.wiki.core.action.prefs.WikiPreferences; import org.jboss.seam.wiki.core.dao.UserDAO; import org.jboss.seam.wiki.core.model.User; import org.jboss.seam.wiki.preferences.Preferences; import org.jboss.seam.wiki.util.Hash; import static org.jboss.seam.international.StatusMessage.Severity.ERROR; import static org.jboss.seam.international.StatusMessage.Severity.WARN; import static org.jboss.seam.international.StatusMessage.Severity.INFO; import javax.persistence.EntityManager; import java.io.Serializable; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * The password reset feature works as follows: * * - User enters username and e-mail address. The account does not have to be activated, so * the "Reset Password" functionality can also serve as "Resend Activation E-Mail". * * - Both username and e-mail address are checked with what we have in the database. * * - An activation code is generated and stored in the database for this user account. * * - An e-mail with the activation code is send to the users e-mail address. * * - If the user clicks on the activation link, the login form on the page will * switch to a password reset form. (If the activation code was correct.) * * - After typing in the password twice, the user account gets a new password and * we also activate it. * * * @author Christian Bauer */ @Name("userPasswordReset") @Scope(ScopeType.CONVERSATION) public class UserPasswordReset implements Serializable { public static final String RESET_PASSWORD_OF_USER = "resetPasswordOfUser"; @Logger Log log; @In private StatusMessages statusMessages; @In("#{preferences.get('UserManagement')}") UserManagementPreferences prefs; @In(create = true) private Renderer renderer; @In private UserDAO userDAO; @In protected EntityManager entityManager; @In private Hash hashUtil; private String activationCode; private String username; private String email; private String password; private String passwordControl; public String getActivationCode() { return activationCode; } public void setActivationCode(String activationCode) { this.activationCode = activationCode; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPasswordControl() { return passwordControl; } public void setPasswordControl(String passwordControl) { this.passwordControl = passwordControl; } public void sendResetPasswordEmail() { log.debug("trying to reset password of user: " + username); User user = getUserForEmail(username, email); if (user == null) { statusMessages.addFromResourceBundleOrDefault( WARN, "lacewiki.msg.resetPassword.NotValid", "Your account and e-mail address information didn't match, please try again to reset your password." ); username = null; email = null; return; } // Set activation code (unique user in time) String seed = user.getUsername() + System.currentTimeMillis() + prefs.getActivationCodeSalt(); user.setActivationCode( ((Hash) Component.getInstance(Hash.class)).hash(seed) ); // TODO: Flush by side effect? try { // Outject for email Contexts.getEventContext().set(RESET_PASSWORD_OF_USER, user); // Send confirmation email renderer.render("/themes/" + Preferences.instance().get(WikiPreferences.class).getThemeName() + "/mailtemplates/resetPassword.xhtml"); statusMessages.addFromResourceBundleOrDefault( INFO, "lacewiki.msg.resetPassword.EmailSent", "A new activation code has been sent to your e-mail address, please read this e-mail to reset your password." ); } catch (Exception ex) { statusMessages.add(WARN, "Couldn't send password reset email: " + ex.getMessage()); } } public String prepare() { User user = userDAO.findUserWithActivationCode(activationCode); if (user != null) { log.debug("preparing password reset of: " + user); user.setActivationCode(null); // Outject for form Contexts.getSessionContext().set(RESET_PASSWORD_OF_USER, user); return "prepared"; } else { return "notFound"; } } public void reset() { User user = (User)Component.getInstance(RESET_PASSWORD_OF_USER); if (user == null) { throw new IllegalStateException("No user for password reset in SESSION context"); } // Validate if (!passwordAndControlNotNull() || !passwordMatchesRegex() || !passwordMatchesControl()) { // Force re-entry setPassword(null); setPasswordControl(null); return; } log.debug("resetting password of: " + user); User persistentUser = userDAO.findUser(user.getId()); persistentUser.setPasswordHash(hashUtil.hash(getPassword())); // As a side effect, also activate the user! http://jira.jboss.com/jira/browse/JBSEAM-2687 persistentUser.setActivated(true); Contexts.getSessionContext().remove(RESET_PASSWORD_OF_USER); statusMessages.addFromResourceBundleOrDefault( INFO, "lacewiki.msg.resetPassword.Complete", "Successfully reset password of account '{0}', please log in.", persistentUser.getUsername() ); } private User getUserForEmail(String username, String email) { if (User.GUEST_USERNAME.equals(username)) return null; User user = userDAO.findUser(username, false, true); return user != null && user.getEmail().equals(email) ? user : null; } public boolean passwordAndControlNotNull() { if (getPassword() == null || getPassword().length() == 0 || getPasswordControl() == null || getPasswordControl().length() == 0) { statusMessages.addFromResourceBundleOrDefault( WARN, "lacewiki.msg.PasswordOrPasswordControlEmpty", "Please enter your password twice!" ); return false; } return true; } public boolean passwordMatchesRegex() { Matcher matcher = Pattern.compile(prefs.getPasswordRegex()).matcher(getPassword()); if (!matcher.find()) { statusMessages.addFromResourceBundleOrDefault( WARN, "lacewiki.msg.PasswordDoesntMatchPattern", "Password does not match the pattern: {0}", prefs.getPasswordRegex() ); return false; } return true; } public boolean passwordMatchesControl() { if (!password.equals(passwordControl) ) { statusMessages.addFromResourceBundleOrDefault( WARN, "lacewiki.msg.PasswordControlNoMatch", "The passwords don't match." ); return false; } return true; } }