Rework authentication and session management code:
authorlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sun, 31 Aug 2008 08:40:56 +0000 (08:40 +0000)
committerlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sun, 31 Aug 2008 08:40:56 +0000 (08:40 +0000)
  - better tracking of JAAS Subject related properties (principals, public, and priviate credentials)
  - better tracking for which authentication method is used for which principal
  - no more reliance on the serlvet container session during any step of the authentication process
  - raise an error when, during force authn, the authenticated principal is not the same as the previously authenticated principal - SIDP - 196

git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/branches/REL_2@2754 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

23 files changed:
doc/RELEASE_NOTES.txt
src/installer/resources/conf-tmpl/service.xml
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/AuthenticationEngine.java
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/LoginContext.java
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/LoginHandler.java
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/Saml2LoginContext.java
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/UsernamePrincipal.java [new file with mode: 0644]
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/provider/IPAddressLoginHandler.java
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/provider/PreviousSessionLoginHandler.java
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/provider/UsernamePasswordCredential.java [new file with mode: 0644]
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/provider/UsernamePasswordLoginHandler.java
src/main/java/edu/internet2/middleware/shibboleth/idp/authn/provider/UsernamePasswordLoginServlet.java
src/main/java/edu/internet2/middleware/shibboleth/idp/config/profile/authn/PreviousSessionLoginHandlerBeanDefinitionParser.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/AuthenticationMethodInformation.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/ContainerSessionListener.java [deleted file]
src/main/java/edu/internet2/middleware/shibboleth/idp/session/IdPSessionFilter.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/ServiceInformation.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/Session.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/impl/AuthenticationMethodInformationImpl.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/impl/ServiceInformationImpl.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/impl/SessionImpl.java
src/main/java/edu/internet2/middleware/shibboleth/idp/session/impl/SessionManagerImpl.java
src/main/webapp/WEB-INF/web.xml

index 34938bc..a3d71b4 100644 (file)
@@ -1,6 +1,10 @@
 Changes in Release 2.1.0
 =============================================
-[SIDP-20] - Cannot deploy on Windows. Sping and DOS device names?
+NOTE, this release requires a small change to the service.xml file, if you are upgrading from 2.0.0.
+In the last service, shibboleth.ServiceServletContextAttributeExporter, you MUST add the service 
+'shibboleth.StorageService' to the whitespace delimited list of services already present.
+
+[SIDP-20] - Cannot deploy on Windows. Spring and DOS device names?
 [SIDP-164] - Option to make session cookie secure
 [SIDP-165] - Support for SessionNotOnOrAfter
 [SIDP-167] - Missing tags and incomplete login.jsp
@@ -14,6 +18,7 @@ Changes in Release 2.1.0
 [SIDP-185] - NullPointerException after AttributeQuery when Security Rule fails
 [SIDP-189] - NPE in AbstractSAML2ProfileHandler
 [SIDP-194] - Installer can remember the wrong thing
+[SIDP-196] - IdP continues to use old principal name after forced reauthentication
 [SIDP-197] - Misleading error message for ValidationInfo element in relying-party.xml
 [SIDP-199] - loss of login context when deploying the IdP to tomcat's ROOT context
 [SIDP-201] - IdP sends SAML 1 authentication responses without audience conditions
index fdbd3c9..2c58c51 100644 (file)
@@ -57,6 +57,7 @@
     <Service id="shibboleth.ServiceServletContextAttributeExporter"
              depends-on="shibboleth.AttributeResolver shibboleth.AttributeFilterEngine
                          shibboleth.SAML1AttributeAuthority shibboleth.SAML2AttributeAuthority 
-                         shibboleth.RelyingPartyConfigurationManager shibboleth.HandlerManager"
+                         shibboleth.RelyingPartyConfigurationManager shibboleth.HandlerManager
+                         shibboleth.StorageService"
              xsi:type="ServletContextAttributeExporter" />
 </Services>
\ No newline at end of file
index 2be4144..6c944ba 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 package edu.internet2.middleware.shibboleth.idp.authn;
 
 import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.Map.Entry;
 
 import javax.security.auth.Subject;
@@ -31,17 +35,19 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
 
 import org.joda.time.DateTime;
+import org.opensaml.common.IdentifierGenerator;
+import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
 import org.opensaml.saml2.core.AuthnContext;
+import org.opensaml.util.storage.ExpiringObject;
+import org.opensaml.util.storage.StorageService;
 import org.opensaml.xml.util.DatatypeHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
 import edu.internet2.middleware.shibboleth.common.util.HttpHelper;
-import edu.internet2.middleware.shibboleth.idp.authn.provider.PreviousSessionLoginHandler;
 import edu.internet2.middleware.shibboleth.idp.profile.IdPProfileHandlerManager;
 import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
@@ -49,20 +55,38 @@ import edu.internet2.middleware.shibboleth.idp.session.Session;
 import edu.internet2.middleware.shibboleth.idp.session.impl.AuthenticationMethodInformationImpl;
 import edu.internet2.middleware.shibboleth.idp.session.impl.ServiceInformationImpl;
 
-/**
- * Manager responsible for handling authentication requests.
- */
+/** Manager responsible for handling authentication requests. */
 public class AuthenticationEngine extends HttpServlet {
 
+    /** Name of the Servlet config init parameter that holds the partition name for login contexts. */
+    public static final String LOGIN_CONTEXT_PARTITION_NAME_INIT_PARAM_NAME = "loginContextPartitionName";
+
+    /** Name of the Servlet config init parameter that holds lifetime of a login context in the storage service. */
+    public static final String LOGIN_CONTEXT_LIFETIME_INIT_PARAM_NAME = "loginContextEntryLifetime";
+
     /** Name of the IdP Cookie containing the IdP session ID. */
     public static final String IDP_SESSION_COOKIE_NAME = "_idp_session";
 
+    /** Name of the key under which to bind the storage service key for a login context. */
+    public static final String LOGIN_CONTEXT_KEY_NAME = "_idp_authn_lc_key";
+
     /** Serial version UID. */
-    private static final long serialVersionUID = 8494202791991613148L;
 
     /** Class logger. */
     private static final Logger LOG = LoggerFactory.getLogger(AuthenticationEngine.class);
 
+    /** Storage service used to store {@link LoginContext}s while authentication is in progress. */
+    private static StorageService<String, LoginContextEntry> storageService;
+
+    /** Name of the storage service partition used to store login contexts. */
+    private static String loginContextPartitionName;
+
+    /** Lifetime of stored login contexts. */
+    private static long loginContextEntryLifetime;
+
+    /** ID generator. */
+    private static IdentifierGenerator idGen;
+
     /** Profile handler manager. */
     private IdPProfileHandlerManager handlerManager;
 
@@ -83,22 +107,103 @@ public class AuthenticationEngine extends HttpServlet {
         if (DatatypeHelper.isEmpty(sessionManagerId)) {
             sessionManagerId = "shibboleth.SessionManager";
         }
-
         sessionManager = (SessionManager<Session>) getServletContext().getAttribute(sessionManagerId);
+
+        String storageServiceId = config.getInitParameter("storageServiceId");
+        if (DatatypeHelper.isEmpty(storageServiceId)) {
+            storageServiceId = "shibboleth.StorageService";
+        }
+        storageService = (StorageService<String, LoginContextEntry>) getServletContext().getAttribute(storageServiceId);
+
+        String partitionName = DatatypeHelper.safeTrimOrNullString(config
+                .getInitParameter(LOGIN_CONTEXT_PARTITION_NAME_INIT_PARAM_NAME));
+        if (partitionName != null) {
+            loginContextPartitionName = partitionName;
+        } else {
+            loginContextPartitionName = "loginContexts";
+        }
+
+        String lifetime = DatatypeHelper.safeTrimOrNullString(config
+                .getInitParameter(LOGIN_CONTEXT_LIFETIME_INIT_PARAM_NAME));
+        if (lifetime != null) {
+            loginContextEntryLifetime = Long.parseLong(lifetime);
+        } else {
+            loginContextEntryLifetime = 1000 * 60 * 30;
+        }
+
+        try {
+            idGen = new SecureRandomIdentifierGenerator();
+        } catch (NoSuchAlgorithmException e) {
+            throw new ServletException("Error create random number generator", e);
+        }
+    }
+
+    /**
+     * Retrieves a login context.
+     * 
+     * @param httpRequest current HTTP request
+     * @param removeFromStorageService whether the login context should be removed from the storage service as it is
+     *            retrieved
+     * 
+     * @return the login context or null if one is not available (e.g. because it has expired)
+     */
+    protected static LoginContext retrieveLoginContext(HttpServletRequest httpRequest, boolean removeFromStorageService) {
+        // When the login context comes from the profile handlers its attached to the request
+        // Prior to the authentication engine handing control over to a login handler it stores
+        // the login context into the storage service so that the login handlers do not have to
+        // maintain a reference to the context and return it to the engine.
+        LoginContext loginContext = (LoginContext) httpRequest.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+        if (loginContext != null) {
+            LOG.trace("Login context retrieved from HTTP request attribute");
+            return loginContext;
+        }
+
+        String contextId = DatatypeHelper.safeTrimOrNullString((String) httpRequest
+                .getAttribute(LOGIN_CONTEXT_KEY_NAME));
+
+        if (contextId == null) {
+            Cookie[] requestCookies = httpRequest.getCookies();
+            if (requestCookies != null) {
+                for (Cookie requestCookie : requestCookies) {
+                    if (DatatypeHelper.safeEquals(requestCookie.getName(), LOGIN_CONTEXT_KEY_NAME)) {
+                        LOG.trace("Located cookie with login context key");
+                        contextId = requestCookie.getValue();
+                        break;
+                    }
+                }
+            }
+        }
+
+        LOG.trace("Using login context key {} to look up login context", contextId);
+        LoginContextEntry entry;
+        if (removeFromStorageService) {
+            entry = storageService.remove(loginContextPartitionName, contextId);
+        } else {
+            entry = storageService.get(loginContextPartitionName, contextId);
+        }
+        if (entry == null) {
+            LOG.trace("No entry for login context found in storage service.");
+            return null;
+        } else if (entry.isExpired()) {
+            LOG.trace("Login context entry found in storage service but it was expired.");
+            return null;
+        } else {
+            LOG.trace("Login context entry found in storage service.");
+            return entry.getLoginContext();
+        }
     }
 
     /**
      * Returns control back to the authentication engine.
      * 
-     * @param httpRequest current http request
-     * @param httpResponse current http response
+     * @param httpRequest current HTTP request
+     * @param httpResponse current HTTP response
      */
     public static void returnToAuthenticationEngine(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
         LOG.debug("Returning control to authentication engine");
-        HttpSession httpSession = httpRequest.getSession();
-        LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+        LoginContext loginContext = retrieveLoginContext(httpRequest, false);
         if (loginContext == null) {
-            LOG.error("User HttpSession did not contain a login context.  Unable to return to authentication engine");
+            LOG.error("No login context available, unable to return to authentication engine");
             forwardRequest("/idp-error.jsp", httpRequest, httpResponse);
         } else {
             forwardRequest(loginContext.getAuthenticationEngineURL(), httpRequest, httpResponse);
@@ -109,13 +214,12 @@ public class AuthenticationEngine extends HttpServlet {
      * Returns control back to the profile handler that invoked the authentication engine.
      * 
      * @param loginContext current login context
-     * @param httpRequest current http request
-     * @param httpResponse current http response
+     * @param httpRequest current HTTP request
+     * @param httpResponse current HTTP response
      */
     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);
     }
@@ -150,14 +254,7 @@ public class AuthenticationEngine extends HttpServlet {
             LOG.error("HTTP Response already committed");
         }
 
-        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);
-        }
-
+        LoginContext loginContext = retrieveLoginContext(httpRequest, true);
         if (loginContext == null) {
             LOG.error("Incoming request does not have attached login context");
             throw new ServletException("Incoming request does not have attached login context");
@@ -203,14 +300,23 @@ public class AuthenticationEngine extends HttpServlet {
             // If the user already has a session and its usage is acceptable than use it
             // otherwise just use the first candidate login handler
             LOG.debug("Possible authentication handlers after filtering: {}", possibleLoginHandlers);
+            LoginHandler loginHandler;
             if (idpSession != null && possibleLoginHandlers.containsKey(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX)) {
-                authenticateUserWithPreviousSession(loginContext, possibleLoginHandlers, httpRequest, httpResponse);
+                loginContext.setAttemptedAuthnMethod(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
+                loginHandler = possibleLoginHandlers.get(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
             } else {
                 possibleLoginHandlers.remove(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
                 Entry<String, LoginHandler> chosenLoginHandler = possibleLoginHandlers.entrySet().iterator().next();
-                authenticateUser(chosenLoginHandler.getKey(), chosenLoginHandler.getValue(), loginContext, httpRequest,
-                        httpResponse);
+                loginContext.setAttemptedAuthnMethod(chosenLoginHandler.getKey());
+                loginHandler = chosenLoginHandler.getValue();
             }
+
+            // Send the request to the login handler
+            LOG.debug("Authenticating user with login handler of type {}", loginHandler.getClass().getName());
+            loginContext.setAuthenticationAttempted();
+            loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
+            storeLoginContext(loginContext, httpRequest, httpResponse);
+            loginHandler.login(httpRequest, httpResponse);
         } catch (AuthenticationException e) {
             loginContext.setAuthenticationFailure(e);
             returnToProfileHandler(loginContext, httpRequest, httpResponse);
@@ -340,66 +446,32 @@ public class AuthenticationEngine extends HttpServlet {
     }
 
     /**
-     * Completes the authentication request using an existing, active, authentication method for the current user.
+     * Stores the login context in the storage service. The key for the stored login context is then bound to an HTTP
+     * request attribute and set a cookie.
      * 
-     * @param loginContext current login context
-     * @param possibleLoginHandlers login handlers that meet the peers authentication requirements
+     * @param loginContext login context to store
      * @param httpRequest current HTTP request
      * @param httpResponse current HTTP response
      */
-    protected void authenticateUserWithPreviousSession(LoginContext loginContext,
-            Map<String, LoginHandler> possibleLoginHandlers, HttpServletRequest httpRequest,
+    protected void storeLoginContext(LoginContext loginContext, HttpServletRequest httpRequest,
             HttpServletResponse httpResponse) {
-        LOG.debug("Authenticating user by way of existing session.");
+        String contextId = idGen.generateIdentifier();
 
-        Session idpSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
-        PreviousSessionLoginHandler loginHandler = (PreviousSessionLoginHandler) handlerManager.getLoginHandlers().get(
-                AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
-
-        AuthenticationMethodInformation authenticationMethod = null;
-        for (String possibleAuthnMethod : idpSession.getAuthenticationMethods().keySet()) {
-            authenticationMethod = idpSession.getAuthenticationMethods().get(possibleAuthnMethod);
-            if (authenticationMethod != null) {
-                break;
-            }
-        }
+        storageService.put(loginContextPartitionName, contextId, new LoginContextEntry(loginContext,
+                loginContextEntryLifetime));
 
-        if (loginHandler.reportPreviousSessionAuthnMethod()) {
-            loginContext.setAuthenticationDuration(loginHandler.getAuthenticationDuration());
-            loginContext.setAuthenticationInstant(new DateTime());
-            loginContext.setAuthenticationMethod(AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
+        httpRequest.setAttribute(LOGIN_CONTEXT_KEY_NAME, contextId);
+
+        Cookie cookie = new Cookie(LOGIN_CONTEXT_KEY_NAME, contextId);
+        String contextPath = httpRequest.getContextPath();
+        if (DatatypeHelper.isEmpty(contextPath)) {
+            cookie.setPath("/");
         } else {
-            loginContext.setAuthenticationDuration(authenticationMethod.getAuthenticationDuration());
-            loginContext.setAuthenticationInstant(authenticationMethod.getAuthenticationInstant());
-            loginContext.setAuthenticationMethod(authenticationMethod.getAuthenticationMethod());
+            cookie.setPath(contextPath);
         }
-        loginContext.setPrincipalName(idpSession.getPrincipalName());
-
-        loginContext.setAuthenticationAttempted();
-        httpRequest.getSession().setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginContext);
-        loginHandler.login(httpRequest, httpResponse);
-    }
-
-    /**
-     * 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 loginHandler login handler that will authenticate user
-     * @param loginContext current login context
-     * @param httpRequest current HTTP request
-     * @param httpResponse current HTTP response
-     */
-    protected void authenticateUser(String authnMethod, LoginHandler loginHandler, LoginContext loginContext,
-            HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
-        LOG.debug("Authenticating user with login handler of type {}", loginHandler.getClass().getName());
-
-        loginContext.setAuthenticationAttempted();
-        loginContext.setAuthenticationInstant(new DateTime());
-        loginContext.setAuthenticationDuration(loginHandler.getAuthenticationDuration());
-        loginContext.setAuthenticationMethod(authnMethod);
-        loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
-        httpRequest.getSession().setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginContext);
-        loginHandler.login(httpRequest, httpResponse);
+        cookie.setSecure(httpRequest.isSecure());
+        cookie.setMaxAge(-1);
+        httpResponse.addCookie(cookie);
     }
 
     /**
@@ -417,69 +489,166 @@ public class AuthenticationEngine extends HttpServlet {
             HttpServletResponse httpResponse) {
         LOG.debug("Completing user authentication process");
 
-        // We check if the principal name was already set in the login context
-        // if not attempt to pull it from where login handlers are supposed to provide it
-        String principalName = DatatypeHelper.safeTrimOrNullString(loginContext.getPrincipalName());
-        if (principalName == null) {
-            principalName = DatatypeHelper.safeTrimOrNullString((String) httpRequest
-                    .getAttribute(LoginHandler.PRINCIPAL_NAME_KEY));
-            if (principalName != null) {
-                loginContext.setPrincipalName(principalName);
-            } else {
-                loginContext.setPrincipalAuthenticated(false);
-                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;
+        Session idpSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
+
+        try {
+            // Check to make sure the login handler did the right thing
+            validateSuccessfulAuthentication(loginContext, httpRequest);
+
+            // 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) {
+                actualAuthnMethod = loginContext.getAttemptedAuthnMethod();
             }
-        }
-        loginContext.setPrincipalAuthenticated(true);
 
-        // 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);
+            // 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);
+            }
+
+            loginContext.setPrincipalAuthenticated(true);
+            updateUserSession(loginContext, subject, actualAuthnMethod, httpRequest, httpResponse);
+            LOG.debug("User {} authenticated with method {}", loginContext.getPrincipalName(), actualAuthnMethod);
+        } catch (AuthenticationException e) {
+            LOG.error("Authentication failed with the error:", e);
+            loginContext.setPrincipalAuthenticated(false);
+            loginContext.setAuthenticationFailure(e);
         }
 
-        LOG.debug("User {} authenticated with method {}", loginContext.getPrincipalName(), loginContext
-                .getAuthenticationMethod());
-        updateUserSession(loginContext, httpRequest, httpResponse);
         returnToProfileHandler(loginContext, httpRequest, httpResponse);
     }
 
     /**
+     * Validates that the authentication was successfully performed by the login handler. An authentication is
+     * considered successful if no error is bound to the request attribute {@link LoginHandler#AUTHENTICATION_ERROR_KEY}
+     * and there is a value for at least one of the following request attributes: {@link LoginHandler#SUBJECT_KEY},
+     * {@link LoginHandler#PRINCIPAL_KEY}, or {@link LoginHandler#PRINCIPAL_NAME_KEY}.
+     * 
+     * @param loginContext current login context
+     * @param httpRequest current HTTP request
+     * 
+     * @throws AuthenticationException thrown if the authentication was not successful
+     */
+    protected void validateSuccessfulAuthentication(LoginContext loginContext, HttpServletRequest httpRequest)
+            throws AuthenticationException {
+        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);
+            throw new AuthenticationException(errorMessage);
+        }
+
+        Subject subject = (Subject) httpRequest.getAttribute(LoginHandler.SUBJECT_KEY);
+        Principal principal = (Principal) httpRequest.getAttribute(LoginHandler.PRINCIPAL_KEY);
+        String principalName = DatatypeHelper.safeTrimOrNullString((String) httpRequest
+                .getAttribute(LoginHandler.PRINCIPAL_NAME_KEY));
+
+        if (subject == null && principal == null && principalName == null) {
+            LOG.error("No user identified by login handler.");
+            throw new AuthenticationException("No user identified by login handler.");
+        }
+    }
+
+    /**
+     * Gets the subject from the request coming back from the login handler.
+     * 
+     * @param httpRequest request coming back from the login handler
+     * 
+     * @return the {@link Subject} created from the request
+     * 
+     * @throws AuthenticationException thrown if no subject can be retrieved from the request
+     */
+    protected Subject getLoginHandlerSubject(HttpServletRequest httpRequest) throws AuthenticationException {
+        Subject subject = (Subject) httpRequest.getAttribute(LoginHandler.SUBJECT_KEY);
+        Principal principal = (Principal) httpRequest.getAttribute(LoginHandler.PRINCIPAL_KEY);
+        String principalName = DatatypeHelper.safeTrimOrNullString((String) httpRequest
+                .getAttribute(LoginHandler.PRINCIPAL_NAME_KEY));
+
+        if (subject == null && (principal != null || principalName != null)) {
+            subject = new Subject();
+            if (principal == null) {
+                principal = new UsernamePrincipal(principalName);
+            }
+            subject.getPrincipals().add(principal);
+        }
+
+        return subject;
+    }
+
+    /**
+     * If forced authentication was required this method checks to ensure that the re-authenticated subject contains a
+     * principal name that is equal to the principal name associated with the authentication method. If this is the
+     * first time the subject has authenticated with this method than this check always passes.
+     * 
+     * @param idpSession user's IdP session
+     * @param authnMethod method used to authenticate the user
+     * @param subject subject that was authenticated
+     * 
+     * @throws AuthenticationException thrown if this check fails
+     */
+    protected void validateForcedReauthentication(Session idpSession, String authnMethod, Subject subject)
+            throws AuthenticationException {
+        if (idpSession != null) {
+            AuthenticationMethodInformation authnMethodInfo = idpSession.getAuthenticationMethods().get(authnMethod);
+            if (authnMethodInfo != null) {
+                boolean princpalMatch = false;
+                for (Principal princpal : subject.getPrincipals()) {
+                    if (authnMethodInfo.getAuthenticationPrincipal().equals(princpal)) {
+                        princpalMatch = true;
+                        break;
+                    }
+                }
+
+                if (!princpalMatch) {
+                    throw new ForceAuthenticationException(
+                            "Authenticated principal does not match previously authenticated principal");
+                }
+            }
+        }
+    }
+
+    /**
      * 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 authenticationSubject subject created from the authentication method
+     * @param authenticationMethod the method used to authenticate the subject
      * @param httpRequest current HTTP request
      * @param httpResponse current HTTP response
      */
-    protected void updateUserSession(LoginContext loginContext, HttpServletRequest httpRequest,
-            HttpServletResponse httpResponse) {
+    protected void updateUserSession(LoginContext loginContext, Subject authenticationSubject,
+            String authenticationMethod, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+
+        Principal authenticationPrincipal = authenticationSubject.getPrincipals().iterator().next();
+
         Session idpSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
         if (idpSession == null) {
-            LOG.debug("Creating shibboleth session for principal {}", loginContext.getPrincipalName());
-            idpSession = (Session) sessionManager.createSession(loginContext.getPrincipalName());
+            LOG.debug("Creating shibboleth session for principal {}", authenticationPrincipal.getName());
+            idpSession = (Session) sessionManager.createSession();
             loginContext.setSessionID(idpSession.getSessionID());
             addSessionCookie(httpRequest, httpResponse, idpSession);
         }
 
-        LOG.debug("Recording authentication and service information in Shibboleth session for principal: {}",
-                loginContext.getPrincipalName());
-        Subject subject = (Subject) httpRequest.getAttribute(LoginHandler.SUBJECT_KEY);
-        String authnMethod = (String) httpRequest.getAttribute(LoginHandler.AUTHENTICATION_METHOD_KEY);
-        if (DatatypeHelper.isEmpty(authnMethod)) {
-            authnMethod = loginContext.getAuthenticationMethod();
-        }
+        // Merge the information in the current session subject with the information from the
+        // login handler subject
+        idpSession.setSubject(mergeSubjects(idpSession.getSubject(), authenticationSubject));
 
-        AuthenticationMethodInformation authnMethodInfo = new AuthenticationMethodInformationImpl(subject, authnMethod,
-                loginContext.getAuthenticationInstant(), loginContext.getAuthenticationDuration());
+        LOG.debug("Recording authentication and service information in Shibboleth session for principal: {}",
+                authenticationPrincipal.getName());
+        LoginHandler loginHandler = handlerManager.getLoginHandlers().get(authenticationMethod);
+        AuthenticationMethodInformation authnMethodInfo = new AuthenticationMethodInformationImpl(idpSession
+                .getSubject(), authenticationPrincipal, authenticationMethod, new DateTime(), loginHandler
+                .getAuthenticationDuration());
 
+        loginContext.setAuthenticationMethodInformation(authnMethodInfo);
         idpSession.getAuthenticationMethods().put(authnMethodInfo.getAuthenticationMethod(), authnMethodInfo);
+        sessionManager.indexSession(idpSession, authnMethodInfo.getAuthenticationPrincipal().getName());
 
         ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
                 authnMethodInfo);
@@ -487,6 +656,42 @@ public class AuthenticationEngine extends HttpServlet {
     }
 
     /**
+     * Merges the principals and public and private credentials from two subjects into a new subject.
+     * 
+     * @param subject1 first subject to merge, may be null
+     * @param subject2 second subject to merge, may be null
+     * 
+     * @return subject containing the merged information
+     */
+    protected Subject mergeSubjects(Subject subject1, Subject subject2) {
+        if (subject1 == null) {
+            return subject2;
+        }
+
+        if (subject2 == null) {
+            return subject1;
+        }
+
+        if (subject1 == null && subject2 == null) {
+            return new Subject();
+        }
+
+        Set<Principal> principals = new HashSet<Principal>();
+        principals.addAll(subject1.getPrincipals());
+        principals.addAll(subject2.getPrincipals());
+
+        Set<Object> publicCredentials = new HashSet<Object>();
+        publicCredentials.addAll(subject1.getPublicCredentials());
+        publicCredentials.addAll(subject2.getPublicCredentials());
+
+        Set<Object> privateCredentials = new HashSet<Object>();
+        privateCredentials.addAll(subject1.getPrivateCredentials());
+        privateCredentials.addAll(subject2.getPrivateCredentials());
+
+        return new Subject(false, principals, publicCredentials, privateCredentials);
+    }
+
+    /**
      * Adds an IdP session cookie to the outbound response.
      * 
      * @param httpRequest current request
@@ -499,17 +704,62 @@ public class AuthenticationEngine extends HttpServlet {
 
         LOG.debug("Adding IdP session cookie to HTTP response");
         Cookie sessionCookie = new Cookie(IDP_SESSION_COOKIE_NAME, userSession.getSessionID());
-        
+
         String contextPath = httpRequest.getContextPath();
-        if(DatatypeHelper.isEmpty(contextPath)){
+        if (DatatypeHelper.isEmpty(contextPath)) {
             sessionCookie.setPath("/");
-        }else{
+        } else {
             sessionCookie.setPath(contextPath);
         }
-        
+
         sessionCookie.setSecure(httpRequest.isSecure());
         sessionCookie.setMaxAge(-1);
 
         httpResponse.addCookie(sessionCookie);
     }
+
+    /** Storage service entry for login contexts. */
+    public class LoginContextEntry implements ExpiringObject {
+
+        /** Stored login context. */
+        private LoginContext loginCtx;
+
+        /** Time the entry expires. */
+        private DateTime expirationTime;
+
+        /**
+         * Constructor.
+         * 
+         * @param ctx context to store
+         * @param lifetime lifetime of the entry
+         */
+        public LoginContextEntry(LoginContext ctx, long lifetime) {
+            loginCtx = ctx;
+            expirationTime = new DateTime().plus(lifetime);
+        }
+
+        /**
+         * Gets the login context.
+         * 
+         * @return login context
+         */
+        public LoginContext getLoginContext() {
+            return loginCtx;
+        }
+
+        /** {@inheritDoc} */
+        public DateTime getExpirationTime() {
+            return expirationTime;
+        }
+
+        /** {@inheritDoc} */
+        public boolean isExpired() {
+            return expirationTime.isBeforeNow();
+        }
+
+        /** {@inheritDoc} */
+        public void onExpire() {
+
+        }
+    }
 }
\ No newline at end of file
index a5081df..657b92a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,24 +24,27 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import org.joda.time.DateTime;
 
+import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
+
 /**
  * Login context created by a profile handler and interpreted by the authentication package.
  * 
  * Two properties are tracked by default:
+ * <ul>
+ * <li><code>forceAuth</code> - Should user authentication be forced (default value: false).</li>
+ * <li><code>passiveAuth</code> - Should user authentication not control the UI (default value: false).</li>
+ * </ul>
  * 
- * <code>forceAuth</code> - Should user authentication be forced. <code>passiveAuth</code> - Should user
- * authentication not control the UI.
- * 
- * A Map&lt;String, Object&gt; is provided to store other properties. Alternatively, a profile handler may create a
- * subclass of LoginContext with extra fields.
+ * A {@link Map}&lt;String, Object&gt; is provided to store other properties. Alternatively, a profile handler may
+ * create a subclass of LoginContext with extra fields.
  * 
  * LoginContexts should be created by a profile handler when authentication is needed. Once control has returned to the
  * profile handler, it should remove the LoginContext from the HttpSession.
  * 
- * The {@link AuthenticationEngine} or an {@link LoginHandler} should set the
- * {@link LoginContext#setAuthenticationAttempted()}, {@link LoginContext#setPrincipalAuthenticated(boolean)},
+ * The {@link AuthenticationEngine} should set the {@link LoginContext#setAuthenticationAttempted()},
+ * {@link LoginContext#setPrincipalAuthenticated(boolean)},
  * {@link LoginContext#setAuthenticationFailure(AuthenticationException)},
- * {@link LoginContext#setAuthenticationDuration(long)}, {@link LoginContext#setAuthenticationInstant(DateTime)}
+ * 
  * appropriately.
  */
 public class LoginContext implements Serializable {
@@ -61,7 +64,7 @@ public class LoginContext implements Serializable {
     /** Must authentication not interact with the UI. */
     private boolean passiveAuth;
 
-    /** a catch-all map for other properties. */
+    /** A catch-all map for other properties. */
     private Map<String, Serializable> propsMap = new ConcurrentHashMap<String, Serializable>();
 
     /** The ProfileHandler URL. */
@@ -70,33 +73,27 @@ public class LoginContext implements Serializable {
     /** The authentication engine's URL. */
     private String authnEngineURL;
 
-    /** has authentication been attempted yet. */
+    /** Whether authentication been attempted yet. */
     private boolean authnAttempted;
 
-    /** The id of the authenticated user. */
-    private String principalName;
+    /** Attempted user authentication method. */
+    private String attemptedAuthnMethod;
 
     /** Did authentication succeed? */
     private boolean principalAuthenticated;
 
-    /** Exception that occured during authentication. */
+    /** Exception that occurred during authentication. */
     private AuthenticationException authnException;
 
-    /** The instant of authentication. */
-    private DateTime authnInstant;
-
-    /** The duration of authentication. */
-    private long authnDuration;
-
-    /** The method used to authenticate the user. */
-    private String authnMethod;
-
     /** The session id. */
     private String sessionID;
 
     /** List of request authentication methods. */
     private ArrayList<String> requestAuthenticationMethods;
 
+    /** Information about the authentication method. */
+    private AuthenticationMethodInformation authenticationMethodInformation;
+
     /** Creates a new instance of LoginContext. */
     public LoginContext() {
         requestAuthenticationMethods = new ArrayList<String>();
@@ -115,179 +112,188 @@ public class LoginContext implements Serializable {
     }
 
     /**
-     * Gets the entity ID of the relying party.
+     * Gets the authentication method that was used when attempting to authenticate the user. Note, this may be
+     * different than the authentication method reported within {@link #getAuthenticationMethodInformation()}.
      * 
-     * @return entity ID of the relying party
+     * @return authentication method that was used when attempting to authenticate the user
      */
-    public String getRelyingPartyId() {
-        return relyingPartyId;
+    public String getAttemptedAuthnMethod() {
+        return attemptedAuthnMethod;
     }
 
     /**
-     * Gets the entity ID of the relying party.
+     * Returns if authentication has been attempted for this user.
      * 
-     * @param id entity ID of the relying party
+     * @return if authentication has been attempted for this user
      */
-    public void setRelyingParty(String id) {
-        relyingPartyId = id;
+    public boolean getAuthenticationAttempted() {
+        return authnAttempted;
     }
 
     /**
-     * Returns if authentication must be forced.
+     * Gets the duration of authentication.
      * 
-     * @return <code>true</code> if the authentication manager must re-authenticate the user.
+     * @return The duration of authentication, or zero if none was set.
      */
-    public boolean isForceAuthRequired() {
-        return forceAuth;
+    public long getAuthenticationDuration() {
+        return authenticationMethodInformation.getAuthenticationDuration();
     }
 
     /**
-     * Returns if authentication must be passive.
+     * Gets the authentication engine's URL.
      * 
-     * @return <code>true</code> if the authentication manager must not interact with the users UI.
+     * @return the URL of the authentication engine
      */
-    public boolean isPassiveAuthRequired() {
-        return passiveAuth;
+    public String getAuthenticationEngineURL() {
+        return authnEngineURL;
     }
 
     /**
-     * Sets if authentication must be forced.
+     * Gets the error that occurred during authentication.
      * 
-     * @param force if the authentication manager must re-authenticate the user.
+     * @return error that occurred during authentication
      */
-    public void setForceAuthRequired(boolean force) {
-        forceAuth = force;
+    public AuthenticationException getAuthenticationFailure() {
+        return authnException;
     }
 
     /**
-     * Sets if authentication must be passive.
+     * Gets the authentication instant.
      * 
-     * @param passive if the authentication manager must not interact with the users UI.
+     * @return The instant of authentication, or <code>null</code> if none was set.
      */
-    public void setPassiveAuthRequired(boolean passive) {
-        passiveAuth = passive;
+    public DateTime getAuthenticationInstant() {
+        return authenticationMethodInformation.getAuthenticationInstant();
     }
 
     /**
-     * Get an optional property object.
-     * 
-     * @param key The key in the properties Map.
+     * Gets the method used to authenticate the user.
      * 
-     * @return The object, or <code>null</code> is no object exists for the key.
+     * @return The method used to authenticate the user.
      */
-    public Object getProperty(String key) {
-        return propsMap.get(key);
+    public String getAuthenticationMethod() {
+        return authenticationMethodInformation.getAuthenticationMethod();
     }
 
     /**
-     * Sets an optional property object.
+     * Gets information about the authentication event.
      * 
-     * If an object is already associated with key, it will be overwritten.
+     * @return information about the authentication event.
+     */
+    public AuthenticationMethodInformation getAuthenticationMethodInformation() {
+        return authenticationMethodInformation;
+    }
+
+    /**
+     * Returns the ID of the authenticated user.
      * 
-     * @param key The key to set.
-     * @param obj The object to associate with key.
+     * @return the ID of the user, or <code>null</code> if authentication failed.
      */
-    public void setProperty(String key, final Serializable obj) {
-        propsMap.put(key, obj);
+    public String getPrincipalName() {
+        return authenticationMethodInformation.getAuthenticationPrincipal().getName();
     }
 
     /**
-     * Sets if authentication succeeded.
+     * Gets the ProfileHandler URL.
      * 
-     * @param authnOK if authentication succeeded;
+     * @return the URL of the profile handler that is invoking the Authentication Manager.
      */
-    public void setPrincipalAuthenticated(boolean authnOK) {
-        this.principalAuthenticated = authnOK;
+    public String getProfileHandlerURL() {
+        return profileHandlerURL;
     }
 
     /**
-     * Returns if authentication succeeded.
+     * Get an optional property object.
      * 
-     * @return <code>true</code> is the user was successfully authenticated.
+     * @param key The key in the properties Map.
+     * 
+     * @return The object, or <code>null</code> is no object exists for the key.
      */
-    public boolean isPrincipalAuthenticated() {
-        return principalAuthenticated;
+    public Object getProperty(String key) {
+        return propsMap.get(key);
     }
 
     /**
-     * Sets the error that occurred during authentication.
+     * Gets the entity ID of the relying party.
      * 
-     * @param error error that occurred during authentication
+     * @return entity ID of the relying party
      */
-    public void setAuthenticationFailure(AuthenticationException error) {
-        authnException = error;
+    public String getRelyingPartyId() {
+        return relyingPartyId;
     }
 
     /**
-     * Gets the error that occurred during authentication.
+     * Return the acceptable authentication handler URIs, in preference order, for authenticating this user. If no
+     * authentication methods are preferred the resultant list will be empty.
      * 
-     * @return error that occurred during authentication
+     * @return an list of authentication method identifiers
      */
-    public AuthenticationException getAuthenticationFailure() {
-        return authnException;
+    public List<String> getRequestedAuthenticationMethods() {
+        return requestAuthenticationMethods;
     }
 
     /**
-     * Set if authentication has been attempted.
+     * Gets the {@link edu.internet2.middleware.shibboleth.idp.session.Session} ID.
      * 
-     * This method should be called by an {@link LoginHandler} while processing a request.
+     * @return the Session id
      */
-    public void setAuthenticationAttempted() {
-        authnAttempted = true;
+    public String getSessionID() {
+        return sessionID;
     }
 
     /**
-     * Returns if authentication has been attempted for this user.
+     * Returns if authentication must be forced.
      * 
-     * @return if authentication has been attempted for this user
+     * @return <code>true</code> if the authentication manager must re-authenticate the user.
      */
-    public boolean getAuthenticationAttempted() {
-        return authnAttempted;
+    public boolean isForceAuthRequired() {
+        return forceAuth;
     }
 
     /**
-     * Sets the ID of the authenticated user.
+     * Returns if authentication must be passive.
      * 
-     * @param id The userid.
+     * @return <code>true</code> if the authentication manager must not interact with the users UI.
      */
-    public void setPrincipalName(String id) {
-        principalName = id;
+    public boolean isPassiveAuthRequired() {
+        return passiveAuth;
     }
 
     /**
-     * Returns the ID of the authenticated user.
+     * Returns if authentication succeeded.
      * 
-     * @return the ID of the user, or <code>null</code> if authentication failed.
+     * @return <code>true</code> is the user was successfully authenticated.
      */
-    public String getPrincipalName() {
-        return principalName;
+    public boolean isPrincipalAuthenticated() {
+        return principalAuthenticated;
     }
 
     /**
-     * Gets the ProfileHandler URL.
+     * Sets the authentication method that was used when attempting to authenticate the user.
      * 
-     * @return the URL of the profile handler that is invoking the Authentication Manager.
+     * @param method authentication method that was used when attempting to authenticate the user
      */
-    public String getProfileHandlerURL() {
-        return profileHandlerURL;
+    public void setAttemptedAuthnMethod(String method) {
+        attemptedAuthnMethod = method;
     }
 
     /**
-     * Sets the ProfileHandler URL.
+     * Set if authentication has been attempted.
      * 
-     * @param url The URL of the profile handler that invoked the AuthenticationManager/
+     * This method should be called by an {@link LoginHandler} while processing a request.
      */
-    public void setProfileHandlerURL(String url) {
-        profileHandlerURL = url;
+    public void setAuthenticationAttempted() {
+        authnAttempted = true;
     }
 
     /**
-     * Gets the authentication engine's URL.
+     * Sets the duration of authentication.
      * 
-     * @return the URL of the authentication engine
+     * @param duration The duration of authentication.
+     * 
+     * @deprecated this information is contained in the {@link AuthenticationMethodInformation}
      */
-    public String getAuthenticationEngineURL() {
-        return authnEngineURL;
+    public void setAuthenticationDuration(long duration) {
     }
 
     /**
@@ -300,84 +306,117 @@ public class LoginContext implements Serializable {
     }
 
     /**
-     * Gets the authentication instant.
+     * Sets the error that occurred during authentication.
      * 
-     * @return The instant of authentication, or <code>null</code> if none was set.
+     * @param error error that occurred during authentication
      */
-    public DateTime getAuthenticationInstant() {
-        return authnInstant;
+    public void setAuthenticationFailure(AuthenticationException error) {
+        authnException = error;
     }
 
     /**
      * Sets the authentication instant.
      * 
      * @param instant The instant of authentication.
+     * 
+     * @deprecated this information is contained in the {@link AuthenticationMethodInformation}
      */
     public void setAuthenticationInstant(final DateTime instant) {
-        authnInstant = instant;
     }
 
     /**
-     * Gets the duration of authentication.
+     * Sets the method used to authenticate the user.
      * 
-     * @return The duration of authentication, or zero if none was set.
+     * @param method The method used to authenticate the user.
+     * 
+     * @deprecated this information is contained in the {@link AuthenticationMethodInformation}
      */
-    public long getAuthenticationDuration() {
-        return authnDuration;
+    public void setAuthenticationMethod(String method) {
     }
 
     /**
-     * Sets the duration of authentication.
+     * Sets the information about the authentication event.
      * 
-     * @param duration The duration of authentication.
+     * @param info information about the authentication event
      */
-    public void setAuthenticationDuration(long duration) {
-        authnDuration = duration;
+    public void setAuthenticationMethodInformation(AuthenticationMethodInformation info) {
+        authenticationMethodInformation = info;
     }
 
     /**
-     * Gets the method used to authenticate the user.
+     * Sets if authentication must be forced.
      * 
-     * @return The method used to authenticate the user.
+     * @param force if the authentication manager must re-authenticate the user.
      */
-    public String getAuthenticationMethod() {
-        return authnMethod;
+    public void setForceAuthRequired(boolean force) {
+        forceAuth = force;
     }
 
     /**
-     * Sets the method used to authenticate the user.
+     * Sets if authentication must be passive.
      * 
-     * @param method The method used to authenticate the user.
+     * @param passive if the authentication manager must not interact with the users UI.
      */
-    public void setAuthenticationMethod(String method) {
-        authnMethod = method;
+    public void setPassiveAuthRequired(boolean passive) {
+        passiveAuth = passive;
     }
 
     /**
-     * Gets the {@link edu.internet2.middleware.shibboleth.idp.session.Session} ID.
+     * Sets if authentication succeeded.
      * 
-     * @return the Session id
+     * @param authnOK if authentication succeeded;
      */
-    public String getSessionID() {
-        return sessionID;
+    public void setPrincipalAuthenticated(boolean authnOK) {
+        this.principalAuthenticated = authnOK;
     }
 
     /**
-     * Sets the {@link edu.internet2.middleware.shibboleth.idp.session.Session} ID.
+     * Sets the ID of the authenticated user.
      * 
-     * @param id the Session ID
+     * @param id The userid.
+     * 
+     * @deprecated this information is contained in the {@link AuthenticationMethodInformation}
      */
-    public void setSessionID(String id) {
-        sessionID = id;
+    public void setPrincipalName(String id) {
+
     }
 
     /**
-     * Return the acceptable authentication handler URIs, in preference order, for authenticating this user. If no
-     * authentication methods are preferred the resultant list will be empty.
+     * Sets the ProfileHandler URL.
      * 
-     * @return an list of authentication method identifiers
+     * @param url The URL of the profile handler that invoked the AuthenticationManager/
      */
-    public List<String> getRequestedAuthenticationMethods() {
-        return requestAuthenticationMethods;
+    public void setProfileHandlerURL(String url) {
+        profileHandlerURL = url;
+    }
+
+    /**
+     * Sets an optional property object.
+     * 
+     * If an object is already associated with key, it will be overwritten.
+     * 
+     * @param key The key to set.
+     * @param obj The object to associate with key.
+     */
+    public void setProperty(String key, final Serializable obj) {
+        propsMap.put(key, obj);
+    }
+
+    /**
+     * Gets the entity ID of the relying party.
+     * 
+     * @param id entity ID of the relying party
+     */
+    public void setRelyingParty(String id) {
+        relyingPartyId = id;
+    }
+
+    /**
+     * Sets the {@link edu.internet2.middleware.shibboleth.idp.session.Session} ID.
+     * 
+     * @param id the Session ID
+     */
+    public void setSessionID(String id) {
+        sessionID = id;
     }
 }
\ No newline at end of file
index a528249..fe95119 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,30 +18,42 @@ package edu.internet2.middleware.shibboleth.idp.authn;
 
 import java.util.List;
 
-import javax.security.auth.Subject;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
-
 /**
  * Authentication handlers authenticate a user in an implementation specific manner. Some examples of this might be by
- * collecting a user name and password and validating it against an LDAP directory or collecting and validating a client
- * certificate or one-time password.
- * 
- * After the handler has authenticated the user it <strong>MUST</strong> bind the user's principal name to the
- * {@link HttpServletRequest} attribute identified by {@link LoginHandler#PRINCIPAL_NAME_KEY}.
+ * collecting a user name and password and validating it against an LDAP directory, validating a client certificate, or
+ * validating one-time password.
  * 
- * The handler may bind a {@link Subject} to the attribute identified by {@link #SUBJECT_KEY} if one was created during
- * the authentication process. This Subject is stored in the {@link AuthenticationMethodInformation}, created for this
- * authentication, in the user's session.
+ * When a login handler is invoked the user's {@link edu.internet2.middleware.shibboleth.idp.session.Session} is bound
+ * to the {@link javax.servlet.http.HttpSession} under the attribute with the name
+ * {@link edu.internet2.middleware.shibboleth.idp.session.Session#HTTP_SESSION_BINDING_ATTRIBUTE}.
  * 
- * The handler may designate the a URI representing the authentication method actually used, for example if a handler is
- * capable of performing multiple types of authentication, by binding the URI, as a String, to a request attribute
- * identified by {@link #AUTHENTICATION_METHOD_KEY}.
+ * After a successful authentication has been completed the handler <strong>MUST</strong> either:
+ * <ul>
+ * <li>Bind a {@link javax.security.auth.Subject} to the attribute identified by {@link #SUBJECT_KEY} if one was
+ * created during the authentication process. The principals, public, and private credentials from this subject will be
+ * merged with those in the {@link javax.security.auth.Subject} within the
+ * {@link edu.internet2.middleware.shibboleth.idp.session.Session}.</li>
+ * <li>Bind a {@link java.security.Principal} for the user to the request attribute identified by
+ * {@link #PRINCIPAL_KEY}. Such a {@link java.security.Principal} <strong>MUST</strong> implement
+ * {@link java.io.Serializable}. This principal will be added to the {@link javax.security.auth.Subject} within the
+ * {@link edu.internet2.middleware.shibboleth.idp.session.Session}.</li>
+ * <li>Bind a principal name string to the request attribute identified by {@link #PRINCIPAL_NAME_KEY}. In this case
+ * the {@link AuthenticationEngine} will create a {@link java.security.Principal} object of type
+ * {@link edu.internet2.middleware.shibboleth.idp.authn.UsernamePrincipal} and add that to the
+ * {@link javax.security.auth.Subject} within the {@link edu.internet2.middleware.shibboleth.idp.session.Session}.</li>
+ * </ul>
  * 
- * The handler may also bind an error message, if an error occurred during authentication to the request attribute
- * identified by {@link LoginHandler#AUTHENTICATION_ERROR_KEY}.
+ * The handler <strong>MAY</strong> also:
+ * <ul>
+ * <li>Bind a URI string, representing the authentication method actually used, to a request attribute identified by
+ * {@link #AUTHENTICATION_METHOD_KEY}. This may be used if a handler is capable of performing multiple types of
+ * authentication.</li>
+ * <li>bind an error message, if an error occurred during authentication to the request attribute identified by
+ * {@link LoginHandler#AUTHENTICATION_ERROR_KEY}.</li>
+ * </ul>
  * 
  * Finally, the handler must return control to the authentication engine by invoking
  * {@link AuthenticationEngine#returnToAuthenticationEngine(HttpServletRequest, HttpServletResponse)}. After which the
@@ -53,8 +65,11 @@ import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInfor
  */
 public interface LoginHandler {
 
+    /** Request attribute to which user's principal should be bound. */
+    public static final String PRINCIPAL_KEY = "principal";
+
     /** Request attribute to which user's principal name should be bound. */
-    public static final String PRINCIPAL_NAME_KEY = "principal";
+    public static final String PRINCIPAL_NAME_KEY = "principal_name";
 
     /** Request attribute to which user's subject should be bound. */
     public static final String SUBJECT_KEY = "subject";
index 7173e98..e3921b8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,11 +51,8 @@ import org.xml.sax.InputSource;
 public class Saml2LoginContext extends LoginContext implements Serializable {
 
     /** Serial version UID. */
-    private static final long serialVersionUID = -2518779446947534977L;
+    private static final long serialVersionUID = -7117092606828289070L;
 
-    /** Class logger. */
-    private final Logger log = LoggerFactory.getLogger(Saml2LoginContext.class);
-    
     /** Relay state from authentication request. */
     private String relayState;
 
@@ -183,6 +180,7 @@ public class Saml2LoginContext extends LoginContext implements Serializable {
         // For the immediate future, we only support the "exact" comparator.
         AuthnContextComparisonTypeEnumeration comparator = authnContext.getComparison();
         if (comparator != null && comparator != AuthnContextComparisonTypeEnumeration.EXACT) {
+            Logger log = LoggerFactory.getLogger(Saml2LoginContext.class);
             log.error("Unsupported comparision operator ( " + comparator
                     + ") in RequestedAuthnContext. Only exact comparisions are supported.");
             return requestedMethods;
diff --git a/src/main/java/edu/internet2/middleware/shibboleth/idp/authn/UsernamePrincipal.java b/src/main/java/edu/internet2/middleware/shibboleth/idp/authn/UsernamePrincipal.java
new file mode 100644 (file)
index 0000000..e4ff0b1
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2008 University Corporation for Advanced Internet Development, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.idp.authn;
+
+import java.security.Principal;
+
+import org.opensaml.xml.util.DatatypeHelper;
+
+/** A basic implementation of {@link Principal}. */
+public class UsernamePrincipal implements Principal {
+
+    /** Name of the principal. */
+    private String name;
+
+    /**
+     * Constructor.
+     * 
+     * @param principalName name of the principal
+     */
+    public UsernamePrincipal(String principalName) {
+        name = DatatypeHelper.safeTrimOrNullString(principalName);
+    }
+
+    /** {@inheritDoc} */
+    public String getName() {
+        return name;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "{BasicPrincipal}" + getName();
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof UsernamePrincipal) {
+            return DatatypeHelper.safeEquals(getName(), ((UsernamePrincipal) obj).getName());
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
index f98ad34..21465ce 100644 (file)
@@ -80,7 +80,7 @@ public class IPAddressLoginHandler extends AbstractLoginHandler {
                 ipList.add(new edu.internet2.middleware.shibboleth.idp.authn.provider.IPAddressLoginHandler.IPEntry(
                         addr));
             } catch (UnknownHostException ex) {
-                log.error("IPAddressHandler: Error parsing entry \"" + addr + "\". Ignoring.");
+                log.error("IPAddressHandler: Error parsing IP entry \"" + addr + "\". Ignoring.");
             }
         }
     }
@@ -130,6 +130,7 @@ public class IPAddressLoginHandler extends AbstractLoginHandler {
         boolean ipAllowed = searchIpList(request);
 
         if (ipAllowed) {
+            log.debug("Authenticated user by IP address");
             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
         }
     }
@@ -139,6 +140,7 @@ public class IPAddressLoginHandler extends AbstractLoginHandler {
         boolean ipDenied = searchIpList(request);
 
         if (!ipDenied) {
+            log.debug("Authenticated user by IP address");
             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
         }
     }
@@ -171,7 +173,7 @@ public class IPAddressLoginHandler extends AbstractLoginHandler {
             }
 
         } catch (UnknownHostException ex) {
-            log.error("IPAddressHandler: Error resolving hostname.", ex);
+            log.error("Error resolving hostname.", ex);
             return false;
         }
 
@@ -227,13 +229,13 @@ public class IPAddressLoginHandler extends AbstractLoginHandler {
 
             int cidrOffset = entry.indexOf("/");
             if (cidrOffset == -1) {
-                log.error("IPAddressHandler: invalid entry \"" + entry + "\" -- it lacks a netmask component.");
+                log.error("Invalid entry \"" + entry + "\" -- it lacks a netmask component.");
                 throw new UnknownHostException("entry lacks a netmask component.");
             }
 
             // ensure that only one "/" is present.
             if (entry.indexOf("/", cidrOffset + 1) != -1) {
-                log.error("IPAddressHandler: invalid entry \"" + entry + "\" -- too many \"/\" present.");
+                log.error("Invalid entry \"" + entry + "\" -- too many \"/\" present.");
                 throw new UnknownHostException("entry has too many netmask components.");
             }
 
@@ -248,9 +250,9 @@ public class IPAddressLoginHandler extends AbstractLoginHandler {
 
             // ensure that the netmask isn't too large
             if ((tempAddr instanceof Inet4Address) && (masklen > 32)) {
-                throw new UnknownHostException("IPAddressHandler: Netmask is too large for an IPv4 address: " + masklen);
+                throw new UnknownHostException("Netmask is too large for an IPv4 address: " + masklen);
             } else if ((tempAddr instanceof Inet6Address) && masklen > 128) {
-                throw new UnknownHostException("IPAddressHandler: Netmask is too large for an IPv6 address: " + masklen);
+                throw new UnknownHostException("Netmask is too large for an IPv6 address: " + masklen);
             }
 
             netmask = new BitSet(addrlen);
index 02f073d..6601669 100644 (file)
 
 package edu.internet2.middleware.shibboleth.idp.authn.provider;
 
-import java.io.IOException;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.opensaml.util.URLBuilder;
+import org.opensaml.saml2.core.AuthnContext;
 import org.opensaml.xml.util.DatatypeHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
+import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
+import edu.internet2.middleware.shibboleth.idp.session.Session;
 
-/**
- * Login handler that is called when user is logged in under a previously existing session.
- * 
- * This login handler can optionally redirect the browser to a given URL. This provides a mechanism for extensions to
- * hook into the authentication process on every request. If this option is used and the servlet to which the browser is
- * redirected does not take visible control of the request be sure to indicate passive authentication support by means
- * of {@link PreviousSessionLoginHandler#setSupportsPassive(boolean)}.
- * 
- * When the servlet has completed it's work it <strong>MUST</strong> call
- * {@link AuthenticationEngine#returnToAuthenticationEngine(HttpServletRequest, HttpServletResponse)} in order to
- * transfer control back to the authentication engine.
- */
+/** Login handler that is called when user is logged in under a previously existing session. */
 public class PreviousSessionLoginHandler extends AbstractLoginHandler {
-
+    
     /** Class logger. */
     private final Logger log = LoggerFactory.getLogger(PreviousSessionLoginHandler.class);
 
@@ -55,6 +44,7 @@ public class PreviousSessionLoginHandler extends AbstractLoginHandler {
     public PreviousSessionLoginHandler() {
         super();
         servletPath = null;
+        setSupportsPassive(true);
         setSupportsForceAuthentication(false);
     }
 
@@ -62,6 +52,8 @@ public class PreviousSessionLoginHandler extends AbstractLoginHandler {
      * Get the path of the servlet to which the user agent may be redirected.
      * 
      * @return path of the servlet to which the user agent may be redirected
+     * 
+     * @deprecated
      */
     public String getServletPath() {
         return servletPath;
@@ -71,6 +63,8 @@ public class PreviousSessionLoginHandler extends AbstractLoginHandler {
      * Set the path of the servlet to which the user agent may be redirected.
      * 
      * @param path path of the servlet to which the user agent may be redirected
+     * 
+     * @deprecated
      */
     public void setServletPath(String path) {
         servletPath = DatatypeHelper.safeTrimOrNullString(path);
@@ -105,29 +99,16 @@ public class PreviousSessionLoginHandler extends AbstractLoginHandler {
 
     /** {@inheritDoc} */
     public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
-        if (servletPath == null) {
-            AuthenticationEngine.returnToAuthenticationEngine(httpRequest, httpResponse);
-        } else {
-            try {
-                StringBuilder pathBuilder = new StringBuilder();
-                pathBuilder.append(httpRequest.getContextPath());
-                if (!servletPath.startsWith("/")) {
-                    pathBuilder.append("/");
-                }
-                pathBuilder.append(servletPath);
-
-                URLBuilder urlBuilder = new URLBuilder();
-                urlBuilder.setScheme(httpRequest.getScheme());
-                urlBuilder.setHost(httpRequest.getServerName());
-                urlBuilder.setPort(httpRequest.getServerPort());
-                urlBuilder.setPath(pathBuilder.toString());
-
-                log.debug("Redirecting to {}", urlBuilder.buildURL());
-                httpResponse.sendRedirect(urlBuilder.buildURL());
-                return;
-            } catch (IOException ex) {
-                log.error("Unable to redirect to previous session authentication servlet.", ex);
-            }
+        if (reportPreviousSessionAuthnMethod) {
+            httpRequest.setAttribute(LoginHandler.AUTHENTICATION_METHOD_KEY, AuthnContext.PREVIOUS_SESSION_AUTHN_CTX);
         }
+        
+        Session idpSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
+        if(idpSession != null){
+            log.error("No existing IdP session available.");
+        }
+        httpRequest.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, idpSession.getPrincipalName());
+
+        AuthenticationEngine.returnToAuthenticationEngine(httpRequest, httpResponse);
     }
 }
\ No newline at end of file
diff --git a/src/main/java/edu/internet2/middleware/shibboleth/idp/authn/provider/UsernamePasswordCredential.java b/src/main/java/edu/internet2/middleware/shibboleth/idp/authn/provider/UsernamePasswordCredential.java
new file mode 100644 (file)
index 0000000..a38e686
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008 University Corporation for Advanced Internet Development, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.idp.authn.provider;
+
+/** Represents a username and password entered used to authenticate a subject. */
+public class UsernamePasswordCredential {
+
+    /** Username of a subject. */
+    private String username;
+
+    /** Password of a subject. */
+    private String password;
+
+    /**
+     * Constructor.
+     * 
+     * @param name username of the subject
+     * @param pass password of the subject
+     */
+    public UsernamePasswordCredential(String name, String pass) {
+        username = name;
+        password = pass;
+    }
+
+    /**
+     * Gets the username of the subject.
+     * 
+     * @return username of the subject
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Gets the password of the subject.
+     * 
+     * @return password of the subject
+     */
+    public String getPassword() {
+        return password;
+    }
+}
\ No newline at end of file
index 8ff46ad..9a17b7a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,11 +28,12 @@ import org.slf4j.LoggerFactory;
 /**
  * Authenticate a username and password against a JAAS source.
  * 
- * This authenticaiton handler requires a JSP to collect a username and password from the user. It also requires a JAAS
- * configuration file to validate the username and password.
- * 
- * If an Authentication Context Class or DeclRef URI is not specified, it will default to
- * "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport".
+ * This login handler creates a {@link javax.security.auth.Subject} and binds it to the request as described in the
+ * {@link edu.internet2.middleware.shibboleth.idp.authn.LoginHandler} documentation. If the JAAS module does not create
+ * a principal for the user a {@link edu.internet2.middleware.shibboleth.idp.authn.UsernamePrincipal} is created, using the
+ * entered username. If the <code>storeCredentialsInSubject</code> init parameter of the authentication servlet is set
+ * to true a {@link UsernamePasswordCredential} is created, based on the entered username and password, and stored in
+ * the Subject's private credentials.
  */
 public class UsernamePasswordLoginHandler extends AbstractLoginHandler {
 
index 3138580..7419c1a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 [University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
+import edu.internet2.middleware.shibboleth.idp.authn.UsernamePrincipal;
 import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
 
 /**
@@ -55,6 +56,9 @@ public class UsernamePasswordLoginServlet extends HttpServlet {
     /** Class logger. */
     private final Logger log = LoggerFactory.getLogger(UsernamePasswordLoginServlet.class);
 
+    /** Whether to store a user's credentials within the {@link Subject}. */
+    private boolean storeCredentialsInSubject;
+
     /** Name of JAAS configuration used to authenticate users. */
     private String jaasConfigName = "ShibUserPassAuth";
 
@@ -130,9 +134,8 @@ public class UsernamePasswordLoginServlet extends HttpServlet {
             if (queryParams == null) {
                 queryParams = new ArrayList<Pair<String, String>>();
             }
-            
-            queryParams.add(new Pair<String, String>("actionUrl", request.getContextPath()
-                    + request.getServletPath()));
+
+            queryParams.add(new Pair<String, String>("actionUrl", request.getContextPath() + request.getServletPath()));
             urlBuilder.getQueryParams().addAll(queryParams);
 
             log.debug("Redirecting to login page {}", urlBuilder.buildURL());
@@ -152,7 +155,6 @@ public class UsernamePasswordLoginServlet extends HttpServlet {
      * @return true of authentication succeeds, false if not
      */
     protected boolean authenticateUser(HttpServletRequest request) {
-
         try {
             String username = DatatypeHelper.safeTrimOrNullString(request.getParameter(usernameAttribute));
             String password = DatatypeHelper.safeTrimOrNullString(request.getParameter(passwordAttribute));
@@ -165,22 +167,23 @@ public class UsernamePasswordLoginServlet extends HttpServlet {
             jaasLoginCtx.login();
             log.debug("Successfully authenticated user {}", username);
 
-            Subject subject = jaasLoginCtx.getSubject();
-            Set<Principal> principals = subject.getPrincipals();
+            Subject loginSubject = jaasLoginCtx.getSubject();
 
+            Set<Principal> principals = loginSubject.getPrincipals();
             if (principals.isEmpty()) {
-                request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
-            } else {
-                Principal principal = principals.iterator().next();
-                String principalName = DatatypeHelper.safeTrimOrNullString(principal.getName());
-                if (principalName == null) {
-                    request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
-                } else {
-                    request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, principal.getName());
-                }
-                request.setAttribute(LoginHandler.SUBJECT_KEY, jaasLoginCtx.getSubject());
+                principals.add(new UsernamePrincipal(username));
             }
 
+            Set<Object> publicCredentials = loginSubject.getPublicCredentials();
+
+            Set<Object> privateCredentials = loginSubject.getPrivateCredentials();
+            if (storeCredentialsInSubject) {
+                privateCredentials.add(new UsernamePasswordCredential(username, password));
+            }
+
+            Subject userSubject = new Subject(false, principals, publicCredentials, privateCredentials);
+            request.setAttribute(LoginHandler.SUBJECT_KEY, userSubject);
+
             return true;
         } catch (LoginException e) {
             log.debug("User authentication failed", e);
index afc823b..986a672 100644 (file)
@@ -18,8 +18,9 @@ package edu.internet2.middleware.shibboleth.idp.config.profile.authn;
 
 import javax.xml.namespace.QName;
 
-import org.opensaml.xml.util.DatatypeHelper;
 import org.opensaml.xml.util.XMLHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.w3c.dom.Element;
 
@@ -33,6 +34,9 @@ public class PreviousSessionLoginHandlerBeanDefinitionParser extends AbstractLog
     /** Schema type. */
     public static final QName SCHEMA_TYPE = new QName(ProfileHandlerNamespaceHandler.NAMESPACE, "PreviousSession");
 
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(PreviousSessionLoginHandlerBeanDefinitionParser.class);
+
     /** {@inheritDoc} */
     protected Class getBeanClass(Element arg0) {
         return PreviousSessionLoginHandlerFactoryBean.class;
@@ -42,14 +46,12 @@ public class PreviousSessionLoginHandlerBeanDefinitionParser extends AbstractLog
     protected void doParse(Element config, BeanDefinitionBuilder builder) {
         super.doParse(config, builder);
 
-        builder.addPropertyValue("servletPath", DatatypeHelper.safeTrimOrNullString(config.getAttributeNS(null,
-                "servletPath")));
-
+        if (config.hasAttributeNS(null, "servletPath")) {
+            log.warn("The 'servletPath' configuration option has been deprecated and is no longer supported.");
+        }
+        
         if (config.hasAttributeNS(null, "supportsPassiveAuthentication")) {
-            builder.addPropertyValue("supportsPassiveAuth", XMLHelper.getAttributeValueAsBoolean(config
-                    .getAttributeNodeNS(null, "supportsPassiveAuthentication")));
-        } else {
-            builder.addPropertyValue("supportsPassiveAuth", false);
+            log.warn("The 'supportsPassiveAuthentication' configuration option has been deprecated and is no longer supported.");
         }
 
         if (config.hasAttributeNS(null, "reportPreviousSessionAuthnMethod")) {
index 0274c62..6ed3cf6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package edu.internet2.middleware.shibboleth.idp.session;
 
+import java.security.Principal;
+
 import javax.security.auth.Subject;
 
 import org.joda.time.DateTime;
 
-/**
- * Information about an authentication method employed by a user.
- */
+/** Information about an authentication method employed by a user. */
 public interface AuthenticationMethodInformation {
-    
+
     /**
      * Gets the Subject created by this authentication method.
      * 
      * @return subject created by this authentication method
+     * 
+     * @deprecated use {@link Session#getSubject()}
      */
     public Subject getAuthenticationSubject();
 
     /**
+     * Gets the principal, for the {@link Subject} of the session, created by this authentication method.
+     * 
+     * @return principal created by this authentication method
+     */
+    public Principal getAuthenticationPrincipal();
+
+    /**
      * Gets the unique identifier for the authentication method.
      * 
      * @return unique identifier for the authentication method
diff --git a/src/main/java/edu/internet2/middleware/shibboleth/idp/session/ContainerSessionListener.java b/src/main/java/edu/internet2/middleware/shibboleth/idp/session/ContainerSessionListener.java
deleted file mode 100644 (file)
index 02f5061..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package edu.internet2.middleware.shibboleth.idp.session;
-
-import javax.servlet.http.HttpSession;
-import javax.servlet.http.HttpSessionEvent;
-import javax.servlet.http.HttpSessionListener;
-
-import edu.internet2.middleware.shibboleth.common.session.SessionManager;
-
-/**
- * A listener that listens for the destruction of {@link HttpSession}s. This allows the {@link SessionManager} to
- * appropriately destroy a Shibboleth session whether the HTTP session is destroyed explicitly or through inactivity.
- */
-public class ContainerSessionListener implements HttpSessionListener {
-
-    /** {@inheritDoc} */
-    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
-        // we don't care about session creations
-    }
-
-    /** {@inheritDoc} */
-    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
-        HttpSession httpSession = httpSessionEvent.getSession();
-        SessionManager<Session> sessionManager = (SessionManager<Session>) httpSession.getServletContext()
-                .getAttribute("sessionManager");
-
-        sessionManager.destroySession((String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE));
-    }
-}
\ No newline at end of file
index 4836e89..1b6fd29 100644 (file)
@@ -65,7 +65,6 @@ public class IdPSessionFilter implements Filter {
                 log.trace("Updating IdP session activity time and adding session object to the request");
                 idpSession.setLastActivityInstant(new DateTime());
                 MDC.put("idpSessionId", idpSession.getSessionID());
-                MDC.put("principalName", idpSession.getPrincipalName());
                 httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, idpSession);
             }
         }
index a497e5d..40195f3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,9 +18,7 @@ package edu.internet2.middleware.shibboleth.idp.session;
 
 import org.joda.time.DateTime;
 
-/**
- * Information about a service a user has logged in to.
- */
+/** Information about a service a user has logged in to. */
 public interface ServiceInformation {
 
     /**
index d5fe520..2eabc95 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
index 61b700b..d0e4e44 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
 
 package edu.internet2.middleware.shibboleth.idp.session.impl;
 
+import java.security.Principal;
+
 import javax.security.auth.Subject;
 
 import org.joda.time.DateTime;
@@ -29,7 +31,10 @@ public class AuthenticationMethodInformationImpl implements AuthenticationMethod
 
     /** Subject created by this authentication mechanism. */
     private Subject authenticationSubject;
-    
+
+    /** Principal created by the authentication method. */
+    private Principal authenticationPrincipal;
+
     /** The authentication method (a URI). */
     private String authenticationMethod;
 
@@ -43,51 +48,40 @@ public class AuthenticationMethodInformationImpl implements AuthenticationMethod
     private DateTime expirationInstant;
 
     /**
-     * Default constructor.
-     * 
-     * @param method The unique identifier for the authentication method.
-     * @param instant The time the user authenticated with this member.
-     * @param duration The duration of this authentication method.
-     */
-    public AuthenticationMethodInformationImpl(String method, DateTime instant, long duration) {
-
-        if (method == null || instant == null || duration < 0) {
-            throw new IllegalArgumentException("Authentication method, instant, and duration may not be null");
-        }
-
-        authenticationMethod = method;
-        authenticationInstant = instant;
-        authenticationDuration = duration;
-        expirationInstant = instant.plus(duration);
-    }
-    
-    /**
-     * Default constructor.
+     * Default constructor.  This constructor does NOT add the given principal to the given subject.
      * 
-     * @param subject Subject created by the authentication method
-     * @param method The unique identifier for the authentication method.
-     * @param instant The time the user authenticated with this member.
-     * @param duration The duration of this authentication method.
+     * @param subject subject associated with the user's session
+     * @param principal principal created by the authentication method
+     * @param method The unique identifier for the authentication method
+     * @param instant The time the user authenticated with this member
+     * @param duration The duration of this authentication method
      */
-    public AuthenticationMethodInformationImpl(Subject subject, String method, DateTime instant, long duration) {
+    public AuthenticationMethodInformationImpl(Subject subject, Principal principal, String method, DateTime instant,
+            long duration) {
 
         if (method == null || instant == null || duration < 0) {
             throw new IllegalArgumentException("Authentication method, instant, and duration may not be null");
         }
 
         authenticationSubject = subject;
+        authenticationPrincipal = principal;
         authenticationMethod = method;
         authenticationInstant = instant;
         authenticationDuration = duration;
         expirationInstant = instant.plus(duration);
     }
-    
+
     /** {@inheritDoc} */
     public Subject getAuthenticationSubject() {
         return authenticationSubject;
     }
 
     /** {@inheritDoc} */
+    public Principal getAuthenticationPrincipal() {
+        return authenticationPrincipal;
+    }
+
+    /** {@inheritDoc} */
     public String getAuthenticationMethod() {
         return authenticationMethod;
     }
@@ -106,7 +100,7 @@ public class AuthenticationMethodInformationImpl implements AuthenticationMethod
     public boolean isExpired() {
         return expirationInstant.isBeforeNow();
     }
-    
+
     /** {@inheritDoc} */
     public int hashCode() {
         return authenticationMethod.hashCode();
@@ -114,10 +108,10 @@ public class AuthenticationMethodInformationImpl implements AuthenticationMethod
 
     /** {@inheritDoc} */
     public boolean equals(Object obj) {
-        if(obj == this){
+        if (obj == this) {
             return true;
         }
-        
+
         if (!(obj instanceof AuthenticationMethodInformation)) {
             return false;
         }
index 0dbd976..b41f384 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,9 +21,7 @@ import org.joda.time.DateTime;
 import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
 
-/**
- * Information about a service a user has logged in to.
- */
+/** Information about a service a user has logged in to. */
 public class ServiceInformationImpl implements ServiceInformation {
 
     /** Entity ID of the service. */
@@ -70,10 +68,10 @@ public class ServiceInformationImpl implements ServiceInformation {
 
     /** {@inheritDoc} */
     public boolean equals(Object obj) {
-        if(obj == this){
+        if (obj == this) {
             return true;
         }
-        
+
         if (!(obj instanceof ServiceInformation)) {
             return false;
         }
index b74f93c..a923ab0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,9 +24,7 @@ import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInfor
 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
 import edu.internet2.middleware.shibboleth.idp.session.Session;
 
-/**
- * Session information for user logged into the IdP.
- */
+/** Session information for user logged into the IdP. */
 public class SessionImpl extends AbstractSession implements Session {
 
     /** Serial version UID. */
@@ -39,14 +37,13 @@ public class SessionImpl extends AbstractSession implements Session {
     private HashMap<String, ServiceInformation> servicesInformation;
 
     /**
-     * Default constructor.
+     * Constructor.
      * 
      * @param sessionId ID of the session
-     * @param principal principal ID of the user
      * @param timeout inactivity timeout for the session in milliseconds
      */
-    public SessionImpl(String sessionId, String principal, long timeout) {
-        super(sessionId, principal, timeout);
+    public SessionImpl(String sessionId, long timeout) {
+        super(sessionId, timeout);
 
         authnMethods = new HashMap<String, AuthenticationMethodInformation>();
         servicesInformation = new HashMap<String, ServiceInformation>();
@@ -61,7 +58,7 @@ public class SessionImpl extends AbstractSession implements Session {
     public Map<String, ServiceInformation> getServicesInformation() {
         return servicesInformation;
     }
-    
+
     /**
      * Gets the service information for the given entity ID.
      * 
index 68cdead..5982a79 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
+ * Copyright 2007 University Corporation for Advanced Internet Development, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
 package edu.internet2.middleware.shibboleth.idp.session.impl;
 
 import java.security.SecureRandom;
+import java.util.List;
+import java.util.Vector;
 
 import org.apache.commons.ssl.util.Hex;
 import org.joda.time.DateTime;
@@ -32,9 +34,7 @@ import edu.internet2.middleware.shibboleth.common.session.LogoutEvent;
 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
 import edu.internet2.middleware.shibboleth.idp.session.Session;
 
-/**
- * Manager of IdP sessions.
- */
+/** Manager of IdP sessions. */
 public class SessionManagerImpl implements SessionManager<Session>, ApplicationContextAware {
 
     /** Spring context used to publish login and logout events. */
@@ -86,8 +86,20 @@ public class SessionManagerImpl implements SessionManager<Session>, ApplicationC
     }
 
     /** {@inheritDoc} */
-    public void setApplicationContext(ApplicationContext applicationContext) {
-        appCtx = applicationContext;
+    public Session createSession() {
+        // generate a random session ID
+        byte[] sid = new byte[sessionIDSize];
+        prng.nextBytes(sid);
+        String sessionID = Hex.encode(sid);
+
+        Session session = new SessionImpl(sessionID, sessionLifetime);
+        SessionManagerEntry sessionEntry = new SessionManagerEntry(this, session, sessionLifetime);
+        sessionStore.put(partition, sessionID, sessionEntry);
+
+        MDC.put("idpSessionId", sessionID);
+
+        appCtx.publishEvent(new LoginEvent(session));
+        return session;
     }
 
     /** {@inheritDoc} */
@@ -98,12 +110,10 @@ public class SessionManagerImpl implements SessionManager<Session>, ApplicationC
         String sessionID = Hex.encode(sid);
 
         MDC.put("idpSessionId", sessionID);
-        MDC.put("principalName", principal);
-        
-        Session session = new SessionImpl(sessionID, principal, sessionLifetime);
+
+        Session session = new SessionImpl(sessionID, sessionLifetime);
         SessionManagerEntry sessionEntry = new SessionManagerEntry(this, session, sessionLifetime);
         sessionStore.put(partition, sessionID, sessionEntry);
-        sessionStore.put(partition, principal, sessionEntry);
         appCtx.publishEvent(new LoginEvent(session));
         return session;
     }
@@ -140,10 +150,36 @@ public class SessionManagerImpl implements SessionManager<Session>, ApplicationC
     }
 
     /** {@inheritDoc} */
-    public Session getSessionByPrincipalName(String name) {
+    public boolean indexSession(Session session, String index) {
+        if (sessionStore.contains(partition, index)) {
+            return false;
+        }
+
+        SessionManagerEntry sessionEntry = sessionStore.get(partition, session.getSessionID());
+        if (sessionEntry == null) {
+            return false;
+        }
+
+        if (sessionEntry.getSessionIndexes().contains(index)) {
+            return true;
+        }
+
+        sessionEntry.getSessionIndexes().add(index);
+        sessionStore.put(partition, index, sessionEntry);
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public void removeSessionIndex(String index) {
+        SessionManagerEntry sessionEntry = sessionStore.remove(partition, index);
+        if (sessionEntry != null) {
+            sessionEntry.getSessionIndexes().remove(index);
+        }
+    }
 
-        // TODO
-        return null;
+    /** {@inheritDoc} */
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        appCtx = applicationContext;
     }
 
     /**
@@ -154,6 +190,9 @@ public class SessionManagerImpl implements SessionManager<Session>, ApplicationC
         /** User's session. */
         private Session userSession;
 
+        /** Indexes for this session. */
+        private List<String> indexes;
+
         /** Manager that owns the session. */
         private SessionManager<Session> sessionManager;
 
@@ -171,6 +210,13 @@ public class SessionManagerImpl implements SessionManager<Session>, ApplicationC
             sessionManager = manager;
             userSession = session;
             expirationTime = new DateTime().plus(lifetime);
+            indexes = new Vector<String>();
+            indexes.add(userSession.getSessionID());
+        }
+
+        /** {@inheritDoc} */
+        public DateTime getExpirationTime() {
+            return expirationTime;
         }
 
         /**
@@ -191,9 +237,13 @@ public class SessionManagerImpl implements SessionManager<Session>, ApplicationC
             return userSession.getSessionID();
         }
 
-        /** {@inheritDoc} */
-        public DateTime getExpirationTime() {
-            return expirationTime;
+        /**
+         * Gets the list of indexes for this session.
+         * 
+         * @return list of indexes for this session
+         */
+        public List<String> getSessionIndexes() {
+            return indexes;
         }
 
         /** {@inheritDoc} */
index c016192..1953c7e 100644 (file)
     <listener>
         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
     </listener>
-    
-    <listener>
-        <listener-class>edu.internet2.middleware.shibboleth.idp.session.ContainerSessionListener</listener-class>
-    </listener>
 
     <!--  Add IdP Session object to incoming profile requests -->
     <filter>