NIHVIVO-2279 first steps toward associating a UserAccount with a Profile.

This commit is contained in:
j2blake 2011-06-29 15:36:14 +00:00
parent 239256187d
commit 87dc7698e1
8 changed files with 380 additions and 33 deletions

View file

@ -747,6 +747,15 @@
<url-pattern>/accountsAdmin/*</url-pattern> <url-pattern>/accountsAdmin/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet>
<servlet-name>AccountsAjax</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.accounts.admin.ajax.UserAccountsAjaxController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AccountsAjax</servlet-name>
<url-pattern>/accountsAjax/*</url-pattern>
</servlet-mapping>
<servlet> <servlet>
<servlet-name>AccountsUser</servlet-name> <servlet-name>AccountsUser</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.accounts.user.UserAccountsUserController</servlet-class> <servlet-class>edu.cornell.mannlib.vitro.webapp.controller.accounts.user.UserAccountsUserController</servlet-class>

View file

@ -130,6 +130,7 @@ public abstract class UserAccountsPage {
map.put("createPassword", UrlBuilder.getUrl("/accounts/createPassword")); map.put("createPassword", UrlBuilder.getUrl("/accounts/createPassword"));
map.put("resetPassword", UrlBuilder.getUrl("/accounts/resetPassword")); map.put("resetPassword", UrlBuilder.getUrl("/accounts/resetPassword"));
map.put("firstTimeExternal", UrlBuilder.getUrl("/accounts/firstTimeExternal")); map.put("firstTimeExternal", UrlBuilder.getUrl("/accounts/firstTimeExternal"));
map.put("accountsAjax", UrlBuilder.getUrl("/accountsAjax"));
return map; return map;
} }

View file

@ -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.");
}
}

View file

@ -32,7 +32,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
private static final String PARAMETER_FIRST_NAME = "firstName"; private static final String PARAMETER_FIRST_NAME = "firstName";
private static final String PARAMETER_LAST_NAME = "lastName"; private static final String PARAMETER_LAST_NAME = "lastName";
private static final String PARAMETER_ROLE = "role"; 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_NO_EMAIL = "errorEmailIsEmpty";
private static final String ERROR_EMAIL_IN_USE = "errorEmailInUse"; private static final String ERROR_EMAIL_IN_USE = "errorEmailInUse";
@ -54,7 +54,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
private String firstName = ""; private String firstName = "";
private String lastName = ""; private String lastName = "";
private String selectedRoleUri = ""; private String selectedRoleUri = "";
private boolean associateWithProfile; private String associatedProfileUri = "";
private UserAccount userAccount; private UserAccount userAccount;
@ -86,8 +86,8 @@ public class UserAccountsEditPage extends UserAccountsPage {
firstName = getStringParameter(PARAMETER_FIRST_NAME, ""); firstName = getStringParameter(PARAMETER_FIRST_NAME, "");
lastName = getStringParameter(PARAMETER_LAST_NAME, ""); lastName = getStringParameter(PARAMETER_LAST_NAME, "");
selectedRoleUri = getStringParameter(PARAMETER_ROLE, ""); selectedRoleUri = getStringParameter(PARAMETER_ROLE, "");
associateWithProfile = isParameterAsExpected( associatedProfileUri = getStringParameter(
PARAMETER_ASSOCIATE_WITH_PROFILE, "yes"); PARAMETER_ASSOCIATED_PROFILE_URI, "");
strategy.parseAdditionalParameters(); strategy.parseAdditionalParameters();
} }
@ -186,9 +186,6 @@ public class UserAccountsEditPage extends UserAccountsPage {
body.put("roles", buildRolesList()); body.put("roles", buildRolesList());
} }
if (associateWithProfile) {
body.put("associate", Boolean.TRUE);
}
body.put("formUrls", buildUrlsMapWithEditUrl()); body.put("formUrls", buildUrlsMapWithEditUrl());
if (!errorCode.isEmpty()) { if (!errorCode.isEmpty()) {
@ -216,6 +213,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
} }
public void updateAccount() { public void updateAccount() {
// Assemble the fields of the account.
userAccount.setEmailAddress(emailAddress); userAccount.setEmailAddress(emailAddress);
userAccount.setFirstName(firstName); userAccount.setFirstName(firstName);
userAccount.setLastName(lastName); userAccount.setLastName(lastName);
@ -227,11 +225,15 @@ public class UserAccountsEditPage extends UserAccountsPage {
userAccount.setPermissionSetUris(Collections userAccount.setPermissionSetUris(Collections
.singleton(selectedRoleUri)); .singleton(selectedRoleUri));
} }
strategy.setAdditionalProperties(userAccount); strategy.setAdditionalProperties(userAccount);
// Update the account.
userAccountsDao.updateUserAccount(userAccount); userAccountsDao.updateUserAccount(userAccount);
// Associate the profile, as appropriate.
UserAccountsAssociatedProfileHelper.reconcile(userAccount, associatedProfileUri);
// Tell the user.
strategy.notifyUser(); strategy.notifyUser();
} }

View file

@ -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("[]");
}
}
}

View file

@ -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();
});

View file

@ -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 -->
<table>
<tr>
<td>
<label for="externalAuthId">Account Reference</label>
<input type="text" name="externalAuthId" value="${externalAuthId}" id="externalAuthId" role="input "/>
<p>
External Auth. ID or other unique ID. Can associate the account to the user's profile.
</p>
<p id="externalAuthIdInUse">
That Account Reference is already in use.
</p>
</td>
<td>
<#-- If there is an associated profile, show these -->
<div id="associated">
<p>
<label for="associatedProfileName">Associated profile:</label>
<span class="acSelectionInfo" id="associatedProfileName"></span>
<a href="" id="verifyProfileLink">(verify this match)</a>
</p>
<input type="hidden" id="associatedProfileUri" name="associatedProfileUri" value="" />
</div>
<#-- If we haven't selected one, show these instead -->
<div id="associationOptions">
<p>
<label for="associateProfileName">Select an existing profile</label>
<input type="text" id="associateProfileName" name="associateProfileName" class="acSelector" size="35">
</p>
<p> - or - </p>
<p>
<label for="">Create an associated profile</label>
<select name="degreeUri" id="degreeUri" >
<option value="" selected="selected">Select one</option>
<option value="" disabled>Bogus</option>
</select>
</p>
</div>
</td>
</tr>
</table>
<script type="text/javascript">
var associateProfileFieldsData = {
ajaxUrl: '${formUrls.accountsAjax}'
};
</script>
${scripts.add('<script type="text/javascript" src="${urls.base}/js/account/accountAssociateProfile.js"></script>')}

View file

@ -55,7 +55,7 @@
<fieldset> <fieldset>
<legend>Edit new account</legend> <legend>Edit new account</legend>
<form method="POST" action="${formUrls.edit}" class="customForm" role="edit account"> <form method="POST" action="${formUrls.edit}" id="userAccountForm" class="customForm" role="edit account">
<label for="email-address">Email address<span class="requiredHint"> *</span></label> <label for="email-address">Email address<span class="requiredHint"> *</span></label>
<input type="text" name="emailAddress" value="${emailAddress}" id="email-address" role="input" /> <input type="text" name="emailAddress" value="${emailAddress}" id="email-address" role="input" />
@ -65,8 +65,7 @@
<label for="last-name">Last name<span class="requiredHint"> *</span></label> <label for="last-name">Last name<span class="requiredHint"> *</span></label>
<input type="text" name="lastName" value="${lastName}" id="last-name" role="input" /> <input type="text" name="lastName" value="${lastName}" id="last-name" role="input" />
<label for="external-auth-id">External authorization ID</label> <#include "userAccounts-associateProfilePanel.ftl">
<input type="text" name="externalAuthId" value="${externalAuthId}" id="external-auth-id" role="input "/>
<#if roles?has_content> <#if roles?has_content>
<p>Roles<span class="requiredHint"> *</span> </p> <p>Roles<span class="requiredHint"> *</span> </p>
@ -77,7 +76,16 @@
</#list> </#list>
</#if> </#if>
<#if !emailIsEnabled??> <#if emailIsEnabled??>
<input type="checkbox" name="resetPassword" value="" id="reset-password" role="checkbox" <#if resetPassword??>checked</#if> />
<label class="inline" for="reset-password"> Reset password</label>
<p class="note">
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.
</p>
<#else>
<label for="new-password">New password<span class="requiredHint"> *</span></label> <label for="new-password">New password<span class="requiredHint"> *</span></label>
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" /> <input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />
@ -85,28 +93,9 @@
<p>Leaving this blank means that the password will not be changed.</p> <p>Leaving this blank means that the password will not be changed.</p>
<label for="confirm-password">Confirm initial password<span class="requiredHint"> *</span></label> <label for="confirm-password">Confirm initial password<span class="requiredHint"> *</span></label>
<input type="text" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input" /> <input type="password" name="confirmPassword" value="${confirmPassword}" id="confirm-password" role="input" />
</#if> </#if>
<p>Associate a profile with this account</p>
<input type="radio" name="associate" value="yes" role="radio" <#if associate??>checked</#if> id="associate" />
<label class="inline" for="associate"> Yes</label>
<input type="radio" name="associate" value="no" role="radio" <#if !associate??>checked</#if> id="no-associate" />
<label class="inline" for="no-associate"> No</label>
<br />
<input type="checkbox" name="resetPassword" value="" id="reset-password" role="checkbox" <#if resetPassword??>checked</#if> />
<label class="inline" for="reset-password"> Reset password</label>
<#if emailIsEnabled??>
<p class="note">
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.
</p>
</#if>
<input type="submit" name="submitEdit" value="Save changes" class="submit" /> or <a class="cancel" href="${formUrls.list}">Cancel</a> <input type="submit" name="submitEdit" value="Save changes" class="submit" /> or <a class="cancel" href="${formUrls.list}">Cancel</a>
<p class="requiredHint">* required fields</p> <p class="requiredHint">* required fields</p>
@ -115,4 +104,4 @@
</section> </section>
${stylesheets.add('<link rel="stylesheet" href="${urls.base}/css/account/account.css" />')} ${stylesheets.add('<link rel="stylesheet" href="${urls.base}/css/account/account.css" />')}
${stylesheets.add('<link rel="stylesheet" href="${urls.base}/edit/forms/css/customForm.css" />')} ${stylesheets.add('<link rel="stylesheet" href="${urls.base}/edit/forms/css/customForm.css" />')}