NIHVIVO-2279 Implement account editing and password change.

This commit is contained in:
j2blake 2011-05-26 16:29:33 +00:00
parent d6bacf0c95
commit a1915c8398
22 changed files with 1006 additions and 223 deletions

View file

@ -3,8 +3,10 @@
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -18,6 +20,7 @@ import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.ontology.OntModel;
import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap;
@ -32,6 +35,12 @@ import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
public abstract class UserAccountsPage {
private static final Log log = LogFactory.getLog(UserAccountsPage.class);
/**
* After the account is created, or the password is reset, the user has this
* many days to repond to the email.
*/
protected static final int DAYS_TO_USE_PASSWORD_LINK = 90;
protected final VitroRequest vreq;
protected final ServletContext ctx;
protected final OntModel userAccountsModel;
@ -82,6 +91,17 @@ public abstract class UserAccountsPage {
return (value != null);
}
/**
* Treat the presence of a certain parameter, with a desired value, as a
* boolean flag.
*
* An example would be radio buttons with values of "yes" and
* "no". The expected value would be "yes".
*/
protected boolean isParameterAsExpected(String key, String expected) {
return expected.equals(getStringParameter(key, ""));
}
/**
* Create a list of all known PermissionSets.
*/
@ -107,12 +127,25 @@ public abstract class UserAccountsPage {
map.put("add", UrlBuilder.getUrl("/accountsAdmin/add"));
map.put("delete", UrlBuilder.getUrl("/accountsAdmin/delete"));
map.put("createPassword", UrlBuilder.getUrl("/accounts/createPassword"));
map.put("resetPassword", UrlBuilder.getUrl("/accounts/resetPassword"));
return map;
}
protected static String editAccountUrl(String uri) {
return UrlBuilder.getUrl("/accountsAdmin/edit",
new ParamMap("editAccount", uri));
return UrlBuilder.getUrl("/accountsAdmin/edit", new ParamMap(
"editAccount", uri));
}
protected Date figureExpirationDate() {
Calendar c = Calendar.getInstance();
c.add(Calendar.DATE, DAYS_TO_USE_PASSWORD_LINK);
return c.getTime();
}
protected boolean checkPasswordLength(String pw) {
return pw.length() >= UserAccount.MIN_PASSWORD_LENGTH
&& pw.length() <= UserAccount.MAX_PASSWORD_LENGTH;
}
}

View file

