NIHVIVO-638 Check in first phase of changes to the Login form and backend.

This commit is contained in:
jeb228 2010-07-07 19:35:46 +00:00
parent 2253f889ed
commit 74c7660321
12 changed files with 999 additions and 342 deletions

View file

@ -2,8 +2,12 @@
package edu.cornell.mannlib.vitro.webapp.controller.edit;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Calendar;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -13,6 +17,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -22,244 +27,529 @@ import edu.cornell.mannlib.vedit.beans.LoginFormBean;
import edu.cornell.mannlib.vitro.webapp.auth.policy.RoleBasedPolicy.AuthRole;
import edu.cornell.mannlib.vitro.webapp.beans.User;
import edu.cornell.mannlib.vitro.webapp.controller.Controllers;
import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreeMarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean;
import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.Message;
import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.State;
import edu.cornell.mannlib.vitro.webapp.dao.UserDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.dao.jena.LoginEvent;
import edu.cornell.mannlib.vitro.webapp.dao.jena.LoginLogoutEvent;
public class Authenticate extends VitroHttpServlet {
private static final int DEFAULT_PORTAL_ID=1;
public static final String USER_SESSION_MAP_ATTR = "userURISessionMap";
private UserDao userDao = null;
private static final Log log = LogFactory.getLog(Authenticate.class.getName());
public class Authenticate extends FreeMarkerHttpServlet {
private static final Log log = LogFactory.getLog(Authenticate.class
.getName());
public void doPost( HttpServletRequest request, HttpServletResponse response ) {
try {
HttpSession session = request.getSession();
if(session.isNew()){
session.setMaxInactiveInterval(300); // seconds, not milliseconds
}
userDao = ((WebappDaoFactory)session.getServletContext().getAttribute("webappDaoFactory")).getUserDao();
LoginFormBean f = (LoginFormBean) session.getAttribute( "loginHandler" );
/** The username field on the login form. */
private static final String PARAMETER_USERNAME = "loginName";
//obtain a db connection and perform a db query
//ensuring that the username exists
/** The password field on the login form. */
private static final String PARAMETER_PASSWORD = "loginPassword";
// JCR 20040905 passing on portal home parameter
String portalIdStr=(portalIdStr=request.getParameter("home"))==null?String.valueOf(DEFAULT_PORTAL_ID):portalIdStr;
//request.setAttribute("home",portalIdStr);
/** The new password field on the password change form. */
private static final String PARAMETER_NEW_PASSWORD = "newPassword";
// Build the redirect URLs
String contextPath = request.getContextPath();
String urlParams = "?home=" + portalIdStr + "&login=block";
String loginUrl = contextPath + Controllers.LOGIN + urlParams;
String siteAdminUrl = contextPath + Controllers.SITE_ADMIN + urlParams;
/** The confirm password field on the password change form. */
private static final String PARAMETER_CONFIRM_PASSWORD = "confirmPassword";
if (userDao==null) {
f.setErrorMsg("loginPassword","unable to get UserDao");
f.setLoginStatus("no UserDao");
response.sendRedirect(loginUrl);
return;
}
/** If they are logging in, show them this form. */
public static final String TEMPLATE_LOGIN = "login/login.ftl";
/* used for encoding cleartext passwords sent via http before store in database
String loginPassword = "";
String passwordQuery = "SELECT PASSWORD('" + f.getLoginPassword() + "')";
ResultSet ps = stmt.executeQuery( passwordQuery );
while ( ps.next() ) {
loginPassword = ps.getString(1);
}
*/
String userEnteredPasswordAfterMd5Conversion=f.getLoginPassword(); // won't be null
if ( userEnteredPasswordAfterMd5Conversion.equals("") ) { // shouldn't get through JS form verification
f.setErrorMsg( "loginPassword","Please enter a password" );
f.setLoginStatus("bad_password");
response.sendRedirect(loginUrl);
return;
}
/** If they are changing their password on first login, show them this form. */
public static final String TEMPLATE_FORCE_PASSWORD_CHANGE = "login/forcedPasswordChange.ftl";
User user = userDao.getUserByUsername(f.getLoginName());
public static final String BODY_LOGIN_NAME = "loginName";
public static final String BODY_FORM_ACTION = "formAction";
public static final String BODY_ERROR_MESSAGE = "errorMessage";
if (user==null) {
f.setErrorMsg( "loginName","No user found with username " + f.getLoginName() );
f.setLoginStatus("unknown_username");
response.sendRedirect(loginUrl);
return;
}
/** If no portal is specified in the request, use this one. */
private static final int DEFAULT_PORTAL_ID = 1;
// logic for authentication
// first check for new users (loginCount==0)
// 1) cold (have username but haven't received initial password)
// 2) initial password has been set but user mis-typed it
// 3) correctly typed initial password and oldpassword set to provided password; have to enter a different one
// 4) entered same password again
// 5) entered a new private password, and bypass this stage because logincount set to 1
// then check for users DBA has set to require changing password (md5password is null, oldpassword is not)
//
// check password; dbMd5Password is md5password from database
if (user.getLoginCount() == 0 ) { // new user
if ( user.getMd5password() == null ) { // user is known but has not been given initial password
f.setErrorMsg( "loginPassword", "Please request a username and initial password via the link below" ); // store password in database but force immediate re-entry
f.setLoginStatus("first_login_no_password");
} else if (!user.getMd5password().equals( userEnteredPasswordAfterMd5Conversion )) { // mis-typed CCRP-provided initial password
if ( user.getOldPassword() == null ) { // did not make it through match of initially supplied password
f.setErrorMsg( "loginPassword", "Please try entering provided password again" );
f.setLoginStatus("first_login_mistyped");
} else if (user.getOldPassword().equals( userEnteredPasswordAfterMd5Conversion ) ) {
f.setErrorMsg( "loginPassword", "Please pick a different password from the one provided initially" );
f.setLoginStatus("changing_password_repeated_old");
} else { // successfully provided different, private password
f.setErrorMsg( "loginPassword", "Please re-enter new private password for confirmation" );
user.setMd5password(userEnteredPasswordAfterMd5Conversion);
user.setLoginCount(1);
userDao.updateUser(user);
f.setLoginStatus("changing_password");
}
} else if (f.getLoginStatus().equals("first_login_changing_password")) { // User has been prompted to change password, but has re-entered the original one
f.setErrorMsg( "loginPassword", "Please pick a different password from the one provided initially" ); // store password in database but force immediate re-entry
user.setOldPassword(user.getMd5password());
userDao.updateUser(user);
f.setLoginStatus("first_login_changing_password");
} else { // entered a password that matches initial md5password in database; now force them to change it
// oldpassword could be null or not null depending on number of mistries
f.setErrorMsg( "loginPassword", "Please now choose a private password" ); // store password in database but force immediate re-entry
user.setOldPassword(user.getMd5password());
userDao.updateUser(user);
f.setLoginStatus("first_login_changing_password");
}
response.sendRedirect(loginUrl);
return;
} else if ( user.getMd5password()==null ) { // DBA has forced entry of a new password for user with a loginCount > 0
if ( user.getOldPassword() != null && user.getOldPassword().equals( userEnteredPasswordAfterMd5Conversion ) ) {
f.setErrorMsg( "loginPassword", "Please pick a different password from your previous one" );
f.setLoginStatus("changing_password_repeated_old");
} else if (f.getLoginStatus().equals("changing_password")){ // User has been prompted to change password, but has re-entered the original one
f.setErrorMsg( "loginPassword", "Please pick a different password from the one provided initially" );
user.setMd5password(userEnteredPasswordAfterMd5Conversion);
userDao.updateUser(user);
f.setLoginStatus("changing_password");
} else { // User has entered provided password; now prompt to change password
f.setErrorMsg( "loginPassword", "Please re-enter new password for confirmation" );
user.setMd5password(userEnteredPasswordAfterMd5Conversion);
userDao.updateUser(user);
f.setLoginStatus("changing_password");
}
response.sendRedirect(loginUrl);
return;
} else if (!user.getMd5password().equals( userEnteredPasswordAfterMd5Conversion )) {
f.setErrorMsg( "loginPassword", "Incorrect password: try again");
f.setLoginStatus("bad_password");
f.setLoginPassword(""); // don't even reveal how many characters there were
response.sendRedirect(loginUrl);
return;
}
/** Where do we find the User/Session map in the servlet context? */
public static final String USER_SESSION_MAP_ATTR = "userURISessionMap";
//set the login bean properties from the database
/**
* Find out where they are in the login process, and check for progress. If
* they succeed in logging in, record the information. Show the next page.
*/
public void doPost(HttpServletRequest request, HttpServletResponse response) {
//System.out.println("authenticated; setting login status in loginformbean");
VitroRequest vreq = new VitroRequest(request);
f.setUserURI(user.getURI());
f.setLoginStatus( "authenticated" );
f.setSessionId( session.getId());
f.setLoginRole( user.getRoleURI() );
try {
int loginRoleInt = Integer.decode(f.getLoginRole());
if( (loginRoleInt>1) && (session.isNew()) ) {
session.setMaxInactiveInterval(32000); // set longer timeout for editors
}
} catch (Exception e) {}
// TODO : might be a problem in next line - no ID
f.setLoginUserId( -2 );
//f.setEmailAddress ( email );
f.setLoginPassword( "" );
f.setErrorMsg( "loginPassword", "" ); // remove any error messages
f.setErrorMsg( "loginUsername", "" );
User user = null;
//System.out.println("updating loginCount and modTime");
Map<String,HttpSession> userURISessionMap = getUserURISessionMapFromContext( getServletContext() );
userURISessionMap.put( user.getURI(), request.getSession() );
sendLoginNotifyEvent(new LoginEvent( user.getURI() ), getServletContext(), session);
user.setLoginCount(user.getLoginCount()+1);
userDao.updateUser(user);
try {
// Process any input from the login form.
State entryState = getCurrentLoginState(vreq);
log.debug("State on entry: " + entryState);
if ( user.getLoginCount() == 2 ) { // first login
Calendar cal = Calendar.getInstance();
user.setFirstTime(cal.getTime());
userDao.updateUser(user);
}
switch (entryState) {
case LOGGING_IN:
user = checkLoginProgress(vreq);
if (user != null) {
whatNextForThisGuy(vreq, user);
}
break;
case FORCED_PASSWORD_CHANGE:
user = checkChangeProgress(vreq);
if (user != null) {
recordSuccessfulPasswordChange(vreq, user);
}
break;
default:
break;
}
/*
*If you set a postLoginRequest attribute in the session and forward to about
*then this will attempt to send the client back to the original page after the login.
*/
String forwardStr = (String) request.getSession().getAttribute("postLoginRequest");
request.getSession().removeAttribute("postLoginRequest");
if (forwardStr == null) {
String contextPostLoginRequest = (String) getServletContext().getAttribute("postLoginRequest");
if (contextPostLoginRequest != null) {
forwardStr = (contextPostLoginRequest.indexOf(":") == -1)
? request.getContextPath() + contextPostLoginRequest
: contextPostLoginRequest;
}
}
if( AuthRole.USER.roleUri().equals( user.getRoleURI() ) ){
/* if this is a self editor, redirect the to their page */
List<String> uris = userDao.getIndividualsUserMayEditAs(user.getURI());
if( uris != null && uris.size() > 0 ){
forwardStr = request.getContextPath() + "/individual?uri=" + URLEncoder.encode(uris.get(0), "UTF-8");
}
}
if (forwardStr != null) {
response.sendRedirect(forwardStr);
} else {
response.sendRedirect(siteAdminUrl);
//RequestDispatcher rd = getServletContext().getRequestDispatcher(url);
//rd.forward(request,response);
}
} catch (Throwable t) {
log.error( t.getMessage() );
t.printStackTrace();
}
}
// Figure out where they should be, and redirect.
State exitState = getCurrentLoginState(vreq);
log.debug("State on exit: " + exitState);
switch (exitState) {
case LOGGED_IN:
redirectLoggedInUser(vreq, response);
break;
default:
showLoginScreen(vreq, response);
break;
}
} catch (Exception e) {
showSystemError(e, response);
}
}
/**
* They are logging in. Are they successful?
*/
private User checkLoginProgress(HttpServletRequest request) {
String username = request.getParameter(PARAMETER_USERNAME);
String password = request.getParameter(PARAMETER_PASSWORD);
LoginProcessBean bean = getLoginProcessBean(request);
bean.clearMessage();
log.trace("username=" + username + ", password=" + password + ", bean="
+ bean);
if ((username == null) || username.isEmpty()) {
bean.setMessage(Message.NO_USERNAME);
return null;
} else {
bean.setUsername(username);
}
User user = getUserDao(request).getUserByUsername(username);
log.trace("User is " + (user == null ? "null" : user.getURI()));
if (user == null) {
bean.setMessage(Message.UNKNOWN_USERNAME, username);
return null;
}
if ((password == null) || password.isEmpty()) {
bean.setMessage(Message.NO_PASSWORD);
return null;
}
String md5Password = applyMd5Encoding(password);
if (!md5Password.equals(user.getMd5password())) {
log.trace("Encoded passwords don't match: right="
+ user.getMd5password() + ", wrong=" + md5Password);
bean.setMessage(Message.INCORRECT_PASSWORD);
return null;
}
return user;
}
/**
* Successfully applied username and password. Are we forcing a password
* change, or is this guy logged in?
*/
private void whatNextForThisGuy(HttpServletRequest request, User user) {
if (user.getLoginCount() == 0) {
log.debug("Forcing first-time password change");
LoginProcessBean bean = getLoginProcessBean(request);
bean.setState(State.FORCED_PASSWORD_CHANGE);
} else {
recordLoginInfo(request, user);
}
}
/**
* They are changing password. Are they successful?
*/
private User checkChangeProgress(HttpServletRequest request) {
String newPassword = request.getParameter(PARAMETER_NEW_PASSWORD);
String confirm = request.getParameter(PARAMETER_CONFIRM_PASSWORD);
LoginProcessBean bean = getLoginProcessBean(request);
bean.clearMessage();
log.trace("newPassword=" + newPassword + ", confirm=" + confirm
+ ", bean=" + bean);
if ((newPassword == null) || newPassword.isEmpty()) {
bean.setMessage(Message.NO_NEW_PASSWORD);
return null;
}
if (!newPassword.equals(confirm)) {
bean.setMessage(Message.MISMATCH_PASSWORD);
return null;
}
if ((newPassword.length() < User.MIN_PASSWORD_LENGTH)
|| (newPassword.length() > User.MAX_PASSWORD_LENGTH)) {
bean.setMessage(Message.PASSWORD_LENGTH, User.MIN_PASSWORD_LENGTH,
User.MAX_PASSWORD_LENGTH);
return null;
}
User user = getUserDao(request).getUserByUsername(bean.getUsername());
log.trace("User is " + (user == null ? "null" : user.getURI()));
if (user == null) {
throw new IllegalStateException(
"Changing password but bean has no user: '"
+ bean.getUsername() + "'");
}
String md5NewPassword = applyMd5Encoding(newPassword);
log.trace("Old password: " + user.getMd5password() + ", new password: "
+ md5NewPassword);
if (md5NewPassword.equals(user.getMd5password())) {
bean.setMessage(Message.USING_OLD_PASSWORD);
return null;
}
return user;
}
/**
* Store the changed password. They're not logged in yet, but they no longer
* need to change their password.
*/
private void recordSuccessfulPasswordChange(HttpServletRequest request,
User user) {
String newPassword = request.getParameter(PARAMETER_NEW_PASSWORD);
String md5NewPassword = applyMd5Encoding(newPassword);
user.setOldPassword(user.getMd5password());
user.setMd5password(md5NewPassword);
user.setLoginCount(user.getLoginCount() + 1);
getUserDao(request).updateUser(user);
log.debug("Completed first-time password change.");
LoginProcessBean bean = getLoginProcessBean(request);
bean.setState(State.LOGGING_IN);
bean.setMessage(Message.PASSWORD_CHANGE_SAVED);
}
/**
* The user provided the correct information, and changed the password if
* that was required. Record that they have logged in.
*/
private void recordLoginInfo(HttpServletRequest request, User user) {
log.debug("Completed login.");
HttpSession session = request.getSession();
// Put the login info into the session.
LoginFormBean lfb = new LoginFormBean();
lfb.setUserURI(user.getURI());
lfb.setLoginStatus("authenticated");
lfb.setSessionId(session.getId());
lfb.setLoginRole(user.getRoleURI());
lfb.setLoginRemoteAddr(request.getRemoteAddr());
lfb.setLoginName(user.getUsername());
session.setAttribute("loginHandler", lfb);
// Remove the login process info from the session.
session.removeAttribute(LoginProcessBean.SESSION_ATTRIBUTE);
// Record the login on the user.
user.setLoginCount(user.getLoginCount() + 1);
if (user.getFirstTime() == null) { // first login
user.setFirstTime(new Date());
}
getUserDao(request).updateUser(user);
// Set the timeout limit on the session - editors, etc, get more.
session.setMaxInactiveInterval(300); // seconds, not milliseconds
try {
if ((int) Integer.decode(lfb.getLoginRole()) > 1) {
session.setMaxInactiveInterval(32000);
}
} catch (NumberFormatException e) {
// No problem - leave it at the default.
}
// Record the user in the user/Session map.
Map<String, HttpSession> userURISessionMap = getUserURISessionMapFromContext(getServletContext());
userURISessionMap.put(user.getURI(), request.getSession());
// Notify the other users of this model.
sendLoginNotifyEvent(new LoginEvent(user.getURI()),
getServletContext(), session);
}
/**
* User is in the login process. Show them the login screen.
*/
private void showLoginScreen(VitroRequest vreq, HttpServletResponse response)
throws IOException {
response.sendRedirect(getLoginScreenUrl(vreq));
return;
}
/**
* User is logged in. They might go to:
* <ul>
* <li>A one-time redirect, stored in the session, if they had tried to
* bookmark to a page that requires login.</li>
* <li>An application-wide redirect, stored in the servlet context.</li>
* <li>Their home page, if they are a self-editor.</li>
* <li>The site admin page.</li>
* </ul>
*/
private void redirectLoggedInUser(HttpServletRequest request,
HttpServletResponse response) throws IOException,
UnsupportedEncodingException {
// Did they have a one-time redirect stored on the session?
String sessionRedirect = (String) request.getSession().getAttribute(
"postLoginRequest");
if (sessionRedirect != null) {
request.getSession().removeAttribute("postLoginRequest");
log.debug("User is logged in. Redirect by session to "
+ sessionRedirect);
response.sendRedirect(sessionRedirect);
return;
}
// Is there a login-redirect stored in the application as a whole?
// It could lead to another page in this app, or to any random URL.
String contextRedirect = (String) getServletContext().getAttribute(
"postLoginRequest");
if (contextRedirect != null) {
log.debug("User is logged in. Redirect by application to "
+ contextRedirect);
if (contextRedirect.indexOf(":") == -1) {
response.sendRedirect(request.getContextPath()
+ contextRedirect);
} else {
response.sendRedirect(contextRedirect);
}
return;
}
// If the user is a self-editor, send them to their home page.
User user = getLoggedInUser(request);
if (AuthRole.USER.roleUri().equals(user.getRoleURI())) {
UserDao userDao = getUserDao(request);
if (userDao != null) {
List<String> uris = userDao.getIndividualsUserMayEditAs(user
.getURI());
if (uris != null && uris.size() > 0) {
log.debug("User is logged in. Redirect as self-editor to "
+ sessionRedirect);
String userHomePage = request.getContextPath()
+ "/individual?uri="
+ URLEncoder.encode(uris.get(0), "UTF-8");
log.debug("User is logged in. Redirect as self-editor to "
+ sessionRedirect);
response.sendRedirect(userHomePage);
return;
}
}
}
// If nothing else applies, send them to the Site Admin page.
log.debug("User is logged in. Redirect to site admin page.");
response.sendRedirect(getSiteAdminUrl(request));
}
/**
* There has been an unexpected exception. Complain mightily.
*/
private void showSystemError(Exception e, HttpServletResponse response) {
log.error("Unexpected error in login process" + e);
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (IOException e1) {
log.error(e1, e1);
}
}
/**
* Where are we in the process? Logged in? Not? Somewhere in between?
*/
private State getCurrentLoginState(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return State.NOWHERE;
}
LoginFormBean lfb = (LoginFormBean) session
.getAttribute("loginHandler");
if ((lfb != null) && (lfb.getLoginStatus().equals("authenticated"))) {
return State.LOGGED_IN;
}
return getLoginProcessBean(request).getState();
}
/**
* What user are we logged in as?
*/
private User getLoggedInUser(HttpServletRequest request) {
UserDao userDao = getUserDao(request);
if (userDao == null) {
return null;
}
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
LoginFormBean lfb = (LoginFormBean) session
.getAttribute("loginHandler");
if (lfb == null) {
log.debug("getLoggedInUser: not logged in");
return null;
}
return userDao.getUserByUsername(lfb.getLoginName());
}
/**
* Get a reference to the {@link UserDao}, or <code>null</code>.
*/
private UserDao getUserDao(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
ServletContext servletContext = session.getServletContext();
WebappDaoFactory wadf = (WebappDaoFactory) servletContext
.getAttribute("webappDaoFactory");
if (wadf == null) {
log.error("getUserDao: no WebappDaoFactory");
return null;
}
UserDao userDao = wadf.getUserDao();
if (userDao == null) {
log.error("getUserDao: no UserDao");
}
return userDao;
}
/** What's the URL for the login screen? */
private String getLoginScreenUrl(HttpServletRequest request) {
String contextPath = request.getContextPath();
String urlParams = "?home=" + getPortalIdString(request)
+ "&login=block";
return contextPath + Controllers.LOGIN + urlParams;
}
/** What's the URL for the site admin screen? */
private String getSiteAdminUrl(HttpServletRequest request) {
String contextPath = request.getContextPath();
String urlParams = "?home=" + getPortalIdString(request)
+ "&login=block";
return contextPath + Controllers.SITE_ADMIN + urlParams;
}
/**
* What portal are we currently in?
*/
private String getPortalIdString(HttpServletRequest request) {
String portalIdParameter = request.getParameter("home");
if (portalIdParameter == null) {
return String.valueOf(DEFAULT_PORTAL_ID);
} else {
return portalIdParameter;
}
}
/**
* How is the login process coming along?
*/
private LoginProcessBean getLoginProcessBean(HttpServletRequest request) {
HttpSession session = request.getSession();
LoginProcessBean bean = (LoginProcessBean) session
.getAttribute(LoginProcessBean.SESSION_ATTRIBUTE);
if (bean == null) {
bean = new LoginProcessBean();
session.setAttribute(LoginProcessBean.SESSION_ATTRIBUTE, bean);
}
return bean;
}
/**
* Encode this password for storage in the database. Apply an MD5 encoding,
* and store the result as a string of hex digits.
*/
private String applyMd5Encoding(String password) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(password.getBytes());
char[] hexChars = Hex.encodeHex(digest);
return new String(hexChars).toUpperCase();
} catch (NoSuchAlgorithmException e) {
// This can't happen with a normal Java runtime.
throw new RuntimeException(e);
}
}
// ----------------------------------------------------------------------
// Public utility methods.
// ----------------------------------------------------------------------
/**
* The servlet context should contain a map from User URIs to
* {@link HttpSession}s. Get a reference to it, creating it if necessary.
*/
@SuppressWarnings("unchecked")
public static Map<String, HttpSession> getUserURISessionMapFromContext(
ServletContext ctx) {
Map<String, HttpSession> m = (Map<String, HttpSession>) ctx
.getAttribute(USER_SESSION_MAP_ATTR);
if (m == null) {
m = new HashMap<String, HttpSession>();
ctx.setAttribute(USER_SESSION_MAP_ATTR, m);
}
return m;
}
/**
* Let everyone know that somebody has logged in or logged out.
*/
public static void sendLoginNotifyEvent(LoginLogoutEvent event,
ServletContext context, HttpSession session) {
if (event == null) {
log.warn("Unable to notify audit model of login "
+ "because a null event was passed");
return;
}
OntModel jenaOntModel = (OntModel) session.getAttribute("jenaOntModel");
if (jenaOntModel == null) {
jenaOntModel = (OntModel) context.getAttribute("jenaOntModel");
}
if (jenaOntModel == null) {
log.error("Unable to notify audit model of login event "
+ "because no model could be found");
return;
}
jenaOntModel.getBaseModel().notifyEvent(event);
}
public static void sendLoginNotifyEvent(LoginLogoutEvent event, ServletContext context, HttpSession session){
Object sessionOntModel = null;
if( session != null )
sessionOntModel = session.getAttribute("jenaOntModel");
Object contextOntModel = null;
if( context != null )
contextOntModel = context.getAttribute("jenaOntModel");
OntModel jenaOntModel =
( (sessionOntModel != null && sessionOntModel instanceof OntModel)
? (OntModel)sessionOntModel: (OntModel) context.getAttribute("jenaOntModel") );
if( jenaOntModel == null ){
log.error( "Unable to notify audit model of login event because no model could be found");
} else {
if( event == null ){
log.warn("Unable to notify audit model of login because a null event was passed");
}else{
jenaOntModel.getBaseModel().notifyEvent( event );
}
}
}
public static Map<String,HttpSession> getUserURISessionMapFromContext( ServletContext ctx ) {
Map<String,HttpSession> m = (Map<String,HttpSession>) ctx.getAttribute( USER_SESSION_MAP_ATTR );
if ( m == null ) {
m = new HashMap<String,HttpSession>();
ctx.setAttribute( USER_SESSION_MAP_ATTR, m );
}
return m;
}
}

