username/pass auth handler - initial rev.
authordmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Fri, 18 May 2007 19:28:33 +0000 (19:28 +0000)
committerdmorr <dmorr@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Fri, 18 May 2007 19:28:33 +0000 (19:28 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@2199 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/idp/authn/impl/UsernamePasswordAuthenticationHandler.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/authn/impl/UsernamePasswordAuthenticationServlet.java [new file with mode: 0644]

diff --git a/src/edu/internet2/middleware/shibboleth/idp/authn/impl/UsernamePasswordAuthenticationHandler.java b/src/edu/internet2/middleware/shibboleth/idp/authn/impl/UsernamePasswordAuthenticationHandler.java
new file mode 100644 (file)
index 0000000..08fc167
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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.authn.impl;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationHandler;
+
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+
+/**
+ * Authenticate a username and password against a JAAS source.
+ * 
+ * This {@link AuthenticationHandler} 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".
+ */
+public class UsernamePasswordAuthenticationHandler implements
+               AuthenticationHandler {
+
+       /** Key in an HttpSession for the JAAS configuration name. */
+       public static final String JAAS_CONFIG_NAME = "UsernamePasswordAuthenticationHandler.JAAS_CONFIG_NAME";
+
+       /** Key in an HttpSession for the username. */
+       public static final String USERNAME = "UsernamePasswordAuthenticationHandler.USERNAME";
+
+       /** Key in an HttpSession for the authentication instant. */
+       public static final String AUTHN_INSTANT = "UsernamePasswordAuthenticationHandler.AUTHN_INSTANT";
+
+       private static final Logger log = Logger
+                       .getLogger(UsernamePasswordAuthenticationHandler.class);
+
+       /** The name of the JAAS Configuration to use. */
+       protected String jaasConfigurationName;
+
+       /** The name of the login page. */
+       protected String loginURL;
+
+       /** The authN duration, in seconds. */
+       protected int authnDuration;
+
+       /** The URI of the AuthnContextDeclRef or the AuthnContextClass */
+       private String authnMethodURI = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport";
+
+       public UsernamePasswordAuthenticationHandler() {
+       }
+
+       /** @{inheritDoc} */
+       public boolean supportsPassive() {
+               return true;
+       }
+
+       /** @{inheritDoc} */
+       public boolean supportsForceAuthentication() {
+               return true;
+       }
+
+       /** {@inheritDoc} */
+       public void login(
+                       final HttpServletRequest request,
+                       final HttpServletResponse response,
+                       final edu.internet2.middleware.shibboleth.idp.authn.LoginContext loginCtx) {
+
+               HttpSession session = request.getSession();
+
+               // these fields will need to be set, regardless of how we branch.
+               loginCtx.setAuthenticationAttempted();
+               loginCtx.setAuthenticationMethod(authnMethodURI);
+
+               // If forceAuth is set, we must forward to the login JSP.
+               if (loginCtx.getForceAuth()) {
+
+                       if (loginCtx.getPassiveAuth()) {
+                               log
+                                               .error("UsernamePasswordAuthenticationHandler: Unable to authenticate user: both forceAuthN and passiveAuthnN are set in the login context.");
+                               redirectControl(loginCtx.getAuthenticationManagerURL(),
+                                               "AuthenticationManager", request, response);
+                       }
+
+                       session.setAttribute(JAAS_CONFIG_NAME, jaasConfigurationName);
+                       redirectControl(loginURL, "login page", request, response);
+               }
+
+               // If the user has already been authenticated, forceAuth is not set,
+               // and the authentication hasn't expired, then populate the LoginCtx
+               // and return control to the AuthenticationManager.
+               // Otherwise, redirect the user to loginJSPURL to collect a username and
+               // password.
+
+               // implementation note: There is a race condition here, but I'm not sure
+               // how to avoid it. I need a way to instantiate a lock in the session to
+               // protect the
+               // username and authnInstant fields.
+
+               Object o = session.getAttribute(USERNAME);
+               if (!(o instanceof String)) {
+                       log
+                                       .debug("UsernamePasswordAuthenticationHandler: Username attribute found in HttpSession, but it is not a String.");
+
+                       redirectControl(loginURL, "login page", request, response);
+               }
+
+               String username = (String) o;
+
+               o = session.getAttribute(AUTHN_INSTANT);
+               if (!(o instanceof DateTime)) {
+                       log
+                                       .debug("UsernamePasswordAuthenticationHandler: AuthnInstant attribute found in HttpSession for user "
+                                                       + username + ", but it is not a DateTime.");
+
+                       redirectControl(loginURL, "login page", request, response);
+               }
+
+               DateTime authnInstant = (DateTime) o;
+               DateTime authnExpires = authnInstant.plusSeconds(authnDuration);
+               DateTime now = new DateTime();
+               if (now.isAfter(authnExpires)) {
+                       log
+                                       .info("UsernamePasswordAuthenticationHandler: Authentication has expired for user "
+                                                       + username);
+                       redirectControl(loginURL, "login page", request, response);
+               }
+
+               // the current authentication information is still valid, so return it.
+               loginCtx.setAuthenticationOK(true);
+               loginCtx.setUserID(username);
+               loginCtx.setAuthenticationInstant(authnInstant);
+
+               // XXX: adjust for the appropriate units?
+               loginCtx.setAuthenticationDuration(authnDuration);
+
+       }
+
+       /** {@inheritDoc} */
+       public void logout(final HttpServletRequest request,
+                       final HttpServletResponse response, String principal) {
+               return;
+       }
+
+       /**
+        * Set the name of the JAAS Configuration to use for user authentication.
+        * 
+        * @param configurationName
+        *            The name of the JAAS Configuration entry.
+        */
+       public void setJAASConfigurationName(String configurationName) {
+               jaasConfigurationName = configurationName;
+       }
+
+       /**
+        * Get the name of the JAAS Configuraiton to use for user authentication.
+        * 
+        * @return The name of the JAAS Configuration entry.
+        */
+       public String getJAASConfiguraitonName() {
+               return jaasConfigurationName;
+       }
+
+       /**
+        * Set the duration of the authentication.
+        * 
+        * @param duration
+        *            The duration, in seconds, of the authentication.
+        */
+       public void setAuthNDuration(int duration) {
+               authnDuration = duration;
+       }
+
+       /**
+        * Get the duration of the authentication.
+        * 
+        * @return The duration, in seconds, of the authentication.
+        */
+       public int getAuthNDuration() {
+               return authnDuration;
+       }
+
+       /**
+        * Return control to the AuthNManager.
+        * 
+        * @param url
+        *            The URL to which control should be redirected.
+        * @param urlDescription
+        *            An optional textual description of <code>url</code>.
+        * @param request
+        *            The HttpServletRequest.
+        * @param response
+        *            The HttpServletResponse.
+        */
+       protected void redirectControl(String url, String urlDescription,
+                       final HttpServletRequest request, final HttpServletResponse response) {
+
+               try {
+                       RequestDispatcher dispatcher = request.getRequestDispatcher(url);
+                       dispatcher.forward(request, response);
+               } catch (ServletException ex) {
+                       log.error(
+                                       "UsernamePasswordAuthenticationHandler: Error returning control to "
+                                                       + urlDescription, ex);
+               } catch (IOException ex) {
+                       log.error(
+                                       "UsernamePasswordAuthenticationHandler: Error returning control to "
+                                                       + urlDescription, ex);
+               }
+       }
+}
diff --git a/src/edu/internet2/middleware/shibboleth/idp/authn/impl/UsernamePasswordAuthenticationServlet.java b/src/edu/internet2/middleware/shibboleth/idp/authn/impl/UsernamePasswordAuthenticationServlet.java
new file mode 100644 (file)
index 0000000..043a130
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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.authn.impl;
+
+import java.io.IOException;
+
+import java.security.Principal;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
+
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+
+/**
+ * This servlet should be protected by a filter which populates REMOTE_USER. The
+ * serlvet will then set the remote user field in a LoginContext.
+ */
+public class UsernamePasswordAuthenticationServlet extends HttpServlet {
+
+       // Implementation note:
+       // Pay attention to namespaces in this file. There are two classes named
+       // LoginContext.
+       // One is used by the IdP
+       // (edu.internet2.middleware.shibboleth.idp.authn.LoginContext).
+       // The other is used by JAAS (javax.security.auth.login.LoginContext).
+       //
+
+       /**
+        * Inner class to hold the username and password.
+        * 
+        * The web form will give us a username and password. We call out to a JAAS
+        * mechanism(s) to authenticate the user. This inner class implements the
+        * {@link CallbackHandler} interface to deliver the username and password to
+        * a JAAS {@link LoginModule}.
+        * 
+        * Note, this class only handles the {@link NameCallback} and
+        * {@link PasswordCallback}.
+        */
+       protected class SimpleCallbackHandler implements CallbackHandler {
+
+               private String uname;
+
+               private String pass;
+
+               /**
+                * @param username
+                *            The username
+                * @param password
+                *            The password
+                */
+               public SimpleCallbackHandler(String username, String password) {
+                       uname = username;
+                       pass = password;
+               }
+
+               /**
+                * Handle a callback.
+                * 
+                * @param callbacks
+                *            The list of callbacks to process.
+                * 
+                * @throws UnsupportedCallbackException
+                *             If callbacks has a callback other than
+                *             {@link NameCallback} or {@link PasswordCallback}.
+                */
+               public void handle(final Callback[] callbacks)
+                               throws UnsupportedCallbackException {
+
+                       if (callbacks == null || callbacks.length == 0) {
+                               return;
+                       }
+
+                       for (Callback cb : callbacks) {
+                               if (cb instanceof NameCallback) {
+                                       NameCallback ncb = (NameCallback) cb;
+                                       ncb.setName(uname);
+                               } else if (cb instanceof PasswordCallback) {
+                                       PasswordCallback pcb = (PasswordCallback) cb;
+                                       pcb.setPassword(pass.toCharArray());
+                               } else {
+                                       throw new UnsupportedCallbackException(cb,
+                                                       "This class only handles NameCallback and PasswordCallback");
+                               }
+                       }
+               }
+       }
+
+       /** Login form element containing the username. */
+       protected static final String LOGIN_FORM_USERNAME = "username";
+
+       /** Login form element containing the password. */
+       protected static final String LOGIN_FORM_PASSWORD = "password";
+
+       private static final Logger log = Logger
+                       .getLogger(RemoteUserAuthServlet.class);
+
+       public UsernamePasswordAuthenticationServlet() {
+       }
+
+       public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        
+        HttpSession httpSession = request.getSession();
+        
+        Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
+        if (!(o instanceof LoginContext)) {
+            log.error("RemoteUSerAuthServlet - Invalid login context object -- object is not an instance of LoginContext");
+            return; // where this will return to, I don't know.
+        }
+        
+        LoginContext loginContext = (LoginContext)o;
+        
+        o = httpSession.getAttribute(UsernamePasswordAuthenticationHandler.JAAS_CONFIG_NAME);
+        httpSession.removeAttribute(UsernamePasswordAuthenticationHandler.JAAS_CONFIG_NAME);
+        if (!(o instanceof String)) {
+            log.error("UsernamePasswordAuthenticationServlet: Unable to authenticate user - Invalid JAAS configuration name specified: " + o.toString());
+            
+            loginContext.setAuthenticationOK(false);
+            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
+            redirectControl(loginContext.getAuthenticationManagerURL(), "AuthenticationManager", request, response);
+        }
+        
+        String jassConfiguration = (String)o;
+        
+        String username;
+        String password;
+        
+        o = request.getAttribute(LOGIN_FORM_USERNAME);
+        if (!(o instanceof String)) {
+            log.error("UsernamePasswordAuthenticationServlet: Login form's username is not a String.");
+            loginContext.setAuthenticationOK(false);
+            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
+            loginContext.setAuthenticationOK(false);
+            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
+            redirectControl(loginContext.getAuthenticationManagerURL(), "AuthenticationManager", request, response);
+            
+        }
+        username = (String)o;
+        
+        o = request.getAttribute(LOGIN_FORM_PASSWORD);
+        if (!(o instanceof String)) {
+            log.error("UsernamePasswordAuthenticationServlet: Login form's password is not a String.");
+            loginContext.setAuthenticationOK(false);
+            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
+            loginContext.setAuthenticationOK(false);
+            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
+            redirectControl(loginContext.getAuthenticationManagerURL(), "AuthenticationManager", request, response);
+            
+        }
+        password = (String)o;
+        
+        authenticateUser(username, password, jassConfiguration, loginContext);
+        password = null
+        redirectControl(loginContext.getAuthenticationManagerURL(), "AuthenticationManager", request, response);
+    }
+
+       /**
+        * Return control to the AuthNManager.
+        * 
+        * @param url
+        *            The URL to which control should be redirected.
+        * @param urlDescription
+        *            An optional textual description of <code>url</code>.
+        * @param request
+        *            The HttpServletRequest.
+        * @param response
+        *            The HttpServletResponse.
+        */
+       protected void redirectControl(String url, String urlDescription,
+                       final HttpServletRequest request, final HttpServletResponse response) {
+
+               try {
+                       RequestDispatcher dispatcher = request.getRequestDispatcher(url);
+                       dispatcher.forward(request, response);
+               } catch (ServletException ex) {
+                       log.error(
+                                       "UsernamePasswordAuthenticationServlet: Error returning control to "
+                                                       + urlDescription, ex);
+               } catch (IOException ex) {
+                       log.error(
+                                       "UsernamePasswordAuthenticationServlet: Error returning control to "
+                                                       + urlDescription, ex);
+               }
+       }
+
+       /**
+        * Authenticate a username and password against JAAS.
+        * 
+        * @param username
+        *            The username
+        * @param password
+        *            The password.
+        * @param jaasConfigurationName
+        *            The name of the JAAS configuration entry.
+        * @param idpLoginCtx
+        *            The authentication request's LoginContext
+        */
+       protected void authenticateUser(
+                       String username,
+                       String password,
+                       String jaasConfigurationName,
+                       final edu.internet2.middleware.shibboleth.idp.authn.LoginContext idpLoginCtx) {
+
+               try {
+                       SimpleCallbackHandler cbh = new SimpleCallbackHandler(username,
+                                       password);
+
+                       javax.security.auth.login.LoginContext jaasLoginCtx = new javax.security.auth.login.LoginContext(
+                                       jaasConfigurationName, cbh);
+
+                       idpLoginCtx.setAuthenticationAttempted();
+                       idpLoginCtx.setAuthenticationInstant(new DateTime());
+
+                       jaasLoginCtx.login();
+                       log
+                                       .debug("UsernamePasswordAuthenticationServlet: Authentication successful for "
+                                                       + username);
+                       idpLoginCtx.setAuthenticationOK(true);
+
+                       // if JAAS returned multiple usernames, only use the first one.
+                       Set<Principal> principals = jaasLoginCtx.getSubject()
+                                       .getPrincipals();
+                       Principal[] temp = new Principal[principals.size()];
+                       principals.toArray(temp);
+                       idpLoginCtx.setUserID(temp[0].getName());
+
+               } catch (LoginException ex) {
+                       log
+                                       .error(
+                                                       "UsernamePasswordAuthenticationServlet: Error authenticating user.",
+                                                       ex);
+                       idpLoginCtx.setAuthenticationOK(false);
+               }
+       }
+}