NIHVIVO-2279 Implement the MyAccount page.

This commit is contained in:
j2blake 2011-06-01 15:32:00 +00:00
parent 64fa779638
commit 3b5eb3a846
10 changed files with 528 additions and 14 deletions

View file

@ -59,8 +59,8 @@ public abstract class UserAccountsPage {
userAccountsDao = wdf.getUserAccountsDao();
}
protected static boolean isEmailEnabled(HttpServletRequest req) {
return FreemarkerEmailFactory.isConfigured(req);
protected boolean isEmailEnabled() {
return FreemarkerEmailFactory.isConfigured(vreq);
}
protected String getStringParameter(String key, String defaultValue) {
@ -126,6 +126,7 @@ public abstract class UserAccountsPage {
map.put("list", UrlBuilder.getUrl("/accountsAdmin/list"));
map.put("add", UrlBuilder.getUrl("/accountsAdmin/add"));
map.put("delete", UrlBuilder.getUrl("/accountsAdmin/delete"));
map.put("myAccount", UrlBuilder.getUrl("/accounts/myAccount"));
map.put("createPassword", UrlBuilder.getUrl("/accounts/createPassword"));
map.put("resetPassword", UrlBuilder.getUrl("/accounts/resetPassword"));

View file

@ -54,7 +54,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
super(vreq);
this.strategy = UserAccountsAddPageStrategy.getInstance(vreq, this,
isEmailEnabled(vreq));
isEmailEnabled());
parseRequestParameters();

View file

@ -63,7 +63,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
super(vreq);
this.strategy = UserAccountsEditPageStrategy.getInstance(vreq, this,
isEmailEnabled(vreq));
isEmailEnabled());
parseRequestParameters();
validateUserAccountInfo();

View file

@ -13,6 +13,7 @@ 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.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage;
@ -166,7 +167,7 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
protected String additionalValidations() {
if (newPassword.isEmpty() && confirmPassword.isEmpty()) {
return "";
} else if (!checkPasswordLength()) {
} else if (!checkPasswordLength(newPassword)) {
return ERROR_WRONG_PASSWORD_LENGTH;
} else if (!newPassword.equals(confirmPassword)) {
return ERROR_PASSWORDS_DONT_MATCH;
@ -175,11 +176,6 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
}
}
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);
@ -190,8 +186,10 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
@Override
protected void setAdditionalProperties(UserAccount u) {
u.setMd5Password(newPassword);
u.setPasswordChangeRequired(true);
if (!newPassword.isEmpty()) {
u.setMd5Password(Authenticator.applyMd5Encoding(newPassword));
u.setPasswordChangeRequired(true);
}
}
@Override

View file

@ -0,0 +1,176 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.accounts.user;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.beans.User;
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.admin.UserAccountsEditPage;
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.dao.UserDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
/**
* Handle the "My Account" form display and submission.
*/
public class UserAccountsMyAccountPage extends UserAccountsPage {
private static final Log log = LogFactory
.getLog(UserAccountsEditPage.class);
private static final String PARAMETER_SUBMIT = "submitMyAccount";
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 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 TEMPLATE_NAME = "userAccounts-myAccount.ftl";
private final UserAccountsMyAccountPageStrategy strategy;
private final UserAccount userAccount;
/* The request parameters */
private boolean submit;
private String emailAddress = "";
private String firstName = "";
private String lastName = "";
/** The result of validating a "submit" request. */
private String errorCode = "";
/** The result of updating the account. */
private String confirmationCode = "";
public UserAccountsMyAccountPage(VitroRequest vreq) {
super(vreq);
this.userAccount = getLoggedInUser();
this.strategy = UserAccountsMyAccountPageStrategy.getInstance(vreq,
this, isExternalAccount());
parseRequestParameters();
if (isSubmit()) {
validateParameters();
}
}
public UserAccount getUserAccount() {
return userAccount;
}
private void parseRequestParameters() {
submit = isFlagOnRequest(PARAMETER_SUBMIT);
emailAddress = getStringParameter(PARAMETER_EMAIL_ADDRESS, "");
firstName = getStringParameter(PARAMETER_FIRST_NAME, "");
lastName = getStringParameter(PARAMETER_LAST_NAME, "");
strategy.parseAdditionalParameters();
}
public boolean isSubmit() {
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 {
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();
}
private UserAccount getLoggedInUser() {
// TODO This is a bogus measure.
// TODO It only works because for now we are not deleting old User
// structures, and there is a new UserAccount with email set to the old
// User username.
String uri = LoginStatusBean.getBean(vreq).getUserURI();
WebappDaoFactory wdf = (WebappDaoFactory) this.ctx
.getAttribute("webappDaoFactory");
User u = wdf.getUserDao().getUserByURI(uri);
UserAccount ua = userAccountsDao.getUserAccountByEmail(u.getUsername());
if (ua == null) {
throw new IllegalStateException("Couldn't find a UserAccount "
+ "for uri: '" + uri + "'");
}
log.debug("Logged-in user is " + ua);
return ua;
}
private boolean isExternalAccount() {
return LoginStatusBean.getBean(vreq).hasExternalAuthentication();
}
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);
} else {
body.put("emailAddress", userAccount.getEmailAddress());
body.put("firstName", userAccount.getFirstName());
body.put("lastName", userAccount.getLastName());
}
body.put("formUrls", buildUrlsMap());
if (!errorCode.isEmpty()) {
body.put(errorCode, Boolean.TRUE);
}
if (!confirmationCode.isEmpty()) {
body.put(confirmationCode, Boolean.TRUE);
}
strategy.addMoreBodyValues(body);
return new TemplateResponseValues(TEMPLATE_NAME, body);
}
public void updateAccount() {
userAccount.setEmailAddress(emailAddress);
userAccount.setFirstName(firstName);
userAccount.setLastName(lastName);
strategy.setAdditionalProperties(userAccount);
userAccountsDao.updateUserAccount(userAccount);
strategy.notifyUser();
confirmationCode = strategy.getConfirmationCode();
}
}