View file

@ -68,7 +68,7 @@ public class UserEditController extends BaseEditController {
}
ArrayList results = new ArrayList();
results.add("User");
results.add("Email address");
results.add("first name");
results.add("last name");
results.add("login count");

View file

@ -0,0 +1,139 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.login;
import java.text.MessageFormat;
import java.util.Arrays;
/**
* Where are we in the process of logging on? What message should we show to the
* user?
*/
public class LoginProcessBean {
private static Object[] NO_ARGUMENTS = new Object[0];
public static final String SESSION_ATTRIBUTE = LoginProcessBean.class
.getName();
public enum State {
NOWHERE, LOGGING_IN, FORCED_PASSWORD_CHANGE, LOGGED_IN
}
private enum MLevel {
NONE, INFO, ERROR
}
public enum Message {
NO_MESSAGE("", MLevel.NONE),
PASSWORD_CHANGE_SAVED("Your password has been saved.<br/>"
+ "Please log in.", MLevel.INFO),
NO_USERNAME("Please enter your email address.", MLevel.ERROR),
NO_PASSWORD("Please enter your password.", MLevel.ERROR),
UNKNOWN_USERNAME("The email or password you entered is incorrect.",
MLevel.ERROR),
INCORRECT_PASSWORD("The email or password you entered is incorrect.",
MLevel.ERROR),
NO_NEW_PASSWORD("Please enter your new password.", MLevel.ERROR),
MISMATCH_PASSWORD("The passwords entered do not match.", MLevel.ERROR),
PASSWORD_LENGTH(
"Please enter a password between {0} and {1} characters long",
MLevel.ERROR),
USING_OLD_PASSWORD("Please choose a different password from the "
+ "temporary one provided initially.", MLevel.ERROR);
private final String format;
private final MLevel messageLevel;
Message(String format, MLevel messageLevel) {
this.format = format;
this.messageLevel = messageLevel;
}
String getFormat() {
return this.format;
}
MLevel getMessageLevel() {
return this.messageLevel;
}
String formatMessage(Object[] args) {
return new MessageFormat(this.format).format(args);
}
}
/** Where are we in the process? */
private State currentState = State.NOWHERE;
/** What message should we display on the screen? */
private Message message = Message.NO_MESSAGE;
/** What arguments are needed to format the message? */
private Object[] messageArguments = NO_ARGUMENTS;
/**
* What username was submitted to the form? This isn't just for display --
* if they are changing passwords, we need to remember who it is.
*/
private String username = "";
public void setState(State newState) {
this.currentState = newState;
}
public State getState() {
return currentState;
}
public void clearMessage() {
this.message = Message.NO_MESSAGE;
this.messageArguments = NO_ARGUMENTS;
}
public void setMessage(Message message, Object... args) {
this.message = message;
this.messageArguments = args;
}
public String getInfoMessage() {
if (message.getMessageLevel() == MLevel.INFO) {
return message.formatMessage(messageArguments);
} else {
return "";
}
}
public String getErrorMessage() {
if (message.getMessageLevel() == MLevel.ERROR) {
return message.formatMessage(messageArguments);
} else {
return "";
}
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "LoginProcessBean[state=" + currentState + ", message="
+ message + ", messageArguments="
+ Arrays.deepToString(messageArguments) + ", username="
+ username + "]";
}
}

