NIHVIVO-2279 Implement Add Account (no associations) and Delete Accounts.

This commit is contained in:
j2blake 2011-05-24 21:15:56 +00:00
parent 3fbbd561c5
commit 0aef0368c6
12 changed files with 426 additions and 90 deletions

View file

@ -7,6 +7,8 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
/**
* Information about the account of a user. URI, email, password, etc.
*/
@ -107,6 +109,11 @@ public class UserAccount {
return passwordLinkExpires;
}
public String getPasswordLinkExpiresHash() {
return Authenticator.applyMd5Encoding(String
.valueOf(passwordLinkExpires));
}
public void setPasswordLinkExpires(long passwordLinkExpires) {
this.passwordLinkExpires = Math.max(0, passwordLinkExpires);
}

View file

@ -16,11 +16,6 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.Tem
* Handle the "Add new account" form display and submission.
*
* TODO Associate a profile from this account
*
* TODO Handle sending of email.
*
* TODO Handle initial password set if email isn't available. Set password
* fields, change-required flag, account is active.
*/
public class UserAccountsAddPage extends UserAccountsPage {
private static final String PARAMETER_SUBMIT = "submitAdd";
@ -38,6 +33,8 @@ public class UserAccountsAddPage extends UserAccountsPage {
private static final String TEMPLATE_NAME = "userAccounts-add.ftl";
private final UserAccountsAddPageStrategy strategy;
/* The request parameters */
private boolean submit;
private String emailAddress = "";
@ -46,14 +43,18 @@ public class UserAccountsAddPage extends UserAccountsPage {
private String selectedRoleUri = "";
private boolean associateWithProfile;
/* The result of validating a "submit" request. */
/** The result of validating a "submit" request. */
private String errorCode = "";
/** The new user account, if one was created. */
private UserAccount addedAccount;
public UserAccountsAddPage(VitroRequest vreq) {
super(vreq);
}
public void parseParametersAndValidate() {
this.strategy = UserAccountsAddPageStrategy.getInstance(this,
isEmailEnabled(vreq));
parseRequestParameters();
if (submit) {
@ -68,6 +69,8 @@ public class UserAccountsAddPage extends UserAccountsPage {
lastName = getStringParameter(PARAMETER_LAST_NAME, "");
selectedRoleUri = getRoleChoices();
associateWithProfile = getAssociateFlag();
strategy.parseAdditionalParameters();
}
public boolean isSubmit() {
@ -85,6 +88,8 @@ public class UserAccountsAddPage extends UserAccountsPage {
errorCode = ERROR_NO_LAST_NAME;
} else if (selectedRoleUri.isEmpty()) {
errorCode = ERROR_NO_ROLE;
} else {
errorCode = strategy.additionalValidations();
}
}
@ -96,7 +101,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
return errorCode.isEmpty();
}
public UserAccount createNewAccount() {
public void createNewAccount() {
UserAccount u = new UserAccount();
u.setEmailAddress(emailAddress);
u.setFirstName(firstName);
@ -112,8 +117,12 @@ public class UserAccountsAddPage extends UserAccountsPage {
u.setPermissionSetUris(Collections.singleton(selectedRoleUri));
strategy.setAdditionalProperties(u);
String uri = userAccountsDao.insertUserAccount(u);
return userAccountsDao.getUserAccountByUri(uri);
this.addedAccount = userAccountsDao.getUserAccountByUri(uri);
strategy.notifyUser();
}
/** What role are they asking for? */
@ -132,40 +141,34 @@ public class UserAccountsAddPage extends UserAccountsPage {
PARAMETER_ASSOCIATE_WITH_PROFILE, "no"));
}
public ResponseValues showPage() {
public final ResponseValues showPage() {
Map<String, Object> body = new HashMap<String, Object>();
body.put("emailAddress", emailAddress);
body.put("firstName", firstName);
body.put("lastName", lastName);
body.put("selectedRole", selectedRoleUri);
body.put("associate", associateWithProfile);
if (associateWithProfile) {
body.put("associate", Boolean.TRUE);
}
body.put("roles", buildRolesList());
body.put("formUrls", buildUrlsMap());
if (!errorCode.isEmpty()) {
body.put(errorCode, Boolean.TRUE);
}
strategy.addMoreBodyValues(body);
return new TemplateResponseValues(TEMPLATE_NAME, body);
}
/**
* @return
*/
public UserAccount getAddedAccount() {
// TODO Auto-generated method stub
throw new RuntimeException("UserAccountsAddPage.getAddedAccount() not implemented.");
return addedAccount;
}
/**
* @return
*/
public boolean wasPasswordEmailSent() {
// TODO Auto-generated method stub
throw new RuntimeException("UserAccountsAddPage.wasPasswordEmailSent() not implemented.");
return this.strategy.wasPasswordEmailSent();
}
}

View file

@ -0,0 +1,197 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
import static javax.mail.Message.RecipientType.TO;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
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.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 UserAccountsAddPageStrategy {
protected final UserAccountsAddPage page;
public static UserAccountsAddPageStrategy getInstance(
UserAccountsAddPage page, boolean emailEnabled) {
if (emailEnabled) {
return new EmailStrategy(page);
} else {
return new NoEmailStrategy(page);
}
}
public UserAccountsAddPageStrategy(UserAccountsAddPage page) {
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 UserAccountsAddPageStrategy {
public static final String CREATE_PASSWORD_URL = "/userAccounts/createPassword";
private boolean sentEmail;
public EmailStrategy(UserAccountsAddPage page) {
super(page);
}
@Override
protected void parseAdditionalParameters() {
// No additional parameters
}
@Override
protected String additionalValidations() {
// No additional validations
return "";
}
@Override
protected void setAdditionalProperties(UserAccount u) {
u.setPasswordLinkExpires(new Date().getTime());
u.setStatus(Status.INACTIVE);
}
@Override
protected void addMoreBodyValues(Map<String, Object> body) {
body.put("emailIsEnabled", Boolean.TRUE);
}
@Override
protected void notifyUser() {
Map<String, Object> body = new HashMap<String, Object>();
body.put("userAccount", page.getAddedAccount());
body.put("passwordLink", buildCreatePasswordLink());
body.put("subjectLine", "Your VIVO account has been created.");
FreemarkerEmailMessage email = FreemarkerEmailFactory
.createNewMessage(page.vreq);
email.addRecipient(TO, page.getAddedAccount().getEmailAddress());
email.setSubject("Your VIVO account has been created.");
email.setHtmlTemplate("userAccounts-createdEmail-html.ftl");
email.setTextTemplate("userAccounts-createdEmail-text.ftl");
email.setBodyMap(body);
email.send();
sentEmail = true;
}
private String buildCreatePasswordLink() {
try {
String uri = page.getAddedAccount().getUri();
String hash = page.getAddedAccount()
.getPasswordLinkExpiresHash();
String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL, "user",
uri, "key", hash);
URL context = new URL(page.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 UserAccountsAddPageStrategy {
private static final String PARAMETER_INITIAL_PASSWORD = "initialPassword";
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";
private String initialPassword;
private String confirmPassword;
public NoEmailStrategy(UserAccountsAddPage page) {
super(page);
}
@Override
protected void parseAdditionalParameters() {
initialPassword = page.getStringParameter(
PARAMETER_INITIAL_PASSWORD, "");
confirmPassword = page.getStringParameter(
PARAMETER_CONFIRM_PASSWORD, "");
}
@Override
protected String additionalValidations() {
if (initialPassword.isEmpty()) {
return ERROR_NO_PASSWORD;
} else if (!checkPasswordLength()) {
return ERROR_WRONG_PASSWORD_LENGTH;
} else if (!initialPassword.equals(confirmPassword)) {
return ERROR_PASSWORDS_DONT_MATCH;
} else {
return "";
}
}
private boolean checkPasswordLength() {
return initialPassword.length() >= UserAccount.MIN_PASSWORD_LENGTH
&& initialPassword.length() <= UserAccount.MAX_PASSWORD_LENGTH;
}
@Override
protected void addMoreBodyValues(Map<String, Object> body) {
body.put("initialPassword", initialPassword);
body.put("confirmPassword", confirmPassword);
}
@Override
protected void setAdditionalProperties(UserAccount u) {
u.setMd5Password(initialPassword);
u.setPasswordChangeRequired(true);
u.setStatus(Status.ACTIVE);
}
@Override
protected void notifyUser() {
// Do nothing.
}
@Override
protected boolean wasPasswordEmailSent() {
return false;
}
}
}

View file

@ -39,50 +39,52 @@ public class UserAccountsController extends FreemarkerHttpServlet {
log.debug("action = '" + action + "'");
if (ACTION_ADD.equals(action)) {
return handleAddRequest(vreq);
} else if (ACTION_EDIT.equals(action)) {
return handleEditRequest(vreq);
} else if (ACTION_DELETE.equals(action)) {
return handleDeleteRequest(vreq);
} else {
return handleListRequest(vreq);
}
}
private ResponseValues handleAddRequest(VitroRequest vreq) {
UserAccountsAddPage page = new UserAccountsAddPage(vreq);
page.parseParametersAndValidate();
if (page.isSubmit() && page.isValid()) {
return addAccountAndShowList(vreq, page);
page.createNewAccount();
UserAccountsListPage listPage = new UserAccountsListPage(vreq);
return listPage.showPageWithNewAccount(page.getAddedAccount(),
page.wasPasswordEmailSent());
} else {
return page.showPage();
}
}
} else if (ACTION_EDIT.equals(action)) {
private ResponseValues handleEditRequest(VitroRequest vreq) {
UserAccountsEditPage page = new UserAccountsEditPage(vreq);
page.parseParametersAndValidate();
if (page.isSubmit() && page.isValid()) {
return editAccountAndShowList(vreq, page);
page.updateAccount();
UserAccountsListPage listPage = new UserAccountsListPage(vreq);
return listPage.showPageWithUpdatedAccount(
page.getUpdatedAccount(), page.wasPasswordEmailSent());
} else {
return page.showPage();
}
}
} else if (ACTION_DELETE.equals(action)) {
private ResponseValues handleDeleteRequest(VitroRequest vreq) {
UserAccountsDeleter deleter = new UserAccountsDeleter(vreq);
Collection<String> deletedUris = deleter.delete();
return new UserAccountsListPage(vreq)
.showPageWithDeletions(deletedUris);
}
} else {
private ResponseValues handleListRequest(VitroRequest vreq) {
UserAccountsListPage page = new UserAccountsListPage(vreq);
return page.showPage();
}
}
private ResponseValues addAccountAndShowList(VitroRequest vreq,
UserAccountsAddPage addPage) {
addPage.createNewAccount();
UserAccountsListPage listPage = new UserAccountsListPage(vreq);
return listPage.showPageWithNewAccount(addPage.getAddedAccount(),
addPage.wasPasswordEmailSent());
}
private ResponseValues editAccountAndShowList(VitroRequest vreq,
UserAccountsEditPage editPage) {
editPage.updateAccount();
UserAccountsListPage listPage = new UserAccountsListPage(vreq);
return listPage.showPageWithUpdatedAccount(
editPage.getUpdatedAccount(), editPage.wasPasswordEmailSent());
}
}

View file

@ -2,29 +2,51 @@
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
/**
* TODO delete and kick to Accounts list with message, telling how many were
* deleted. If there was a problem, the user will need to infer it from the
* count??
* Process a request to delete User Accounts.
*/
public class UserAccountsDeleter extends UserAccountsPage {
private static final String PARAMETER_DELETE_ACCOUNT = "deleteAccount";
/** Might be empty, but never null. */
private final String[] uris;
protected UserAccountsDeleter(VitroRequest vreq) {
super(vreq);
String[] values = vreq.getParameterValues(PARAMETER_DELETE_ACCOUNT);
if (values == null) {
this.uris = new String[0];
} else {
this.uris = values;
}
}
/**
* @return
*
*/
public Collection<String> delete() {
// TODO Auto-generated method stub
throw new RuntimeException(
"UserAccountsDeleter.delete() not implemented.");
List<String> deletedUris = new ArrayList<String>();
WebappDaoFactory wadf = vreq.getWebappDaoFactory();
UserAccountsDao dao = wadf.getUserAccountsDao();
for (String uri: uris) {
UserAccount u = dao.getUserAccountByUri(uri);
if (u != null) {
dao.deleteUserAccount(uri);
deletedUris.add(uri);
}
}
return deletedUris;
}
}

View file

@ -54,12 +54,13 @@ public class UserAccountsListPage extends UserAccountsPage {
public UserAccountsListPage(VitroRequest vreq) {
super(vreq);
parseParameters();
}
/**
* Build the criteria from the request parameters.
*/
public void parseParameters() {
private void parseParameters() {
int accountsPerPage = getIntegerParameter(PARAMETER_ACCOUNTS_PER_PAGE,
DEFAULT_ACCOUNTS_PER_PAGE);
int pageIndex = getIntegerParameter(PARAMETER_PAGE_INDEX, 1);
@ -76,6 +77,7 @@ public class UserAccountsListPage extends UserAccountsPage {
criteria = new UserAccountsSelectionCriteria(accountsPerPage,
pageIndex, ordering, roleFilterUri, searchTerm);
log.debug("selection criteria is: " + criteria);
}
/**

View file

@ -2,7 +2,6 @@
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
/**
* How are the accounts to be sorted?
*/
@ -78,4 +77,10 @@ public class UserAccountsOrdering {
public Direction getDirection() {
return direction;
}
@Override
public String toString() {
return "UserAccountsOrdering[field=" + field + ", direction="
+ direction + "]";
}
}

View file

@ -10,6 +10,7 @@ import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -22,6 +23,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.dao.jena.OntModelSelector;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
/**
* Common routines for the page controllers.
@ -47,6 +49,10 @@ public abstract class UserAccountsPage {
userAccountsDao = wdf.getUserAccountsDao();
}
protected static boolean isEmailEnabled(HttpServletRequest req) {
return FreemarkerEmailFactory.isConfigured(req);
}
protected String getStringParameter(String key, String defaultValue) {
String value = vreq.getParameter(key);
return (value == null) ? defaultValue : value;
@ -94,13 +100,11 @@ public abstract class UserAccountsPage {
* Make these URLs available to all of the pages.
*/
protected Map<String, String> buildUrlsMap() {
UrlBuilder urlBuilder = new UrlBuilder(vreq.getAppBean());
Map<String, String> map = new HashMap<String, String>();
map.put("list", urlBuilder.getPortalUrl("/userAccounts/list"));
map.put("add", urlBuilder.getPortalUrl("/userAccounts/add"));
map.put("delete", urlBuilder.getPortalUrl("/userAccounts/delete"));
map.put("list", UrlBuilder.getUrl("/userAccounts/list"));
map.put("add", UrlBuilder.getUrl("/userAccounts/add"));
map.put("delete", UrlBuilder.getUrl("/userAccounts/delete"));
return map;
}

View file

@ -75,4 +75,11 @@ public class UserAccountsSelectionCriteria {
return (t == null) ? nullValue : t;
}
@Override
public String toString() {
return "UserAccountsSelectionCriteria[accountsPerPage="
+ accountsPerPage + ", pageIndex=" + pageIndex + ", orderBy="
+ orderBy + ", roleFilterUri='" + roleFilterUri
+ "', searchTerm='" + searchTerm + "']";
}
}

View file

@ -24,6 +24,18 @@
<#assign errorMessage = "You must select a role." />
</#if>
<#if errorPasswordIsEmpty??>
<#assign errorMessage = "No password supplied." />
</#if>
<#if errorPasswordIsWrongLength??>
<#assign errorMessage = "Password must be between 6 and 12 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"/>
@ -48,10 +60,20 @@
Roles *
<br/>
<#list roles as role>
<input type="radio" name="role" value="${role.uri}" <#if selectedRole = role.uri>selected</#if> />${role.label}
<input type="radio" name="role" value="${role.uri}" <#if selectedRole = role.uri>checked</#if> />${role.label}
<br>
</#list>
<br/>
<#if !emailIsEnabled??>
Initial password *
<input type="password" name="initialPassword" value="${initialPassword}" />
<br/>
Confirm initial password *
<input type="password" name="confirmPassword" value="${confirmPassword}" />
<br/>
</#if>
Associate a profile with this account
<br/>
<input type="radio" name="associate" value="yes" <#if associate??>checked</#if> />Yes
@ -59,11 +81,13 @@
<input type="radio" name="associate" value="no" <#if !associate??>checked</#if> />No
<br/>
<#if emailIsEnabled??>
<p>
Note: An email will be sent to the address entered above
notifying that an account has been created.
It will include instructions for activating the account and creating a password.
</p>
</#if>
<input type="submit" name="submitAdd" value="Add new account" />
or <a href="${formUrls.list}">Cancel</a>

View file

@ -0,0 +1,43 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that an account has been created. -->
<html>
<head>
<title>${subjectLine}</title>
</head>
<body>
<p>
${userAccount.firstName} ${userAccount.lastName}
</p>
<p>
<strong>Congratulations!</strong>
</p>
<p>
We have created your new VIVO account associated with ${userAccount.emailAddress}.
</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 create your password for your new account 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>
Thanks!
</p>
</body>
</html>

View file

@ -0,0 +1,20 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Confirmation that an account has been created. -->
${userAccount.firstName} ${userAccount.lastName}
Congratulations!
We have created your new VIVO account associated with
${userAccount.emailAddress}.
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 create your password
for your new account using our secure server.
${passwordLink}
Thanks!