NIHVIVO-151 Get better control of redirection after logging in.

1) The login link goes to the controller, not directly to the form.
2) The process bean holds the URL where the form is located, and the URL we will go to on success.
3) Only the controller alters the state of the bean, not the widget.
4) The process bean is kept until the redirector can get information from it.
5) Finally, with more control in the redirector, change the behavior.
This commit is contained in:
jeb228 2010-12-08 22:14:39 +00:00
parent ba3163da20
commit 8676ec5544
8 changed files with 571 additions and 314 deletions

View file

@ -15,7 +15,6 @@ 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.controller.login.LoginProcessBean;
/**
* Handle the return from the external authorization login server. If we are
@ -74,7 +73,6 @@ public class LoginExternalAuthReturn extends BaseLoginServlet {
}
private void removeLoginProcessArtifacts(HttpServletRequest req) {
LoginProcessBean.removeBean(req);
req.getSession().removeAttribute(ATTRIBUTE_REFERRER);
}

View file

@ -17,6 +17,7 @@ import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage;
import edu.cornell.mannlib.vitro.webapp.controller.Controllers;
import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean;
/**
* A user has just completed the login process. What page do we direct them to?
@ -24,14 +25,13 @@ import edu.cornell.mannlib.vitro.webapp.controller.Controllers;
public class LoginRedirector {
private static final Log log = LogFactory.getLog(LoginRedirector.class);
private static final String ATTRIBUTE_RETURN_FROM_FORCED_LOGIN = "return_from_forced_login";
private final HttpServletRequest request;
private final HttpServletResponse response;
private final HttpSession session;
private final String urlOfRestrictedPage;
private final String uriOfAssociatedIndividual;
private final String loginProcessPage;
private final String afterLoginPage;
public LoginRedirector(HttpServletRequest request,
HttpServletResponse response) {
@ -39,18 +39,12 @@ public class LoginRedirector {
this.session = request.getSession();
this.response = response;
urlOfRestrictedPage = getUrlOfRestrictedPage();
uriOfAssociatedIndividual = getAssociatedIndividualUri();
}
/** Were we forced to log in when trying to access a restricted page? */
private String getUrlOfRestrictedPage() {
String url = (String) session
.getAttribute(ATTRIBUTE_RETURN_FROM_FORCED_LOGIN);
session.removeAttribute(ATTRIBUTE_RETURN_FROM_FORCED_LOGIN);
log.debug("URL of restricted page is " + url);
return url;
LoginProcessBean processBean = LoginProcessBean.getBean(request);
log.debug("process bean is: " + processBean);
loginProcessPage = processBean.getLoginPageUrl();
afterLoginPage = processBean.getAfterLoginUrl();
}
/** Is there an Individual associated with this user? */
@ -76,30 +70,79 @@ public class LoginRedirector {
}
public void redirectLoggedInUser() throws IOException {
if (isForcedFromRestrictedPage()) {
log.debug("Returning to restricted page.");
response.sendRedirect(urlOfRestrictedPage);
} else if (isUserEditorOrBetter()) {
log.debug("Going to site admin page.");
response.sendRedirect(getSiteAdminPageUrl());
} else if (isSelfEditorWithIndividual()) {
log.debug("Going to Individual home page.");
response.sendRedirect(getAssociatedIndividualHomePage());
} else {
log.debug("User not recognized. Going to application home.");
DisplayMessage.setMessage(request, "You have logged in, "
+ "but the system contains no profile for you.");
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.");
DisplayMessage.setMessage(request, "You have logged in, "
+ "but the system contains no profile for you.");
response.sendRedirect(getApplicationHomePageUrl());
} else {
if (hasSomeplaceToGoAfterLogin()) {
log.debug("Returning to requested page: " + afterLoginPage);
response.sendRedirect(afterLoginPage);
} else if (loginProcessPage == null) {
log.debug("Don't know what to do. Go home.");
response.sendRedirect(getApplicationHomePageUrl());
} else if (isLoginPage(loginProcessPage)) {
log.debug("Coming from /login. Going to site admin page.");
response.sendRedirect(getSiteAdminPageUrl());
} else {
log.debug("Coming from a login widget. Going back there.");
response.sendRedirect(loginProcessPage);
}
}
LoginProcessBean.removeBean(request);
} catch (IOException e) {
log.debug("Problem with re-direction", e);
response.sendRedirect(getApplicationHomePageUrl());
}
}
private boolean isForcedFromRestrictedPage() {
return urlOfRestrictedPage != null;
public void redirectCancellingUser() throws IOException {
try {
if (hasSomeplaceToGoAfterLogin()) {
log.debug("Returning to requested page: " + afterLoginPage);
response.sendRedirect(afterLoginPage);
} else if (loginProcessPage == null) {
log.debug("Don't know what to do. Go home.");
response.sendRedirect(getApplicationHomePageUrl());
} else if (isLoginPage(loginProcessPage)) {
log.debug("Coming from /login. Going to home.");
response.sendRedirect(getApplicationHomePageUrl());
} else {
log.debug("Coming from a login widget. Going back there.");
response.sendRedirect(loginProcessPage);
}
LoginProcessBean.removeBean(request);
} catch (IOException e) {
log.debug("Problem with re-direction", e);
response.sendRedirect(getApplicationHomePageUrl());
}
}
private boolean isUserEditorOrBetter() {
return LoginStatusBean.getBean(session).isLoggedInAtLeast(
LoginStatusBean.EDITOR);
public void redirectUnrecognizedExternalUser(String username)
throws IOException {
log.debug("Redirecting unrecognized external user: " + username);
DisplayMessage.setMessage(request,
"VIVO cannot find a profile for your account.");
response.sendRedirect(getApplicationHomePageUrl());
}
private boolean hasSomeplaceToGoAfterLogin() {
return afterLoginPage != null;
}
private boolean isMerelySelfEditor() {
return LoginStatusBean.getBean(session).isLoggedInExactly(
LoginStatusBean.NON_EDITOR);
}
private boolean isLoginPage(String page) {
return ((page != null) && page.endsWith(request.getContextPath()
+ Controllers.LOGIN));
}
private String getSiteAdminPageUrl() {
@ -120,14 +163,6 @@ public class LoginRedirector {
}
}
public void redirectUnrecognizedExternalUser(String username)
throws IOException {
log.debug("Redirecting unrecognized external user: " + username);
DisplayMessage.setMessage(request,
"VIVO cannot find a profile for your account.");
response.sendRedirect(getApplicationHomePageUrl());
}
/**
* The application home page can be overridden by an attribute in the
* ServletContext. Further, it can either be an absolute URL, or it can be
@ -145,14 +180,4 @@ public class LoginRedirector {
}
return request.getContextPath();
}
// ----------------------------------------------------------------------
// static helper methods
// ----------------------------------------------------------------------
public static void setReturnUrlFromForcedLogin(HttpServletRequest request,
String url) {
request.getSession().setAttribute(ATTRIBUTE_RETURN_FROM_FORCED_LOGIN,
url);
}
}

View file

@ -8,6 +8,8 @@ import static edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean
import static edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.State.NOWHERE;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
@ -28,6 +30,7 @@ import com.hp.hpl.jena.ontology.OntModel;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource;
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.authenticate.Authenticator;
@ -41,6 +44,24 @@ public class Authenticate extends VitroHttpServlet {
private static final Log log = LogFactory.getLog(Authenticate.class
.getName());
/**
* If this is set at any point in the process, store it as the post-login
* destination.
*
* NOTE: we expect URL-encoding on this parameter, and will decode it when
* we read it.
*/
private static final String PARAMETER_AFTER_LOGIN = "afterLogin";
/**
* If this is set at any point in the process, store the referrer as the
* post-login destination.
*/
private static final String PARAMETER_RETURN = "return";
/** If this is set, a status of NOWHERE should be treated as LOGGING_IN. */
private static final String PARAMETER_LOGGING_IN = "loginForm";
/** The username field on the login form. */
private static final String PARAMETER_USERNAME = "loginName";
@ -68,6 +89,8 @@ public class Authenticate extends VitroHttpServlet {
VitroRequest vreq = new VitroRequest(request);
try {
recordLoginProcessPages(vreq);
// Where do we stand in the process?
State entryState = getCurrentLoginState(vreq);
dumpStateToLog("entry", entryState, vreq);
@ -95,7 +118,7 @@ public class Authenticate extends VitroHttpServlet {
// Send them on their way.
switch (exitState) {
case NOWHERE:
redirectCancellingUser(vreq, response);
new LoginRedirector(vreq, response).redirectCancellingUser();
break;
case LOGGING_IN:
showLoginScreen(vreq, response);
@ -113,30 +136,77 @@ public class Authenticate extends VitroHttpServlet {
}
/**
* If they supply an after-login page, record it and use the Login page for
* the process.
*
* If they supply a return flag, record the referrer as the after-login page
* and use the Login page for the process.
*
* Otherwise, use the current page for the process.
*/
private void recordLoginProcessPages(HttpServletRequest request) {
LoginProcessBean bean = LoginProcessBean.getBean(request);
String afterLoginUrl = request.getParameter(PARAMETER_AFTER_LOGIN);
if (afterLoginUrl != null) {
try {
String decoded = URLDecoder.decode(afterLoginUrl, "UTF-8");
bean.setAfterLoginUrl(decoded);
} catch (UnsupportedEncodingException e) {
log.error("Really? No UTF-8 encoding?");
}
}
String returnParameter = request.getParameter(PARAMETER_RETURN);
if (returnParameter != null) {
String referrer = request.getHeader("referer");
bean.setAfterLoginUrl(referrer);
}
if (bean.getAfterLoginUrl() != null) {
bean.setLoginPageUrl(request.getContextPath() + Controllers.LOGIN);
} else {
bean.setLoginPageUrl(request.getHeader("referer"));
}
}
/**
* Where are we in the process? Logged in? Not? Somewhere in between?
*/
private State getCurrentLoginState(HttpServletRequest request) {
State currentState;
HttpSession session = request.getSession(false);
if (session == null) {
currentState = NOWHERE;
log.debug("no session: current state is NOWHERE");
return NOWHERE;
}
if (LoginStatusBean.getBean(request).isLoggedIn()) {
} else if (LoginStatusBean.getBean(request).isLoggedIn()) {
currentState = LOGGED_IN;
log.debug("found a LoginStatusBean: current state is LOGGED IN");
return LOGGED_IN;
}
if (LoginProcessBean.isBean(request)) {
State state = LoginProcessBean.getBean(request).getState();
log.debug("state from LoginProcessBean is " + state);
return state;
} else if (LoginProcessBean.isBean(request)) {
currentState = LoginProcessBean.getBean(request).getState();
log.debug("state from LoginProcessBean is " + currentState);
} else {
currentState = NOWHERE;
log.debug("no LoginSessionBean, no LoginProcessBean: "
+ "current state is NOWHERE");
return NOWHERE;
}
if ((currentState == NOWHERE) && isLoggingInByParameter(request)) {
currentState = LOGGING_IN;
log.debug("forced from NOWHERE to LOGGING_IN by '"
+ PARAMETER_LOGGING_IN + "' parameter");
}
return currentState;
}
/**
* If this parameter is present, we aren't NOWHERE.
*/
private boolean isLoggingInByParameter(HttpServletRequest request) {
return (request.getParameter(PARAMETER_LOGGING_IN) != null);
}
/**
@ -288,7 +358,6 @@ public class Authenticate extends VitroHttpServlet {
log.debug("Completed login: " + username);
getAuthenticator(request).recordLoginAgainstUserAccount(username,
AuthenticationSource.INTERNAL);
LoginProcessBean.removeBean(request);
}
/**
@ -301,15 +370,14 @@ public class Authenticate extends VitroHttpServlet {
getAuthenticator(request).recordNewPassword(username, newPassword);
getAuthenticator(request).recordLoginAgainstUserAccount(username,
AuthenticationSource.INTERNAL);
LoginProcessBean.removeBean(request);
}
/**
* State change: they decided to cancel the login.
*/
private void transitionToNowhere(HttpServletRequest request) {
LoginProcessBean.getBean(request).setState(NOWHERE);
log.debug("Cancelling login.");
LoginProcessBean.removeBean(request);
}
/**
@ -331,35 +399,17 @@ public class Authenticate extends VitroHttpServlet {
throws IOException {
log.debug("logging in.");
String referringPage = vreq.getHeader("referer");
if (referringPage == null) {
log.warn("No referring page on the request");
referringPage = getHomeUrl(vreq);
}
response.sendRedirect(referringPage);
String loginProcessPage = LoginProcessBean.getBean(vreq)
.getLoginPageUrl();
response.sendRedirect(loginProcessPage);
return;
}
/**
* Exit: user cancelled the login, so show them the home page.
*/
private void redirectCancellingUser(HttpServletRequest request,
HttpServletResponse response) throws IOException {
log.debug("User cancelled the login. Redirect to site admin page.");
LoginProcessBean.removeBean(request);
response.sendRedirect(getHomeUrl(request));
}
/** Get a reference to the Authenticator. */
private Authenticator getAuthenticator(HttpServletRequest request) {
return Authenticator.getInstance(request);
}
/** What's the URL for the home page? */
private String getHomeUrl(HttpServletRequest request) {
return request.getContextPath();
}
// ----------------------------------------------------------------------
// Public utility methods.
// ----------------------------------------------------------------------
@ -422,7 +472,7 @@ public class Authenticate extends VitroHttpServlet {
private void dumpStateToLog(String label, State state, VitroRequest vreq) {
log.debug("State on " + label + ": " + state);
if (log.isTraceEnabled()) {
log.trace("Status bean on " + label + ": "
+ LoginStatusBean.getBean(vreq));

View file

@ -319,7 +319,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
}
urls.put("search", urlBuilder.getPortalUrl(Route.SEARCH));
urls.put("termsOfUse", urlBuilder.getPortalUrl(Route.TERMS_OF_USE));
urls.put("login", urlBuilder.getPortalUrl(Route.LOGIN));
urls.put("login", urlBuilder.getLoginUrl());
urls.put("logout", urlBuilder.getLogoutUrl());
urls.put("siteAdmin", urlBuilder.getPortalUrl(Route.SITE_ADMIN));
urls.put("siteIcons", urlBuilder.getPortalUrl(themeDir + "/site_icons")); // deprecated

View file

@ -26,6 +26,7 @@ public class UrlBuilder {
public enum Route {
ABOUT("/about"),
AUTHENTICATE("/authenticate"),
BROWSE("/browse"),
CONTACT("/contact"),
INDIVIDUAL("/individual"),
@ -120,6 +121,10 @@ public class UrlBuilder {
return contextPath;
}
public String getLoginUrl() {
return getPortalUrl(Route.AUTHENTICATE, "return", "true");
}
public String getLogoutUrl() {
return getPortalUrl(Route.LOGOUT);
}

View file

@ -164,6 +164,12 @@ public class LoginProcessBean {
/** What arguments are needed to format the message? */
private Object[] messageArguments = NO_ARGUMENTS;
/** Where is the interaction taking place? */
private String loginPageUrl;
/** Where do we go when finished? */
private String afterLoginUrl;
/**
* 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.
@ -214,12 +220,29 @@ public class LoginProcessBean {
this.username = username;
}
public String getLoginPageUrl() {
return loginPageUrl;
}
public void setLoginPageUrl(String loginPageUrl) {
this.loginPageUrl = loginPageUrl;
}
public String getAfterLoginUrl() {
return afterLoginUrl;
}
public void setAfterLoginUrl(String afterLoginUrl) {
this.afterLoginUrl = afterLoginUrl;
}
@Override
public String toString() {
return "LoginProcessBean[state=" + currentState + ", message="
+ message + ", messageArguments="
+ Arrays.deepToString(messageArguments) + ", username="
+ username + "]";
+ username + ", loginPageUrl=" + loginPageUrl
+ ", afterLoginUrl=" + afterLoginUrl + "]";
}
}

View file

@ -106,14 +106,11 @@ public class LoginWidget extends Widget {
}
/**
* User is just starting the login process. Be sure that we have a
* {@link LoginProcessBean} with the correct status. Show them the login
* screen.
* User is starting the login process. Show them the login screen.
*/
private WidgetTemplateValues showLoginScreen(HttpServletRequest request)
throws IOException {
LoginProcessBean bean = LoginProcessBean.getBean(request);
bean.setState(State.LOGGING_IN);
log.trace("Going to login screen: " + bean);
WidgetTemplateValues values = new WidgetTemplateValues(Macro.LOGIN.toString());
@ -150,7 +147,6 @@ public class LoginWidget extends Widget {
*/
private WidgetTemplateValues showPasswordChangeScreen(HttpServletRequest request) {
LoginProcessBean bean = LoginProcessBean.getBean(request);
bean.setState(State.FORCED_PASSWORD_CHANGE);
log.trace("Going to password change screen: " + bean);
WidgetTemplateValues values = new WidgetTemplateValues(