View file

@ -0,0 +1,225 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.login;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginFormBean;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.edit.Authenticate;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreeMarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.State;
import freemarker.template.Configuration;
/**
* A temporary means of displaying the Login templates within the SiteAdmin
* form.
*
* This class contains stuff that I swiped from {@link Authenticate}. The base
* class, {@link LoginTemplateHelperBase}, contains stuff that I swiped from
* {@link FreeMarkerHttpServlet}.
*/
public class LoginTemplateHelper extends LoginTemplateHelperBase {
private static final Log log = LogFactory.getLog(LoginTemplateHelper.class);
/** If they are logging in, show them this form. */
public static final String TEMPLATE_LOGIN = "login/login.ftl";
/** If they are changing their password on first login, show them this form. */
public static final String TEMPLATE_FORCE_PASSWORD_CHANGE = "login/forcedPasswordChange.ftl";
public static final String BODY_LOGIN_NAME = "loginName";
public static final String BODY_FORM_ACTION = "formAction";
public static final String BODY_INFO_MESSAGE = "infoMessage";
public static final String BODY_ERROR_MESSAGE = "errorMessage";
public static final String BODY_ALERT_ICON_URL = "alertImageUrl";
/** Use this icon for an info message. */
public static final String URL_INFO_ICON = "/images/iconAlert.png";
/** Use this icon for an error message. */
public static final String URL_ERROR_ICON = "/images/iconAlert.png";
/** If no portal is specified in the request, use this one. */
private static final int DEFAULT_PORTAL_ID = 1;
public LoginTemplateHelper(HttpServletRequest req) {
super(req);
}
public String showLoginPage(HttpServletRequest request) {
try {
VitroRequest vreq = new VitroRequest(request);
State state = getCurrentLoginState(vreq);
log.debug("State on exit: " + state);
switch (state) {
case LOGGED_IN:
return "";
case FORCED_PASSWORD_CHANGE:
return doTemplate(vreq, showPasswordChangeScreen(vreq));
default:
return doTemplate(vreq, showLoginScreen(vreq));
}
} catch (Exception e) {
log.error(e);
return "<h2>Internal server error:<br/>" + e + "</h2>";
}
}
/**
* User is just starting the login process. Be sure that we have a
* {@link LoginProcessBean} with the correct status. Show them the login
* screen.
*/
private TemplateResponseValues showLoginScreen(VitroRequest vreq)
throws IOException {
LoginProcessBean bean = getLoginProcessBean(vreq);
bean.setState(State.LOGGING_IN);
log.trace("Going to login screen: " + bean);
TemplateResponseValues trv = new TemplateResponseValues(TEMPLATE_LOGIN);
trv.put(BODY_FORM_ACTION, getAuthenticateUrl(vreq));
trv.put(BODY_LOGIN_NAME, bean.getUsername());
String infoMessage = bean.getInfoMessage();
if (!infoMessage.isEmpty()) {
trv.put(BODY_INFO_MESSAGE, infoMessage);
trv.put(BODY_ALERT_ICON_URL, UrlBuilder.getUrl(URL_INFO_ICON));
}
String errorMessage = bean.getErrorMessage();
if (!errorMessage.isEmpty()) {
trv.put(BODY_ERROR_MESSAGE, errorMessage);
trv.put(BODY_ALERT_ICON_URL, UrlBuilder.getUrl(URL_ERROR_ICON));
}
return trv;
}
/**
* The user has given the correct password, but now they are required to
* change it.
*/
private TemplateResponseValues showPasswordChangeScreen(VitroRequest vreq) {
LoginProcessBean bean = getLoginProcessBean(vreq);
bean.setState(State.FORCED_PASSWORD_CHANGE);
log.trace("Going to password change screen: " + bean);
TemplateResponseValues trv = new TemplateResponseValues(
TEMPLATE_FORCE_PASSWORD_CHANGE);
trv.put(BODY_FORM_ACTION, getAuthenticateUrl(vreq));
String errorMessage = bean.getErrorMessage();
if (!errorMessage.isEmpty()) {
trv.put(BODY_ERROR_MESSAGE, errorMessage);
trv.put(BODY_ALERT_ICON_URL, UrlBuilder.getUrl(URL_ERROR_ICON));
}
return trv;
}
/**
* We processed a response, and want to show a template.
*/
private String doTemplate(VitroRequest vreq, TemplateResponseValues values) {
// Set it up like FreeMarkerHttpServlet.doGet() would do.
Configuration config = getConfig(vreq);
Map<String, Object> sharedVariables = getSharedVariables(vreq);
Map<String, Object> root = new HashMap<String, Object>(sharedVariables);
Map<String, Object> body = new HashMap<String, Object>(sharedVariables);
setUpRoot(vreq, root);
// Add the values that we got, and merge to the template.
body.putAll(values.getBodyMap());
return mergeBodyToTemplate(values.getTemplateName(), body, config);
}
/**
* Where are we in the process? Logged in? Not? Somewhere in between?
*/
private State getCurrentLoginState(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return State.NOWHERE;
}
LoginFormBean lfb = (LoginFormBean) session
.getAttribute("loginHandler");
if ((lfb != null) && (lfb.getLoginStatus().equals("authenticated"))) {
return State.LOGGED_IN;
}
return getLoginProcessBean(request).getState();
}
/**
* How is the login process coming along?
*/
private LoginProcessBean getLoginProcessBean(HttpServletRequest request) {
HttpSession session = request.getSession();
LoginProcessBean bean = (LoginProcessBean) session
.getAttribute(LoginProcessBean.SESSION_ATTRIBUTE);
if (bean == null) {
bean = new LoginProcessBean();
session.setAttribute(LoginProcessBean.SESSION_ATTRIBUTE, bean);
}
return bean;
}
/** What's the URL for this servlet? */
private String getAuthenticateUrl(HttpServletRequest request) {
String contextPath = request.getContextPath();
String urlParams = "?home=" + getPortalIdString(request)
+ "&login=block";
return contextPath + "/authenticate" + urlParams;
}
/**
* What portal are we currently in?
*/
private String getPortalIdString(HttpServletRequest request) {
String portalIdParameter = request.getParameter("home");
if (portalIdParameter == null) {
return String.valueOf(DEFAULT_PORTAL_ID);
} else {
return portalIdParameter;
}
}
/**
* Holds the name of the template and the map of values.
*/
private static class TemplateResponseValues {
private final String templateName;
private final Map<String, Object> bodyMap = new HashMap<String, Object>();
public TemplateResponseValues(String templateName) {
this.templateName = templateName;
}
public TemplateResponseValues put(String key, Object value) {
this.bodyMap.put(key, value);
return this;
}
public Map<? extends String, ? extends Object> getBodyMap() {
return Collections.unmodifiableMap(this.bodyMap);
}
public String getTemplateName() {
return this.templateName;
}
}
}