View file

@ -0,0 +1,196 @@
/* $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 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.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage;
/**
* Handle the variant details of the MyAccounts page
*/
public abstract class UserAccountsMyAccountPageStrategy extends
UserAccountsPage {
private static final String CONFIRM_CHANGE = "confirmChange";
private static final String CONFIRM_EMAIL_SENT = "confirmEmailSent";
protected final UserAccountsMyAccountPage page;
public static UserAccountsMyAccountPageStrategy getInstance(
VitroRequest vreq, UserAccountsMyAccountPage page,
boolean externalAuth) {
if (externalAuth) {
return new ExternalAuthStrategy(vreq, page);
} else {
return new InternalAuthStrategy(vreq, page);
}
}
protected UserAccountsMyAccountPageStrategy(VitroRequest vreq,
UserAccountsMyAccountPage page) {
super(vreq);
this.page = page;
}
public abstract void parseAdditionalParameters();
public abstract String additionalValidations();
public abstract void addMoreBodyValues(Map<String, Object> body);
public abstract void setAdditionalProperties(UserAccount userAccount);
public abstract void notifyUser();
public abstract String getConfirmationCode();
// ----------------------------------------------------------------------
// Strategy to use if the account used External Authentication
// ----------------------------------------------------------------------
private static class ExternalAuthStrategy extends
UserAccountsMyAccountPageStrategy {
ExternalAuthStrategy(VitroRequest vreq, UserAccountsMyAccountPage page) {
super(vreq, page);
}
@Override
public void parseAdditionalParameters() {
// No additional parameters
}
@Override
public String additionalValidations() {
// No additional validations
return "";
}
@Override
public void addMoreBodyValues(Map<String, Object> body) {
body.put("externalAuth", Boolean.TRUE);
}
@Override
public void setAdditionalProperties(UserAccount userAccount) {
// No additional properties.
}
@Override
public void notifyUser() {
// No notification beyond the screen message.
}
@Override
public String getConfirmationCode() {
return CONFIRM_CHANGE;
}
}
// ----------------------------------------------------------------------
// Strategy to use if the account used Internal Authentication
// ----------------------------------------------------------------------
private static class InternalAuthStrategy extends
UserAccountsMyAccountPageStrategy {
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 final String originalEmail;
private String newPassword;
private String confirmPassword;
private boolean emailSent;
InternalAuthStrategy(VitroRequest vreq, UserAccountsMyAccountPage page) {
super(vreq, page);
originalEmail = page.getUserAccount().getEmailAddress();
}
@Override
public void parseAdditionalParameters() {
newPassword = getStringParameter(PARAMETER_NEW_PASSWORD, "");
confirmPassword = getStringParameter(PARAMETER_CONFIRM_PASSWORD, "");
}
@Override
public String additionalValidations() {
if (newPassword.isEmpty() && confirmPassword.isEmpty()) {
return "";
} else if (!newPassword.equals(confirmPassword)) {
return ERROR_PASSWORDS_DONT_MATCH;
} else if (!checkPasswordLength(newPassword)) {
return ERROR_WRONG_PASSWORD_LENGTH;
} else {
return "";
}
}
@Override
public 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
public void setAdditionalProperties(UserAccount userAccount) {
if (!newPassword.isEmpty()) {
userAccount.setMd5Password(Authenticator
.applyMd5Encoding(newPassword));
userAccount.setPasswordChangeRequired(false);
userAccount.setPasswordLinkExpires(0L);
}
}
@Override
public void notifyUser() {
if (!isEmailEnabled()) {
return;
}
if (!emailHasChanged()) {
return;
}
Map<String, Object> body = new HashMap<String, Object>();
body.put("userAccount", page.getUserAccount());
body.put("subjectLine", "Your VIVO email account has been changed.");
FreemarkerEmailMessage email = FreemarkerEmailFactory
.createNewMessage(vreq);
email.addRecipient(TO, page.getUserAccount().getEmailAddress());
email.setSubject("Your VIVO email account has been changed.");
email.setHtmlTemplate("userAccounts-confirmEmailChangedEmail-html.ftl");
email.setTextTemplate("userAccounts-confirmEmailChangedEmail-text.ftl");
email.setBodyMap(body);
email.send();
emailSent = true;
}
private boolean emailHasChanged() {
return !page.getUserAccount().getEmailAddress()
.equals(originalEmail);
}
@Override
public String getConfirmationCode() {
return emailSent ? CONFIRM_EMAIL_SENT : CONFIRM_CHANGE;
}
}
}

