Authentication engine rewrite to properly support force and passive authentication
authorlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sat, 12 Jan 2008 08:35:05 +0000 (08:35 +0000)
committerlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sat, 12 Jan 2008 08:35:05 +0000 (08:35 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@2560 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/idp/authn/AuthenticationEngine.java
src/edu/internet2/middleware/shibboleth/idp/authn/AuthenticationException.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/authn/ForceAuthenticationException.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/authn/LoginContext.java
src/edu/internet2/middleware/shibboleth/idp/authn/PassiveAuthenticationException.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/authn/Saml2LoginContext.java
src/edu/internet2/middleware/shibboleth/idp/profile/IdPProfileHandlerManager.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml1/ShibbolethSSOProfileHandler.java
src/edu/internet2/middleware/shibboleth/idp/profile/saml2/SSOProfileHandler.java
test/edu/internet2/middleware/shibboleth/idp/system/conf1/SAML2SSOTestCase.java
test/edu/internet2/middleware/shibboleth/idp/system/conf1/ShibbolethSSOTestCase.java

index 098a887..b7599ef 100644 (file)
 package edu.internet2.middleware.shibboleth.idp.authn;
 
 import java.io.IOException;
-import java.util.List;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import javax.security.auth.Subject;
 import javax.servlet.RequestDispatcher;
@@ -30,7 +34,6 @@ import javax.servlet.http.HttpSession;
 
 import org.joda.time.DateTime;
 import org.opensaml.xml.util.DatatypeHelper;
-import org.opensaml.xml.util.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -143,38 +146,187 @@ public class AuthenticationEngine extends HttpServlet {
         }
 
         if (!loginContext.getAuthenticationAttempted()) {
-            Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
-
-            AuthenticationMethodInformation authenticationMethod = getUsableExistingAuthenticationMethod(loginContext,
-                    shibSession);
-            if (authenticationMethod != null) {
-                LOG.debug("An active authentication method is applicable for relying party.  Using authentication "
-                        + "method {} as authentication method to relying party without re-authenticating user.",
-                        authenticationMethod.getAuthenticationMethod());
-                authenticateUserWithActiveMethod(httpRequest, httpResponse, authenticationMethod);
-                return;
+            startUserAuthentication(loginContext, httpRequest, httpResponse);
+        } else {
+            completeAuthenticationWithoutActiveMethod(loginContext, httpRequest, httpResponse);
+        }
+    }
+
+    /**
+     * Begins the authentication process. Determines if forced re-authentication is required or if an existing, active,
+     * authentication method is sufficient. Also determines, when authentication is required, which handler to use
+     * depending on whether passive authentication is required.
+     * 
+     * @param loginContext current login context
+     * @param httpRequest current HTTP request
+     * @param httpResponse current HTTP response
+     */
+    protected void startUserAuthentication(LoginContext loginContext, HttpServletRequest httpRequest,
+            HttpServletResponse httpResponse) {
+        LOG.debug("Beginning user authentication process");
+        try {
+            Map<String, LoginHandler> possibleLoginHandlers = determinePossibleLoginHandlers(loginContext);
+            Session userSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
+            Collection<AuthenticationMethodInformation> activeAuthnMethods = userSession.getAuthenticationMethods()
+                    .values();
+
+            if (loginContext.isForceAuthRequired()) {
+                LOG.debug("Forced authentication is required, filtering possible login handlers accordingly");
+                filterByForceAuthentication(loginContext, activeAuthnMethods, possibleLoginHandlers);
+            } else {
+                if (activeAuthnMethods != null) {
+                    LOG.debug("Forced authentication not required, using existing authentication method");
+                    for (AuthenticationMethodInformation activeAuthnMethod : activeAuthnMethods) {
+                        if (possibleLoginHandlers.containsKey(activeAuthnMethod.getAuthenticationMethod())) {
+                            completeAuthenticationWithActiveMethod(activeAuthnMethod, httpRequest, httpResponse);
+                            return;
+                        }
+                    }
+                }
             }
 
-            LOG.debug("No active authentication method is applicable for relying party.  "
-                    + "Authenticating user with to be determined method.");
-            authenticateUserWithoutActiveMethod1(httpRequest, httpResponse);
-        } else {
-            LOG.debug("Request returned from authentication handler, completing authentication process.");
-            authenticateUserWithoutActiveMethod2(httpRequest, httpResponse);
+            if (loginContext.isPassiveAuthRequired()) {
+                LOG.debug("Passive authentication is required, filtering poassibl login handlers accordingly.");
+                filterByPassiveAuthentication(loginContext, possibleLoginHandlers);
+            }
+
+            // Since we made it this far, just pick the first remaining login handler from the list
+            Entry<String, LoginHandler> chosenLoginHandler = possibleLoginHandlers.entrySet().iterator().next();
+            LOG.debug("Authenticating user with login handler of type {}", chosenLoginHandler.getValue().getClass()
+                    .getName());
+            authenticateUser(chosenLoginHandler.getKey(), chosenLoginHandler.getValue(), loginContext, httpRequest,
+                    httpResponse);
+        } catch (AuthenticationException e) {
+            loginContext.setAuthenticationFailure(e);
+            returnToProfileHandler(loginContext, httpRequest, httpResponse);
         }
 
-        return;
     }
 
     /**
-     * Completes the authentication request using an existing, active, authentication method for the current user.
+     * Determines which configured login handlers will support the requested authentication methods.
+     * 
+     * @param loginContext current login context
+     * 
+     * @return login methods that may be used to authenticate the user
+     * 
+     * @throws AuthenticationException thrown if no login handler meets the given requirements
+     */
+    protected Map<String, LoginHandler> determinePossibleLoginHandlers(LoginContext loginContext)
+            throws AuthenticationException {
+        Map<String, LoginHandler> supportedLoginHandlers = new HashMap<String, LoginHandler>(getProfileHandlerManager()
+                .getLoginHandlers());
+        LOG.trace("Supported login handlers: {}", supportedLoginHandlers);
+        LOG.trace("Requested authentication methods: {}", loginContext.getRequestedAuthenticationMethods());
+
+        Iterator<Entry<String, LoginHandler>> supportedLoginHandlerItr = supportedLoginHandlers.entrySet().iterator();
+        Entry<String, LoginHandler> supportedLoginHandler;
+        while (supportedLoginHandlerItr.hasNext()) {
+            supportedLoginHandler = supportedLoginHandlerItr.next();
+            if (!loginContext.getRequestedAuthenticationMethods().contains(supportedLoginHandler.getKey())) {
+                supportedLoginHandlerItr.remove();
+                continue;
+            }
+        }
+
+        if (supportedLoginHandlers.isEmpty()) {
+            LOG.error("No authentication method, requested by the service provider, is supported");
+            throw new AuthenticationException(
+                    "No authentication method, requested by the service provider, is supported");
+        }
+
+        return supportedLoginHandlers;
+    }
+
+    /**
+     * Filters out any login handler based on the requirement for forced authentication.
+     * 
+     * During forced authentication any handler that has not previously been used to authenticate the the user or any
+     * handlers that have been and support force re-authentication may be used. Filter out any of the other ones.
+     * 
+     * @param loginContext current login context
+     * @param activeAuthnMethods currently active authentication methods
+     * @param loginHandlers login handlers to filter
+     * 
+     * @throws ForceAuthenticationException thrown if no handlers remain after filtering
+     */
+    protected void filterByForceAuthentication(LoginContext loginContext,
+            Collection<AuthenticationMethodInformation> activeAuthnMethods, Map<String, LoginHandler> loginHandlers)
+            throws ForceAuthenticationException {
+
+        LoginHandler loginHandler;
+
+        if (activeAuthnMethods != null) {
+            for (AuthenticationMethodInformation activeAuthnMethod : activeAuthnMethods) {
+                loginHandler = loginHandlers.get(activeAuthnMethod.getAuthenticationMethod());
+                if (loginHandler != null && !loginHandler.supportsForceAuthentication()) {
+                    for (String handlerSupportedMethods : loginHandler.getSupportedAuthenticationMethods()) {
+                        loginHandlers.remove(handlerSupportedMethods);
+                    }
+                }
+            }
+        }
+
+        if (loginHandlers.isEmpty()) {
+            LOG.error("Force authentication required but no login handlers available to support it");
+            throw new ForceAuthenticationException();
+        }
+    }
+
+    /**
+     * Filters out any login handler that doesn't support passive authentication if the login context indicates passive
+     * authentication is required.
      * 
+     * @param loginContext current login context
+     * @param loginHandlers login handlers to filter
+     * 
+     * @throws PassiveAuthenticationException thrown if no handlers remain after filtering
+     */
+    protected void filterByPassiveAuthentication(LoginContext loginContext, Map<String, LoginHandler> loginHandlers)
+            throws PassiveAuthenticationException {
+        LoginHandler loginHandler;
+        Iterator<Entry<String, LoginHandler>> authnMethodItr = loginHandlers.entrySet().iterator();
+        while (authnMethodItr.hasNext()) {
+            loginHandler = authnMethodItr.next().getValue();
+            if (!loginHandler.supportsPassive()) {
+                authnMethodItr.remove();
+            }
+        }
+
+        if (loginHandlers.isEmpty()) {
+            LOG.error("Passive authentication required but no login handlers available to support it");
+            throw new PassiveAuthenticationException();
+        }
+    }
+
+    /**
+     * Authenticates the user with the given authentication method provided by the given login handler.
+     * 
+     * @param authnMethod the authentication method that will be used to authenticate the user
+     * @param logingHandler login handler that will authenticate user
+     * @param loginContext current login context
      * @param httpRequest current HTTP request
      * @param httpResponse current HTTP response
+     */
+    protected void authenticateUser(String authnMethod, LoginHandler logingHandler, LoginContext loginContext,
+            HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+
+        loginContext.setAuthenticationAttempted();
+        loginContext.setAuthenticationDuration(logingHandler.getAuthenticationDuration());
+        loginContext.setAuthenticationMethod(authnMethod);
+        loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
+        logingHandler.login(httpRequest, httpResponse);
+    }
+
+    /**
+     * Completes the authentication request using an existing, active, authentication method for the current user.
+     * 
      * @param authenticationMethod authentication method to use to complete the request
+     * @param httpRequest current HTTP request
+     * @param httpResponse current HTTP response
      */
-    protected void authenticateUserWithActiveMethod(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
-            AuthenticationMethodInformation authenticationMethod) {
+    protected void completeAuthenticationWithActiveMethod(AuthenticationMethodInformation authenticationMethod,
+            HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
         HttpSession httpSession = httpRequest.getSession();
 
         Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
@@ -195,77 +347,71 @@ public class AuthenticationEngine extends HttpServlet {
     }
 
     /**
-     * Performs the first part of user authentication. An authentication handler is determined, the login context is
-     * populated with some initial information, and control is forward to the selected handler so that it may
-     * authenticate the user.
+     * Completes the authentication process when and already active authentication mechanism wasn't used, that is, when
+     * the user was really authenticated.
+     * 
+     * The principal name set by the authentication handler is retrieved and pushed in to the login context, a
+     * Shibboleth session is created if needed, information indicating that the user has logged into the service is
+     * recorded and finally control is returned back to the profile handler.
      * 
+     * @param loginContext current login context
      * @param httpRequest current HTTP request
      * @param httpResponse current HTTP response
      */
-    protected void authenticateUserWithoutActiveMethod1(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
-        HttpSession httpSession = httpRequest.getSession();
-        LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-        LOG.debug("Selecting appropriate authentication method for request.");
-        Pair<String, LoginHandler> handler = getProfileHandlerManager().getAuthenticationHandler(loginContext);
+    protected void completeAuthenticationWithoutActiveMethod(LoginContext loginContext, HttpServletRequest httpRequest,
+            HttpServletResponse httpResponse) {
 
-        if (handler == null) {
+        String principalName = DatatypeHelper.safeTrimOrNullString((String) httpRequest
+                .getAttribute(LoginHandler.PRINCIPAL_NAME_KEY));
+        if (principalName == null) {
             loginContext.setPrincipalAuthenticated(false);
-            loginContext.setAuthenticationAttempted();
-            loginContext.setAuthenticationFailureMessage("No AuthenticationHandler satisfies the request from: "
-                    + loginContext.getRelyingPartyId());
-            LOG.error("No AuthenticationHandler satisfies the request from relying party: "
-                    + loginContext.getRelyingPartyId());
+            loginContext.setAuthenticationFailure(new AuthenticationException(
+                    "No principal name returned from authentication handler."));
+            LOG.error("No principal name returned from authentication method: "
+                    + loginContext.getAuthenticationMethod());
             returnToProfileHandler(loginContext, httpRequest, httpResponse);
             return;
         }
 
-        LOG.debug("Authentication method {} will be used to authenticate user.", handler.getFirst());
-        loginContext.setAuthenticationAttempted();
-        loginContext.setAuthenticationDuration(handler.getSecond().getAuthenticationDuration());
-        loginContext.setAuthenticationMethod(handler.getFirst());
-        loginContext.setAuthenticationEngineURL(HttpHelper.getRequestUriWithoutContext(httpRequest));
+        loginContext.setPrincipalAuthenticated(true);
+        loginContext.setPrincipalName(principalName);
+        loginContext.setAuthenticationInstant(new DateTime());
+
+        // 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);
+        }
+
+        updateUserSession(loginContext, httpRequest, httpResponse);
 
-        LOG.debug("Transferring control to authentication handler of type: {}", handler.getSecond().getClass()
-                .getName());
-        handler.getSecond().login(httpRequest, httpResponse);
+        LOG.debug("User {} authentication with authentication method {}", loginContext.getPrincipalName(), loginContext
+                .getAuthenticationMethod());
+
+        returnToProfileHandler(loginContext, httpRequest, httpResponse);
     }
 
     /**
-     * Performs the second part of user authentication. The principal name set by the authentication handler is
-     * retrieved and pushed in to the login context, a Shibboleth session is created if needed, information indicating
-     * that the user has logged into the service is recorded and finally control is returned back to the profile
-     * handler.
+     * Updates the user's Shibboleth session with authentication information. If no session exists a new one will be
+     * created.
      * 
+     * @param loginContext current login context
      * @param httpRequest current HTTP request
      * @param httpResponse current HTTP response
      */
-    protected void authenticateUserWithoutActiveMethod2(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
-        HttpSession httpSession = httpRequest.getSession();
-
-        String principalName = (String) httpRequest.getAttribute(LoginHandler.PRINCIPAL_NAME_KEY);
-        LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-        if (DatatypeHelper.isEmpty(principalName)) {
-            loginContext.setPrincipalAuthenticated(false);
-            loginContext.setAuthenticationFailureMessage("No principal name returned from authentication handler.");
-            LOG.error("No principal name returned from authentication method: "
-                    + loginContext.getAuthenticationMethod());
-            returnToProfileHandler(loginContext, httpRequest, httpResponse);
-            return;
-        }
-        loginContext.setPrincipalAuthenticated(true);
-        loginContext.setPrincipalName(principalName);
-        loginContext.setAuthenticationInstant(new DateTime());
-
+    protected void updateUserSession(LoginContext loginContext, HttpServletRequest httpRequest,
+            HttpServletResponse httpResponse) {
         Session shibSession = (Session) httpRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
         if (shibSession == null) {
-            LOG.debug("Creating shibboleth session for principal {}", principalName);
+            LOG.debug("Creating shibboleth session for principal {}", loginContext.getPrincipalName());
             shibSession = (Session) getSessionManager().createSession(loginContext.getPrincipalName());
             loginContext.setSessionID(shibSession.getSessionID());
             addSessionCookie(httpRequest, httpResponse, shibSession);
         }
 
         LOG.debug("Recording authentication and service information in Shibboleth session for principal: {}",
-                principalName);
+                loginContext.getPrincipalName());
         Subject subject = (Subject) httpRequest.getAttribute(LoginHandler.SUBJECT_KEY);
         String authnMethod = (String) httpRequest.getAttribute(LoginHandler.AUTHENTICATION_METHOD_KEY);
         if (DatatypeHelper.isEmpty(authnMethod)) {
@@ -280,56 +426,6 @@ public class AuthenticationEngine extends HttpServlet {
         ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
                 authnMethodInfo);
         shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
-
-        returnToProfileHandler(loginContext, httpRequest, httpResponse);
-    }
-
-    /**
-     * Gets the authentication method, currently active for the user, that also meets the requirements expressed by the
-     * login context. If a method is returned the user does not need to authenticate again, if null is returned then the
-     * user must be authenticated.
-     * 
-     * @param loginContext user login context
-     * @param shibSession user's shibboleth session
-     * 
-     * @return active authentication method that meets authentication requirements or null
-     */
-    protected AuthenticationMethodInformation getUsableExistingAuthenticationMethod(LoginContext loginContext,
-            Session shibSession) {
-
-        if (shibSession == null) {
-            LOG.debug("No existing authentication methods due to the lack of existing IdP sessions");
-            return null;
-        }
-
-        if (loginContext.getForceAuth()) {
-            LOG.debug("Request for forced re-authentication, no existing authentication method considered usable");
-            return null;
-        }
-
-        List<String> preferredAuthnMethods = loginContext.getRequestedAuthenticationMethods();
-        AuthenticationMethodInformation authnMethodInformation = null;
-        if (preferredAuthnMethods == null || preferredAuthnMethods.size() == 0) {
-            for (AuthenticationMethodInformation info : shibSession.getAuthenticationMethods().values()) {
-                if (!info.isExpired()) {
-                    authnMethodInformation = info;
-                    break;
-                }
-            }
-        } else {
-            for (String preferredAuthnMethod : preferredAuthnMethods) {
-                if (shibSession.getAuthenticationMethods().containsKey(preferredAuthnMethod)) {
-                    AuthenticationMethodInformation info = shibSession.getAuthenticationMethods().get(
-                            preferredAuthnMethod);
-                    if (!info.isExpired()) {
-                        authnMethodInformation = info;
-                        break;
-                    }
-                }
-            }
-        }
-
-        return authnMethodInformation;
     }
 
     /**
diff --git a/src/edu/internet2/middleware/shibboleth/idp/authn/AuthenticationException.java b/src/edu/internet2/middleware/shibboleth/idp/authn/AuthenticationException.java
new file mode 100644 (file)
index 0000000..d0d5ffb
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * An exception indicating a problem with user authentication.
+ */
+public class AuthenticationException extends Exception {
+
+    /** Serial version UID. */
+    private static final long serialVersionUID = -889930177786373711L;
+
+    /** Constructor. */
+    public AuthenticationException() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     */
+    public AuthenticationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public AuthenticationException(Exception wrappedException) {
+        super(wrappedException);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public AuthenticationException(String message, Exception wrappedException) {
+        super(message, wrappedException);
+    }
+}
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/idp/authn/ForceAuthenticationException.java b/src/edu/internet2/middleware/shibboleth/idp/authn/ForceAuthenticationException.java
new file mode 100644 (file)
index 0000000..ce94ed1
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating that forced authentication was requested but could not be performed.
+ */
+public class ForceAuthenticationException extends AuthenticationException {
+
+    /** Serial version UID. */
+    private static final long serialVersionUID = 6955738708697468055L;
+
+    /** Constructor. */
+    public ForceAuthenticationException() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     */
+    public ForceAuthenticationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public ForceAuthenticationException(Exception wrappedException) {
+        super(wrappedException);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public ForceAuthenticationException(String message, Exception wrappedException) {
+        super(message, wrappedException);
+    }
+}
\ No newline at end of file
index 47f6a69..4232da1 100644 (file)
@@ -79,8 +79,8 @@ public class LoginContext implements Serializable {
     /** Did authentication succeed? */
     private boolean principalAuthenticated;
 
-    /** Optional failure message. */
-    private String authnFailureMessage;
+    /** Exception that occured during authentication. */
+    private AuthenticationException authnException;
 
     /** The instant of authentication. */
     private DateTime authnInstant;
@@ -137,7 +137,7 @@ public class LoginContext implements Serializable {
      * 
      * @return <code>true</code> if the authentication manager must re-authenticate the user.
      */
-    public boolean getForceAuth() {
+    public boolean isForceAuthRequired() {
         return forceAuth;
     }
 
@@ -146,7 +146,7 @@ public class LoginContext implements Serializable {
      * 
      * @return <code>true</code> if the authentication manager must not interact with the users UI.
      */
-    public boolean getPassiveAuth() {
+    public boolean isPassiveAuthRequired() {
         return passiveAuth;
     }
 
@@ -155,7 +155,7 @@ public class LoginContext implements Serializable {
      * 
      * @param force if the authentication manager must re-authenticate the user.
      */
-    public void setForceAuth(boolean force) {
+    public void setForceAuthRequired(boolean force) {
         forceAuth = force;
     }
 
@@ -164,7 +164,7 @@ public class LoginContext implements Serializable {
      * 
      * @param passive if the authentication manager must not interact with the users UI.
      */
-    public void setPassiveAuth(boolean passive) {
+    public void setPassiveAuthRequired(boolean passive) {
         passiveAuth = passive;
     }
 
@@ -210,21 +210,21 @@ public class LoginContext implements Serializable {
     }
 
     /**
-     * Sets the optional authentication failure message.
+     * Sets the error that occurred during authentication.
      * 
-     * @param failureMessage A description of why authN failed.
+     * @param error error that occurred during authentication
      */
-    public void setAuthenticationFailureMessage(String failureMessage) {
-        authnFailureMessage = failureMessage;
+    public void setAuthenticationFailure(AuthenticationException error) {
+        authnException = error;
     }
 
     /**
-     * Returns the optional authentication failure message.
+     * Gets the error that occurred during authentication.
      * 
-     * @return The failure message, or <code>null</code> is none was set.
+     * @return error that occurred during authentication
      */
-    public String getAuthenticationFailureMessage() {
-        return authnFailureMessage;
+    public AuthenticationException getAuthenticationFailure() {
+        return authnException;
     }
 
     /**
diff --git a/src/edu/internet2/middleware/shibboleth/idp/authn/PassiveAuthenticationException.java b/src/edu/internet2/middleware/shibboleth/idp/authn/PassiveAuthenticationException.java
new file mode 100644 (file)
index 0000000..beb0135
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating that passive authentication was requested but could not be performed.
+ */
+public class PassiveAuthenticationException extends AuthenticationException {
+
+    /** Serial version UID. */
+    private static final long serialVersionUID = 8333401210912649476L;
+
+    /** Constructor. */
+    public PassiveAuthenticationException() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     */
+    public PassiveAuthenticationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public PassiveAuthenticationException(Exception wrappedException) {
+        super(wrappedException);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public PassiveAuthenticationException(String message, Exception wrappedException) {
+        super(message, wrappedException);
+    }
+}
\ No newline at end of file
index 7443816..543e851 100644 (file)
@@ -83,8 +83,8 @@ public class Saml2LoginContext extends LoginContext implements Serializable {
         authnRequest = request;
         serialAuthnRequest = serializeRequest(request);
         
-        setForceAuth(authnRequest.isForceAuthn());
-        setPassiveAuth(authnRequest.isPassive());
+        setForceAuthRequired(authnRequest.isForceAuthn());
+        setPassiveAuthRequired(authnRequest.isPassive());
         getRequestedAuthenticationMethods().addAll(extractRequestedAuthenticationMethods());
     }
 
index 928b95a..a32863d 100644 (file)
@@ -54,8 +54,8 @@ public class IdPProfileHandlerManager extends BaseReloadableService implements P
     /** Map of request paths to profile handlers. */
     private Map<String, AbstractRequestURIMappedProfileHandler> profileHandlers;
 
-    /** Map of authentication methods to authentication handlers. */
-    private Map<String, LoginHandler> authenticationHandlers;
+    /** Map of authentication methods to login handlers. */
+    private Map<String, LoginHandler> loginHandlers;
 
     /**
      * Constructor. Configuration resources are not monitored for changes.
@@ -65,7 +65,7 @@ public class IdPProfileHandlerManager extends BaseReloadableService implements P
     public IdPProfileHandlerManager(List<Resource> configurations) {
         super(configurations);
         profileHandlers = new HashMap<String, AbstractRequestURIMappedProfileHandler>();
-        authenticationHandlers = new HashMap<String, LoginHandler>();
+        loginHandlers = new HashMap<String, LoginHandler>();
     }
 
     /**
@@ -79,7 +79,7 @@ public class IdPProfileHandlerManager extends BaseReloadableService implements P
     public IdPProfileHandlerManager(List<Resource> configurations, Timer timer, long pollingFrequency) {
         super(timer, configurations, pollingFrequency);
         profileHandlers = new HashMap<String, AbstractRequestURIMappedProfileHandler>();
-        authenticationHandlers = new HashMap<String, LoginHandler>();
+        loginHandlers = new HashMap<String, LoginHandler>();
     }
 
     /** {@inheritDoc} */
@@ -130,54 +130,12 @@ public class IdPProfileHandlerManager extends BaseReloadableService implements P
     }
 
     /**
-     * Gets the authentication handler appropriate for the given loging context. The mechanism used to determine the
-     * "appropriate" handler is implementation specific.
-     * 
-     * @param loginContext current login context
-     * 
-     * @return authentication method URI and handler appropriate for given login context
-     */
-    public Pair<String, LoginHandler> getAuthenticationHandler(LoginContext loginContext) {
-        log.debug("{}: Looking up authentication method for relying party {}", getId(), loginContext
-                .getRelyingPartyId());
-
-        List<String> requestedMethods = loginContext.getRequestedAuthenticationMethods();
-        if (requestedMethods != null) {
-            LoginHandler candidateHandler;
-            for (String requestedMethod : requestedMethods) {
-                log.debug(getId() + "{}: Checking for authentication handler for method {}", getId(), requestedMethod);
-                candidateHandler = authenticationHandlers.get(requestedMethod);
-                if (candidateHandler != null) {
-                    log.debug(getId()
-                            + "{}: Authentication handler for method {} found.  Checking if it meets othe criteria.",
-                            getId(), requestedMethod);
-
-                    if (loginContext.getPassiveAuth() && !candidateHandler.supportsPassive()) {
-                        log.debug("{}: Authentication handler for method {} does not meet required support for passive auth.  Skipping it",
-                                        getId(), requestedMethod);
-                        continue;
-                    }
-
-                    log.debug(getId() + "{}: Authentication handler for method {}  meets all requirements, using it.",
-                            getId(), requestedMethod);
-                    return new Pair<String, LoginHandler>(requestedMethod, candidateHandler);
-                }
-            }
-        } else {
-            log.error("{}: No requested authentication methods for relying party {}", getId(), loginContext
-                    .getRelyingPartyId());
-        }
-
-        return null;
-    }
-
-    /**
      * Gets the registered authentication handlers.
      * 
      * @return registered authentication handlers
      */
-    public Map<String, LoginHandler> getAuthenticationHandlers() {
-        return authenticationHandlers;
+    public Map<String, LoginHandler> getLoginHandlers() {
+        return loginHandlers;
     }
 
     /** {@inheritDoc} */
@@ -233,7 +191,7 @@ public class IdPProfileHandlerManager extends BaseReloadableService implements P
         String[] authnBeanNames = newServiceContext.getBeanNamesForType(LoginHandler.class);
         log.debug("{}: Loading {} new authentication handlers.", getId(), authnBeanNames.length);
 
-        authenticationHandlers.clear();
+        loginHandlers.clear();
         LoginHandler authnHandler;
         for (String authnBeanName : authnBeanNames) {
             authnHandler = (LoginHandler) newServiceContext.getBean(authnBeanName);
@@ -241,7 +199,7 @@ public class IdPProfileHandlerManager extends BaseReloadableService implements P
                     authnHandler.getSupportedAuthenticationMethods());
 
             for (String authnMethod : authnHandler.getSupportedAuthenticationMethods()) {
-                authenticationHandlers.put(authnMethod, authnHandler);
+                loginHandlers.put(authnMethod, authnHandler);
             }
         }
     }
index acfc0bd..e211901 100644 (file)
@@ -243,11 +243,12 @@ public class ShibbolethSSOProfileHandler extends AbstractSAML1ProfileHandler {
 
         Response samlResponse;
         try {
-            if (loginContext.getPrincipalName() == null) {
+            if (loginContext.getAuthenticationFailure() != null) {
+                log.error("User authentication failed with the following error: {}", loginContext
+                        .getAuthenticationFailure().toString());
                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "User failed authentication"));
-                throw new ProfileException("User failed authentication");
             }
-
+            
             resolveAttributes(requestContext);
 
             ArrayList<Statement> statements = new ArrayList<Statement>();
index 53b6cba..f3f5c61 100644 (file)
@@ -64,7 +64,9 @@ import edu.internet2.middleware.shibboleth.common.relyingparty.ProfileConfigurat
 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.SSOConfiguration;
 import edu.internet2.middleware.shibboleth.common.util.HttpHelper;
+import edu.internet2.middleware.shibboleth.idp.authn.ForceAuthenticationException;
 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
+import edu.internet2.middleware.shibboleth.idp.authn.PassiveAuthenticationException;
 import edu.internet2.middleware.shibboleth.idp.authn.Saml2LoginContext;
 import edu.internet2.middleware.shibboleth.idp.session.Session;
 
@@ -220,16 +222,17 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
 
         Response samlResponse;
         try {
-            if (loginContext.getPrincipalName() == null) {
-                log.error("User's login context did not contain a principal, user considered unauthenticiated.");
-                if (loginContext.getPassiveAuth()) {
+            if (loginContext.getAuthenticationFailure() != null) {
+                log.error("User authentication failed with the following error: {}", loginContext
+                        .getAuthenticationFailure().toString());
+
+                if (loginContext.getAuthenticationFailure() instanceof PassiveAuthenticationException) {
                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.NO_PASSIVE_URI,
                             null));
                 } else {
                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.AUTHN_FAILED_URI,
                             null));
                 }
-                throw new ProfileException("User failed authentication");
             }
 
             if (requestContext.getSubjectNameIdentifier() != null) {
@@ -379,7 +382,7 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
                 requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
                 requestContext.setPeerEntityRoleMetadata(relyingPartyMetadata
                         .getSPSSODescriptor(SAMLConstants.SAML20P_NS));
-            }else{
+            } else {
                 log.error("Unable to locate metadata for relying party ({})", relyingPartyId);
                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
                         "Error locating relying party metadata"));
@@ -444,7 +447,7 @@ public class SSOProfileHandler extends AbstractSAML2ProfileHandler {
         statement.setAuthnInstant(loginContext.getAuthenticationInstant());
 
         Session session = getUserSession(requestContext.getInboundMessageTransport());
-        if(session != null){
+        if (session != null) {
             statement.setSessionIndex(session.getSessionID());
         }
 
index 4e7eb8e..2340350 100644 (file)
@@ -65,8 +65,8 @@ public class SAML2SSOTestCase extends BaseConf1TestCase {
 
         assertNotNull(loginContext);
         assertEquals(false, loginContext.getAuthenticationAttempted());
-        assertEquals(false, loginContext.getForceAuth());
-        assertEquals(false, loginContext.getPassiveAuth());
+        assertEquals(false, loginContext.isForceAuthRequired());
+        assertEquals(false, loginContext.isPassiveAuthRequired());
         assertEquals("/AuthnEngine", loginContext.getAuthenticationEngineURL());
         assertEquals("/saml2/POST/SSO", loginContext.getProfileHandlerURL());
         assertEquals("urn:example.org:sp1", loginContext.getRelyingPartyId());
index c0d87de..2209213 100644 (file)
@@ -57,8 +57,8 @@ public class ShibbolethSSOTestCase extends BaseConf1TestCase {
 
         assertNotNull(loginContext);
         assertEquals(false, loginContext.getAuthenticationAttempted());
-        assertEquals(false, loginContext.getForceAuth());
-        assertEquals(false, loginContext.getPassiveAuth());
+        assertEquals(false, loginContext.isForceAuthRequired());
+        assertEquals(false, loginContext.isPassiveAuthRequired());
         assertEquals("/AuthnEngine", loginContext.getAuthenticationEngineURL());
         assertEquals("/shibboleth/SSO", loginContext.getProfileHandlerURL());
         assertEquals("urn:example.org:sp1", loginContext.getRelyingPartyId());