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 9327f9078..de197e5da 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 @@ -129,6 +129,7 @@ public abstract class UserAccountsPage { map.put("myAccount", UrlBuilder.getUrl("/accounts/myAccount")); map.put("createPassword", UrlBuilder.getUrl("/accounts/createPassword")); map.put("resetPassword", UrlBuilder.getUrl("/accounts/resetPassword")); + map.put("firstTimeExternal", UrlBuilder.getUrl("/accounts/firstTimeExternal")); return map; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java new file mode 100644 index 000000000..f180d23c9 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java @@ -0,0 +1,194 @@ +/* $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.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader; +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; + +/** + * Handle the first-time login of an Externally Authenticated user who has no + * UserAccount - let's create one! + * + * If they get here from the login, there should an externalAuthId waiting in + * the session. Otherwise, they should get here by submitting the form, which + * will have the externalAuthId as a hidden field. + */ +public class UserAccountsFirstTimeExternalPage extends UserAccountsPage { + private static final String PARAMETER_SUBMIT = "submit"; + private static final String PARAMETER_EXTERNAL_AUTH_ID = "externalAuthId"; + 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_EMAIL_INVALID_FORMAT = "errorEmailInvalidFormat"; + 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-firstTimeExternal.ftl"; + + private static final String ATTRIBUTE_EXTERNAL_AUTH_ID = UserAccountsFirstTimeExternalPage.class + .getName(); + + /** + * Let some other request set the External Auth ID before redirecting to + * here. + */ + public static void setExternalAuthId(HttpServletRequest req, + String externalAuthId) { + req.getSession().setAttribute(ATTRIBUTE_EXTERNAL_AUTH_ID, + externalAuthId); + } + + private final UserAccountsFirstTimeExternalPageStrategy strategy; + + private boolean submit = false; + private String externalAuthId = ""; + private String emailAddress = ""; + private String firstName = ""; + private String lastName = ""; + + private String errorCode = ""; + private String bogusMessage = ""; + + protected UserAccountsFirstTimeExternalPage(VitroRequest vreq) { + super(vreq); + + this.strategy = UserAccountsFirstTimeExternalPageStrategy.getInstance( + vreq, this, isEmailEnabled()); + + checkSessionForExternalAuthId(); + if (externalAuthId.isEmpty()) { + parseRequestParameters(); + } + + validateExternalAuthId(); + + if (isSubmit() && !isBogus()) { + validateParameters(); + } + } + + private void checkSessionForExternalAuthId() { + HttpSession session = vreq.getSession(); + + Object o = session.getAttribute(ATTRIBUTE_EXTERNAL_AUTH_ID); + session.removeAttribute(ATTRIBUTE_EXTERNAL_AUTH_ID); + + if (o instanceof String) { + externalAuthId = (String) o; + } + } + + private void parseRequestParameters() { + submit = isFlagOnRequest(PARAMETER_SUBMIT); + externalAuthId = getStringParameter(PARAMETER_EXTERNAL_AUTH_ID, ""); + emailAddress = getStringParameter(PARAMETER_EMAIL_ADDRESS, ""); + firstName = getStringParameter(PARAMETER_FIRST_NAME, ""); + lastName = getStringParameter(PARAMETER_LAST_NAME, ""); + } + + private void validateExternalAuthId() { + if (externalAuthId.isEmpty()) { + bogusMessage = "Login failed - External ID is not found."; + return; + } + if (null != userAccountsDao + .getUserAccountByExternalAuthId(externalAuthId)) { + bogusMessage = "User account already exists for '" + externalAuthId + + "'"; + return; + } + } + + public boolean isBogus() { + return !bogusMessage.isEmpty(); + } + + public String getBogusMessage() { + return bogusMessage; + } + + public boolean isSubmit() { + return submit; + } + + private void validateParameters() { + if (firstName.isEmpty()) { + errorCode = ERROR_NO_FIRST_NAME; + } else if (lastName.isEmpty()) { + errorCode = ERROR_NO_LAST_NAME; + } else if (emailAddress.isEmpty()) { + errorCode = ERROR_NO_EMAIL; + } else if (isEmailInUse()) { + errorCode = ERROR_EMAIL_IN_USE; + } else if (!isEmailValidFormat()) { + errorCode = ERROR_EMAIL_INVALID_FORMAT; + } + } + + private boolean isEmailInUse() { + return userAccountsDao.getUserAccountByEmail(emailAddress) != null; + } + + private boolean isEmailValidFormat() { + return Authenticator.isValidEmailAddress(emailAddress); + } + + public boolean isValid() { + return errorCode.isEmpty(); + } + + public final ResponseValues showPage() { + Map body = new HashMap(); + + body.put("emailAddress", emailAddress); + body.put("firstName", firstName); + body.put("lastName", lastName); + body.put("externalAuthId", externalAuthId); + body.put("formUrls", buildUrlsMap()); + + if (!errorCode.isEmpty()) { + body.put(errorCode, Boolean.TRUE); + } + + strategy.addMoreBodyValues(body); + + return new TemplateResponseValues(TEMPLATE_NAME, body); + } + + public UserAccount createAccount() { + UserAccount u = new UserAccount(); + u.setEmailAddress(emailAddress); + u.setFirstName(firstName); + u.setLastName(lastName); + u.setExternalAuthId(externalAuthId); + u.setPasswordChangeRequired(false); + u.setPasswordLinkExpires(0); + u.setLoginCount(0); + u.setStatus(Status.ACTIVE); + u.setPermissionSetUris(Collections + .singleton(PermissionSetsLoader.URI_SELF_EDITOR)); + + userAccountsDao.insertUserAccount(u); + + strategy.notifyUser(u); + + return u; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPageStrategy.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPageStrategy.java new file mode 100644 index 000000000..71ce98161 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPageStrategy.java @@ -0,0 +1,107 @@ +/* $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.email.FreemarkerEmailFactory; +import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage; + +/** + * Handle the variations in the UserAccountsFirstTimeExternal page. If email is + * available, inform the template, and send a notification to the user. + * + * If not, then don't. + */ +public abstract class UserAccountsFirstTimeExternalPageStrategy extends + UserAccountsPage { + + public static UserAccountsFirstTimeExternalPageStrategy getInstance( + VitroRequest vreq, UserAccountsFirstTimeExternalPage page, + boolean emailEnabled) { + if (emailEnabled) { + return new EmailStrategy(vreq, page); + } else { + return new NoEmailStrategy(vreq, page); + } + } + + @SuppressWarnings("unused") + private UserAccountsFirstTimeExternalPage page; + + public UserAccountsFirstTimeExternalPageStrategy(VitroRequest vreq, + UserAccountsFirstTimeExternalPage page) { + super(vreq); + this.page = page; + } + + public abstract void addMoreBodyValues(Map body); + + public abstract void notifyUser(UserAccount ua); + + // ---------------------------------------------------------------------- + // Strategy to use if email is enabled. + // ---------------------------------------------------------------------- + + public static class EmailStrategy extends + UserAccountsFirstTimeExternalPageStrategy { + + public EmailStrategy(VitroRequest vreq, + UserAccountsFirstTimeExternalPage page) { + super(vreq, page); + } + + @Override + public void addMoreBodyValues(Map body) { + body.put("emailIsEnabled", Boolean.TRUE); + } + + @Override + public void notifyUser(UserAccount ua) { + Map body = new HashMap(); + body.put("userAccount", ua); + body.put("subjectLine", "Your VIVO account has been created."); + + FreemarkerEmailMessage email = FreemarkerEmailFactory + .createNewMessage(vreq); + email.addRecipient(TO, ua.getEmailAddress()); + email.setSubject("Your VIVO account has been created."); + email.setHtmlTemplate("userAccounts-firstTimeExternalEmail-html.ftl"); + email.setTextTemplate("userAccounts-firstTimeExternalEmail-text.ftl"); + email.setBodyMap(body); + email.send(); + } + + } + + // ---------------------------------------------------------------------- + // Strategy to use if email is disabled. + // ---------------------------------------------------------------------- + + public static class NoEmailStrategy extends + UserAccountsFirstTimeExternalPageStrategy { + + public NoEmailStrategy(VitroRequest vreq, + UserAccountsFirstTimeExternalPage page) { + super(vreq, page); + } + + @Override + public void addMoreBodyValues(Map body) { + // Nothing to add. + } + + @Override + public void notifyUser(UserAccount ua) { + // No way to notify. + } + + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsUserController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsUserController.java index 75ba9ec72..7628362ac 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsUserController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsUserController.java @@ -2,16 +2,23 @@ package edu.cornell.mannlib.vitro.webapp.controller.accounts.user; +import static edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource.EXTERNAL; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource; 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.beans.UserAccount; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator; +import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginRedirector; 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; +import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean; /** * Parcel out the different actions required of the UserAccounts GUI. @@ -25,6 +32,7 @@ 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"; + private static final String ACTION_FIRST_TIME_EXTERNAL = "/firstTimeExternal"; @Override protected Actions requiredActions(VitroRequest vreq) { @@ -52,6 +60,8 @@ public class UserAccountsUserController extends FreemarkerHttpServlet { return handleCreatePasswordRequest(vreq); } else if (ACTION_RESET_PASSWORD.equals(action)) { return handleResetPasswordRequest(vreq); + } else if (ACTION_FIRST_TIME_EXTERNAL.equals(action)) { + return handleFirstTimeLoginFromExternalAccount(vreq); } else { return handleInvalidRequest(vreq); } @@ -95,6 +105,24 @@ public class UserAccountsUserController extends FreemarkerHttpServlet { } + private ResponseValues handleFirstTimeLoginFromExternalAccount( + VitroRequest vreq) { + UserAccountsFirstTimeExternalPage page = new UserAccountsFirstTimeExternalPage( + vreq); + if (page.isBogus()) { + return showHomePage(vreq, page.getBogusMessage()); + } else if (page.isSubmit() && page.isValid()) { + UserAccount userAccount = page.createAccount(); + Authenticator auth = Authenticator.getInstance(vreq); + auth.recordLoginAgainstUserAccount(userAccount, EXTERNAL); + LoginProcessBean.removeBean(vreq); + + return showLoginRedirection(vreq); + } else { + return page.showPage(); + } + } + private ResponseValues handleInvalidRequest(VitroRequest vreq) { return showHomePage(vreq, BOGUS_STANDARD_MESSAGE); } @@ -104,4 +132,10 @@ public class UserAccountsUserController extends FreemarkerHttpServlet { return new RedirectResponseValues("/"); } + private ResponseValues showLoginRedirection(VitroRequest vreq) { + LoginRedirector lr = new LoginRedirector(vreq); + DisplayMessage.setMessage(vreq, lr.assembleWelcomeMessage()); + String uri = lr.getRedirectionUriForLoggedInUser(); + return new RedirectResponseValues(uri); + } } diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternal.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternal.ftl new file mode 100644 index 000000000..1b6b78791 --- /dev/null +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternal.ftl @@ -0,0 +1,64 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#-- Template for creating an account for the first time an external user logs in. --> + +

First time log in

+ + <#if errorEmailIsEmpty??> + <#assign errorMessage = "You must supply an email address." /> + + + <#if errorEmailInUse??> + <#assign errorMessage = "An account with that email address already exists." /> + + + <#if errorEmailInvalidFormat??> + <#assign errorMessage = "'${emailAddress}' is not a valid email address." /> + + + <#if errorFirstNameIsEmpty??> + <#assign errorMessage = "You must supply a first name." /> + + + <#if errorLastNameIsEmpty??> + <#assign errorMessage = "You must supply a last name." /> + + + <#if errorMessage?has_content> + + + +
+
+

+ Please provide your contact information to finish creating your account. +

+ +
+ + + + + + + + + + + + <#if emailIsEnabled??> +

+ Note: An email will be sent to the address entered above notifying + that an account has been created. +

+ + + or Cancel +
+
+
+ +${stylesheets.add('')} \ No newline at end of file diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail-html.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail-html.ftl new file mode 100644 index 000000000..5faa53dff --- /dev/null +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail-html.ftl @@ -0,0 +1,26 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#-- Confirmation that an account has been created for an externally-authenticated user. --> + + + + ${subjectLine} + + +

+ ${userAccount.firstName} ${userAccount.lastName} +

+ +

+ Congratulations! +

+ +

+ We have created your new VIVO account associated with ${userAccount.emailAddress}. +

+ +

+ Thanks! +

+ + \ No newline at end of file diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail-text.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail-text.ftl new file mode 100644 index 000000000..8b788d56d --- /dev/null +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-firstTimeExternalEmail-text.ftl @@ -0,0 +1,12 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#-- Confirmation that an account has been created for an externally-authenticated user. --> + +${userAccount.firstName} ${userAccount.lastName} + +Congratulations! + +We have created your new VIVO account associated with +${userAccount.emailAddress}. + +Thanks!