View file

@ -6,6 +6,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.EditOwnAccount;
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;
@ -23,10 +24,17 @@ public class UserAccountsUserController extends FreemarkerHttpServlet {
private static final String ACTION_CREATE_PASSWORD = "/createPassword";
private static final String ACTION_RESET_PASSWORD = "/resetPassword";
private static final String ACTION_MY_ACCOUNT = "/myAccount";
@Override
protected Actions requiredActions(VitroRequest vreq) {
return Actions.AUTHORIZED;
String action = vreq.getPathInfo();
if (ACTION_MY_ACCOUNT.equals(action)) {
return new Actions(new EditOwnAccount());
} else {
return Actions.AUTHORIZED;
}
}
@Override
@ -38,7 +46,9 @@ public class UserAccountsUserController extends FreemarkerHttpServlet {
String action = vreq.getPathInfo();
log.debug("action = '" + action + "'");
if (ACTION_CREATE_PASSWORD.equals(action)) {
if (ACTION_MY_ACCOUNT.equals(action)) {
return handleMyAccountRequest(vreq);
} else if (ACTION_CREATE_PASSWORD.equals(action)) {
return handleCreatePasswordRequest(vreq);
} else if (ACTION_RESET_PASSWORD.equals(action)) {
return handleResetPasswordRequest(vreq);
@ -47,6 +57,14 @@ public class UserAccountsUserController extends FreemarkerHttpServlet {
}
}
private ResponseValues handleMyAccountRequest(VitroRequest vreq) {
UserAccountsMyAccountPage page = new UserAccountsMyAccountPage(vreq);
if (page.isSubmit() && page.isValid()) {
page.updateAccount();
}
return page.showPage();
}
private ResponseValues handleCreatePasswordRequest(VitroRequest vreq) {
UserAccountsCreatePasswordPage page = new UserAccountsCreatePasswordPage(
vreq);

View file

@ -0,0 +1,23 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that the user has changed his email account. -->
<html>
<head>
<title>${subjectLine}</title>
</head>
<body>
<p>
Hi, ${userAccount.firstName} ${userAccount.lastName}
</p>
<p>
You recently changed the email address associated with
${userAccount.firstName} ${userAccount.lastName}
</p>
<p>
Thank you.
</p>
</body>
</html>

View file

@ -0,0 +1,10 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that the user has changed his email account. -->
Hi, ${userAccount.firstName} ${userAccount.lastName}
You recently changed the email address associated with
${userAccount.firstName} ${userAccount.lastName}
Thank you.

View file

@ -0,0 +1,92 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Template for editing a user account -->
<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 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>
<#if confirmChange??>
<#assign confirmMessage = "Your changes have been saved." />
</#if>
<#if confirmEmailSent??>
<#assign confirmMessage = "Your changes have been saved. A confirmation email has been sent to ${emailAddress}." />
</#if>
<#if confirmMessage?has_content>
<section id="error-alert" role="alert">
<img src="${urls.images}/iconConfirmation.png" width="24" height="24" alert="Confirmation icon"/>
<p>${confirmMessage}</p>
</section>
</#if>
<section id="my-account" role="region">
<fieldset>
<legend>My account</legend>
<form method="POST" action="${formUrls.myAccount}" class="customForm" role="my account">
<label for="email-address">Email address<span class="requiredHint"> *</span></label>
<input type="text" name="emailAddress" value="${emailAddress}" id="email-address" role="input "/>
<p>Note: if email changes, a confirmation email will be sent to the new email address entered above.</p>
<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 "/>
<#if !externalAuth??>
<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>
<input type="submit" name="submitMyAccount" 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" />')}