NIHVIVO-1207 Add the LoginShibboleth servlet.
This commit is contained in:
parent
160f8da487
commit
bb614c833b
2 changed files with 287 additions and 0 deletions
|
@ -1068,6 +1068,11 @@
|
||||||
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Login</servlet-class>
|
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Login</servlet-class>
|
||||||
</servlet>
|
</servlet>
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>loginShibboleth</servlet-name>
|
||||||
|
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginShibboleth</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
|
||||||
<servlet>
|
<servlet>
|
||||||
<servlet-name>logout</servlet-name>
|
<servlet-name>logout</servlet-name>
|
||||||
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Logout</servlet-class>
|
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Logout</servlet-class>
|
||||||
|
@ -1229,6 +1234,10 @@
|
||||||
<servlet-name>login</servlet-name>
|
<servlet-name>login</servlet-name>
|
||||||
<url-pattern>/login</url-pattern>
|
<url-pattern>/login</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>loginShibboleth</servlet-name>
|
||||||
|
<url-pattern>/loginShibboleth</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
<servlet-mapping>
|
<servlet-mapping>
|
||||||
<!-- for backward-compatibility -->
|
<!-- for backward-compatibility -->
|
||||||
<servlet-name>login</servlet-name>
|
<servlet-name>login</servlet-name>
|
||||||
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
/* $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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* 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.
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@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<String> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue