Forced authentication does not reset the AuthnInstant
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / authn / AuthenticationEngine.java
index 9318a68..4bed051 100644 (file)
@@ -41,7 +41,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.joda.time.DateTime;
-import org.joda.time.chrono.ISOChronology;
 import org.opensaml.saml2.core.AuthnContext;
 import org.opensaml.util.URLBuilder;
 import org.opensaml.util.storage.StorageService;
@@ -50,7 +49,6 @@ import org.opensaml.xml.util.Base64;
 import org.opensaml.xml.util.DatatypeHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.slf4j.helpers.MessageFormatter;
 
 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
 import edu.internet2.middleware.shibboleth.common.util.HttpHelper;
@@ -136,8 +134,7 @@ public class AuthenticationEngine extends HttpServlet {
         storageService = (StorageService<String, LoginContextEntry>) HttpServletHelper.getStorageService(context);
     }
 
-    /**
-     * Returns control back to the authentication engine.
+    /* Returns control back to the authentication engine.
      * 
      * @param httpRequest current HTTP request
      * @param httpResponse current HTTP response
@@ -150,15 +147,6 @@ public class AuthenticationEngine extends HttpServlet {
             forwardRequest("/error.jsp", httpRequest, httpResponse);
         } else {
             forwardRequest(loginContext.getAuthenticationEngineURL(), httpRequest, httpResponse);
-            // URLBuilder urlBuilder = HttpServletHelper.getServletContextUrl(httpRequest);
-            // urlBuilder.setPath(urlBuilder.getPath() + loginContext.getAuthenticationEngineURL());
-            // String authnEngineUrl = urlBuilder.buildURL();
-            // LOG.debug("Redirecting user to authentication engine at {}", authnEngineUrl);
-            // try{
-            // httpResponse.sendRedirect(authnEngineUrl);
-            // }catch(IOException e){
-            // LOG.warn("Error sending user back to authentication engine at " + authnEngineUrl, e);
-            // }
         }
     }
 
@@ -176,9 +164,8 @@ public class AuthenticationEngine extends HttpServlet {
             forwardRequest("/error.jsp", httpRequest, httpResponse);
         }
 
-        URLBuilder urlBuilder = HttpServletHelper.getServletContextUrl(httpRequest);
-        urlBuilder.setPath(urlBuilder.getPath() + loginContext.getProfileHandlerURL());
-        String profileUrl = urlBuilder.buildURL();
+        String profileUrl = HttpServletHelper.getContextRelativeUrl(httpRequest, loginContext.getProfileHandlerURL())
+                .buildURL();
         LOG.debug("Redirecting user to profile handler at {}", profileUrl);
         try {
             httpResponse.sendRedirect(profileUrl);
@@ -285,8 +272,8 @@ public class AuthenticationEngine extends HttpServlet {
      */
     protected Map<String, LoginHandler> determinePossibleLoginHandlers(Session idpSession, LoginContext loginContext)
             throws AuthenticationException {
-        Map<String, LoginHandler> supportedLoginHandlers = new HashMap<String, LoginHandler>(handlerManager
-                .getLoginHandlers());
+        Map<String, LoginHandler> supportedLoginHandlers = new HashMap<String, LoginHandler>(
+                handlerManager.getLoginHandlers());
         LOG.debug("Filtering configured LoginHandlers: {}", supportedLoginHandlers);
 
         // First, if the service provider requested a particular authentication method, filter out everything but
@@ -300,8 +287,9 @@ public class AuthenticationEngine extends HttpServlet {
                 supportedLoginHandlerEntry = supportedLoginHandlerItr.next();
                 if (!supportedLoginHandlerEntry.getKey().equals(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX)
                         && !requestedMethods.contains(supportedLoginHandlerEntry.getKey())) {
-                    LOG.debug("Filtering out login handler for authentication {}, it does not provide a requested authentication method",
-                                    supportedLoginHandlerEntry.getKey());
+                    LOG.debug(
+                            "Filtering out login handler for authentication {}, it does not provide a requested authentication method",
+                            supportedLoginHandlerEntry.getKey());
                     supportedLoginHandlerItr.remove();
                 }
             }
@@ -320,20 +308,20 @@ public class AuthenticationEngine extends HttpServlet {
     }
 
     /**
-     * Filters out the previous session login handler if there is no existing IdP session, no active authentication 
-     * methods, or if at least one of the active authentication methods do not match the requested authentication 
+     * Filters out the previous session login handler if there is no existing IdP session, no active authentication
+     * methods, or if at least one of the active authentication methods do not match the requested authentication
      * methods.
      * 
      * @param supportedLoginHandlers login handlers supported by the authentication engine for this request, never null
      * @param idpSession current IdP session, may be null if no session currently exists
      * @param loginContext current login context, never null
      */
-    protected void filterPreviousSessionLoginHandler(Map<String, LoginHandler> supportedLoginHandlers, 
+    protected void filterPreviousSessionLoginHandler(Map<String, LoginHandler> supportedLoginHandlers,
             Session idpSession, LoginContext loginContext) {
-        if(!supportedLoginHandlers.containsKey(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX)){
+        if (!supportedLoginHandlers.containsKey(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX)) {
             return;
         }
-        
+
         if (idpSession == null) {
             LOG.debug("Filtering out previous session login handler because there is no existing IdP session");
             supportedLoginHandlers.remove(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
@@ -402,7 +390,8 @@ public class AuthenticationEngine extends HttpServlet {
             loginHandler = loginHandlers.get(activeMethod.getAuthenticationMethod());
             if (loginHandler != null && !loginHandler.supportsForceAuthentication()) {
                 for (String handlerSupportedMethods : loginHandler.getSupportedAuthenticationMethods()) {
-                    LOG.debug("Removing LoginHandler {}, it does not support forced re-authentication", loginHandler.getClass().getName());
+                    LOG.debug("Removing LoginHandler {}, it does not support forced re-authentication", loginHandler
+                            .getClass().getName());
                     loginHandlers.remove(handlerSupportedMethods);
                 }
             }
@@ -453,7 +442,6 @@ public class AuthenticationEngine extends HttpServlet {
         }
     }
 
-
     /**
      * Selects a login handler from a list of possible login handlers that could be used for the request.
      * 
@@ -481,20 +469,14 @@ public class AuthenticationEngine extends HttpServlet {
                 if (loginContext.getRequestedAuthenticationMethods().isEmpty()
                         || loginContext.getRequestedAuthenticationMethods().contains(
                                 authnMethod.getAuthenticationMethod())) {
-                    LOG.debug("Basing previous session authentication on active authentication method {}", authnMethod
-                            .getAuthenticationMethod());
+                    LOG.debug("Basing previous session authentication on active authentication method {}",
+                            authnMethod.getAuthenticationMethod());
                     loginContext.setAttemptedAuthnMethod(authnMethod.getAuthenticationMethod());
                     loginContext.setAuthenticationMethodInformation(authnMethod);
                     return loginHandler;
                 }
             }
         }
-//            possibleLoginHandlers.remove(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
-//            if (possibleLoginHandlers.isEmpty()) {
-//                LOG.info("No authentication mechanism available for use with relying party '{}'", loginContext
-//                        .getRelyingPartyId());
-//                throw new AuthenticationException();
-//            }
 
         if (loginContext.getDefaultAuthenticationMethod() != null
                 && possibleLoginHandlers.containsKey(loginContext.getDefaultAuthenticationMethod())) {
@@ -535,31 +517,38 @@ public class AuthenticationEngine extends HttpServlet {
             if (actualAuthnMethod != null) {
                 if (!loginContext.getRequestedAuthenticationMethods().isEmpty()
                         && !loginContext.getRequestedAuthenticationMethods().contains(actualAuthnMethod)) {
-                    String msg = MessageFormatter
-                            .format(
-                                    "Relying patry required an authentication method of '{}' but the login handler performed '{}'",
-                                    loginContext.getRequestedAuthenticationMethods(), actualAuthnMethod);
+                    String msg = "Relying patry required an authentication method of "
+                            + loginContext.getRequestedAuthenticationMethods() + " but the login handler performed "
+                            + actualAuthnMethod;
                     LOG.error(msg);
                     throw new AuthenticationException(msg);
                 }
             } else {
                 actualAuthnMethod = loginContext.getAttemptedAuthnMethod();
             }
-
+            
             // Check to make sure the login handler did the right thing
             validateSuccessfulAuthentication(loginContext, httpRequest, actualAuthnMethod);
 
+            // Check for an overridden authn instant.
+            DateTime actualAuthnInstant = (DateTime) httpRequest.getAttribute(LoginHandler.AUTHENTICATION_INSTANT_KEY);
+
             // Get the Subject from the request. If force authentication was required then make sure the
             // Subject identifies the same user that authenticated before
             Subject subject = getLoginHandlerSubject(httpRequest);
             if (loginContext.isForceAuthRequired()) {
                 validateForcedReauthentication(idpSession, actualAuthnMethod, subject);
+                
+                // Reset the authn instant.
+                if (actualAuthnInstant == null) {
+                    actualAuthnInstant = new DateTime();
+                }
             }
 
             loginContext.setPrincipalAuthenticated(true);
-            updateUserSession(loginContext, subject, actualAuthnMethod, httpRequest, httpResponse);
-            LOG.debug("User {} authenticated with method {}", loginContext.getPrincipalName(), loginContext
-                    .getAuthenticationMethod());
+            updateUserSession(loginContext, subject, actualAuthnMethod, actualAuthnInstant, httpRequest, httpResponse);
+            LOG.debug("User {} authenticated with method {}", loginContext.getPrincipalName(),
+                    loginContext.getAuthenticationMethod());
         } catch (AuthenticationException e) {
             LOG.error("Authentication failed with the error:", e);
             loginContext.setPrincipalAuthenticated(false);
@@ -593,8 +582,8 @@ public class AuthenticationEngine extends HttpServlet {
         String errorMessage = DatatypeHelper.safeTrimOrNullString((String) httpRequest
                 .getAttribute(LoginHandler.AUTHENTICATION_ERROR_KEY));
         if (errorMessage != null) {
-            LOG.error("Error returned from login handler for authentication method {}:\n{}", loginContext
-                    .getAttemptedAuthnMethod(), errorMessage);
+            LOG.error("Error returned from login handler for authentication method {}:\n{}",
+                    loginContext.getAttemptedAuthnMethod(), errorMessage);
             throw new AuthenticationException(errorMessage);
         }
 
@@ -680,11 +669,13 @@ public class AuthenticationEngine extends HttpServlet {
      * @param loginContext current login context
      * @param authenticationSubject subject created from the authentication method
      * @param authenticationMethod the method used to authenticate the subject
+     * @param authenticationInstant the time of authentication
      * @param httpRequest current HTTP request
      * @param httpResponse current HTTP response
      */
     protected void updateUserSession(LoginContext loginContext, Subject authenticationSubject,
-            String authenticationMethod, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+            String authenticationMethod, DateTime authenticationInstant,
+            HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
         Principal authenticationPrincipal = authenticationSubject.getPrincipals().iterator().next();
         LOG.debug("Updating session information for principal {}", authenticationPrincipal.getName());
 
@@ -700,14 +691,21 @@ public class AuthenticationEngine extends HttpServlet {
         // login handler subject
         idpSession.setSubject(mergeSubjects(idpSession.getSubject(), authenticationSubject));
 
-        // Check if an existing authentication method was used (i.e. SSO occurred), if not record the new information
-        AuthenticationMethodInformation authnMethodInfo = loginContext.getAuthenticationMethodInformation();
-        if (authnMethodInfo == null || !authnMethodInfo.getAuthenticationMethod().equals(authenticationMethod)) {
+        // Check if an existing authentication method with no updated timestamp was used (i.e. SSO occurred);
+        // if not record the new information
+        AuthenticationMethodInformation authnMethodInfo = idpSession.getAuthenticationMethods().get(
+                authenticationMethod);
+        if (authnMethodInfo == null || authenticationInstant != null) {
             LOG.debug("Recording authentication and service information in Shibboleth session for principal: {}",
                     authenticationPrincipal.getName());
             LoginHandler loginHandler = handlerManager.getLoginHandlers().get(loginContext.getAttemptedAuthnMethod());
-            authnMethodInfo = new AuthenticationMethodInformationImpl(idpSession.getSubject(), authenticationPrincipal,
-                    authenticationMethod, new DateTime(), loginHandler.getAuthenticationDuration());
+            authnMethodInfo = new AuthenticationMethodInformationImpl(
+                    idpSession.getSubject(),
+                    authenticationPrincipal,
+                    authenticationMethod,
+                    (authenticationInstant != null ? authenticationInstant : new DateTime()),
+                    loginHandler.getAuthenticationDuration()
+                    );
         }
 
         loginContext.setAuthenticationMethodInformation(authnMethodInfo);