View file

@ -0,0 +1,27 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.login;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreeMarkerHttpServlet;
/**
* A temporary means of displaying the Login templates within the SiteAdmin
* form.
*
* The constructor insures that the ServletContext is set.
*/
public class LoginTemplateHelperBase extends FreeMarkerHttpServlet {
private final ServletContext servletContext;
LoginTemplateHelperBase(HttpServletRequest req) {
this.servletContext = req.getSession().getServletContext();
}
public ServletContext getServletContext() {
return servletContext;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -2,120 +2,14 @@
<%-- Included in siteAdmin/main.jsp to handle login/logout form and processing --%>
<%@ page import="edu.cornell.mannlib.vitro.webapp.controller.login.LoginTemplateHelper" %>
<%@ page import="edu.cornell.mannlib.vitro.webapp.controller.VitroRequest" %>
<%@ page import="edu.cornell.mannlib.vitro.webapp.beans.Portal" %>
<%@ page import="edu.cornell.mannlib.vitro.webapp.controller.Controllers" %>
<c:url var="loginJSP" value="<%= Controllers.LOGIN_JSP %>" />
<c:set var="loginFormTitle" value="<h3>Please log in</h3>" />
<%
int securityLevel = loginHandler.ANYBODY;
String loginStatus = loginHandler.getLoginStatus();
if ( loginStatus.equals("authenticated")) {
%>
<div id="logoutPanel">
<%
} else {
String themeDir = new VitroRequest(request).getPortal().getThemeDir().replaceAll("/$", "");
%>
<div id="loginPanel" class="pageBodyGroup">
<%
}
if ( loginStatus.equals("authenticated")) {
// test whether session is still valid
String currentSessionId = session.getId();
String storedSessionId = loginHandler.getSessionId();
if ( currentSessionId.equals( storedSessionId ) ) {
String currentRemoteAddrStr = request.getRemoteAddr();
String storedRemoteAddr = loginHandler.getLoginRemoteAddr();
securityLevel = Integer.parseInt( loginHandler.getLoginRole() );
if ( currentRemoteAddrStr.equals( storedRemoteAddr ) ) {
%>
<form class="logout" name="logout" action="${loginJSP}" method="post">
<input type="hidden" name="home" value="<%=portal.getPortalId()%>"/>
<em>Logged in as</em> <strong><jsp:getProperty name="loginHandler" property="loginName" /></strong>
<input type="submit" name="loginSubmitMode" value="Log out" class="logout-button button" />
</form>
<%
} else {
%>
${loginFormTitle}
<em>(IP address has changed)</em><br />
<%
loginHandler.setLoginStatus("logged out");
}
} else {
loginHandler.setLoginStatus("logged out");
%>
${loginFormTitle}
<em>(session has expired)</em><br/>
<form class="login" name="login" action="${loginJSP}" method="post" onsubmit="return isValidLogin(this) ">
<input type="hidden" name="home" value="<%=portal.getPortalId()%>" />
Username: <input type="text" name="loginName" size="10" class="form-item" /><br />
Password: <input type="password" name="loginPassword" size="10" class="form-item" /><br />
<input type="submit" name="loginSubmitMode" value="Log in" class="form-item button" />
</form>
<%
}
} else { /* not thrown out by coming from different IP address or expired session; check login status returned by authenticate.java */
%>
<h3>Please log in</strong></h3>
<%
if ( loginStatus.equals("logged out")) { %>
<em class="noticeText">(currently logged out)</em>
<% } else if ( loginStatus.equals("bad_password")) { %>
<em class="errorText">(password incorrect)</em><br/>
<% } else if ( loginStatus.equals("unknown_username")) { %>
<em class="errorText">(unknown username)</em><br/>
<% } else if ( loginStatus.equals("first_login_no_password")) { %>
<em class="noticeText">(1st login; need to request initial password below)</em>
<% } else if ( loginStatus.equals("first_login_mistyped")) { %>
<em class="noticeText">(1st login; initial password entered incorrectly)</em>
<% } else if ( loginStatus.equals("first_login_changing_password")) { %>
<em class="noticeText">(1st login; changing to new private password)</em>
<% } else if ( loginStatus.equals("changing_password_repeated_old")) { %>
<em class="noticeText">(changing to a different password)</em>
<% } else if ( loginStatus.equals("changing_password")) { %>
<em class="noticeText">(changing to new password)</em>
<% } else if ( loginStatus.equals("none")) { %>
<em class="noticeText">(new session)</em><br/>
<% } else { %>
<em class="errorText">Status unrecognized: <%=loginStatus.replace("_", " ")%></em><br/>
<% } %>
<form class="old-global-form" name="login" action="${loginJSP}" method="post" onsubmit="return isValidLogin(this) ">
<input type="hidden" name="home" value="<%=portal.getPortalId()%>" />
<label for="loginName">Username:</label>
<%
if ( loginStatus.equals("bad_password") || loginStatus.equals("first_login_no_password")
|| loginStatus.equals("first_login_mistyped") || loginStatus.equals("first_login_changing_password")
|| loginStatus.equals("changing_password_repeated_old") || loginStatus.equals("changing_password") ) { %>
<input id="username" type="text" name="loginName" value='<%=loginHandler.getLoginName()%>' size="10" class="form-item" /><br />
<% } else { %>
<input id="username" type="text" name="loginName" size="10" class="form-item" /><br />
<% if ( loginStatus.equals("unknown_username") ) { %>
<em class="errorText usernameError">Unknown username</em>
<% }
}
%>
<label for="loginPassword">Password:</label>
<input id="password" type="password" name="loginPassword" size="10" class="form-item" /><br />
<link rel="stylesheet" type="text/css" href="<%=themeDir%>/css/login.css"/>
<% String passwordError=loginHandler.getErrorMsg("loginPassword");
if (passwordError!=null && !passwordError.equals("")) {%>
<em class="errorText passwordError"><%=passwordError%></em>
<% } %>
<%= new LoginTemplateHelper(request).showLoginPage(request) %>
<input type="submit" name="loginSubmitMode" value="Log in" class="form-item button" />
</form>
<% } %>
</div> <!-- end loginPanel -->

View file

@ -17,8 +17,14 @@
Portal portal = (Portal) request.getAttribute("portalBean");
final String DEFAULT_SEARCH_METHOD = "fulltext"; /* options are fulltext/termlike */
int securityLevel = loginHandler.ANYBODY;
String loginStatus = loginHandler.getLoginStatus();
if ( loginStatus.equals("authenticated")) {
securityLevel = Integer.parseInt( loginHandler.getLoginRole() );
}
%>
<div id="content">
<div class="tab">

View file

@ -13,14 +13,10 @@ function isValidLogin( theForm ) {
theForm.loginName.focus();
return false;
}
if ( isEmptyOrWrongLength( theForm.loginPassword.value)) {
if ( isEmptyPassword( theForm.loginPassword.value)) {
theForm.loginPassword.focus();
return false;
}
//alert("theForm.loginPassword.value=" + theForm.loginPassword.value );
theForm.loginPassword.value = calcMD5( theForm.loginPassword.value );
//alert("theForm.loginPassword.value=" + theForm.loginPassword.value );
return true;
}
@ -32,15 +28,39 @@ function isEmpty( aStr ) {
return false;
}
function isEmptyOrWrongLength( aStr ) {
function isEmptyPassword( aStr ) {
if ( aStr.length == 0 ) {
alert("Please enter a password to log in");
return true;
} else if ( aStr.length < <%=User.MIN_PASSWORD_LENGTH%> || aStr.length > <%=User.MAX_PASSWORD_LENGTH%>) {
alert("Please enter a password between 6 and 12 characters long");
return true;
}
return false;
}
function isReasonableNewPassword( theForm ) {
if ( isWrongLengthPassword( theForm.newPassword.value)) {
theForm.newPassword.focus();
return false;
}
if ( isMismatchedPasswords( theForm.newPassword.value, theForm.confirmPassword.value)) {
theForm.newPassword.focus();
return false;
}
}
function isWrongLengthPassword( aStr ) {
if ( aStr.length < <%=User.MIN_PASSWORD_LENGTH%> || aStr.length > <%=User.MAX_PASSWORD_LENGTH%>) {
alert("Please enter a password between <%=User.MIN_PASSWORD_LENGTH%> and <%=User.MAX_PASSWORD_LENGTH%> characters long");
return true;
}
return false;
}
function isMismatchedPasswords( one, two ) {
if ( one != two ) {
alert("Passwords do not match");
return true;
}
return false;
}
//Give initial focus to the password or username field

View file

@ -6,7 +6,7 @@
<tr class="editformcell">
<td valign="bottom" colspan="2">
<b>User Name*</b><br/>
<b>Email address*</b><br/>
<input type="text" name="Username" value="${formValue['Username']}" size="60" maxlength="120" />
<span class="warning"><form:error name="Username"/></span>
</td>

View file

@ -0,0 +1,27 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Crop the replacement main image for an Individual, to produce a thumbnail. -->
<h2>Forced password change</h2>
${stylesheets.addFromTheme("/login.css")}
<div id="formLogin" class="pageBodyGroup">
<h2>Create Your New Password</h2>
<#if errorMessage??>
<div id="errorAlert"><img src="${alertImageUrl}" width="32" height="31" alert="Error alert icon"/>
<p>${errorMessage}</p>
</div>
</#if>
<form action="${formAction}" method="post" onsubmit="return isReasonableNewPassword(this)">
<label for="newPassword">Password</label>
<input type="password" name="newPassword" />
<label for="confirmPassword">Confirm Password</label>
<input type="password" name="confirmPassword" />
<br />
<input name="passwordChangeForm" type="submit" class="submit" value="Save Changes"/>
</form>
</div>

View file

@ -0,0 +1,29 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Crop the replacement main image for an Individual, to produce a thumbnail. -->
${stylesheets.addFromTheme("/login.css")}
<div id="formLogin" class="pageBodyGroup">
<h2>Log in</h2>
<#if infoMessage??>
<h3>${infoMessage}</h3>
</#if>
<#if errorMessage??>
<div id="errorAlert"><img src="${alertImageUrl}" width="32" height="31" alert="Error alert icon"/>
<p>${errorMessage}</p>
</div>
</#if>
<form action="${formAction}" method="post" onsubmit="return isValidLogin(this)">
<label for="loginName">Email</label>
<input name="loginName" type="text" value="${loginName}" />
<label for="loginPassword">Password</label>
<input type="password" name="loginPassword" />
<br />
<input name="loginForm" type="submit" class="submit" value="Log in"/>
</form>
</div>