NIHVIVO-1207 Rename Shibboleth servlets to more general ExternalAuth servlets. Add functionality to deal with self-editors as well as Users.

This commit is contained in:
jeb228 2010-11-17 19:25:55 +00:00
parent dae66852f9
commit 37c9eaf4a1
7 changed files with 246 additions and 153 deletions

View file

@ -1087,8 +1087,13 @@
</servlet> </servlet>
<servlet> <servlet>
<servlet-name>loginShibboleth</servlet-name> <servlet-name>loginExternalAuthSetup</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginShibboleth</servlet-class> <servlet-class>edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginExternalAuthSetup</servlet-class>
</servlet>
<servlet>
<servlet-name>loginExternalAuthReturn</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginExternalAuthReturn</servlet-class>
</servlet> </servlet>
<servlet> <servlet>
@ -1096,6 +1101,15 @@
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Logout</servlet-class> <servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Logout</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>unrecognizedUser</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.authenticate.UnrecognizedUserController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>unrecognizedUser</servlet-name>
<url-pattern>/unrecognizedUser</url-pattern>
</servlet-mapping>
<servlet> <servlet>
<servlet-name>browsecontroller</servlet-name> <servlet-name>browsecontroller</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.freemarker.BrowseController</servlet-class> <servlet-class>edu.cornell.mannlib.vitro.webapp.controller.freemarker.BrowseController</servlet-class>
@ -1258,6 +1272,14 @@
<servlet-name>loginShibboleth</servlet-name> <servlet-name>loginShibboleth</servlet-name>
<url-pattern>/loginShibboleth</url-pattern> <url-pattern>/loginShibboleth</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>loginExternalAuthSetup</servlet-name>
<url-pattern>/loginExternalAuth</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>loginExternalAuthReturn</servlet-name>
<url-pattern>/loginExternalAuthReturn</url-pattern>
</servlet-mapping>
<servlet-mapping> <servlet-mapping>
<!-- for backward-compatibility --> <!-- for backward-compatibility -->
<servlet-name>login</servlet-name> <servlet-name>login</servlet-name>

View file

