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/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginExternalAuthReturn.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginExternalAuthReturn.java index b4eca1079..8b525f6fe 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginExternalAuthReturn.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginExternalAuthReturn.java @@ -57,7 +57,7 @@ public class LoginExternalAuthReturn extends BaseLoginServlet { getAuthenticator(req).recordLoginAgainstUserAccount(userAccount, AuthenticationSource.EXTERNAL); removeLoginProcessArtifacts(req); - new LoginRedirector(req, resp).redirectLoggedInUser(); + new LoginRedirector(req).redirectLoggedInUser(resp); return; } @@ -71,14 +71,14 @@ public class LoginExternalAuthReturn extends BaseLoginServlet { getAuthenticator(req).recordLoginWithoutUserAccount(uri); removeLoginProcessArtifacts(req); - new LoginRedirector(req, resp).redirectLoggedInUser(); + new LoginRedirector(req).redirectLoggedInUser(resp); return; } log.debug("User is not recognized: " + externalAuthId); removeLoginProcessArtifacts(req); - new LoginRedirector(req, resp) - .redirectUnrecognizedExternalUser(externalAuthId); + new LoginRedirector(req).redirectUnrecognizedExternalUser(resp, + externalAuthId); } private void removeLoginProcessArtifacts(HttpServletRequest req) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java index d0746ad92..57c9375f1 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java @@ -29,17 +29,14 @@ public class LoginRedirector { private static final Log log = LogFactory.getLog(LoginRedirector.class); private final HttpServletRequest request; - private final HttpServletResponse response; private final HttpSession session; private final String uriOfAssociatedIndividual; private final String afterLoginPage; - public LoginRedirector(HttpServletRequest request, - HttpServletResponse response) { + public LoginRedirector(HttpServletRequest request) { this.request = request; this.session = request.getSession(); - this.response = response; uriOfAssociatedIndividual = getAssociatedIndividualUri(); @@ -70,28 +67,45 @@ public class LoginRedirector { } } - public void redirectLoggedInUser() throws IOException { - DisplayMessage.setMessage(request, assembleWelcomeMessage()); - - try { - if (isSelfEditorWithIndividual()) { - log.debug("Going to Individual home page."); - response.sendRedirect(getAssociatedIndividualHomePage()); - } else if (isMerelySelfEditor()) { - log.debug("User not recognized. Going to application home."); - response.sendRedirect(getApplicationHomePageUrl()); + public String getRedirectionUriForLoggedInUser() { + if (isSelfEditorWithIndividual()) { + log.debug("Going to Individual home page."); + return getAssociatedIndividualHomePage(); + } else if (isMerelySelfEditor()) { + log.debug("User not recognized. Going to application home."); + return getApplicationHomePageUrl(); + } else { + if (isLoginPage(afterLoginPage)) { + log.debug("Coming from /login. Going to site admin page."); + return getSiteAdminPageUrl(); + } else if (null != afterLoginPage) { + log.debug("Returning to requested page: " + afterLoginPage); + return afterLoginPage; } else { - if (isLoginPage(afterLoginPage)) { - log.debug("Coming from /login. Going to site admin page."); - response.sendRedirect(getSiteAdminPageUrl()); - } else if (null != afterLoginPage) { - log.debug("Returning to requested page: " + afterLoginPage); - response.sendRedirect(afterLoginPage); - } else { - log.debug("Don't know what to do. Go home."); - response.sendRedirect(getApplicationHomePageUrl()); - } + log.debug("Don't know what to do. Go home."); + return getApplicationHomePageUrl(); } + } + } + + public String getRedirectionUriForCancellingUser() { + if (isLoginPage(afterLoginPage)) { + log.debug("Coming from /login. Going to home."); + return getApplicationHomePageUrl(); + } else if (null != afterLoginPage) { + log.debug("Returning to requested page: " + afterLoginPage); + return afterLoginPage; + } else { + log.debug("Don't know what to do. Go home."); + return getApplicationHomePageUrl(); + } + } + + public void redirectLoggedInUser(HttpServletResponse response) + throws IOException { + try { + DisplayMessage.setMessage(request, assembleWelcomeMessage()); + response.sendRedirect(getRedirectionUriForLoggedInUser()); LoginProcessBean.removeBean(request); } catch (IOException e) { log.debug("Problem with re-direction", e); @@ -99,7 +113,7 @@ public class LoginRedirector { } } - private String assembleWelcomeMessage() { + public String assembleWelcomeMessage() { if (isMerelySelfEditor() && !isSelfEditorWithIndividual()) { // A special message for unrecognized self-editors: return "You have logged in, " @@ -124,18 +138,10 @@ public class LoginRedirector { return "Welcome" + backString + ", " + greeting; } - public void redirectCancellingUser() throws IOException { + public void redirectCancellingUser(HttpServletResponse response) + throws IOException { try { - if (isLoginPage(afterLoginPage)) { - log.debug("Coming from /login. Going to home."); - response.sendRedirect(getApplicationHomePageUrl()); - } else if (null != afterLoginPage) { - log.debug("Returning to requested page: " + afterLoginPage); - response.sendRedirect(afterLoginPage); - } else { - log.debug("Don't know what to do. Go home."); - response.sendRedirect(getApplicationHomePageUrl()); - } + response.sendRedirect(getRedirectionUriForCancellingUser()); LoginProcessBean.removeBean(request); } catch (IOException e) { log.debug("Problem with re-direction", e); @@ -143,8 +149,8 @@ public class LoginRedirector { } } - public void redirectUnrecognizedExternalUser(String username) - throws IOException { + public void redirectUnrecognizedExternalUser(HttpServletResponse response, + String username) throws IOException { log.debug("Redirecting unrecognized external user: " + username); DisplayMessage.setMessage(request, "VIVO cannot find a profile for your account."); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/Authenticate.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/Authenticate.java index 232e08323..9bf9847f6 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/Authenticate.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/Authenticate.java @@ -124,7 +124,7 @@ public class Authenticate extends VitroHttpServlet { // Send them on their way. switch (exitState) { case NOWHERE: - new LoginRedirector(vreq, response).redirectCancellingUser(); + new LoginRedirector(vreq).redirectCancellingUser(response); break; case LOGGING_IN: showLoginScreen(vreq, response); @@ -133,7 +133,7 @@ public class Authenticate extends VitroHttpServlet { showLoginScreen(vreq, response); break; default: // LOGGED_IN: - new LoginRedirector(vreq, response).redirectLoggedInUser(); + new LoginRedirector(vreq).redirectLoggedInUser(response); break; } } catch (Exception e) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/IndividualDao.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/IndividualDao.java index 87d94c262..d32fa0407 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/IndividualDao.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/IndividualDao.java @@ -10,6 +10,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement; import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.Keyword; import edu.cornell.mannlib.vitro.webapp.beans.VClass; +import edu.cornell.mannlib.vitro.webapp.edit.EditLiteral; import edu.cornell.mannlib.vitro.webapp.search.beans.ObjectSourceIface; public interface IndividualDao extends ObjectSourceIface { @@ -135,6 +136,8 @@ public interface IndividualDao extends ObjectSourceIface { * @throws InsertException Could not create a URI */ String getUnusedURI(Individual individual) throws InsertException; + + EditLiteral getLabelEditLiteral(String individualUri); @Deprecated public abstract Individual getIndividualByExternalId(int externalIdType, diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java index ad813013d..7f530c04e 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/IndividualDaoFiltering.java @@ -19,6 +19,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao; import edu.cornell.mannlib.vitro.webapp.dao.InsertException; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; +import edu.cornell.mannlib.vitro.webapp.edit.EditLiteral; class IndividualDaoFiltering extends BaseFiltering implements IndividualDao{ @@ -242,4 +243,10 @@ class IndividualDaoFiltering extends BaseFiltering implements IndividualDao{ return innerIndividualDao.getUnusedURI(individual); } + + @Override + public EditLiteral getLabelEditLiteral(String individualUri) { + return innerIndividualDao.getLabelEditLiteral(individualUri); + } + } \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/IndividualDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/IndividualDaoJena.java index 29b3b8397..50dd064f9 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/IndividualDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/IndividualDaoJena.java @@ -58,6 +58,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.jena.event.IndividualCreationEvent; import edu.cornell.mannlib.vitro.webapp.dao.jena.event.IndividualDeletionEvent; import edu.cornell.mannlib.vitro.webapp.dao.jena.event.IndividualUpdateEvent; +import edu.cornell.mannlib.vitro.webapp.edit.EditLiteral; public class IndividualDaoJena extends JenaBaseDao implements IndividualDao { @@ -1047,5 +1048,20 @@ public class IndividualDaoJena extends JenaBaseDao implements IndividualDao { return uri; } + + @Override + // This method returns an EditLiteral rather than a Jena Literal, since IndividualDao + // should not reference Jena objects. (However, the problem isn't really solved + // because EditLiteral currently references the Jena API.) + public EditLiteral getLabelEditLiteral(String individualUri) { + Literal literal = getLabelLiteral(individualUri); + if (literal == null) { + return null; + } + String value = literal.getLexicalForm(); + String datatype = literal.getDatatypeURI(); + String lang = literal.getLanguage(); + return new EditLiteral(value, datatype, lang); + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java index 4b7d3c7d2..6b9a40079 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDao.java @@ -776,12 +776,17 @@ public class JenaBaseDao extends JenaBaseDaoCon { } return label; } + + protected Literal getLabelLiteral(String individualUri) { + OntResource resource = webappDaoFactory.getOntModel().createOntResource(individualUri); + return getLabelLiteral(resource); + } /** * works through list of PREFERRED_LANGUAGES to find an appropriate * label, or NULL if not found. */ - public Literal getLabelLiteral(OntResource r) { + protected Literal getLabelLiteral(OntResource r) { Literal labelLiteral = null; r.getOntModel().enterCriticalSection(Lock.READ); try { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyStatementTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyStatementTemplateModel.java index 98316e222..da631e0bc 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyStatementTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/DataPropertyStatementTemplateModel.java @@ -29,7 +29,7 @@ public class DataPropertyStatementTemplateModel extends PropertyStatementTemplat protected String value = null; // Used for editing - private String dataPropHash = null; + protected String dataPropHash = null; //Useful in case additional params to be retrieved for URL private VitroRequest vitroRequest= null; @@ -84,7 +84,7 @@ public class DataPropertyStatementTemplateModel extends PropertyStatementTemplat } // Determine whether the statement can be deleted - // Hack for rdfs:label - the policy doesn't prevent deletion + // Hack for rdfs:label - the policy doesn't prevent deletion. if ( ! propertyUri.equals(VitroVocabulary.LABEL) ) { action = new DropDataPropStmt(dps); if (policyHelper.isAuthorizedAction(action)) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/NameStatementTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/NameStatementTemplateModel.java index b5e6c16e2..d00e5041f 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/NameStatementTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/NameStatementTemplateModel.java @@ -9,16 +9,21 @@ import org.apache.commons.logging.LogFactory; import org.openrdf.model.URI; import org.openrdf.model.impl.URIImpl; -import com.hp.hpl.jena.ontology.OntModel; -import com.hp.hpl.jena.ontology.OntResource; import com.hp.hpl.jena.rdf.model.Literal; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.DropDataPropStmt; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.EditDataPropStmt; +import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement; +import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatementImpl; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyStatementDao; +import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; -import edu.cornell.mannlib.vitro.webapp.dao.jena.JenaBaseDao; import edu.cornell.mannlib.vitro.webapp.dao.jena.WebappDaoFactoryJena; +import edu.cornell.mannlib.vitro.webapp.edit.EditLiteral; +import edu.cornell.mannlib.vitro.webapp.edit.n3editing.processEdit.RdfLiteralHash; public class NameStatementTemplateModel extends DataPropertyStatementTemplateModel { @@ -27,43 +32,49 @@ public class NameStatementTemplateModel extends /* * This method handles the special case where we are creating a DataPropertyStatementTemplateModel outside the GroupedPropertyList. - * Specifically, it allows rdfs:label to be treated like a data property statement and thus have editing links. It is not possible - * to handle rdfs:label like vitro links and vitroPublic image, because it is not possible to construct a DataProperty from - * rdfs:label. + * Specifically, it allows rdfs:label to be treated like a data property statement and thus have editing links. */ NameStatementTemplateModel(String subjectUri, VitroRequest vreq, EditingPolicyHelper policyHelper) { super(subjectUri, VitroVocabulary.LABEL, vreq, policyHelper); - Literal literal = null; WebappDaoFactory wdf = vreq.getWebappDaoFactory(); - // Use the same methods to get the label that are used elsewhere in the application, to - // guarantee consistent results for individuals with multiple labels. - // RY The problem here is we have a WebappDaoFactoryFiltering instead of WebappDaoFactoryJena. - if (wdf instanceof WebappDaoFactoryJena) { - WebappDaoFactoryJena wdfj = (WebappDaoFactoryJena) wdf; - OntResource resource = wdfj.getOntModel().createOntResource(subjectUri); - JenaBaseDao baseDao = wdfj.getJenaBaseDao(); - literal = baseDao.getLabelLiteral(resource); - } else { - DataPropertyStatementDao dpsDao = vreq.getWebappDaoFactory().getDataPropertyStatementDao(); - List literals = dpsDao.getDataPropertyValuesForIndividualByProperty(subjectUri, VitroVocabulary.LABEL); - // Make sure the subject has a value for this property - if (literals.size() > 0) { - literal = literals.get(0); - } - } + // NIHVIVO-2466 Use the same methods to get the label that are used elsewhere in the + // application, to guarantee consistent results for individuals with multiple labels + // across the application. + IndividualDao iDao = wdf.getIndividualDao(); + EditLiteral literal = iDao.getLabelEditLiteral(subjectUri); if (literal != null) { value = literal.getLexicalForm(); setEditAccess(literal, policyHelper); } else { - // If the individual has no rdfs:label, use the local name. It will not be editable (this replicates previous behavior; + // If the individual has no rdfs:label, use the local name. It will not be editable. (This replicates previous behavior; // perhaps we would want to allow a label to be added. But such individuals do not usually have their profiles viewed or - // edited directly. + // edited directly.) URI uri = new URIImpl(subjectUri); value = uri.getLocalName(); } } + + protected void setEditAccess(EditLiteral value, EditingPolicyHelper policyHelper) { + + if (policyHelper != null) { // we're editing + DataPropertyStatement dps = new DataPropertyStatementImpl(subjectUri, propertyUri, value.getLexicalForm()); + // Language and datatype are needed to get the correct hash value + dps.setLanguage(value.getLanguage()); + dps.setDatatypeURI(value.getDatatypeURI()); + this.dataPropHash = String.valueOf(RdfLiteralHash.makeRdfLiteralHash(dps)); + + // Determine whether the statement can be edited + RequestedAction action = new EditDataPropStmt(dps); + if (policyHelper.isAuthorizedAction(action)) { + markEditable(); + } + + // The label cannot be deleted, so we don't need to check + // the policy for the delete action. + } + } } diff --git a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/IndividualDaoStub.java b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/IndividualDaoStub.java index 4caf12cbe..cdbb4d31b 100644 --- a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/IndividualDaoStub.java +++ b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/IndividualDaoStub.java @@ -14,6 +14,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.Keyword; import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao; import edu.cornell.mannlib.vitro.webapp.dao.InsertException; +import edu.cornell.mannlib.vitro.webapp.edit.EditLiteral; /** * A minimal implementation of the IndividualDao. @@ -237,4 +238,10 @@ public class IndividualDaoStub implements IndividualDao { "IndividualDaoStub.getIndividualByExternalId() not implemented."); } + @Override + public EditLiteral getLabelEditLiteral(String individualUri) { + throw new RuntimeException( + "IndividualDaoStub.getLabelLiteral() not implemented."); + } + } diff --git a/webapp/web/css/browseClassGroups.css b/webapp/web/css/browseClassGroups.css index 43d59b4ee..c2c491b04 100644 --- a/webapp/web/css/browseClassGroups.css +++ b/webapp/web/css/browseClassGroups.css @@ -64,11 +64,13 @@ ul#browse-classgroups .count-classes { border: 1px solid #dde6e5; background: #fff; min-height: 230px; + padding-top: 10px; } a.browse-superclass { position: absolute; right: 0.5em; font-size: 0.9em; + top: 3px; } ul#classes-in-classgroup { float: left; diff --git a/webapp/web/js/account/accountUtils.js b/webapp/web/js/account/accountUtils.js index c95585c2c..6f1aa0d69 100644 --- a/webapp/web/js/account/accountUtils.js +++ b/webapp/web/js/account/accountUtils.js @@ -1,10 +1,17 @@ /* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +// Change form actions in account main page +function changeAction(form, url) { + form.action = url; + return true; +} + $(document).ready(function(){ - + //Accounts per page - //Hide is javascrip is disable - $('.accounts-per-page-form input[type="submit"]').hide(); + //Hide is javascrip is enable + $('input[name="accounts-per-page"]').addClass('hide'); $('.accounts-per-page').change(function() { $('#account-display').submit(); 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! diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl index a526db236..0cfe7c7f8 100644 --- a/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl @@ -107,13 +107,6 @@ - -
@@ -126,6 +119,7 @@ | n accounts | <#assign counts = [25, 50, 100]> + - - - accounts per page | + accounts per page | <#if page.previous?has_content> Previous @@ -192,6 +184,41 @@ + +
+ + + + +
${scripts.add('')} \ No newline at end of file