diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/BaseLoginServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/BaseLoginServlet.java new file mode 100644 index 000000000..a376a4c33 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/BaseLoginServlet.java @@ -0,0 +1,61 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.authenticate; + +import java.io.IOException; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean; +import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.Message; + +/** + * Base class for all Login servlets, whether Shibboleth, CuWebAuth, etc. + */ +public class BaseLoginServlet extends HttpServlet { + private static final Log log = LogFactory.getLog(BaseLoginServlet.class); + + protected Authenticator getAuthenticator(HttpServletRequest req) { + return Authenticator.getInstance(req); + } + + /** + * Store an error message in the login bean and go back where we came from. + * + * Remove the referring URL from the session after using it. + */ + protected void complainAndReturnToReferrer(HttpServletRequest req, + HttpServletResponse resp, String sessionAttributeForReferrer, + Message message, Object... args) throws IOException { + log.debug(message.getMessageLevel() + ": " + + message.formatMessage(args)); + LoginProcessBean.getBean(req).setMessage(message, args); + + String referrer = (String) req.getSession().getAttribute( + sessionAttributeForReferrer); + log.debug("returning to referrer: " + referrer); + if (referrer == null) { + referrer = figureHomePageUrl(req); + log.debug("returning to home page: " + referrer); + } + + req.getSession().removeAttribute(sessionAttributeForReferrer); + resp.sendRedirect(referrer); + } + + /** + * If we don't have a referrer, send them to the home page. + */ + protected String figureHomePageUrl(HttpServletRequest req) { + StringBuffer url = req.getRequestURL(); + String uri = req.getRequestURI(); + int authLength = url.length() - uri.length(); + String auth = url.substring(0, authLength); + return auth + req.getContextPath(); + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibboleth.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibboleth.java deleted file mode 100644 index 0f55c1aed..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibboleth.java +++ /dev/null @@ -1,278 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.controller.authenticate; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Enumeration; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import edu.cornell.mannlib.vitro.webapp.ConfigurationProperties; -import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean; -import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.Message; - -/** - * This servlet acts as the interface to the Shibboleth authentication server. - * - * If the request has the "setup" property, it is coming from the Vivo login - * screen and going to the Shibboleth server. - * - * Otherwise, the request is coming from the Shibboleth server and going back to - * Vivo. - */ -public class LoginShibboleth extends HttpServlet { - private static final Log log = LogFactory.getLog(LoginShibboleth.class); - - private static final String CLASSNAME = LoginShibboleth.class.getName(); - - /** This session attribute tells where we came from. */ - private static final String ATTRIBUTE_REFERRER = CLASSNAME + ".referrer"; - - /** This request parameter indicates that we are setting up the login. */ - private static final String PARAMETER_SETUP = "setup"; - - /** This http header holds the referring page. */ - private static final String HEADING_REFERRER = "referer"; - - /** On return froma Shibboleth login, this header holds the provider name. */ - private static final String HEADING_SHIBBOLETH_PROVIDER = "shib-identity-provider"; - - /** On return froma Shibboleth login, this header holds the user name. */ - private static final String HEADING_SHIBBOLETH_USERNAME = "glid"; - - /** The configuration property that points to the Shibboleth server. */ - private static final String PROPERTY_SHIBBOLETH_SERVER_URL = "shibboleth.server.url"; - - /** The configuration property that tells what provider name we expect. */ - private static final String PROPERTY_SHIBBOLETH_PROVIDER = "shibboleth.provider"; - - private static final Message MESSAGE_NO_SHIBBOLETH_SERVER = new LoginProcessBean.Message( - "deploy.properties doesn't contain a value for '" - + PROPERTY_SHIBBOLETH_SERVER_URL + "'", - LoginProcessBean.MLevel.ERROR); - - private static final Message MESSAGE_NO_SHIBBOLETH_PROVIDER = new LoginProcessBean.Message( - "deploy.properties doesn't contain a value for '" - + PROPERTY_SHIBBOLETH_PROVIDER + "'", - LoginProcessBean.MLevel.ERROR); - - private static final Message MESSAGE_LOGIN_FAILED = new LoginProcessBean.Message( - "Shibboleth login failed.", LoginProcessBean.MLevel.ERROR); - - private static final Message MESSAGE_NO_SUCH_USER = new LoginProcessBean.Message( - "Shibboleth login succeeded, but user {0} is unknown to VIVO.", - LoginProcessBean.MLevel.ERROR); - - private static final String ERROR_NO_PARAMETERS = "Likely error in the template: " - + "'setup' parameter was not found, " - + "but there was no info from the Shibboleth server either."; - - private final LoginRedirector loginRedirector = new LoginRedirector(); - - private String shibbolethServerUrl; - private String shibbolethProvider; - private static boolean isFirstCallToServlet = true; - - /** Get the configuration properties. */ - @Override - public void init() throws ServletException { - shibbolethServerUrl = ConfigurationProperties - .getProperty(PROPERTY_SHIBBOLETH_SERVER_URL); - shibbolethProvider = ConfigurationProperties - .getProperty(PROPERTY_SHIBBOLETH_PROVIDER); - } - - /** - *
-	 * The first request to this servlet must include the setup parameter. 
-	 *      If it doesn't, it is totally bogus and will cause a complaint.
-	 *      This means that the login form isn't coded correctly, 
-	 *      	We try to notify the sysadmin in a meaningful way.
-	 *      
-	 * On setup, write down the referring page and redirect to the shib server URL
-	 * 		a URL that comes from the deploy.properties via the widget code.
-	 * 			if no such property, set error message and return to referring page.
-	 * 		it returns to this page
-	 * 
-	 * Not on setup
-	 * 		check for the site name and the username
-	 *      if either is missing,
-	 * 			return to the referring page with an error message: login failed.
-	 * 		if both there and the provider is wrong
-	 * 			return to the referring page with an error message: login failed.
-	 *      if both there and the user doesn't exist
-	 * 			return to the referring page with an error message: no such user.
-	 * 		otherwise (successful)
-	 * 			record the login and redirect like we would on a normal login.
-	 * 
- */ - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - setupLoginProcessBean(req); - - boolean setupParmIsSet = checkSetupParameterIsSet(req); - - if (requestIsTotallyBogus(setupParmIsSet)) { - resp.sendError(500, ERROR_NO_PARAMETERS); - } else if (setupParmIsSet) { - settingUpShibbolethLogin(req, resp); - } else { - returningFromShibbolethLogin(req, resp); - } - } - - /** Record that the login is in progress. */ - private void setupLoginProcessBean(HttpServletRequest req) { - LoginProcessBean bean = LoginProcessBean.getBean(req); - bean.setState(LoginProcessBean.State.LOGGING_IN); - } - - /** Does the request contain a "setup" parameter? */ - private boolean checkSetupParameterIsSet(HttpServletRequest req) { - String setupParm = req.getParameter(PARAMETER_SETUP); - log.debug("setup=" + setupParm); - if ((setupParm == null) || setupParm.isEmpty()) { - return false; - } else { - return true; - } - } - - /** If the first call doesn't include "setup", the template is broken. */ - private boolean requestIsTotallyBogus(boolean setupParmIsSet) { - boolean bogosity = isFirstCallToServlet && !setupParmIsSet; - isFirstCallToServlet = false; - return bogosity; - } - - /** On setup, hand over to the Shibboleth server. */ - private void settingUpShibbolethLogin(HttpServletRequest req, - HttpServletResponse resp) throws IOException { - storeTheReferringPage(req); - if (shibbolethServerUrl == null) { - log.debug("No shibboleth server in deploy.properties"); - complainAndReturnToReferrer(req, resp, MESSAGE_NO_SHIBBOLETH_SERVER); - } else if (shibbolethProvider == null) { - log.debug("No shibboleth provider in deploy.properties"); - complainAndReturnToReferrer(req, resp, - MESSAGE_NO_SHIBBOLETH_PROVIDER); - } else { - log.debug("Sending to shibboleth server."); - resp.sendRedirect(buildShibbolethRedirectUrl(req)); - } - } - - /** Remember where we came from - we'll need to go back there. */ - private void storeTheReferringPage(HttpServletRequest req) { - String referrer = req.getHeader(HEADING_REFERRER); - if (referrer == null) { - dumpRequestHeaders(req); - referrer = figureHomePageUrl(req); - } - log.debug("Referring page is '" + referrer + "'"); - req.getSession().setAttribute(ATTRIBUTE_REFERRER, referrer); - } - - /** How do we get to the Shibboleth server and back? */ - private String buildShibbolethRedirectUrl(HttpServletRequest req) { - try { - String returnUrl = req.getRequestURL().toString(); - String encodedReturnUrl = URLEncoder.encode(returnUrl, "UTF-8"); - String shibbolethUrl = shibbolethServerUrl + "?target=" - + encodedReturnUrl; - log.debug("shibbolethURL is '" + shibbolethUrl + "'"); - return shibbolethUrl; - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // No UTF-8? Really? - } - } - - /** On return from the Shibboleth server, try to apply the results. */ - private void returningFromShibbolethLogin(HttpServletRequest req, - HttpServletResponse resp) throws IOException { - String provider = req.getHeader(HEADING_SHIBBOLETH_PROVIDER); - String user = req.getHeader(HEADING_SHIBBOLETH_USERNAME); - log.debug("Info from Shibboleth: user=" + user + ", provider=" - + provider); - - if ((provider == null) || (user == null)) { - complainAndReturnToReferrer(req, resp, MESSAGE_LOGIN_FAILED); - } else if (!this.shibbolethProvider.equals(provider)) { - log.error("Wrong shibboleth provider: " + provider); - complainAndReturnToReferrer(req, resp, MESSAGE_LOGIN_FAILED); - } else if (!getAuthenticator(req).isExistingUser(user)) { - log.debug("No such user: " + user); - complainAndReturnToReferrer(req, resp, MESSAGE_NO_SUCH_USER, user); - } else { - log.debug("Logging in as " + user); - recordLoginAndRedirect(req, resp, user); - } - } - - /** Success. Record the login and send them to the appropriate page. */ - private void recordLoginAndRedirect(HttpServletRequest req, - HttpServletResponse resp, String username) - throws UnsupportedEncodingException, IOException { - getAuthenticator(req).recordUserIsLoggedIn(username); - req.getSession().removeAttribute(ATTRIBUTE_REFERRER); - loginRedirector.redirectLoggedInUser(req, resp); - } - - /** Store an error message in the login bean and go back where we came from. */ - private void complainAndReturnToReferrer(HttpServletRequest req, - HttpServletResponse resp, Message message, Object... args) - throws IOException { - log.debug(message.getMessageLevel() +": "+ message.formatMessage(args)); - LoginProcessBean.getBean(req).setMessage(message, args); - - String referrer = (String) req.getSession().getAttribute( - ATTRIBUTE_REFERRER); - log.debug("returning to referrer: " + referrer); - if (referrer == null) { - referrer = figureHomePageUrl(req); - log.debug("returning to home page: " + referrer); - } - - req.getSession().removeAttribute(ATTRIBUTE_REFERRER); - resp.sendRedirect(referrer); - } - - private String figureHomePageUrl(HttpServletRequest req) { - StringBuffer url = req.getRequestURL(); - String uri = req.getRequestURI(); - int authLength = url.length() - uri.length(); - String auth = url.substring(0, authLength); - return auth + req.getContextPath(); - } - - private Authenticator getAuthenticator(HttpServletRequest req) { - return Authenticator.getInstance(req); - } - - private void dumpRequestHeaders(HttpServletRequest req) { - if (log.isDebugEnabled()) { - @SuppressWarnings("unchecked") - Enumeration names = req.getHeaderNames(); - while (names.hasMoreElements()) { - String name = names.nextElement(); - log.debug("header: " + name + "=" + req.getHeader(name)); - } - } - } - - @Override - protected void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - doPost(request, response); - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibbolethReturn.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibbolethReturn.java new file mode 100644 index 000000000..e8669a85a --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibbolethReturn.java @@ -0,0 +1,116 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.authenticate; + +import static edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginShibbolethSetup.ATTRIBUTE_REFERRER; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean; +import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.Message; + +/** + * Handle the return from the Shibboleth login server. If we are successful, + * record the login. Otherwise, display the failure. + */ +public class LoginShibbolethReturn extends BaseLoginServlet { + private static final Log log = LogFactory + .getLog(LoginShibbolethReturn.class); + + /** The configuration property that tells what provider name we expect. */ + private static final String PROPERTY_SHIBBOLETH_PROVIDER = "shibboleth.provider"; + + /** On return froma Shibboleth login, this header holds the provider name. */ + private static final String HEADING_SHIBBOLETH_PROVIDER = "shib-identity-provider"; + + /** On return froma Shibboleth login, this header holds the user name. */ + private static final String HEADING_SHIBBOLETH_USERNAME = "glid"; + + private static final Message MESSAGE_LOGIN_FAILED = new LoginProcessBean.Message( + "Shibboleth login failed.", LoginProcessBean.MLevel.ERROR); + + private static final Message MESSAGE_NO_SUCH_USER = new LoginProcessBean.Message( + "Shibboleth login succeeded, but user {0} is unknown to VIVO.", + LoginProcessBean.MLevel.ERROR); + + private static final Message MESSAGE_NO_SHIBBOLETH_PROVIDER = new LoginProcessBean.Message( + "deploy.properties doesn't contain a value for '" + + PROPERTY_SHIBBOLETH_PROVIDER + "'", + LoginProcessBean.MLevel.ERROR); + + private final LoginRedirector loginRedirector = new LoginRedirector(); + private String shibbolethProvider; + + /** Get the configuration properties. */ + @Override + public void init() throws ServletException { + shibbolethProvider = ConfigurationProperties + .getProperty(PROPERTY_SHIBBOLETH_PROVIDER); + } + + /** + * Returning from the Shibboleth server. If we were successful, the headers + * will contain the Shibboleth provider name and the name of the user who + * just logged in. + * + * We report problems if the provider name is missing or we don't know the + * correct value for it. We also report problems if the username is missing + * or if we don't recognize that user. + * + * If there are no problems, record the login and redirect like we would on + * a normal login. + */ + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + String provider = req.getHeader(HEADING_SHIBBOLETH_PROVIDER); + String user = req.getHeader(HEADING_SHIBBOLETH_USERNAME); + log.debug("Info from Shibboleth: user=" + user + ", provider=" + + provider); + + if ((provider == null) || (user == null)) { + complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER, + MESSAGE_LOGIN_FAILED); + } else if (shibbolethProvider == null) { + log.debug("No shibboleth provider in deploy.properties"); + complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER, + MESSAGE_NO_SHIBBOLETH_PROVIDER); + } else if (!this.shibbolethProvider.equals(provider)) { + log.error("Wrong shibboleth provider: " + provider); + complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER, + MESSAGE_LOGIN_FAILED); + } else if (!getAuthenticator(req).isExistingUser(user)) { + log.debug("No such user: " + user); + complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER, + MESSAGE_NO_SUCH_USER, user); + } else { + log.debug("Logging in as " + user); + recordLoginAndRedirect(req, resp, user); + } + } + + /** Success. Record the login and send them to the appropriate page. */ + private void recordLoginAndRedirect(HttpServletRequest req, + HttpServletResponse resp, String username) + throws UnsupportedEncodingException, IOException { + getAuthenticator(req).recordUserIsLoggedIn(username); + LoginProcessBean.removeBean(req); + req.getSession().removeAttribute(ATTRIBUTE_REFERRER); + loginRedirector.redirectLoggedInUser(req, resp); + } + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + doPost(request, response); + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibbolethSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibbolethSetup.java new file mode 100644 index 000000000..f54f7e6b5 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginShibbolethSetup.java @@ -0,0 +1,129 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.authenticate; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Enumeration; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean; +import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean.Message; + +/** + * Set up the Shibboleth login process. + * + * Write down the page that triggered the request, so we can get back to it. + * + * Send a request to the Shibboleth server that will return us to the + * LoginShibbolethReturn servlet for further processing. + */ +public class LoginShibbolethSetup extends BaseLoginServlet { + private static final Log log = LogFactory + .getLog(LoginShibbolethSetup.class); + + /** This session attribute tells where we came from. */ + static final String ATTRIBUTE_REFERRER = LoginShibbolethSetup.class + .getName() + ".referrer"; + + private static final String RETURN_SERVLET_URL = "/loginShibbolethReturn"; + + /** This http header holds the referring page. */ + private static final String HEADING_REFERRER = "referer"; + + /** The configuration property that points to the Shibboleth server. */ + private static final String PROPERTY_SHIBBOLETH_SERVER_URL = "shibboleth.server.url"; + + /** The complaint we make if there is no Shibbolet server property. */ + private static final Message MESSAGE_NO_SHIBBOLETH_SERVER = new LoginProcessBean.Message( + "deploy.properties doesn't contain a value for '" + + PROPERTY_SHIBBOLETH_SERVER_URL + "'", + LoginProcessBean.MLevel.ERROR); + + private String shibbolethServerUrl; + + /** Get the configuration property. */ + @Override + public void init() throws ServletException { + shibbolethServerUrl = ConfigurationProperties + .getProperty(PROPERTY_SHIBBOLETH_SERVER_URL); + } + + /** + * Write down the referring page, record that we are logging in, and + * redirect to the shib server URL. + */ + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + // Record where we came from, so we can get back there. + storeTheReferringPage(req); + + // If we have no URL for the Shibboleth server, give up. + if (shibbolethServerUrl == null) { + log.debug("No shibboleth server in deploy.properties"); + complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER, + MESSAGE_NO_SHIBBOLETH_SERVER); + return; + } + + // Record that we are in the process of logging in. + LoginProcessBean.getBean(req).setState( + LoginProcessBean.State.LOGGING_IN); + + // Hand over to Shibboleth. + log.debug("Sending to shibboleth server."); + resp.sendRedirect(buildShibbolethRedirectUrl(req)); + } + + /** Remember where we came from - we'll need to go back there. */ + private void storeTheReferringPage(HttpServletRequest req) { + String referrer = req.getHeader(HEADING_REFERRER); + if (referrer == null) { + dumpRequestHeaders(req); + referrer = figureHomePageUrl(req); + } + log.debug("Referring page is '" + referrer + "'"); + req.getSession().setAttribute(ATTRIBUTE_REFERRER, referrer); + } + + /** How do we get to the Shibboleth server and back? */ + private String buildShibbolethRedirectUrl(HttpServletRequest req) { + try { + String returnUrl = figureHomePageUrl(req) + RETURN_SERVLET_URL; + String encodedReturnUrl = URLEncoder.encode(returnUrl, "UTF-8"); + String shibbolethUrl = shibbolethServerUrl + "?target=" + + encodedReturnUrl; + log.debug("shibbolethURL is '" + shibbolethUrl + "'"); + return shibbolethUrl; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // No UTF-8? Really? + } + } + + private void dumpRequestHeaders(HttpServletRequest req) { + if (log.isDebugEnabled()) { + @SuppressWarnings("unchecked") + Enumeration names = req.getHeaderNames(); + while (names.hasMoreElements()) { + String name = names.nextElement(); + log.debug("header: " + name + "=" + req.getHeader(name)); + } + } + } + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + doPost(request, response); + } + +}