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;
@ -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;
@ -82,12 +80,6 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
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) {
body.put("emailIsEnabled", Boolean.TRUE);
@ -117,8 +109,8 @@ 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);
@ -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 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);
}

View file

@ -68,6 +68,8 @@
<label for="initial-password">Initial password<span class="requiredHint"> *</span></label>
<input type="password" name="initialPassword" value="${initialPassword}" id="initial-password" role="input "/>
<p>Minimum of ${minimumLength} characters in length.</p>
<label for="confirm-password">Confirm initial password<span class="requiredHint"> *</span></label>
<input type="text" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input "/>
</#if>
@ -79,10 +81,6 @@
<input type="radio" name="associate" value="no" role="radio" <#if !associate??>checked</#if> id="no-associate" />
<label class="inline" for="no-associate"> No</label>
<br />
<input type="checkbox" name="resetPassword" value="" id="reset-password" role="checkbox" />
<label class="inline" for="reset-password"> Reset password</label>
<#if emailIsEnabled??>
<p class="note">
Note: An email will be sent to the address entered above

View file

@ -2,7 +2,7 @@
<#-- Template for adding a user account -->
<h3>Add new account</h3>
<h3>Create your Password</h3>
<#if errorPasswordIsEmpty??>
<#assign errorMessage = "No password supplied." />
@ -23,23 +23,23 @@
</section>
</#if>
<section id="add-account" role="region">
<section id="create-password" role="region">
<fieldset>
<legend>Add new account</legend>
<legend>Please enter your new password for ${userAccount.emailAddress}</legend>
<form method="POST" action="${formUrls.createPassword}" class="customForm" role="create password">
<input type="hidden" name="user" value="${userAccount.emailAddress}" />
<input type="hidden" name="key" value="${userAccount.passwordLinkExpiresHash}" />
<label for="password">Password<span class="requiredHint"> *</span></label>
<input type="password" name="password" value="${password}" id="password" role="input "/>
<label for="new-password">Password<span class="requiredHint"> *</span></label>
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input "/>
<p>Minimum of ${minimumLength} characters in length.</p>
<label for="confirm-password">Confirm Password<span class="requiredHint"> *</span></label>
<input type="password" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input "/>
<input type="submit" name="submitCreatePassword" value="Save changes" class="submit"/>
<input type="submit" name="submit" value="Save changes" class="submit"/>
<p class="requiredHint">* required fields</p>
</form>

View file

@ -2,5 +2,103 @@
<#-- Template for editing a user account -->
<h3>Edit user account</h3>
<h3>Edit account</h3>
<#if errorEmailIsEmpty??>
<#assign errorMessage = "You must supply an email address." />
</#if>
<#if errorEmailInUse??>
<#assign errorMessage = "An account with that email address already exists." />
</#if>
<#if errorFirstNameIsEmpty??>
<#assign errorMessage = "You must supply a first name." />
</#if>
<#if errorLastNameIsEmpty??>
<#assign errorMessage = "You must supply a last name." />
</#if>
<#if errorNoRoleSelected??>
<#assign errorMessage = "You must select a role." />
</#if>
<#if errorPasswordIsEmpty??>
<#assign errorMessage = "No password supplied." />
</#if>
<#if errorPasswordIsWrongLength??>
<#assign errorMessage = "Password must be between ${minimumLength} and ${maximumLength} characters." />
</#if>
<#if errorPasswordsDontMatch??>
<#assign errorMessage = "Passwords do not match." />
</#if>
<#if errorMessage?has_content>
<section id="error-alert" role="alert">
<img src="${urls.images}/iconAlert.png" width="24" height="24" alert="Error alert icon"/>
<p>${errorMessage}</p>
</section>
</#if>
<section id="edit-account" role="region">
<fieldset>
<legend>Edit new account</legend>
<form method="POST" action="${formUrls.edit}" class="customForm" role="edit account">
<label for="email-address">Email address<span class="requiredHint"> *</span></label>
<input type="text" name="emailAddress" value="${emailAddress}" id="email-address" role="input "/>
<label for="first-name">First name<span class="requiredHint"> *</span></label>
<input type="text" name="firstName" value="${firstName}" id="first-name" role="input "/>
<label for="last-name">Last name<span class="requiredHint"> *</span></label>
<input type="text" name="lastName" value="${lastName}" id="last-name" role="input "/>
<p>Roles<span class="requiredHint"> *</span> </p>
<#list roles as role>
<input type="radio" name="role" value="${role.uri}" role="radio" <#if selectedRole = role.uri>selected</#if> />
<label class="inline" for="${role.label}"> ${role.label}</label>
<br />
</#list>
<#if !emailIsEnabled??>
<label for="new-password">New password<span class="requiredHint"> *</span></label>
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input "/>
<p>Minimum of ${minimumLength} characters in length.</p>
<p>Leaving this blank means that the password will not be changed.</p>
<label for="confirm-password">Confirm initial password<span class="requiredHint"> *</span></label>
<input type="text" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input "/>
</#if>
<p>Associate a profile with this account</p>
<input type="radio" name="associate" value="yes" role="radio" <#if associate??>checked</#if> id="associate" />
<label class="inline" for="associate"> Yes</label>
<input type="radio" name="associate" value="no" role="radio" <#if !associate??>checked</#if> id="no-associate" />
<label class="inline" for="no-associate"> No</label>
<br />
<input type="checkbox" name="resetPassword" value="" id="reset-password" role="checkbox" />
<label class="inline" for="reset-password"> Reset password</label>
<#if emailIsEnabled??>
<p class="note">
Note: A confirmation email with instructions for resetting a password
will be sent to the address entered above.
The password will not be reset until the user follows the link provided in this email.
</p>
</#if>
<input type="submit" name="submitEdit" value="Save changes" class="submit"/> or <a href="${formUrls.list}">Cancel</a>
<p class="requiredHint">* required fields</p>
</form>
</fieldset>
</section>
${stylesheets.add('<link rel="stylesheet" href="${urls.base}/edit/forms/css/customForm.css" />')}

