diff --git a/webapp/config/web.xml b/webapp/config/web.xml index e08e3e656..3b5cce82e 100644 --- a/webapp/config/web.xml +++ b/webapp/config/web.xml @@ -747,6 +747,15 @@ /accountsAdmin/* + + AccountsAjax + edu.cornell.mannlib.vitro.webapp.controller.accounts.admin.ajax.UserAccountsAjaxController + + + AccountsAjax + /accountsAjax/* + + AccountsUser edu.cornell.mannlib.vitro.webapp.controller.accounts.user.UserAccountsUserController 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 de197e5da..905c52953 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 @@ -130,6 +130,7 @@ public abstract class UserAccountsPage { map.put("createPassword", UrlBuilder.getUrl("/accounts/createPassword")); map.put("resetPassword", UrlBuilder.getUrl("/accounts/resetPassword")); map.put("firstTimeExternal", UrlBuilder.getUrl("/accounts/firstTimeExternal")); + map.put("accountsAjax", UrlBuilder.getUrl("/accountsAjax")); return map; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAssociatedProfileHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAssociatedProfileHelper.java new file mode 100644 index 000000000..95bfef557 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAssociatedProfileHelper.java @@ -0,0 +1,26 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.admin; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; + +/** + * TODO + */ +public class UserAccountsAssociatedProfileHelper { + private static final Log log = LogFactory + .getLog(UserAccountsAssociatedProfileHelper.class); + + /** + * This profile (if it exists) should be associated with this UserAccount. + * No other profile should be associated with this UserAccount. Make it so. + */ + public static void reconcile(UserAccount userAccount, + String associatedProfileUri) { + log.error("UserAccountsAssociatedProfileHelper.reconcile() not implemented."); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java index 8b88daf94..7c64cfbb2 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java @@ -32,7 +32,7 @@ public class UserAccountsEditPage extends UserAccountsPage { private static final String PARAMETER_FIRST_NAME = "firstName"; private static final String PARAMETER_LAST_NAME = "lastName"; private static final String PARAMETER_ROLE = "role"; - private static final String PARAMETER_ASSOCIATE_WITH_PROFILE = "associate"; + private static final String PARAMETER_ASSOCIATED_PROFILE_URI = "associatedProfileUri"; private static final String ERROR_NO_EMAIL = "errorEmailIsEmpty"; private static final String ERROR_EMAIL_IN_USE = "errorEmailInUse"; @@ -54,7 +54,7 @@ public class UserAccountsEditPage extends UserAccountsPage { private String firstName = ""; private String lastName = ""; private String selectedRoleUri = ""; - private boolean associateWithProfile; + private String associatedProfileUri = ""; private UserAccount userAccount; @@ -86,8 +86,8 @@ public class UserAccountsEditPage extends UserAccountsPage { firstName = getStringParameter(PARAMETER_FIRST_NAME, ""); lastName = getStringParameter(PARAMETER_LAST_NAME, ""); selectedRoleUri = getStringParameter(PARAMETER_ROLE, ""); - associateWithProfile = isParameterAsExpected( - PARAMETER_ASSOCIATE_WITH_PROFILE, "yes"); + associatedProfileUri = getStringParameter( + PARAMETER_ASSOCIATED_PROFILE_URI, ""); strategy.parseAdditionalParameters(); } @@ -186,9 +186,6 @@ public class UserAccountsEditPage extends UserAccountsPage { body.put("roles", buildRolesList()); } - if (associateWithProfile) { - body.put("associate", Boolean.TRUE); - } body.put("formUrls", buildUrlsMapWithEditUrl()); if (!errorCode.isEmpty()) { @@ -216,6 +213,7 @@ public class UserAccountsEditPage extends UserAccountsPage { } public void updateAccount() { + // Assemble the fields of the account. userAccount.setEmailAddress(emailAddress); userAccount.setFirstName(firstName); userAccount.setLastName(lastName); @@ -227,11 +225,15 @@ public class UserAccountsEditPage extends UserAccountsPage { userAccount.setPermissionSetUris(Collections .singleton(selectedRoleUri)); } - strategy.setAdditionalProperties(userAccount); + // Update the account. userAccountsDao.updateUserAccount(userAccount); + + // Associate the profile, as appropriate. + UserAccountsAssociatedProfileHelper.reconcile(userAccount, associatedProfileUri); + // Tell the user. strategy.notifyUser(); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/UserAccountsAjaxController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/UserAccountsAjaxController.java new file mode 100644 index 000000000..72b264877 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/UserAccountsAjaxController.java @@ -0,0 +1,128 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.admin.ajax; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONException; +import org.json.JSONObject; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.ManageUserAccounts; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.ajax.VitroAjaxController; + +/** + * Handle the AJAX functions that are specific to the UserAccounts pages. + */ +public class UserAccountsAjaxController extends VitroAjaxController { + private static final String PARAMETER_FUNCTION = "function"; + + @Override + protected Actions requiredActions(VitroRequest vreq) { + return new Actions(new ManageUserAccounts()); + } + + @Override + protected void doRequest(VitroRequest vreq, HttpServletResponse resp) + throws ServletException, IOException { + String function = vreq.getParameter(PARAMETER_FUNCTION); + if ("checkExternalAuth".equals(function)) { + new ExternalAuthChecker(this, vreq, resp).processRequest(); + } else { + new ErrorResponder(this, vreq, resp).processRequest(); + } + } + + static abstract class AjaxResponder { + protected final HttpServlet parent; + protected final VitroRequest vreq; + protected final HttpServletResponse resp; + + public AjaxResponder(HttpServlet parent, VitroRequest vreq, + HttpServletResponse resp) { + this.parent = parent; + this.vreq = vreq; + this.resp = resp; + } + + public abstract void processRequest() throws IOException; + + protected String getStringParameter(String key, String defaultValue) { + String value = vreq.getParameter(key); + return (value == null) ? defaultValue : value; + } + + } + + /** + * What is our reaction to this possible External Auth ID? + * + * Is somebody already using it (other than ourselves)? Does it match an + * existing Profile? Neither? + */ + private static class ExternalAuthChecker extends AjaxResponder { + private static final String PARAMETER_USER_ACCOUNT_URI = "userAccountUri"; + private static final String PARAMETER_ETERNAL_AUTH_ID = "externalAuthId"; + private static final String RESPONSE_ID_IN_USE = "idInUse"; + private static final String RESPONSE_MATCHES_PROFILE = "matchesProfile"; + private static final String RESPONSE_PROFILE_URI = "profileUri"; + private static final String RESPONSE_PROFILE_URL = "profileUrl"; + private static final String RESPONSE_PROFILE_LABEL = "profileLabel"; + + private final String userAccountUri; + private final String externalAuthId; + + public ExternalAuthChecker(HttpServlet parent, VitroRequest vreq, + HttpServletResponse resp) { + super(parent, vreq, resp); + userAccountUri = getStringParameter(PARAMETER_USER_ACCOUNT_URI, ""); + externalAuthId = getStringParameter(PARAMETER_ETERNAL_AUTH_ID, ""); + } + + @Override + public void processRequest() throws IOException { + // TODO For now, a totally bogus response: + // If "A", somebody else is already using the externalAuthId + // If "B", matches "Joe Blow" + // Anything else, no match. + try { + if ("A".equals(externalAuthId)) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(RESPONSE_ID_IN_USE, true); + resp.getWriter().write(jsonObject.toString()); + } else if ("B".equals(externalAuthId)) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(RESPONSE_MATCHES_PROFILE, true); + jsonObject.put(RESPONSE_PROFILE_URI, + "http://some.bogus.profile"); + jsonObject.put(RESPONSE_PROFILE_URL, + "http://some.bogus.profileUrl"); + jsonObject.put(RESPONSE_PROFILE_LABEL, "bogus label"); + resp.getWriter().write(jsonObject.toString()); + } else { + resp.getWriter().write("[]"); + } + } catch (JSONException e) { + resp.getWriter().write("[]"); + } + } + } + + private static class ErrorResponder extends AjaxResponder { + public ErrorResponder(HttpServlet parent, VitroRequest vreq, + HttpServletResponse resp) { + super(parent, vreq, resp); + } + + @Override + public void processRequest() throws IOException { + resp.getWriter().write("[]"); + } + } + +} diff --git a/webapp/web/js/account/accountAssociateProfile.js b/webapp/web/js/account/accountAssociateProfile.js new file mode 100644 index 000000000..27f9ff6d6 --- /dev/null +++ b/webapp/web/js/account/accountAssociateProfile.js @@ -0,0 +1,137 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +var associateProfileFields = { + + /* *** Initial page setup *** */ + + onLoad: function() { + console.log('Here we are'); + if (this.disableFormInUnsupportedBrowsers()) { + return; + } + this.mixIn(); + this.initObjects(); + this.initPage(); + }, + + disableFormInUnsupportedBrowsers: function() { + var disableWrapper = $('#ie67DisableWrapper'); + + // Check for unsupported browsers only if the element exists on the page + if (disableWrapper.length) { + if (vitro.browserUtils.isIELessThan8()) { + disableWrapper.show(); + $('.noIE67').hide(); + return true; + } + } + return false; + }, + + mixIn: function() { + $.extend(this, associateProfileFieldsData); + }, + + // On page load, create references for easy access to form elements. + initObjects: function() { + this.form = $('#userAccountForm'); + + // The external auth ID field and messages + this.externalAuthIdField = $('#externalAuthId'); + this.externalAuthIdInUseMessage = $('#externalAuthIdInUse'); + + // We have an associated profile + this.associatedArea = $('#associated'); + this.associatedProfileNameSpan = $('#associatedProfileName'); + this.verifyAssociatedProfileLink = $('#verifyProfileLink'); + this.associatedProfileUriField = $('#associatedProfileUri') + + // We want to associate a profile + this.associationOptionsArea = $('#associationOptions'); + }, + + // Initial page setup. Called only at page load. + initPage: function() { + this.checkForAssociatedProfile(); + + this.bindEventListeners(); + }, + + bindEventListeners: function() { + console.log('bindEventListeners'); + + this.externalAuthIdField.change(function() { + associateProfileFields.checkForAssociatedProfile(); + }); + this.externalAuthIdField.keyup(function() { + associateProfileFields.checkForAssociatedProfile(); + }); + + this.verifyAssociatedProfileLink.click(function() { + associateProfileFields.openVerifyWindow(); + return false; + }); + + }, + + checkForAssociatedProfile: function() { + $.ajax({ + url: associateProfileFields.ajaxUrl, + dataType: "json", + data: { + function: "checkExternalAuth", + userAccountUri: "", + externalAuthId: associateProfileFields.externalAuthIdField.val() + }, + complete: function(xhr, status) { + var results = $.parseJSON(xhr.responseText); + if (results.idInUse) { + associateProfileFields.showExternalAuthIdInUse() + } else if (results.matchesProfile) { + associateProfileFields.showExternalAuthIdMatchesProfile(results.profileUri, results.profileUri, results.profileLabel) + } else { + associateProfileFields.showExternalAuthIdNotRecognized() + } + } + }); + }, + + openVerifyWindow: function() { + window.open(this.verifyUrl, 'verifyMatchWindow', 'width=640,height=640,scrollbars=yes,resizable=yes,status=yes,toolbar=no,menubar=no,location=no'); + }, + + showExternalAuthIdInUse: function() { + this.externalAuthIdInUseMessage.show(); + this.associatedArea.hide(); + this.associationOptionsArea.hide(); + }, + + showExternalAuthIdMatchesProfile: function(profileUri, profileUrl, profileLabel) { + console.log('showExternalAuthIdMatchesProfile: profileUri=' + profileUri + ', profileUrl=' + profileUrl + ', profileLabel='+ profileLabel); + + this.externalAuthIdInUseMessage.hide(); + this.associatedArea.show(); + this.associationOptionsArea.hide(); + + this.associatedProfileNameSpan.html(profileLabel); + this.associatedProfileUriField.val(profileUri); + this.verifyUrl = profileUrl; + }, + + showExternalAuthIdNotRecognized: function() { + this.externalAuthIdInUseMessage.hide(); + this.associatedArea.hide(); + + if (this.externalAuthIdField.val().length > 0) { + this.associationOptionsArea.show(); + } else { + this.associationOptionsArea.hide(); + } + }, + +} + +$(document).ready(function() { + associateProfileFields.onLoad(); +}); + \ No newline at end of file diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-associateProfilePanel.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-associateProfilePanel.ftl new file mode 100644 index 000000000..4249fafe4 --- /dev/null +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-associateProfilePanel.ftl @@ -0,0 +1,55 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#-- Template for setting the account reference field, which can also associate a profile with the user account --> + + + + + + +
+ + +

+ External Auth. ID or other unique ID. Can associate the account to the user's profile. +

+

+ That Account Reference is already in use. +

+
+ <#-- If there is an associated profile, show these --> +
+

+ + + (verify this match) +

+ +
+ + <#-- If we haven't selected one, show these instead --> +
+

+ + +

+

- or -

+

+ + +

+
+ +
+ + + +${scripts.add('')} + diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-edit.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-edit.ftl index a1ee51358..863124d5b 100644 --- a/webapp/web/templates/freemarker/body/accounts/userAccounts-edit.ftl +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-edit.ftl @@ -55,7 +55,7 @@
Edit new account -
+ @@ -65,8 +65,7 @@ - - + <#include "userAccounts-associateProfilePanel.ftl"> <#if roles?has_content>

Roles *

@@ -77,7 +76,16 @@ - <#if !emailIsEnabled??> + <#if emailIsEnabled??> + checked /> + + +

+ Note: Instructions for resetting the password will + be emailed to the address entered above. The password will not + be reset until the user follows the link provided in this email. +

+ <#else> @@ -85,28 +93,9 @@

Leaving this blank means that the password will not be changed.

- + -

Associate a profile with this account

- checked id="associate" /> - - - checked id="no-associate" /> - - -
- checked /> - - - <#if emailIsEnabled??> -

- Note: Instructions for resetting the password will - be emailed to the address entered above. The password will not - be reset until the user follows the link provided in this email. -

- - or Cancel

* required fields

@@ -115,4 +104,4 @@ ${stylesheets.add('')} -${stylesheets.add('')} \ No newline at end of file +${stylesheets.add('')}