@ -0,0 +1,117 @@
/* $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.LoginExternalAuthSetup.ATTRIBUTE_REFERRER;
import java.io.IOException;
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.VitroRequest;
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.dao.WebappDaoFactory;
/**
* Handle the return from the external authorization login server. If we are
* successful, record the login. Otherwise, display the failure.
*/
public class LoginExternalAuthReturn extends BaseLoginServlet {
private static final Log log = LogFactory
.getLog(LoginExternalAuthReturn.class);
/* This configuration property tells us what header contains the username. */
public static final String PROPERTY_EXTERNAL_AUTH_USERNAME_HEADER = "externalAuth.headerName";
/** The complaint we make if there is no such property. */
private static final Message MESSAGE_NO_EXTERNAL_AUTH_USERNAME = new LoginProcessBean.Message(
"deploy.properties doesn't contain a value for '"
+ PROPERTY_EXTERNAL_AUTH_USERNAME_HEADER + "'",
LoginProcessBean.MLevel.ERROR);
private static final Message MESSAGE_LOGIN_FAILED = new LoginProcessBean.Message(
"External login failed.", LoginProcessBean.MLevel.ERROR);
private final LoginRedirector loginRedirector = new LoginRedirector();
private String externalAuthUsernameHeader;
/** Get the configuration properties. */
@Override
public void init() throws ServletException {
externalAuthUsernameHeader = ConfigurationProperties
.getProperty(PROPERTY_EXTERNAL_AUTH_USERNAME_HEADER);
}
/**
* <pre>
* Returning from the external authorization server. If we were successful,
* the header will contain the name of the user who just logged in.
*
* Deal with these possibilities:
* - The header name was not configured in deploy.properties. Complain.
* - No username: the login failed. Complain
* - User corresponds to a User acocunt. Record the login.
* - User corresponds to an Individual (self-editor).
* - User is not recognized.
* </pre>
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (externalAuthUsernameHeader == null) {
complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER,
MESSAGE_NO_EXTERNAL_AUTH_USERNAME);
return;
}
String username = req.getHeader(externalAuthUsernameHeader);
String uri = getAssociatedIndividualUri(username, req);
if (username == null) {
log.debug("No username.");
complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER,
MESSAGE_LOGIN_FAILED);
} else if (getAuthenticator(req).isExistingUser(username)) {
log.debug("Logging in as " + username);
getAuthenticator(req).recordUserIsLoggedIn(username);
removeLoginProcessArtifacts(req);
loginRedirector.redirectLoggedInUser(req, resp);
} else if (uri != null) {
log.debug("Recognize '' as self-editor for " + uri);
removeLoginProcessArtifacts(req);
loginRedirector.redirectSelfEditingUser(req, resp, uri);
} else {
log.debug("User is not recognized: " + username);
removeLoginProcessArtifacts(req);
loginRedirector.redirectUnrecognizedUser(req, resp, username);
}
}
private String getAssociatedIndividualUri(String username,
HttpServletRequest req) {
if (username == null) {
return null;
}
VitroRequest vreq = new VitroRequest(req);
WebappDaoFactory wdf = vreq.getWebappDaoFactory();
return wdf.getIndividualDao().getIndividualURIFromNetId(username);
}
private void removeLoginProcessArtifacts(HttpServletRequest req) {
LoginProcessBean.removeBean(req);
req.getSession().removeAttribute(ATTRIBUTE_REFERRER);
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

View file

@ -19,69 +19,71 @@ 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.Message;
/** /**
* Set up the Shibboleth login process. * Set up the external authorization process.
* *
* Write down the page that triggered the request, so we can get back to it. * 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 * Send a request to the external authorization server that will return us to
* LoginShibbolethReturn servlet for further processing. * the LoginExternalAuthReturn servlet for further processing.
*/ */
public class LoginShibbolethSetup extends BaseLoginServlet { public class LoginExternalAuthSetup extends BaseLoginServlet {
private static final Log log = LogFactory private static final Log log = LogFactory
.getLog(LoginShibbolethSetup.class); .getLog(LoginExternalAuthSetup.class);
/** This session attribute tells where we came from. */ /** This session attribute tells where we came from. */
static final String ATTRIBUTE_REFERRER = LoginShibbolethSetup.class static final String ATTRIBUTE_REFERRER = LoginExternalAuthSetup.class
.getName() + ".referrer"; .getName() + ".referrer";
private static final String RETURN_SERVLET_URL = "/loginShibbolethReturn"; private static final String RETURN_SERVLET_URL = "/loginExternalAuthReturn";
/** This http header holds the referring page. */ /** This http header holds the referring page. */
private static final String HEADING_REFERRER = "referer"; 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 configuration property that points to the external authorization
* server.
*/
private static final String PROPERTY_EXTERNAL_AUTH_SERVER_URL = "externalAuth.serverUrl";
/** The complaint we make if there is no Shibbolet server property. */ /**
private static final Message MESSAGE_NO_SHIBBOLETH_SERVER = new LoginProcessBean.Message( * The complaint we make if there is no external authorization server
* property.
*/
private static final Message MESSAGE_NO_EXTERNAL_AUTH_SERVER = new LoginProcessBean.Message(
"deploy.properties doesn't contain a value for '" "deploy.properties doesn't contain a value for '"
+ PROPERTY_SHIBBOLETH_SERVER_URL + "'", + PROPERTY_EXTERNAL_AUTH_SERVER_URL + "'",
LoginProcessBean.MLevel.ERROR); LoginProcessBean.MLevel.ERROR);
private String shibbolethServerUrl; private String extrnalAuthServerUrl;
/** Get the configuration property. */ /** Get the configuration property. */
@Override @Override
public void init() throws ServletException { public void init() throws ServletException {
shibbolethServerUrl = ConfigurationProperties extrnalAuthServerUrl = ConfigurationProperties
.getProperty(PROPERTY_SHIBBOLETH_SERVER_URL); .getProperty(PROPERTY_EXTERNAL_AUTH_SERVER_URL);
} }
/** /**
* Write down the referring page, record that we are logging in, and * Write down the referring page, record that we are logging in, and
* redirect to the shib server URL. * redirect to the external authorization server URL.
*/ */
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { throws ServletException, IOException {
// Record where we came from, so we can get back there.
storeTheReferringPage(req); storeTheReferringPage(req);
// If we have no URL for the Shibboleth server, give up. if (extrnalAuthServerUrl == null) {
if (shibbolethServerUrl == null) { log.debug("No external authorization server in deploy.properties");
log.debug("No shibboleth server in deploy.properties");
complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER, complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER,
MESSAGE_NO_SHIBBOLETH_SERVER); MESSAGE_NO_EXTERNAL_AUTH_SERVER);
return; return;
} }
// Record that we are in the process of logging in.
LoginProcessBean.getBean(req).setState( LoginProcessBean.getBean(req).setState(
LoginProcessBean.State.LOGGING_IN); LoginProcessBean.State.LOGGING_IN);
// Hand over to Shibboleth. log.debug("Sending to external authorization server.");
log.debug("Sending to shibboleth server."); resp.sendRedirect(buildExternalAuthRedirectUrl(req));
resp.sendRedirect(buildShibbolethRedirectUrl(req));
} }
/** Remember where we came from - we'll need to go back there. */ /** Remember where we came from - we'll need to go back there. */
@ -95,15 +97,15 @@ public class LoginShibbolethSetup extends BaseLoginServlet {
req.getSession().setAttribute(ATTRIBUTE_REFERRER, referrer); req.getSession().setAttribute(ATTRIBUTE_REFERRER, referrer);
} }
/** How do we get to the Shibboleth server and back? */ /** How do we get to the external authorization server and back? */
private String buildShibbolethRedirectUrl(HttpServletRequest req) { private String buildExternalAuthRedirectUrl(HttpServletRequest req) {
try { try {
String returnUrl = figureHomePageUrl(req) + RETURN_SERVLET_URL; String returnUrl = figureHomePageUrl(req) + RETURN_SERVLET_URL;
String encodedReturnUrl = URLEncoder.encode(returnUrl, "UTF-8"); String encodedReturnUrl = URLEncoder.encode(returnUrl, "UTF-8");
String shibbolethUrl = shibbolethServerUrl + "?target=" String externalAuthUrl = extrnalAuthServerUrl + "?target="
+ encodedReturnUrl; + encodedReturnUrl;
log.debug("shibbolethURL is '" + shibbolethUrl + "'"); log.debug("externalAuthUrl is '" + externalAuthUrl + "'");
return shibbolethUrl; return externalAuthUrl;
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // No UTF-8? Really? throw new RuntimeException(e); // No UTF-8? Really?
} }

View file

@ -3,6 +3,7 @@
package edu.cornell.mannlib.vitro.webapp.controller.authenticate; package edu.cornell.mannlib.vitro.webapp.controller.authenticate;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.List; import java.util.List;
@ -23,9 +24,23 @@ import edu.cornell.mannlib.vitro.webapp.controller.Controllers;
public class LoginRedirector { public class LoginRedirector {
private static final Log log = LogFactory.getLog(LoginRedirector.class); private static final Log log = LogFactory.getLog(LoginRedirector.class);
public void redirectSelfEditingUser(HttpServletRequest request,
HttpServletResponse response, String uri) throws IOException {
String userHomePage = assembleUserHomePageUrl(request, uri);
log.debug("Redirecting self-editor to " + userHomePage);
response.sendRedirect(userHomePage);
}
public void redirectUnrecognizedUser(HttpServletRequest request,
HttpServletResponse response, String username) throws IOException {
log.debug("Redirecting unrecognized user: " + username);
response.sendRedirect(request.getContextPath()
+ "/unrecognizedUser?username=" + username);
}
/** /**
* <pre> * <pre>
* Exit: the user is logged in. They might go to: * The user is logged in. They might go to:
* - A one-time redirect, stored in the session, if they had tried to * - A one-time redirect, stored in the session, if they had tried to
* bookmark to a page that requires login. * bookmark to a page that requires login.
* - An application-wide redirect, stored in the servlet context. * - An application-wide redirect, stored in the servlet context.
@ -68,9 +83,8 @@ public class LoginRedirector {
List<String> uris = getAuthenticator(request) List<String> uris = getAuthenticator(request)
.asWhomMayThisUserEdit(user); .asWhomMayThisUserEdit(user);
if (uris != null && uris.size() > 0) { if (uris != null && uris.size() > 0) {
String userHomePage = request.getContextPath() String userHomePage = assembleUserHomePageUrl(request,
+ "/individual?uri=" uris.get(0));
+ URLEncoder.encode(uris.get(0), "UTF-8");
log.debug("User is logged in. Redirect as self-editor to " log.debug("User is logged in. Redirect as self-editor to "
+ userHomePage); + userHomePage);
response.sendRedirect(userHomePage); response.sendRedirect(userHomePage);
@ -106,7 +120,6 @@ public class LoginRedirector {
/** What's the URL for the site admin screen? */ /** What's the URL for the site admin screen? */
private String getSiteAdminUrl(HttpServletRequest request) { private String getSiteAdminUrl(HttpServletRequest request) {
// return Route.SITE_ADMIN.url();
String contextPath = request.getContextPath(); String contextPath = request.getContextPath();
return contextPath + Controllers.SITE_ADMIN; return contextPath + Controllers.SITE_ADMIN;
} }
@ -116,4 +129,9 @@ public class LoginRedirector {
return Authenticator.getInstance(request); return Authenticator.getInstance(request);
} }
private String assembleUserHomePageUrl(HttpServletRequest request,
String uri) throws UnsupportedEncodingException {
return request.getContextPath() + "/individual?uri="
+ URLEncoder.encode(uri, "UTF-8");
}
} }

View file

@ -1,116 +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 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);
}
}

View file

@ -0,0 +1,35 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.authenticate;
import java.util.HashMap;
import java.util.Map;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
/**
* TODO
*/
public class UnrecognizedUserController extends FreemarkerHttpServlet {
private static final String TEMPLATE_DEFAULT = "unrecognizedUser.ftl";
private static final String PARAMETER_USERNAME = "username";
@Override
protected ResponseValues processRequest(VitroRequest vreq) {
Map<String, Object> body = new HashMap<String, Object>();
String username = vreq.getParameter(PARAMETER_USERNAME);
if (username != null) {
body.put("username", username);
}
return new TemplateResponseValues(TEMPLATE_DEFAULT, body);
}
@Override
protected String getTitle(String siteName) {
return "Unrecognized user " + siteName;
}
}

View file

@ -0,0 +1,15 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#-- Template for the Unrecognized User page. -->
<section role="region">
<h2>Unrecognized user</h2>
<p>
For some reason, there is no individual in VIVO that is associated with your Net ID.
Perhaps you should contact your VIVO administrator.
</p>
<br/>
<a href="${urls.home}">Continue</a>
</section>