package edu.internet2.middleware.shibboleth.idp.authn;
import java.io.IOException;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
import javax.security.auth.Subject;
import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import org.joda.time.DateTime;
import org.opensaml.xml.util.DatatypeHelper;
-import org.opensaml.xml.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Class logger. */
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationEngine.class);
- /**
- * Gets the manager used to retrieve handlers for requests.
- *
- * @return manager used to retrieve handlers for requests
- */
- public IdPProfileHandlerManager getProfileHandlerManager() {
- return (IdPProfileHandlerManager) getServletContext().getAttribute("handlerManager");
- }
+ /** Profile handler manager. */
+ private IdPProfileHandlerManager handlerManager;
- /**
- * Gets the session manager to be used.
- *
- * @return session manager to be used
- */
- @SuppressWarnings("unchecked")
- public SessionManager<Session> getSessionManager() {
- return (SessionManager<Session>) getServletContext().getAttribute("sessionManager");
+ /** Session manager. */
+ private SessionManager<Session> sessionManager;
+
+ /** {@inheritDoc} */
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ String handlerManagerId = config.getInitParameter("handlerManagerId");
+ if (DatatypeHelper.isEmpty(handlerManagerId)) {
+ handlerManagerId = "shibboleth.HandlerManager";
+ }
+ handlerManager = (IdPProfileHandlerManager) getServletContext().getAttribute(handlerManagerId);
+
+ String sessionManagerId = config.getInitParameter("sessionManagedId");
+ if (DatatypeHelper.isEmpty(handlerManagerId)) {
+ sessionManagerId = "shibboleth.SessionManager";
+ }
+
+ sessionManager = (SessionManager<Session>) getServletContext().getAttribute(sessionManagerId);
}
/**
LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
if (loginContext == null) {
LOG.error("User HttpSession did not contain a login context. Unable to return to authentication engine");
+ forwardRequest("/idp-error.jsp", httpRequest, httpResponse);
+ } else {
+ forwardRequest(loginContext.getAuthenticationEngineURL(), httpRequest, httpResponse);
}
- forwardRequest(loginContext.getAuthenticationEngineURL(), httpRequest, httpResponse);
}
/**
public static void returnToProfileHandler(LoginContext loginContext, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
LOG.debug("Returning control to profile handler at: {}", loginContext.getProfileHandlerURL());
+ httpRequest.getSession().removeAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+ httpRequest.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginContext);
forwardRequest(loginContext.getProfileHandlerURL(), httpRequest, httpResponse);
}
LOG.error("HTTP Response already committed");
}
- HttpSession httpSession = httpRequest.getSession();
- LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+ LoginContext loginContext = (LoginContext) httpRequest.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+ if (loginContext == null) {
+ // When the login context comes from the profile handlers its attached to the request
+ // The authn engine attaches it to the session to allow the handlers to do any number of
+ // request/response pairs without maintaining or losing the login context
+ loginContext = (LoginContext) httpRequest.getSession().getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+ }
+
if (loginContext == null) {
LOG.error("Incoming request does not have attached login context");
throw new ServletException("Incoming request does not have attached login context");
}
if (!loginContext.getAuthenticationAttempted()) {
- Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
-
- AuthenticationMethodInformation authenticationMethod = getUsableExistingAuthenticationMethod(loginContext,
- shibSession);
- if (authenticationMethod != null) {
- LOG.debug("An active authentication method is applicable for relying party. Using authentication "
- + "method {} as authentication method to relying party without re-authenticating user.",
- authenticationMethod.getAuthenticationMethod());
- authenticateUserWithActiveMethod(httpRequest, httpResponse, authenticationMethod);
- return;
+ startUserAuthentication(loginContext, httpRequest, httpResponse);
+ } else {
+ completeAuthenticationWithoutActiveMethod(loginContext, httpRequest, httpResponse);
+ }
+ }
+
+ /**
+ * Begins the authentication process. Determines if forced re-authentication is required or if an existing, active,
+ * authentication method is sufficient. Also determines, when authentication is required, which handler to use
+ * depending on whether passive authentication is required.
+ *
+ * @param loginContext current login context
+ * @param httpRequest current HTTP request
+ * @param httpResponse current HTTP response
+ */
+ protected void startUserAuthentication(LoginContext loginContext, HttpServletRequest httpRequest,
+ HttpServletResponse httpResponse) {
+ LOG.debug("Beginning user authentication process");
+ try {
+ Map<String, LoginHandler> possibleLoginHandlers = determinePossibleLoginHandlers(loginContext);
+ ArrayList<AuthenticationMethodInformation> activeAuthnMethods = new ArrayList<AuthenticationMethodInformation>();
+
+ Session userSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
+ if (userSession != null) {
+ activeAuthnMethods.addAll(userSession.getAuthenticationMethods().values());
}
- LOG.debug("No active authentication method is applicable for relying party. "
- + "Authenticating user with to be determined method.");
- authenticateUserWithoutActiveMethod1(httpRequest, httpResponse);
- } else {
- LOG.debug("Request returned from authentication handler, completing authentication process.");
- authenticateUserWithoutActiveMethod2(httpRequest, httpResponse);
+ if (loginContext.isForceAuthRequired()) {
+ LOG.debug("Forced authentication is required, filtering possible login handlers accordingly");
+ filterByForceAuthentication(loginContext, activeAuthnMethods, possibleLoginHandlers);
+ } else {
+ LOG.debug("Forced authentication not required, trying existing authentication methods");
+ for (AuthenticationMethodInformation activeAuthnMethod : activeAuthnMethods) {
+ if (possibleLoginHandlers.containsKey(activeAuthnMethod.getAuthenticationMethod())) {
+ completeAuthenticationWithActiveMethod(loginContext, activeAuthnMethod, httpRequest,
+ httpResponse);
+ return;
+ }
+ }
+ LOG.debug("No existing authentication method meets service provides requirements");
+ }
+
+ if (loginContext.isPassiveAuthRequired()) {
+ LOG.debug("Passive authentication is required, filtering poassibl login handlers accordingly.");
+ filterByPassiveAuthentication(loginContext, possibleLoginHandlers);
+ }
+
+ // Since we made it this far, just pick the first remaining login handler from the list
+ Entry<String, LoginHandler> chosenLoginHandler = possibleLoginHandlers.entrySet().iterator().next();
+ LOG.debug("Authenticating user with login handler of type {}", chosenLoginHandler.getValue().getClass()
+ .getName());
+ authenticateUser(chosenLoginHandler.getKey(), chosenLoginHandler.getValue(), loginContext, httpRequest,
+ httpResponse);
+ } catch (AuthenticationException e) {
+ loginContext.setAuthenticationFailure(e);
+ returnToProfileHandler(loginContext, httpRequest, httpResponse);
}
- return;
}
/**
- * Completes the authentication request using an existing, active, authentication method for the current user.
+ * Determines which configured login handlers will support the requested authentication methods.
+ *
+ * @param loginContext current login context
+ *
+ * @return login methods that may be used to authenticate the user
+ *
+ * @throws AuthenticationException thrown if no login handler meets the given requirements
+ */
+ protected Map<String, LoginHandler> determinePossibleLoginHandlers(LoginContext loginContext)
+ throws AuthenticationException {
+ Map<String, LoginHandler> supportedLoginHandlers = new HashMap<String, LoginHandler>(handlerManager
+ .getLoginHandlers());
+ LOG.trace("Supported login handlers: {}", supportedLoginHandlers);
+ LOG.trace("Requested authentication methods: {}", loginContext.getRequestedAuthenticationMethods());
+
+ Iterator<Entry<String, LoginHandler>> supportedLoginHandlerItr = supportedLoginHandlers.entrySet().iterator();
+ Entry<String, LoginHandler> supportedLoginHandler;
+ while (supportedLoginHandlerItr.hasNext()) {
+ supportedLoginHandler = supportedLoginHandlerItr.next();
+ if (!loginContext.getRequestedAuthenticationMethods().contains(supportedLoginHandler.getKey())) {
+ supportedLoginHandlerItr.remove();
+ continue;
+ }
+ }
+
+ if (supportedLoginHandlers.isEmpty()) {
+ LOG.error("No authentication method, requested by the service provider, is supported");
+ throw new AuthenticationException(
+ "No authentication method, requested by the service provider, is supported");
+ }
+
+ return supportedLoginHandlers;
+ }
+
+ /**
+ * Filters out any login handler based on the requirement for forced authentication.
+ *
+ * During forced authentication any handler that has not previously been used to authenticate the the user or any
+ * handlers that have been and support force re-authentication may be used. Filter out any of the other ones.
+ *
+ * @param loginContext current login context
+ * @param activeAuthnMethods currently active authentication methods, never null
+ * @param loginHandlers login handlers to filter
+ *
+ * @throws ForceAuthenticationException thrown if no handlers remain after filtering
+ */
+ protected void filterByForceAuthentication(LoginContext loginContext,
+ Collection<AuthenticationMethodInformation> activeAuthnMethods, Map<String, LoginHandler> loginHandlers)
+ throws ForceAuthenticationException {
+
+ LoginHandler loginHandler;
+ for (AuthenticationMethodInformation activeAuthnMethod : activeAuthnMethods) {
+ loginHandler = loginHandlers.get(activeAuthnMethod.getAuthenticationMethod());
+ if (loginHandler != null && !loginHandler.supportsForceAuthentication()) {
+ for (String handlerSupportedMethods : loginHandler.getSupportedAuthenticationMethods()) {
+ loginHandlers.remove(handlerSupportedMethods);
+ }
+ }
+ }
+
+ if (loginHandlers.isEmpty()) {
+ LOG.error("Force authentication required but no login handlers available to support it");
+ throw new ForceAuthenticationException();
+ }
+ }
+
+ /**
+ * Filters out any login handler that doesn't support passive authentication if the login context indicates passive
+ * authentication is required.
+ *
+ * @param loginContext current login context
+ * @param loginHandlers login handlers to filter
+ *
+ * @throws PassiveAuthenticationException thrown if no handlers remain after filtering
+ */
+ protected void filterByPassiveAuthentication(LoginContext loginContext, Map<String, LoginHandler> loginHandlers)
+ throws PassiveAuthenticationException {
+ LoginHandler loginHandler;
+ Iterator<Entry<String, LoginHandler>> authnMethodItr = loginHandlers.entrySet().iterator();
+ while (authnMethodItr.hasNext()) {
+ loginHandler = authnMethodItr.next().getValue();
+ if (!loginHandler.supportsPassive()) {
+ authnMethodItr.remove();
+ }
+ }
+
+ if (loginHandlers.isEmpty()) {
+ LOG.error("Passive authentication required but no login handlers available to support it");
+ throw new PassiveAuthenticationException();
+ }
+ }
+
+ /**
+ * Authenticates the user with the given authentication method provided by the given login handler.
*
+ * @param authnMethod the authentication method that will be used to authenticate the user
+ * @param logingHandler login handler that will authenticate user
+ * @param loginContext current login context
* @param httpRequest current HTTP request
* @param httpResponse current HTTP response
- * @param authenticationMethod authentication method to use to complete the request
*/
- protected void authenticateUserWithActiveMethod(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
- AuthenticationMethodInformation authenticationMethod) {
- HttpSession httpSession = httpRequest.getSession();
+ protected void authenticateUser(String authnMethod, LoginHandler logingHandler, LoginContext loginContext,
+ HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+ loginContext.setAuthenticationAttempted();
+ loginContext.setAuthenticationDuration(logingHandler.getAuthenticationDuration());
+ loginContext.setAuthenticationMethod(authnMethod);
+ loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
+ httpRequest.getSession().setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginContext);
+ logingHandler.login(httpRequest, httpResponse);
+ }
+
+ /**
+ * Completes the authentication request using an existing, active, authentication method for the current user.
+ *
+ * @param loginContext current login context
+ * @param authenticationMethod authentication method to use to complete the request
+ * @param httpRequest current HTTP request
+ * @param httpResponse current HTTP response
+ */
+ protected void completeAuthenticationWithActiveMethod(LoginContext loginContext,
+ AuthenticationMethodInformation authenticationMethod, HttpServletRequest httpRequest,
+ HttpServletResponse httpResponse) {
Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
- LOG.debug("Populating login context with existing session and authentication method information.");
- LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
loginContext.setAuthenticationDuration(authenticationMethod.getAuthenticationDuration());
loginContext.setAuthenticationInstant(authenticationMethod.getAuthenticationInstant());
loginContext.setAuthenticationMethod(authenticationMethod.getAuthenticationMethod());
authenticationMethod);
shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
+ LOG.debug("Treating user {} as authenticated via existing method {}", loginContext.getPrincipalName(),
+ loginContext.getAuthenticationMethod());
returnToProfileHandler(loginContext, httpRequest, httpResponse);
}
/**
- * Performs the first part of user authentication. An authentication handler is determined, the login context is
- * populated with some initial information, and control is forward to the selected handler so that it may
- * authenticate the user.
+ * Completes the authentication process when and already active authentication mechanism wasn't used, that is, when
+ * the user was really authenticated.
*
+ * The principal name set by the authentication handler is retrieved and pushed in to the login context, a
+ * Shibboleth session is created if needed, information indicating that the user has logged into the service is
+ * recorded and finally control is returned back to the profile handler.
+ *
+ * @param loginContext current login context
* @param httpRequest current HTTP request
* @param httpResponse current HTTP response
*/
- protected void authenticateUserWithoutActiveMethod1(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
- HttpSession httpSession = httpRequest.getSession();
- LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
- LOG.debug("Selecting appropriate authentication method for request.");
- Pair<String, LoginHandler> handler = getProfileHandlerManager().getAuthenticationHandler(loginContext);
+ protected void completeAuthenticationWithoutActiveMethod(LoginContext loginContext, HttpServletRequest httpRequest,
+ HttpServletResponse httpResponse) {
- if (handler == null) {
+ String principalName = DatatypeHelper.safeTrimOrNullString((String) httpRequest
+ .getAttribute(LoginHandler.PRINCIPAL_NAME_KEY));
+ if (principalName == null) {
loginContext.setPrincipalAuthenticated(false);
- loginContext.setAuthenticationAttempted();
- loginContext.setAuthenticationFailureMessage("No AuthenticationHandler satisfies the request from: "
- + loginContext.getRelyingPartyId());
- LOG.error("No AuthenticationHandler satisfies the request from relying party: "
- + loginContext.getRelyingPartyId());
+ loginContext.setAuthenticationFailure(new AuthenticationException(
+ "No principal name returned from authentication handler."));
+ LOG.error("No principal name returned from authentication method: "
+ + loginContext.getAuthenticationMethod());
returnToProfileHandler(loginContext, httpRequest, httpResponse);
return;
}
- LOG.debug("Authentication method {} will be used to authenticate user.", handler.getFirst());
- loginContext.setAuthenticationAttempted();
- loginContext.setAuthenticationDuration(handler.getSecond().getAuthenticationDuration());
- loginContext.setAuthenticationMethod(handler.getFirst());
- loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
+ loginContext.setPrincipalAuthenticated(true);
+ loginContext.setPrincipalName(principalName);
+ loginContext.setAuthenticationInstant(new DateTime());
- LOG.debug("Transferring control to authentication handler of type: {}", handler.getSecond().getClass()
- .getName());
- handler.getSecond().login(httpRequest, httpResponse);
+ // We allow a login handler to override the authentication method in the event that it supports multiple methods
+ String actualAuthnMethod = DatatypeHelper.safeTrimOrNullString((String) httpRequest
+ .getAttribute(LoginHandler.AUTHENTICATION_METHOD_KEY));
+ if (actualAuthnMethod != null) {
+ loginContext.setAuthenticationMethod(actualAuthnMethod);
+ }
+
+ LOG.debug("User {} authenticated with method {}", loginContext.getPrincipalName(), loginContext
+ .getAuthenticationMethod());
+ updateUserSession(loginContext, httpRequest, httpResponse);
+ returnToProfileHandler(loginContext, httpRequest, httpResponse);
}
/**
- * Performs the second part of user authentication. The principal name set by the authentication handler is
- * retrieved and pushed in to the login context, a Shibboleth session is created if needed, information indicating
- * that the user has logged into the service is recorded and finally control is returned back to the profile
- * handler.
+ * Updates the user's Shibboleth session with authentication information. If no session exists a new one will be
+ * created.
*
+ * @param loginContext current login context
* @param httpRequest current HTTP request
* @param httpResponse current HTTP response
*/
- protected void authenticateUserWithoutActiveMethod2(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
- HttpSession httpSession = httpRequest.getSession();
-
- String principalName = (String) httpRequest.getAttribute(LoginHandler.PRINCIPAL_NAME_KEY);
- LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
- if (DatatypeHelper.isEmpty(principalName)) {
- loginContext.setPrincipalAuthenticated(false);
- loginContext.setAuthenticationFailureMessage("No principal name returned from authentication handler.");
- LOG.error("No principal name returned from authentication method: "
- + loginContext.getAuthenticationMethod());
- returnToProfileHandler(loginContext, httpRequest, httpResponse);
- return;
- }
- loginContext.setPrincipalAuthenticated(true);
- loginContext.setPrincipalName(principalName);
- loginContext.setAuthenticationInstant(new DateTime());
-
+ protected void updateUserSession(LoginContext loginContext, HttpServletRequest httpRequest,
+ HttpServletResponse httpResponse) {
Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
if (shibSession == null) {
- LOG.debug("Creating shibboleth session for principal {}", principalName);
- shibSession = (Session) getSessionManager().createSession(loginContext.getPrincipalName());
+ LOG.debug("Creating shibboleth session for principal {}", loginContext.getPrincipalName());
+ shibSession = (Session) sessionManager.createSession(loginContext.getPrincipalName());
loginContext.setSessionID(shibSession.getSessionID());
addSessionCookie(httpRequest, httpResponse, shibSession);
}
LOG.debug("Recording authentication and service information in Shibboleth session for principal: {}",
- principalName);
+ loginContext.getPrincipalName());
Subject subject = (Subject) httpRequest.getAttribute(LoginHandler.SUBJECT_KEY);
String authnMethod = (String) httpRequest.getAttribute(LoginHandler.AUTHENTICATION_METHOD_KEY);
if (DatatypeHelper.isEmpty(authnMethod)) {
ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
authnMethodInfo);
shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
-
- returnToProfileHandler(loginContext, httpRequest, httpResponse);
- }
-
- /**
- * Gets the authentication method, currently active for the user, that also meets the requirements expressed by the
- * login context. If a method is returned the user does not need to authenticate again, if null is returned then the
- * user must be authenticated.
- *
- * @param loginContext user login context
- * @param shibSession user's shibboleth session
- *
- * @return active authentication method that meets authentication requirements or null
- */
- protected AuthenticationMethodInformation getUsableExistingAuthenticationMethod(LoginContext loginContext,
- Session shibSession) {
-
- if (shibSession == null) {
- return null;
- }
-
- if (loginContext.getForceAuth()) {
- LOG.debug("Request for forced re-authentication, no existing authentication method considered usable");
- return null;
- }
-
- List<String> preferredAuthnMethods = loginContext.getRequestedAuthenticationMethods();
- AuthenticationMethodInformation authnMethodInformation = null;
- if (preferredAuthnMethods == null || preferredAuthnMethods.size() == 0) {
- for (AuthenticationMethodInformation info : shibSession.getAuthenticationMethods().values()) {
- if (!info.isExpired()) {
- authnMethodInformation = info;
- break;
- }
- }
- } else {
- for (String preferredAuthnMethod : preferredAuthnMethods) {
- if (shibSession.getAuthenticationMethods().containsKey(preferredAuthnMethod)) {
- AuthenticationMethodInformation info = shibSession.getAuthenticationMethods().get(
- preferredAuthnMethod);
- if (!info.isExpired()) {
- authnMethodInformation = info;
- break;
- }
- }
- }
- }
-
- return authnMethodInformation;
}
/**
Cookie sessionCookie = new Cookie(IDP_SESSION_COOKIE_NAME, userSession.getSessionID());
sessionCookie.setPath(httpRequest.getContextPath());
sessionCookie.setSecure(false);
-
- int maxAge = (int) (userSession.getInactivityTimeout() / 1000);
- sessionCookie.setMaxAge(maxAge);
+ sessionCookie.setMaxAge(-1);
httpResponse.addCookie(sessionCookie);
}