@ -68,8 +68,9 @@ public class UserAccountsAddPage extends UserAccountsPage {
emailAddress = getStringParameter(PARAMETER_EMAIL_ADDRESS, "");
firstName = getStringParameter(PARAMETER_FIRST_NAME, "");
lastName = getStringParameter(PARAMETER_LAST_NAME, "");
selectedRoleUri = getRoleChoices();
associateWithProfile = getAssociateFlag();
selectedRoleUri = getStringParameter(PARAMETER_ROLE, "");
associateWithProfile = isParameterAsExpected(
PARAMETER_ASSOCIATE_WITH_PROFILE, "yes");
strategy.parseAdditionalParameters();
}
@ -126,22 +127,6 @@ public class UserAccountsAddPage extends UserAccountsPage {
strategy.notifyUser();
}
/** What role are they asking for? */
private String getRoleChoices() {
String[] roles = vreq.getParameterValues(PARAMETER_ROLE);
if ((roles == null) || (roles.length == 0)) {
return "";
} else {
return roles[0];
}
}
/** Are they associating with an Individual profile? */
private boolean getAssociateFlag() {
return "yes".equals(getStringParameter(
PARAMETER_ASSOCIATE_WITH_PROFILE, "no"));
}
public final ResponseValues showPage() {
Map<String, Object> body = new HashMap<String, Object>();

View file

@ -6,8 +6,6 @@ import static javax.mail.Message.RecipientType.TO;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@ -25,7 +23,7 @@ import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage;
public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
protected final UserAccountsAddPage page;
public static UserAccountsAddPageStrategy getInstance(VitroRequest vreq,
public static UserAccountsAddPageStrategy getInstance(VitroRequest vreq,
UserAccountsAddPage page, boolean emailEnabled) {
if (emailEnabled) {
return new EmailStrategy(vreq, page);
@ -34,7 +32,8 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
}
}
public UserAccountsAddPageStrategy(VitroRequest vreq, UserAccountsAddPage page) {
public UserAccountsAddPageStrategy(VitroRequest vreq,
UserAccountsAddPage page) {
super(vreq);
this.page = page;
}
@ -57,7 +56,6 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
private static class EmailStrategy extends UserAccountsAddPageStrategy {
public static final String CREATE_PASSWORD_URL = "/accounts/createPassword";
private static final int DAYS_TO_ACTIVATE_ACCOUNT = 90;
private boolean sentEmail;
@ -81,12 +79,6 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
u.setPasswordLinkExpires(figureExpirationDate().getTime());
u.setStatus(Status.INACTIVE);
}
private Date figureExpirationDate() {
Calendar c = Calendar.getInstance();
c.add(Calendar.DATE, DAYS_TO_ACTIVATE_ACCOUNT);
return c.getTime();
}
@Override
protected void addMoreBodyValues(Map<String, Object> body) {
@ -117,9 +109,9 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
String email = page.getAddedAccount().getEmailAddress();
String hash = page.getAddedAccount()
.getPasswordLinkExpiresHash();
String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL, "user",
email, "key", hash);
String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL,
"user", email, "key", hash);
URL context = new URL(vreq.getRequestURL().toString());
URL url = new URL(context, relativeUrl);
return url.toExternalForm();
@ -127,7 +119,7 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
return "error_creating_password_link";
}
}
@Override
protected boolean wasPasswordEmailSent() {
return sentEmail;
@ -156,10 +148,8 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
@Override
protected void parseAdditionalParameters() {
initialPassword = getStringParameter(
PARAMETER_INITIAL_PASSWORD, "");
confirmPassword = getStringParameter(
PARAMETER_CONFIRM_PASSWORD, "");
initialPassword = getStringParameter(PARAMETER_INITIAL_PASSWORD, "");
confirmPassword = getStringParameter(PARAMETER_CONFIRM_PASSWORD, "");
}
@Override
@ -184,6 +174,8 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
protected void addMoreBodyValues(Map<String, Object> body) {
body.put("initialPassword", initialPassword);
body.put("confirmPassword", confirmPassword);
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
}
@Override

View file

@ -9,12 +9,15 @@ import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.ManageUserAccounts;
import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
/**
* Parcel out the different actions required of the Administrators portion of the UserAccounts GUI.
* Parcel out the different actions required of the Administrators portion of
* the UserAccounts GUI.
*/
public class UserAccountsAdminController extends FreemarkerHttpServlet {
private static final Log log = LogFactory
@ -64,7 +67,9 @@ public class UserAccountsAdminController extends FreemarkerHttpServlet {
private ResponseValues handleEditRequest(VitroRequest vreq) {
UserAccountsEditPage page = new UserAccountsEditPage(vreq);
if (page.isSubmit() && page.isValid()) {
if (page.isBogus()) {
return showHomePage(vreq, page.getBogusMessage());
} else if (page.isSubmit() && page.isValid()) {
page.updateAccount();
UserAccountsListPage listPage = new UserAccountsListPage(vreq);
return listPage.showPageWithUpdatedAccount(
@ -87,4 +92,9 @@ public class UserAccountsAdminController extends FreemarkerHttpServlet {
return page.showPage();
}
private ResponseValues showHomePage(VitroRequest vreq, String message) {
DisplayMessage.setMessage(vreq, message);
return new RedirectResponseValues("/");
}
}

View file

@ -2,75 +2,217 @@
package edu.cornell.mannlib.vitro.webapp.controller.accounts.admin;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsPage;
import edu.cornell.mannlib.vitro.webapp.controller.accounts.user.UserAccountsUserController;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
/**
* TODO present the form. Get the submission.
* TODO present the form. Get the submission.
*
* TODO If email is available, present the reset flag with message templage, and send email
* TODO If email is available, present the reset flag with message templage, and
* send email
*
* TODO if email is not available, allow password change with checks for validity
* TODO if email is not available, allow password change with checks for
* validity
*
* TODO If successful, go to AccountsList with message and optional password message.
* TODO If successful, go to AccountsList with message and optional password
* message.
*
* TODO if unsuccessful, go back to the page, with errors.
*
* TODO How much of this can be shared with AddPage? Email templates?
*/
public class UserAccountsEditPage extends UserAccountsPage {
private static final Log log = LogFactory
.getLog(UserAccountsEditPage.class);
private static final String PARAMETER_SUBMIT = "submitEdit";
private static final String PARAMETER_USER_URI = "editAccount";
private static final String PARAMETER_EMAIL_ADDRESS = "emailAddress";
private static final String PARAMETER_FIRST_NAME = "firstName";
private static final String PARAMETER_LAST_NAME = "lastName";
private static final String PARAMETER_ROLE = "role";
private static final String PARAMETER_ASSOCIATE_WITH_PROFILE = "associate";
private static final String ERROR_NO_EMAIL = "errorEmailIsEmpty";
private static final String ERROR_EMAIL_IN_USE = "errorEmailInUse";
private static final String ERROR_NO_FIRST_NAME = "errorFirstNameIsEmpty";
private static final String ERROR_NO_LAST_NAME = "errorLastNameIsEmpty";
private static final String ERROR_NO_ROLE = "errorNoRoleSelected";
private static final String TEMPLATE_NAME = "userAccounts-edit.ftl";
private final UserAccountsEditPageStrategy strategy;
/* The request parameters */
private boolean submit;
private String userUri = "";
private String emailAddress = "";
private String firstName = "";
private String lastName = "";
private String selectedRoleUri = "";
private boolean associateWithProfile;
private UserAccount userAccount;
/** The result of checking whether this request is even appropriate. */
private String bogusMessage = "";
/** The result of validating a "submit" request. */
private String errorCode = "";
public UserAccountsEditPage(VitroRequest vreq) {
super(vreq);
this.strategy = UserAccountsEditPageStrategy.getInstance(vreq, this,
isEmailEnabled(vreq));
parseRequestParameters();
validateUserAccountInfo();
if (isSubmit() && !isBogus()) {
validateParameters();
}
}
public ResponseValues showPage() {
return new TemplateResponseValues(TEMPLATE_NAME);
private void parseRequestParameters() {
submit = isFlagOnRequest(PARAMETER_SUBMIT);
userUri = getStringParameter(PARAMETER_USER_URI, "");
emailAddress = getStringParameter(PARAMETER_EMAIL_ADDRESS, "");
firstName = getStringParameter(PARAMETER_FIRST_NAME, "");
lastName = getStringParameter(PARAMETER_LAST_NAME, "");
selectedRoleUri = getStringParameter(PARAMETER_ROLE, "");
associateWithProfile = isParameterAsExpected(
PARAMETER_ASSOCIATE_WITH_PROFILE, "yes");
strategy.parseAdditionalParameters();
}
/**
* @return
*/
public UserAccount updateAccount() {
// TODO Auto-generated method stub
throw new RuntimeException("UserAccountsEditPage.updateAccount() not implemented.");
private void validateUserAccountInfo() {
userAccount = userAccountsDao.getUserAccountByUri(userUri);
if (userAccount == null) {
log.warn("Edit account for '" + userUri
+ "' is bogus: no such user");
bogusMessage = UserAccountsUserController.BOGUS_STANDARD_MESSAGE;
return;
}
}
/**
* @return
*/
public boolean wasPasswordEmailSent() {
// TODO Auto-generated method stub
throw new RuntimeException("UserAccountsEditPage.wasPasswordEmailSent() not implemented.");
public boolean isBogus() {
return !bogusMessage.isEmpty();
}
/**
* @return
*/
public UserAccount getUpdatedAccount() {
// TODO Auto-generated method stub
throw new RuntimeException("UserAccountsEditPage.getUpdatedAccount() not implemented.");
public String getBogusMessage() {
return bogusMessage;
}
/**
* @return
*/
public boolean isValid() {
// TODO Auto-generated method stub
throw new RuntimeException("UserAccountsEditPage.isValid() not implemented.");
}
/**
* @return
*/
public boolean isSubmit() {
// TODO Auto-generated method stub
throw new RuntimeException("UserAccountsEditPage.isSubmit() not implemented.");
return submit;
}
private void validateParameters() {
if (emailAddress.isEmpty()) {
errorCode = ERROR_NO_EMAIL;
} else if (emailIsChanged() && isEmailInUse()) {
errorCode = ERROR_EMAIL_IN_USE;
} else if (firstName.isEmpty()) {
errorCode = ERROR_NO_FIRST_NAME;
} else if (lastName.isEmpty()) {
errorCode = ERROR_NO_LAST_NAME;
} else if (selectedRoleUri.isEmpty()) {
errorCode = ERROR_NO_ROLE;
} else {
errorCode = strategy.additionalValidations();
}
}
private boolean emailIsChanged() {
return !emailAddress.equals(userAccount.getEmailAddress());
}
private boolean isEmailInUse() {
return userAccountsDao.getUserAccountByEmail(emailAddress) != null;
}
public boolean isValid() {
return errorCode.isEmpty();
}
public final ResponseValues showPage() {
Map<String, Object> body = new HashMap<String, Object>();
if (isSubmit()) {
body.put("emailAddress", emailAddress);
body.put("firstName", firstName);
body.put("lastName", lastName);
body.put("selectedRole", selectedRoleUri);
} else {
body.put("emailAddress", userAccount.getEmailAddress());
body.put("firstName", userAccount.getFirstName());
body.put("lastName", userAccount.getLastName());
body.put("selectedRole", getExistingRoleUri());
}
body.put("roles", buildRolesList());
if (associateWithProfile) {
body.put("associate", Boolean.TRUE);
}
body.put("formUrls", buildUrlsMapWithEditUrl());
if (!errorCode.isEmpty()) {
body.put(errorCode, Boolean.TRUE);
}
strategy.addMoreBodyValues(body);
return new TemplateResponseValues(TEMPLATE_NAME, body);
}
private String getExistingRoleUri() {
Set<String> uris = userAccount.getPermissionSetUris();
if (uris.isEmpty()) {
return "";
} else {
return uris.iterator().next();
}
}
private Map<String, String> buildUrlsMapWithEditUrl() {
Map<String, String> map = buildUrlsMap();
map.put("edit", editAccountUrl(userAccount.getUri()));
return map;
}
public void updateAccount() {
userAccount.setEmailAddress(emailAddress);
userAccount.setFirstName(firstName);
userAccount.setLastName(lastName);
userAccount.setPermissionSetUris(Collections.singleton(selectedRoleUri));
strategy.setAdditionalProperties(userAccount);
userAccountsDao.updateUserAccount(userAccount);
strategy.notifyUser();
}
public boolean wasPasswordEmailSent() {
return strategy.wasPasswordEmailSent();
}
public UserAccount getUpdatedAccount() {
return userAccount;
}
}

View file

@ -0,0 +1,209 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.accounts.admin;
import static javax.mail.Message.RecipientType.TO;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsPage;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage;
/**
* Handle the variant details of the UserAccountsAddPage.
*/
public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
protected final UserAccountsEditPage page;
public static UserAccountsEditPageStrategy getInstance(VitroRequest vreq,
UserAccountsEditPage page, boolean emailEnabled) {
if (emailEnabled) {
return new EmailStrategy(vreq, page);
} else {
return new NoEmailStrategy(vreq, page);
}
}
public UserAccountsEditPageStrategy(VitroRequest vreq,
UserAccountsEditPage page) {
super(vreq);
this.page = page;
}
protected abstract void parseAdditionalParameters();
protected abstract String additionalValidations();
protected abstract void addMoreBodyValues(Map<String, Object> body);
protected abstract void setAdditionalProperties(UserAccount u);
protected abstract void notifyUser();
protected abstract boolean wasPasswordEmailSent();
// ----------------------------------------------------------------------
// Strategy to use if email is enabled.
// ----------------------------------------------------------------------
private static class EmailStrategy extends UserAccountsEditPageStrategy {
private static final String PARAMETER_RESET_PASSWORD = "resetPassword";
public static final String RESET_PASSWORD_URL = "/accounts/resetPassword";
private boolean resetPassword;
private boolean sentEmail;
public EmailStrategy(VitroRequest vreq, UserAccountsEditPage page) {
super(vreq, page);
}
@Override
protected void parseAdditionalParameters() {
resetPassword = isFlagOnRequest(PARAMETER_RESET_PASSWORD);
}
@Override
protected String additionalValidations() {
// No additional validations
return "";
}
@Override
protected void setAdditionalProperties(UserAccount u) {
if (resetPassword) {
u.setPasswordLinkExpires(figureExpirationDate().getTime());
}
}
@Override
protected void addMoreBodyValues(Map<String, Object> body) {
body.put("emailIsEnabled", Boolean.TRUE);
if (resetPassword) {
body.put("resetPassword", Boolean.TRUE);
}
}
@Override
protected void notifyUser() {
if (!resetPassword) {
return;
}
Map<String, Object> body = new HashMap<String, Object>();
body.put("userAccount", page.getUpdatedAccount());
body.put("passwordLink", buildResetPasswordLink());
body.put("subjectLine", "Reset password request");
FreemarkerEmailMessage email = FreemarkerEmailFactory
.createNewMessage(vreq);
email.addRecipient(TO, page.getUpdatedAccount().getEmailAddress());
email.setSubject("Reset password request");
email.setHtmlTemplate("userAccounts-resetPasswordEmail-html.ftl");
email.setTextTemplate("userAccounts-resetPasswordEmail-text.ftl");
email.setBodyMap(body);
email.send();
sentEmail = true;
}
private String buildResetPasswordLink() {
try {
String email = page.getUpdatedAccount().getEmailAddress();
String hash = page.getUpdatedAccount()
.getPasswordLinkExpiresHash();
String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL,
"user", email, "key", hash);
URL context = new URL(vreq.getRequestURL().toString());
URL url = new URL(context, relativeUrl);
return url.toExternalForm();
} catch (MalformedURLException e) {
return "error_creating_password_link";
}
}
@Override
protected boolean wasPasswordEmailSent() {
return sentEmail;
}
}
// ----------------------------------------------------------------------
// Strategy to use if email is not enabled.
// ----------------------------------------------------------------------
private static class NoEmailStrategy extends UserAccountsEditPageStrategy {
private static final String PARAMETER_NEW_PASSWORD = "newPassword";
private static final String PARAMETER_CONFIRM_PASSWORD = "confirmPassword";
private static final String ERROR_WRONG_PASSWORD_LENGTH = "errorPasswordIsWrongLength";
private static final String ERROR_PASSWORDS_DONT_MATCH = "errorPasswordsDontMatch";
private String newPassword;
private String confirmPassword;
public NoEmailStrategy(VitroRequest vreq, UserAccountsEditPage page) {
super(vreq, page);
}
@Override
protected void parseAdditionalParameters() {
newPassword = getStringParameter(PARAMETER_NEW_PASSWORD, "");
confirmPassword = getStringParameter(PARAMETER_CONFIRM_PASSWORD, "");
}
@Override
protected String additionalValidations() {
if (newPassword.isEmpty() && confirmPassword.isEmpty()) {
return "";
} else if (!checkPasswordLength()) {
return ERROR_WRONG_PASSWORD_LENGTH;
} else if (!newPassword.equals(confirmPassword)) {
return ERROR_PASSWORDS_DONT_MATCH;
} else {
return "";
}
}
private boolean checkPasswordLength() {
return newPassword.length() >= UserAccount.MIN_PASSWORD_LENGTH
&& newPassword.length() <= UserAccount.MAX_PASSWORD_LENGTH;
}
@Override
protected void addMoreBodyValues(Map<String, Object> body) {
body.put("newPassword", newPassword);
body.put("confirmPassword", confirmPassword);
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
}
@Override
protected void setAdditionalProperties(UserAccount u) {
u.setMd5Password(newPassword);
u.setPasswordChangeRequired(true);
}
@Override
protected void notifyUser() {
// Do nothing.
}
@Override
protected boolean wasPasswordEmailSent() {
return false;
}
}
}

View file

@ -105,6 +105,9 @@ public class UserAccountsListPage extends UserAccountsPage {
body.put("newUserAccount", new UserAccountWrapper(userAccount,
Collections.<String> emptyList()));
if (emailWasSent) {
body.put("emailWasSent", Boolean.TRUE);
}
return new TemplateResponseValues(TEMPLATE_NAME, body);
}
@ -114,8 +117,17 @@ public class UserAccountsListPage extends UserAccountsPage {
*/
public ResponseValues showPageWithUpdatedAccount(UserAccount userAccount,
boolean emailWasSent) {
throw new RuntimeException(
"UserAccountsListPage.showPageWithUpdatedAccount not implemented.");
UserAccountsSelection selection = UserAccountsSelector.select(
userAccountsModel, criteria);
Map<String, Object> body = buildTemplateBodyMap(selection);
body.put("updatedUserAccount", new UserAccountWrapper(userAccount,
Collections.<String> emptyList()));
if (emailWasSent) {
body.put("emailWasSent", Boolean.TRUE);
}
return new TemplateResponseValues(TEMPLATE_NAME, body);
}
/**

View file

@ -2,165 +2,68 @@
package edu.cornell.mannlib.vitro.webapp.controller.accounts.user;
import java.util.Date;
import static javax.mail.Message.RecipientType.TO;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsPage;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage;
/**
* When the user clicks on the link in their notification email, handle their
* request to create a password.
*/
public class UserAccountsCreatePasswordPage extends UserAccountsPage {
public class UserAccountsCreatePasswordPage extends
UserAccountsPasswordBasePage {
private static final Log log = LogFactory
.getLog(UserAccountsCreatePasswordPage.class);
private static final String PARAMETER_SUBMIT = "submitCreatePassword";
private static final String PARAMETER_USER = "user";
private static final String PARAMETER_KEY = "key";
private static final String PARAMETER_PASSWORD = "password";
private static final String PARAMETER_CONFIRM_PASSWORD = "confirmPassword";
private static final String TEMPLATE_NAME = "userAccounts-createPassword.ftl";
private static final String ERROR_NO_PASSWORD = "errorPasswordIsEmpty";
private static final String ERROR_WRONG_PASSWORD_LENGTH = "errorPasswordIsWrongLength";
private static final String ERROR_PASSWORDS_DONT_MATCH = "errorPasswordsDontMatch";
private boolean submit;
private String userEmail = "";
private String key = "";
private String password = "";
private String confirmPassword = "";
private UserAccount userAccount;
/** The result of checking whether this request is even appropriate. */
private String bogusMessage = "";
/** The result of validating a "submit" request. */
private String errorCode = "";
public UserAccountsCreatePasswordPage(VitroRequest vreq) {
super(vreq);
parseRequestParameters();
validateUserAccountInfo();
if (isSubmit() && !isBogus()) {
validateParameters();
}
}
private void parseRequestParameters() {
submit = isFlagOnRequest(PARAMETER_SUBMIT);
userEmail = getStringParameter(PARAMETER_USER, "");
key = getStringParameter(PARAMETER_KEY, "");
password = getStringParameter(PARAMETER_PASSWORD, "");
confirmPassword = getStringParameter(PARAMETER_CONFIRM_PASSWORD, "");
}
private void validateUserAccountInfo() {
userAccount = userAccountsDao.getUserAccountByEmail(userEmail);
if (userAccount == null) {
log.warn("Create password for '" + userEmail
+ "' is bogus: no such user");
bogusMessage = UserAccountsUserController.BOGUS_STANDARD_MESSAGE;
return;
}
if (userAccount.getPasswordLinkExpires() == 0L) {
log.warn("Create password for '" + userEmail
+ "' is bogus: password change is not pending.");
bogusMessage = "The account for " + userEmail
+ " has already been activated.";
return;
}
Date expirationDate = new Date(userAccount.getPasswordLinkExpires());
if (expirationDate.before(new Date())) {
log.warn("Create password for '" + userEmail
+ "' is bogus: expiration date has passed.");
bogusMessage = UserAccountsUserController.BOGUS_STANDARD_MESSAGE;
return;
}
String expectedKey = userAccount.getPasswordLinkExpiresHash();
if (!key.equals(expectedKey)) {
log.warn("Create password for '" + userEmail + "' is bogus: key ("
+ key + ") doesn't match expected key (" + expectedKey
+ ")");
bogusMessage = UserAccountsUserController.BOGUS_STANDARD_MESSAGE;
return;
}
}
private void validateParameters() {
if (password.isEmpty()) {
errorCode = ERROR_NO_PASSWORD;
} else if (!checkPasswordLength(password)) {
errorCode = ERROR_WRONG_PASSWORD_LENGTH;
} else if (!password.equals(confirmPassword)) {
errorCode = ERROR_PASSWORDS_DONT_MATCH;
}
}
private boolean checkPasswordLength(String pw) {
return pw.length() >= UserAccount.MIN_PASSWORD_LENGTH
&& pw.length() <= UserAccount.MAX_PASSWORD_LENGTH;
}
public boolean isBogus() {
return bogusMessage.isEmpty();
}
public String getBogusMessage() {
return bogusMessage;
}
public boolean isSubmit() {
return submit;
}
public boolean isValid() {
return errorCode.isEmpty();
}
public void createPassword() {
userAccount.setMd5Password(Authenticator.applyMd5Encoding(password));
userAccount.setMd5Password(Authenticator.applyMd5Encoding(newPassword));
userAccount.setPasswordLinkExpires(0L);
userAccount.setStatus(Status.ACTIVE);
userAccountsDao.updateUserAccount(userAccount);
log.debug("Set password on '" + userAccount.getEmailAddress()
+ "' to '" + password + "'");
+ "' to '" + newPassword + "'");
notifyUser();
}
public final ResponseValues showPage() {
@Override
protected String passwordChangeNotPendingMessage() {
return "The account for " + userEmail + " has already been activated.";
}
@Override
protected String templateName() {
return TEMPLATE_NAME;
}
private void notifyUser() {
Map<String, Object> body = new HashMap<String, Object>();
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
body.put("userAccount", userAccount);
body.put("key", userAccount.getPasswordLinkExpiresHash());
body.put("password", password);
body.put("confirmPassword", confirmPassword);
body.put("formUrls", buildUrlsMap());
body.put("subjectLine", "Password successfully created.");
if (!errorCode.isEmpty()) {
body.put(errorCode, Boolean.TRUE);
}
return new TemplateResponseValues(TEMPLATE_NAME, body);
FreemarkerEmailMessage email = FreemarkerEmailFactory
.createNewMessage(vreq);
email.addRecipient(TO, userAccount.getEmailAddress());
email.setSubject("Password successfully created.");
email.setHtmlTemplate("userAccounts-passwordCreatedEmail-html.ftl");
email.setTextTemplate("userAccounts-passwordCreatedEmail-text.ftl");
email.setBodyMap(body);
email.send();
}
}

View file

@ -0,0 +1,150 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.accounts.user;
import static edu.cornell.mannlib.vitro.webapp.controller.accounts.user.UserAccountsUserController.BOGUS_STANDARD_MESSAGE;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsPage;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
/**
* Routines in common to the "Create Password" and "Reset Password" pages.
*/
public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
private static final Log log = LogFactory
.getLog(UserAccountsPasswordBasePage.class);
private static final String PARAMETER_SUBMIT = "submit";
private static final String PARAMETER_USER = "user";
private static final String PARAMETER_KEY = "key";
private static final String PARAMETER_NEW_PASSWORD = "newPassword";
private static final String PARAMETER_CONFIRM_PASSWORD = "confirmPassword";
private static final String ERROR_NO_PASSWORD = "errorPasswordIsEmpty";
private static final String ERROR_WRONG_PASSWORD_LENGTH = "errorPasswordIsWrongLength";
private static final String ERROR_PASSWORDS_DONT_MATCH = "errorPasswordsDontMatch";
protected boolean submit;
protected String userEmail = "";
protected String key = "";
protected String newPassword = "";
protected String confirmPassword = "";
protected UserAccount userAccount;
/** The result of checking whether this request is even appropriate. */
private String bogusMessage = "";
/** The result of validating a "submit" request. */
private String errorCode = "";
protected UserAccountsPasswordBasePage(VitroRequest vreq) {
super(vreq);
parseRequestParameters();
validateUserAccountInfo();
if (isSubmit() && !isBogus()) {
validateParameters();
}
}
private void parseRequestParameters() {
submit = isFlagOnRequest(PARAMETER_SUBMIT);
userEmail = getStringParameter(PARAMETER_USER, "");
key = getStringParameter(PARAMETER_KEY, "");
newPassword = getStringParameter(PARAMETER_NEW_PASSWORD, "");
confirmPassword = getStringParameter(PARAMETER_CONFIRM_PASSWORD, "");
}
public boolean isSubmit() {
return submit;
}
private void validateUserAccountInfo() {
userAccount = userAccountsDao.getUserAccountByEmail(userEmail);
if (userAccount == null) {
log.warn("Password request for '" + userEmail
+ "' is bogus: no such user");
bogusMessage = BOGUS_STANDARD_MESSAGE;
return;
}
if (userAccount.getPasswordLinkExpires() == 0L) {
log.warn("Password request for '" + userEmail
+ "' is bogus: password change is not pending.");
bogusMessage = passwordChangeNotPendingMessage();
return;
}
Date expirationDate = new Date(userAccount.getPasswordLinkExpires());
if (expirationDate.before(new Date())) {
log.warn("Password request for '" + userEmail
+ "' is bogus: expiration date has passed.");
bogusMessage = BOGUS_STANDARD_MESSAGE;
return;
}
String expectedKey = userAccount.getPasswordLinkExpiresHash();
if (!key.equals(expectedKey)) {
log.warn("Password request for '" + userEmail + "' is bogus: key ("
+ key + ") doesn't match expected key (" + expectedKey
+ ")");
bogusMessage = BOGUS_STANDARD_MESSAGE;
return;
}
}
public boolean isBogus() {
return !bogusMessage.isEmpty();
}
public String getBogusMessage() {
return bogusMessage;
}
private void validateParameters() {
if (newPassword.isEmpty()) {
errorCode = ERROR_NO_PASSWORD;
} else if (!checkPasswordLength(newPassword)) {
errorCode = ERROR_WRONG_PASSWORD_LENGTH;
} else if (!newPassword.equals(confirmPassword)) {
errorCode = ERROR_PASSWORDS_DONT_MATCH;
}
}
public boolean isValid() {
return errorCode.isEmpty();
}
public final ResponseValues showPage() {
Map<String, Object> body = new HashMap<String, Object>();
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
body.put("userAccount", userAccount);
body.put("key", userAccount.getPasswordLinkExpiresHash());
body.put("newPassword", newPassword);
body.put("confirmPassword", confirmPassword);
body.put("formUrls", buildUrlsMap());
if (!errorCode.isEmpty()) {
body.put(errorCode, Boolean.TRUE);
}
return new TemplateResponseValues(templateName(), body);
}
protected abstract String passwordChangeNotPendingMessage();
protected abstract String templateName();
}

View file

@ -0,0 +1,69 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.accounts.user;
import static javax.mail.Message.RecipientType.TO;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage;
/**
* When the user clicks on the link in their notification email, handle their
* request to reset their password.
*/
public class UserAccountsResetPasswordPage extends UserAccountsPasswordBasePage {
private static final Log log = LogFactory
.getLog(UserAccountsResetPasswordPage.class);
private static final String TEMPLATE_NAME = "userAccounts-resetPassword.ftl";
protected UserAccountsResetPasswordPage(VitroRequest vreq) {
super(vreq);
}
public void resetPassword() {
userAccount.setMd5Password(Authenticator.applyMd5Encoding(newPassword));
userAccount.setPasswordLinkExpires(0L);
userAccount.setStatus(Status.ACTIVE);
userAccountsDao.updateUserAccount(userAccount);
log.debug("Set password on '" + userAccount.getEmailAddress()
+ "' to '" + newPassword + "'");
notifyUser();
}
@Override
protected String passwordChangeNotPendingMessage() {
return "The password for " + userEmail + " has already been reset.";
}
@Override
protected String templateName() {
return TEMPLATE_NAME;
}
private void notifyUser() {
Map<String, Object> body = new HashMap<String, Object>();
body.put("userAccount", userAccount);
body.put("subjectLine", "Password changed.");
FreemarkerEmailMessage email = FreemarkerEmailFactory
.createNewMessage(vreq);
email.addRecipient(TO, userAccount.getEmailAddress());
email.setSubject("Password changed.");
email.setHtmlTemplate("userAccounts-passwordResetEmail-html.ftl");
email.setTextTemplate("userAccounts-passwordResetEmail-text.ftl");
email.setBodyMap(body);
email.send();
}
}

View file

@ -22,6 +22,7 @@ public class UserAccountsUserController extends FreemarkerHttpServlet {
public static final String BOGUS_STANDARD_MESSAGE = "Request failed. Please contact your system administrator.";
private static final String ACTION_CREATE_PASSWORD = "/createPassword";
private static final String ACTION_RESET_PASSWORD = "/resetPassword";
@Override
protected Actions requiredActions(VitroRequest vreq) {
@ -39,6 +40,8 @@ public class UserAccountsUserController extends FreemarkerHttpServlet {
if (ACTION_CREATE_PASSWORD.equals(action)) {
return handleCreatePasswordRequest(vreq);
} else if (ACTION_RESET_PASSWORD.equals(action)) {
return handleResetPasswordRequest(vreq);
} else {
return handleInvalidRequest(vreq);
}
@ -59,6 +62,21 @@ public class UserAccountsUserController extends FreemarkerHttpServlet {
}
private ResponseValues handleResetPasswordRequest(VitroRequest vreq) {
UserAccountsResetPasswordPage page = new UserAccountsResetPasswordPage(
vreq);
if (page.isBogus()) {
return showHomePage(vreq, page.getBogusMessage());
} else if (page.isSubmit() && page.isValid()) {
page.resetPassword();
return showHomePage(vreq,
"Your password has been saved. Please log in.");
} else {
return page.showPage();
}
}
private ResponseValues handleInvalidRequest(VitroRequest vreq) {
return showHomePage(vreq, BOGUS_STANDARD_MESSAGE);
}