Java tutorial
/*L * Copyright Ekagra Software Technologies Ltd. * Copyright SAIC, SAIC-Frederick * * Distributed under the OSI-approved BSD 3-Clause License. * See http://ncip.github.com/common-security-module/LICENSE.txt for details. */ package gov.nih.nci.security.cgmm.authenticators; import gov.nih.nci.security.cgmm.CGMMManager; import gov.nih.nci.security.cgmm.CGMMManagerImpl; import gov.nih.nci.security.cgmm.exceptions.CGMMAuthenticationURLException; import gov.nih.nci.security.cgmm.exceptions.CGMMConfigurationException; import gov.nih.nci.security.cgmm.exceptions.CGMMGridAuthenticationServiceException; import gov.nih.nci.security.cgmm.exceptions.CGMMGridDorianException; import gov.nih.nci.security.cgmm.exceptions.CGMMInputException; import java.io.IOException; import java.io.InputStream; import java.security.Principal; import java.util.Enumeration; import java.util.Iterator; import java.util.Locale; import javax.servlet.RequestDispatcher; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Realm; import org.apache.catalina.Session; import org.apache.catalina.authenticator.AuthenticatorBase; import org.apache.catalina.authenticator.Constants; import org.apache.catalina.authenticator.SavedRequest; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.deploy.LoginConfig; import org.apache.catalina.realm.GenericPrincipal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.coyote.ActionCode; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.CharChunk; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.MimeHeaders; import org.globus.gsi.GlobusCredential; /** * An <b>Authenticator</b> and <b>Valve</b> implementation of CAGRID FORM BASED * Authentication, as described in the Servlet API Specification, Version 2.2. * * This class is extended based on the FormAuthenticator. * * */ public class CaGridFormAuthenticator extends AuthenticatorBase { private static Log log = LogFactory.getLog(CaGridFormAuthenticator.class); // ----------------------------------------------------- Instance Variables /** * Descriptive information about this implementation. */ protected static final String info = "gov.nih.nci.security.cgmm.authenticators.CaGridFormAuthenticator/1.0"; /** * Character encoding to use to read the username and password parameters * from the request. If not set, the encoding of the request body will be * used. */ protected String characterEncoding = null; // ------------------------------------------------------------- Properties /** * Return descriptive information about this Valve implementation. */ public String getInfo() { return (info); } /** * Return the character encoding to use to read the username and password. */ public String getCharacterEncoding() { return characterEncoding; } /** * Set the character encoding to be used to read the username and password. */ public void setCharacterEncoding(String encoding) { characterEncoding = encoding; } // --------------------------------------------------------- Public Methods /** * Authenticate the user making this request, based on the specified * login configuration. Return <code>true</code> if any specified * constraint has been satisfied, or <code>false</code> if we have * created a response challenge already. * * @param request Request we are processing * @param response Response we are creating * @param config Login configuration describing how authentication * should be performed * * @exception IOException if an input/output error occurs */ public boolean authenticate(Request request, Response response, LoginConfig config) throws IOException { // References to objects we will need later Session session = null; // Have we already authenticated someone? Principal principal = request.getUserPrincipal(); String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE); if (principal != null) { if (log.isDebugEnabled()) log.debug("Already authenticated '" + principal.getName() + "'"); // Associate the session with any existing SSO session if (ssoId != null) associate(ssoId, request.getSessionInternal(true)); return (true); } // Is there an SSO session against which we can try to reauthenticate? if (ssoId != null) { if (log.isDebugEnabled()) log.debug("SSO Id " + ssoId + " set; attempting " + "reauthentication"); // Try to reauthenticate using data cached by SSO. If this fails, // either the original SSO logon was of DIGEST or SSL (which // we can't reauthenticate ourselves because there is no // cached username and password), or the realm denied // the user's reauthentication for some reason. // In either case we have to prompt the user for a logon */ if (reauthenticateFromSSO(ssoId, request)) return true; } // Have we authenticated this user before but have caching disabled? if (!cache) { session = request.getSessionInternal(true); if (log.isDebugEnabled()) log.debug("Checking for reauthenticate in session " + session); String username = (String) session.getNote(Constants.SESS_USERNAME_NOTE); String password = (String) session.getNote(Constants.SESS_PASSWORD_NOTE); if ((username != null) && (password != null)) { if (log.isDebugEnabled()) log.debug("Reauthenticating username '" + username + "'"); principal = context.getRealm().authenticate(username, password); if (principal != null) { session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal); if (!matchRequest(request)) { register(request, response, principal, Constants.FORM_METHOD, username, password); return (true); } } if (log.isDebugEnabled()) log.debug("Reauthentication failed, proceed normally"); } } // Is this the re-submit of the original request URI after successful // authentication? If so, forward the *original* request instead. if (matchRequest(request)) { session = request.getSessionInternal(true); if (log.isDebugEnabled()) log.debug("Restore request from session '" + session.getIdInternal() + "'"); principal = (Principal) session.getNote(Constants.FORM_PRINCIPAL_NOTE); register(request, response, principal, Constants.FORM_METHOD, (String) session.getNote(Constants.SESS_USERNAME_NOTE), (String) session.getNote(Constants.SESS_PASSWORD_NOTE)); // If we're caching principals we no longer need the username // and password in the session, so remove them if (cache) { session.removeNote(Constants.SESS_USERNAME_NOTE); session.removeNote(Constants.SESS_PASSWORD_NOTE); } if (restoreRequest(request, session)) { if (log.isDebugEnabled()) log.debug("Proceed to restored request"); return (true); } else { if (log.isDebugEnabled()) log.debug("Restore of original request failed"); response.sendError(HttpServletResponse.SC_BAD_REQUEST); return (false); } } // Acquire references to objects we will need to evaluate MessageBytes uriMB = MessageBytes.newInstance(); CharChunk uriCC = uriMB.getCharChunk(); uriCC.setLimit(-1); String contextPath = request.getContextPath(); String requestURI = request.getDecodedRequestURI(); response.setContext(request.getContext()); // Is this the action request from the login page? boolean loginAction = requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION); // No -- Save this request and redirect to the form login page if (!loginAction) { session = request.getSessionInternal(true); if (log.isDebugEnabled()) log.debug("Save request in session '" + session.getIdInternal() + "'"); try { saveRequest(request, session); } catch (IOException ioe) { log.debug("Request body too big to save during authentication"); response.sendError(HttpServletResponse.SC_FORBIDDEN, sm.getString("authenticator.requestBodyTooBig")); return (false); } //Make sure Authentication Source is available. try { CGMMManager cgmmManager = new CGMMManagerImpl(); request.setAttribute("AUTHENTICATION_SOURCE_MAP", cgmmManager.getAuthenticationServiceURLMap()); if (log.isDebugEnabled()) log.debug("Successfully retrieved Authentication Source Map from CGMM in session '" + session.getIdInternal() + "'"); } catch (CGMMConfigurationException e) { if (log.isDebugEnabled()) log.debug("Unable to retrieve Authentication Source Map from CGMM in session '" + session.getIdInternal() + "'"); } forwardToLoginPage(request, response, config); return (false); } // Custom Authenticate for CGMM-caGrid-CSM. boolean isPerformDefaultFormAuthentication = false; String username = request.getParameter(Constants.FORM_USERNAME); String password = request.getParameter(Constants.FORM_PASSWORD); String authenticationServiceURL = request.getParameter("authenticationServiceURL"); if (null == authenticationServiceURL) { // AuthenticationServiceURL is not available. // Check if Default Form Authenticator functionality is requested. String performDefaultFormAuthentication = request.getParameter("performDefaultFormAuthentication"); if (null == performDefaultFormAuthentication) { isPerformDefaultFormAuthentication = false; } else { if (performDefaultFormAuthentication.equalsIgnoreCase("YES")) { isPerformDefaultFormAuthentication = true; if (log.isDebugEnabled()) log.debug( "CaGridFormAuthenticator: Default Form Authenticator functionality is requested."); } } } else { // Resume caGrid Authentication } // Authenticate caGrid User GlobusCredential gc; if (!isPerformDefaultFormAuthentication) { try { CGMMManager cgmmManager = new CGMMManagerImpl(); principal = new GenericPrincipal(context.getRealm(), username, password); gc = cgmmManager.performGridLogin(username, password, authenticationServiceURL); if (log.isDebugEnabled()) log.debug("CaGridFormAuthenticator: Successfully performed Grid Login "); } catch (CGMMConfigurationException e) { if (log.isDebugEnabled()) log.debug("CGMMConfigurationException: " + e.getMessage()); forwardToErrorPage(request, response, config); return (false); } catch (CGMMInputException e) { if (log.isDebugEnabled()) log.debug("CGMMInputException: " + e.getMessage()); forwardToErrorPage(request, response, config); return (false); } catch (CGMMGridDorianException e) { if (log.isDebugEnabled()) log.debug("CGMMGridDorianException: " + e.getMessage()); forwardToErrorPage(request, response, config); return (false); } catch (CGMMGridAuthenticationServiceException e) { if (log.isDebugEnabled()) log.debug("CGMMGridAuthenticationServiceException: " + e.getMessage()); forwardToErrorPage(request, response, config); return (false); } catch (CGMMAuthenticationURLException e) { if (log.isDebugEnabled()) log.debug("CGMMAuthenticationURLException: " + e.getMessage()); forwardToErrorPage(request, response, config); return (false); } if (gc == null) { if (log.isDebugEnabled()) log.debug("Unable to obtain the Grid Credentials. Forwarding to error page."); forwardToErrorPage(request, response, config); return (false); } else { // Set the Grid ID as the Username for LoginModule(s). username = gc.getIdentity(); } } // Yes -- Validate the specified credentials and redirect // to the error page if they are not correct Realm realm = context.getRealm(); if (characterEncoding != null) { request.setCharacterEncoding(characterEncoding); } if (log.isDebugEnabled()) log.debug("Authenticating username '" + username + "'"); principal = realm.authenticate(username, password); if (principal == null) { forwardToErrorPage(request, response, config); return (false); } if (log.isDebugEnabled()) log.debug("Authentication of '" + username + "' was successful"); if (session == null) session = request.getSessionInternal(false); if (session == null) { if (containerLog.isDebugEnabled()) containerLog.debug("User took so long to log on the session expired"); response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT, sm.getString("authenticator.sessionExpired")); return (false); } // Save the authenticated Principal in our session session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal); // Save the username and password as well session.setNote(Constants.SESS_USERNAME_NOTE, username); session.setNote(Constants.SESS_PASSWORD_NOTE, password); // Redirect the user to the original request URI (which will cause // the original request to be restored) requestURI = savedRequestURL(session); if (log.isDebugEnabled()) log.debug("Redirecting to original '" + requestURI + "'"); if (requestURI == null) response.sendError(HttpServletResponse.SC_BAD_REQUEST, sm.getString("authenticator.formlogin")); else response.sendRedirect(response.encodeRedirectURL(requestURI)); return (false); } // ------------------------------------------------------ Protected Methods /** * Called to forward to the login page * * @param request Request we are processing * @param response Response we are creating * @param config Login configuration describing how authentication * should be performed */ protected void forwardToLoginPage(Request request, Response response, LoginConfig config) { RequestDispatcher disp = context.getServletContext().getRequestDispatcher(config.getLoginPage()); try { disp.forward(request.getRequest(), response.getResponse()); response.finishResponse(); } catch (Throwable t) { log.warn("Unexpected error forwarding to login page", t); } } /** * Called to forward to the error page * * @param request Request we are processing * @param response Response we are creating * @param config Login configuration describing how authentication * should be performed */ protected void forwardToErrorPage(Request request, Response response, LoginConfig config) { RequestDispatcher disp = context.getServletContext().getRequestDispatcher(config.getErrorPage()); try { disp.forward(request.getRequest(), response.getResponse()); } catch (Throwable t) { log.warn("Unexpected error forwarding to error page", t); } } /** * Does this request match the saved one (so that it must be the redirect * we signalled after successful authentication? * * @param request The request to be verified */ protected boolean matchRequest(Request request) { // Has a session been created? Session session = request.getSessionInternal(false); if (session == null) return (false); // Is there a saved request? SavedRequest sreq = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); if (sreq == null) return (false); // Is there a saved principal? if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null) return (false); // Does the request URI match? String requestURI = request.getRequestURI(); if (requestURI == null) return (false); return (requestURI.equals(sreq.getRequestURI())); } /** * Restore the original request from information stored in our session. * If the original request is no longer present (because the session * timed out), return <code>false</code>; otherwise, return * <code>true</code>. * * @param request The request to be restored * @param session The session containing the saved information */ protected boolean restoreRequest(Request request, Session session) throws IOException { // Retrieve and remove the SavedRequest object from our session SavedRequest saved = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); session.removeNote(Constants.FORM_REQUEST_NOTE); session.removeNote(Constants.FORM_PRINCIPAL_NOTE); if (saved == null) return (false); // Modify our current request to reflect the original one request.clearCookies(); Iterator cookies = saved.getCookies(); while (cookies.hasNext()) { request.addCookie((Cookie) cookies.next()); } MimeHeaders rmh = request.getCoyoteRequest().getMimeHeaders(); rmh.recycle(); boolean cachable = "GET".equalsIgnoreCase(saved.getMethod()) || "HEAD".equalsIgnoreCase(saved.getMethod()); Iterator names = saved.getHeaderNames(); while (names.hasNext()) { String name = (String) names.next(); // The browser isn't expecting this conditional response now. // Assuming that it can quietly recover from an unexpected 412. // BZ 43687 if (!("If-Modified-Since".equalsIgnoreCase(name) || (cachable && "If-None-Match".equalsIgnoreCase(name)))) { Iterator values = saved.getHeaderValues(name); while (values.hasNext()) { rmh.addValue(name).setString((String) values.next()); } } } request.clearLocales(); Iterator locales = saved.getLocales(); while (locales.hasNext()) { request.addLocale((Locale) locales.next()); } request.getCoyoteRequest().getParameters().recycle(); if ("POST".equalsIgnoreCase(saved.getMethod())) { ByteChunk body = saved.getBody(); if (body != null) { request.getCoyoteRequest().action(ActionCode.ACTION_REQ_SET_BODY_REPLAY, body); // Set content type MessageBytes contentType = MessageBytes.newInstance(); // If no content type specified, use default for POST String savedContentType = null;//saved.getContentType(); if (savedContentType == null) { savedContentType = "application/x-www-form-urlencoded"; } contentType.setString(savedContentType); request.getCoyoteRequest().setContentType(contentType); } } request.getCoyoteRequest().method().setString(saved.getMethod()); request.getCoyoteRequest().queryString().setString(saved.getQueryString()); request.getCoyoteRequest().requestURI().setString(saved.getRequestURI()); return (true); } /** * Save the original request information into our session. * * @param request The request to be saved * @param session The session to contain the saved information * @throws IOException */ protected void saveRequest(Request request, Session session) throws IOException { // Create and populate a SavedRequest object for this request SavedRequest saved = new SavedRequest(); Cookie cookies[] = request.getCookies(); if (cookies != null) { for (int i = 0; i < cookies.length; i++) saved.addCookie(cookies[i]); } Enumeration names = request.getHeaderNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); Enumeration values = request.getHeaders(name); while (values.hasMoreElements()) { String value = (String) values.nextElement(); saved.addHeader(name, value); } } Enumeration locales = request.getLocales(); while (locales.hasMoreElements()) { Locale locale = (Locale) locales.nextElement(); saved.addLocale(locale); } if ("POST".equalsIgnoreCase(request.getMethod())) { ByteChunk body = new ByteChunk(); body.setLimit(request.getConnector().getMaxSavePostSize()); byte[] buffer = new byte[4096]; int bytesRead; InputStream is = request.getInputStream(); while ((bytesRead = is.read(buffer)) >= 0) { body.append(buffer, 0, bytesRead); } saved.setBody(body); //saved.setContentType(request.getContentType()); } saved.setMethod(request.getMethod()); saved.setQueryString(request.getQueryString()); saved.setRequestURI(request.getRequestURI()); // Stash the SavedRequest in our session for later use session.setNote(Constants.FORM_REQUEST_NOTE, saved); } /** * Return the request URI (with the corresponding query string, if any) * from the saved request so that we can redirect to it. * * @param session Our current session */ protected String savedRequestURL(Session session) { SavedRequest saved = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); if (saved == null) return (null); StringBuffer sb = new StringBuffer(saved.getRequestURI()); if (saved.getQueryString() != null) { sb.append('?'); sb.append(saved.getQueryString()); } return (sb.toString()); } }