View file

@ -47,8 +47,26 @@
<p>
A new account for
<a href="${newUserAccount.editUrl}">${newUserAccount.firstName} ${newUserAccount.lastName}</a>
was successfully created. A notification email has been sent to ${newUserAccount.emailAddress}
with instructions for activating the account and creating a password.
was successfully created.
<#if emailWasSent?? >
A notification email has been sent to ${newUserAccount.emailAddress}
with instructions for activating the account and creating a password.
</#if>
</p>
</section>
</#if>
<#if updatedUserAccount?? >
<section class="account-feedback">
<p>
The account for
<a href="${updatedUserAccount.editUrl}">${updatedUserAccount.firstName} ${updatedUserAccount.lastName}</a>
has been updated.
<#if emailWasSent?? >
A confirmation email has been sent to ${updatedUserAccount.emailAddress}
with instructions for resetting a password.
The password will not be reset until the user follows the link provided in this email.
</#if>
</p>
</section>
</#if>

View file

@ -1,6 +1,6 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that an account has been created. -->
<#-- Confirmation that an password has been created. -->
<html>
<head>
@ -16,7 +16,7 @@
</p>
<p>
Yout new password associated with ${userAccount.emailAddress} has been created.
Your new password associated with ${userAccount.emailAddress} has been created.
</p>
<p>

View file

@ -1,6 +1,6 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that an account has been created. -->
<#-- Confirmation that a password has been created. -->
${userAccount.firstName} ${userAccount.lastName}

View file

@ -0,0 +1,26 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that an password has been reset. -->
<html>
<head>
<title>${subjectLine}</title>
</head>
<body>
<p>
${userAccount.firstName} ${userAccount.lastName}
</p>
<p>
<strong>Password successfully changed.</strong>
</p>
<p>
Your new password associated with ${userAccount.emailAddress} has been changed.
</p>
<p>
Thank you.
</p>
</body>
</html>

View file

@ -0,0 +1,12 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that a password has been reset. -->
${userAccount.firstName} ${userAccount.lastName}
Password successfully changed.
Your new password associated with ${userAccount.emailAddress}
has been changed.
Thank you.

View file

@ -0,0 +1,49 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Template for adding a user account -->
<h3>Reset your Password</h3>
<#if errorPasswordIsEmpty??>
<#assign errorMessage = "No password supplied." />
</#if>
<#if errorPasswordIsWrongLength??>
<#assign errorMessage = "Password must be between ${minimumLength} and ${maximumLength} characters." />
</#if>
<#if errorPasswordsDontMatch??>
<#assign errorMessage = "Passwords do not match." />
</#if>
<#if errorMessage?has_content>
<section id="error-alert" role="alert">
<img src="${urls.images}/iconAlert.png" width="24" height="24" alert="Error alert icon"/>
<p>${errorMessage}</p>
</section>
</#if>
<section id="reset-password" role="region">
<fieldset>
<legend>Please enter your new password for ${userAccount.emailAddress}</legend>
<form method="POST" action="${formUrls.resetPassword}" class="customForm" role="create password">
<input type="hidden" name="user" value="${userAccount.emailAddress}" />
<input type="hidden" name="key" value="${userAccount.passwordLinkExpiresHash}" />
<label for="new-password">Password<span class="requiredHint"> *</span></label>
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input "/>
<p>Minimum of ${minimumLength} characters in length.</p>
<label for="confirm-password">Confirm Password<span class="requiredHint"> *</span></label>
<input type="password" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input "/>
<input type="submit" name="submit" value="Save changes" class="submit"/>
<p class="requiredHint">* required fields</p>
</form>
</fieldset>
</section>
${stylesheets.add('<link rel="stylesheet" href="${urls.base}/edit/forms/css/customForm.css" />')}

View file

@ -0,0 +1,40 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Notification that your password has been reset. -->
<html>
<head>
<title>${subjectLine}</title>
</head>
<body>
<p>
${userAccount.firstName} ${userAccount.lastName}
</p>
<p>
We received a request to reset the password for your account (${userAccount.emailAddress}).
Please follow the instructions below to proceed with your password reset.
</p>
<p>
If you did not request this new account you can safely ignore this email.
This request will expire if not acted upon for 30 days.
</p>
<p>
Click the link below to reset your password using our secure server.
</p>
<p>
<a href="${passwordLink}">${passwordLink}</a>
</p>
<p>
If the link above doesn't work, you can copy and paste the link directly into your browser's address bar.
</p>
<p>
Thank you!
</p>
</body>
</html>

View file

@ -0,0 +1,19 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Notification that your password has been reset. -->
${userAccount.firstName} ${userAccount.lastName}
We received a request to reset the password for your account
(${userAccount.emailAddress}).
Please follow the instructions below to proceed with your password reset.
If you did not request this new account you can safely ignore this email.
This request will expire if not acted upon for 30 days.
Paste the link below into your browser's address bar to reset your password
using our secure server.
${passwordLink}
Thank you!