diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java index 27de0ffc1..cb0dba311 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java @@ -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); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPage.java index c6d77d99b..470088620 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPage.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPage.java @@ -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,24 +101,28 @@ public class UserAccountsAddPage extends UserAccountsPage { return errorCode.isEmpty(); } - public UserAccount createNewAccount() { + public void createNewAccount() { UserAccount u = new UserAccount(); u.setEmailAddress(emailAddress); u.setFirstName(firstName); u.setLastName(lastName); u.setExternalAuthId(""); - + u.setMd5Password(""); u.setOldPassword(""); u.setPasswordChangeRequired(false); u.setPasswordLinkExpires(0); u.setLoginCount(0); u.setStatus(Status.INACTIVE); - + 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 body = new HashMap(); - + 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(); } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPageStrategy.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPageStrategy.java new file mode 100644 index 000000000..fd3aa8f99 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPageStrategy.java @@ -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 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 body) { + body.put("emailIsEnabled", Boolean.TRUE); + } + + @Override + protected void notifyUser() { + Map body = new HashMap(); + 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 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; + } + + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsController.java index c70992c47..4e3846f99 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsController.java @@ -39,50 +39,52 @@ public class UserAccountsController extends FreemarkerHttpServlet { log.debug("action = '" + action + "'"); if (ACTION_ADD.equals(action)) { - UserAccountsAddPage page = new UserAccountsAddPage(vreq); - page.parseParametersAndValidate(); - if (page.isSubmit() && page.isValid()) { - return addAccountAndShowList(vreq, page); - } else { - return page.showPage(); - } - + return handleAddRequest(vreq); } else if (ACTION_EDIT.equals(action)) { - UserAccountsEditPage page = new UserAccountsEditPage(vreq); - page.parseParametersAndValidate(); - if (page.isSubmit() && page.isValid()) { - return editAccountAndShowList(vreq, page); - } else { - return page.showPage(); - } - + return handleEditRequest(vreq); } else if (ACTION_DELETE.equals(action)) { - UserAccountsDeleter deleter = new UserAccountsDeleter(vreq); - Collection deletedUris = deleter.delete(); - - return new UserAccountsListPage(vreq) - .showPageWithDeletions(deletedUris); - + return handleDeleteRequest(vreq); + } else { + return handleListRequest(vreq); + } + } + + private ResponseValues handleAddRequest(VitroRequest vreq) { + UserAccountsAddPage page = new UserAccountsAddPage(vreq); + if (page.isSubmit() && page.isValid()) { + page.createNewAccount(); + + UserAccountsListPage listPage = new UserAccountsListPage(vreq); + return listPage.showPageWithNewAccount(page.getAddedAccount(), + page.wasPasswordEmailSent()); } else { - 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 handleEditRequest(VitroRequest vreq) { + UserAccountsEditPage page = new UserAccountsEditPage(vreq); + page.parseParametersAndValidate(); + if (page.isSubmit() && page.isValid()) { + page.updateAccount(); + UserAccountsListPage listPage = new UserAccountsListPage(vreq); + return listPage.showPageWithUpdatedAccount( + page.getUpdatedAccount(), page.wasPasswordEmailSent()); + } else { + return page.showPage(); + } } - private ResponseValues editAccountAndShowList(VitroRequest vreq, - UserAccountsEditPage editPage) { - editPage.updateAccount(); - UserAccountsListPage listPage = new UserAccountsListPage(vreq); - return listPage.showPageWithUpdatedAccount( - editPage.getUpdatedAccount(), editPage.wasPasswordEmailSent()); + private ResponseValues handleDeleteRequest(VitroRequest vreq) { + UserAccountsDeleter deleter = new UserAccountsDeleter(vreq); + Collection deletedUris = deleter.delete(); + + return new UserAccountsListPage(vreq) + .showPageWithDeletions(deletedUris); + } + + private ResponseValues handleListRequest(VitroRequest vreq) { + UserAccountsListPage page = new UserAccountsListPage(vreq); + return page.showPage(); } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsDeleter.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsDeleter.java index 834d2a60f..4096081c5 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsDeleter.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsDeleter.java @@ -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 delete() { - // TODO Auto-generated method stub - throw new RuntimeException( - "UserAccountsDeleter.delete() not implemented."); + List deletedUris = new ArrayList(); + + 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; } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListPage.java index 866a77e60..500f53921 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListPage.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListPage.java @@ -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); } /** diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsOrdering.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsOrdering.java index 506cb8964..a21f61743 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsOrdering.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsOrdering.java @@ -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 + "]"; + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsPage.java index 92cae670a..768e684f3 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsPage.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsPage.java @@ -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; @@ -74,7 +80,7 @@ public abstract class UserAccountsPage { String value = vreq.getParameter(key); return (value != null); } - + /** * Create a list of all known PermissionSets. */ @@ -94,13 +100,11 @@ public abstract class UserAccountsPage { * Make these URLs available to all of the pages. */ protected Map buildUrlsMap() { - UrlBuilder urlBuilder = new UrlBuilder(vreq.getAppBean()); - Map map = new HashMap(); - 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; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java index 8a10c5c2d..dc43a35fb 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java @@ -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 + "']"; + } } diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-add.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-add.ftl index 1f937f6c1..8b3de3b17 100644 --- a/webapp/web/templates/freemarker/body/accounts/userAccounts-add.ftl +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-add.ftl @@ -24,6 +24,18 @@ <#assign errorMessage = "You must select a role." /> + <#if errorPasswordIsEmpty??> + <#assign errorMessage = "No password supplied." /> + + + <#if errorPasswordIsWrongLength??> + <#assign errorMessage = "Password must be between 6 and 12 characters." /> + + + <#if errorPasswordsDontMatch??> + <#assign errorMessage = "Passwords do not match." /> + + <#if errorMessage?has_content>