/* * JOSSO: Java Open Single Sign-On * * Copyright 2004-2009, Atricore, Inc. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.josso.servlet.agent; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.josso.agent.Constants; import org.josso.agent.LocalSession; import org.josso.agent.Lookup; import org.josso.agent.SSOAgentRequest; import org.josso.agent.SSOPartnerAppConfig; import org.josso.agent.SingleSignOnEntry; import org.josso.agent.http.HttpSSOAgent; import org.josso.agent.http.WebAccessControlUtil; /** * JOSSO Servlet Filter for Generic SSO Agent, this replaces the Valve in tomcat or other container specific components. * The fillter will handle web logic to authenticate, login and logout users. * * Date: Nov 27, 2007 * Time: 9:28:53 AM * * @author <a href="mailto:sgonzalez@josso.org">Sebastian Gonzalez Oyuela</a> */ public class GenericServletSSOAgentFilter implements Filter { public static final String KEY_SESSION_MAP = "org.josso.servlet.agent.sessionMap"; public static final String LAZY_STARTUP ="lazy"; /** * The servlet context */ protected ServletContext context; /** * One agent instance for all applications. */ protected HttpSSOAgent agent; /** * Logger */ private static final Log log = LogFactory.getLog(GenericServletSSOAgentFilter.class); public GenericServletSSOAgentFilter() { } protected void startup() throws ServletException { try { Lookup lookup = Lookup.getInstance(); lookup.init("josso-agent-config.xml"); // For spring compatibility ... // We need at least an abstract SSO Agent agent = (HttpSSOAgent) lookup.lookupSSOAgent(); if (log.isDebugEnabled()) agent.setDebug(1); agent.start(); // Publish agent in servlet context context.setAttribute("org.josso.agent", agent); } catch (Exception e) { throw new ServletException("Error starting SSO Agent : " + e.getMessage(), e); } } public void init(FilterConfig filterConfig) throws ServletException { // Validate and update our current component state context = filterConfig.getServletContext(); context.setAttribute(KEY_SESSION_MAP, new HashMap()); // Lazy startup shifts filter initialization upon the first request is received // This allows the container to setup web application's classloader // In some containers - such as JRun - a filter will not be able to access web application's resources // (e.g. WEB-INF/classes) during initialization due to that this is not fully initialized yet. if (agent == null && (filterConfig.getInitParameter("init") == null || ( filterConfig.getInitParameter("init") != null && !filterConfig.getInitParameter("init").equals(LAZY_STARTUP)))) { startup(); } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { if (agent == null) { startup(); } HttpServletRequest hreq = (HttpServletRequest) request; HttpServletResponse hres = (HttpServletResponse) response; if (log.isDebugEnabled()) log.debug("Processing : " + hreq.getContextPath()); try { // ------------------------------------------------------------------ // Check with the agent if this context should be processed. // ------------------------------------------------------------------ String contextPath = hreq.getContextPath(); String vhost = hreq.getServerName(); // Take the node from the request first and store it if found. String nodeId = hreq.getParameter("josso_node"); if (nodeId != null) { if (log.isDebugEnabled()) log.debug("Storing JOSSO Node id : " + nodeId); agent.setAttribute(hreq, hres, "JOSSO_NODE", nodeId); } else { nodeId = agent.getAttribute(hreq, "JOSSO_NODE"); if (log.isDebugEnabled()) log.debug("Found JOSSO Node id : " + nodeId); } // In catalina, the empty context is considered the root context if ("".equals(contextPath)) contextPath = "/"; if (!agent.isPartnerApp(vhost, contextPath)) { filterChain.doFilter(hreq, hres); if (log.isDebugEnabled()) log.debug("Context is not a josso partner app : " + hreq.getContextPath()); return; } // ------------------------------------------------------------------ // Check some basic HTTP handling // ------------------------------------------------------------------ // P3P Header for IE 6+ compatibility when embedding JOSSO in a IFRAME SSOPartnerAppConfig cfg = agent.getPartnerAppConfig(vhost, contextPath); if (cfg.isSendP3PHeader() && !hres.isCommitted()) { hres.setHeader("P3P", cfg.getP3PHeaderValue()); } // Get our session ... HttpSession session = hreq.getSession(true); // ------------------------------------------------------------------ // Check if the partner application required the login form // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("Checking if its a josso_login_request for '" + hreq.getRequestURI() + "'"); if (hreq.getRequestURI().endsWith(agent.getJossoLoginUri()) || hreq.getRequestURI().endsWith(agent.getJossoUserLoginUri())) { if (log.isDebugEnabled()) log.debug("josso_login_request received for uri '" + hreq.getRequestURI() + "'"); //save referer url in case the user clicked on Login from some public resource (page) //so agent can redirect the user back to that page after successful login if (hreq.getRequestURI().endsWith(agent.getJossoUserLoginUri())) { saveLoginBackToURL(hreq, hres, session, true); } else { saveLoginBackToURL(hreq, hres, session, false); } String loginUrl = agent.buildLoginUrl(hreq); if (log.isDebugEnabled()) log.debug("Redirecting to login url '" + loginUrl + "'"); //set non cache headers agent.prepareNonCacheResponse(hres); hres.sendRedirect(hres.encodeRedirectURL(loginUrl)); return; } // ------------------------------------------------------------------ // Check if the partner application required a logout // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("Checking if its a josso_logout request for '" + hreq.getRequestURI() + "'"); if (hreq.getRequestURI().endsWith(agent.getJossoLogoutUri())) { if (log.isDebugEnabled()) log.debug("josso_logout request received for uri '" + hreq.getRequestURI() + "'"); String logoutUrl = agent.buildLogoutUrl(hreq, cfg); if (log.isDebugEnabled()) log.debug("Redirecting to logout url '" + logoutUrl + "'"); // Clear previous COOKIE ... Cookie ssoCookie = agent.newJossoCookie(hreq.getContextPath(), "-", hreq.isSecure()); hres.addCookie(ssoCookie); // invalidate session (unbind josso security context) session.invalidate(); //set non cache headers agent.prepareNonCacheResponse(hres); hres.sendRedirect(hres.encodeRedirectURL(logoutUrl)); return; } // ------------------------------------------------------------------ // Check for the single sign on cookie // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("Checking for SSO cookie"); Cookie cookie = null; Cookie cookies[] = hreq.getCookies(); if (cookies == null) cookies = new Cookie[0]; for (int i = 0; i < cookies.length; i++) { if (org.josso.gateway.Constants.JOSSO_SINGLE_SIGN_ON_COOKIE.equals(cookies[i].getName())) { cookie = cookies[i]; break; } } String jossoSessionId = (cookie == null) ? null : cookie.getValue(); if (log.isDebugEnabled()) log.debug("Session is:" + session); // Get session map for this servlet context. Map sessionMap = (Map) hreq.getSession().getServletContext().getAttribute(KEY_SESSION_MAP); if (sessionMap == null) { synchronized (this) { sessionMap = (Map) hreq.getSession().getServletContext().getAttribute(KEY_SESSION_MAP); if (sessionMap == null) { sessionMap = Collections.synchronizedMap(new HashMap()); hreq.getSession().getServletContext().setAttribute(KEY_SESSION_MAP, sessionMap); } } } GenericServletLocalSession localSession = (GenericServletLocalSession) sessionMap.get(session.getId()); if (sessionMap.get(session.getId()) == null) { localSession = new GenericServletLocalSession(session); // the local session is new so, make the valve listen for its events so that it can // map them to local session events. // Not supported : session.addSessionListener(this); sessionMap.put(session.getId(), localSession); } else { // Update the session, just in case we have an older copy wrapped on our local session. localSession.updateSession(session); } // ------------------------------------------------------------------ // Check if the partner application submitted custom login form // ------------------------------------------------------------------ if (log.isDebugEnabled()){ log.debug("Checking if its a josso_authentication for '" + hreq.getRequestURI() + "'"); } if (hreq.getRequestURI().endsWith(agent.getJossoAuthenticationUri())) { if (log.isDebugEnabled()){ log.debug("josso_authentication received for uri '" + hreq.getRequestURI() + "'"); } GenericServletSSOAgentRequest customAuthRequest = (GenericServletSSOAgentRequest) doMakeSSOAgentRequest(cfg.getId(), SSOAgentRequest.ACTION_CUSTOM_AUTHENTICATION, jossoSessionId, nodeId, localSession, null, hreq, hres); agent.processRequest(customAuthRequest); return; } if (cookie == null || cookie.getValue().equals("-")) { // ------------------------------------------------------------------ // Trigger LOGIN OPTIONAL if required // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("SSO cookie is not present, verifying optional login process "); // We have no cookie, remember me is enabled and a security check without assertion was received ... // This means that the user could not be identified ... go back to the original resource if (hreq.getRequestURI().endsWith(agent.getJossoSecurityCheckUri()) && hreq.getParameter("josso_assertion_id") == null) { if (log.isDebugEnabled()) log.debug(agent.getJossoSecurityCheckUri() + " received without assertion. Login Optional Process failed"); String requestURI = getSavedRequestURL(hreq); agent.prepareNonCacheResponse(hres); hres.sendRedirect(hres.encodeRedirectURL(requestURI)); return; } // This is a standard anonymous request! if (!hreq.getRequestURI().endsWith(agent.getJossoSecurityCheckUri())) { if (!agent.isResourceIgnored(cfg, hreq) && agent.isAutomaticLoginRequired(hreq, hres)) { if (log.isDebugEnabled()) log.debug("SSO cookie is not present, attempting automatic login"); // Save current request, so we can co back to it later ... saveRequestURL(hreq, hres); String loginUrl = agent.buildLoginOptionalUrl(hreq); if (log.isDebugEnabled()) log.debug("Redirecting to login url '" + loginUrl + "'"); //set non cache headers agent.prepareNonCacheResponse(hres); hres.sendRedirect(hres.encodeRedirectURL(loginUrl)); return; } else { if (log.isDebugEnabled()) log.debug("SSO cookie is not present, but login optional process is not required"); } // save requested resource if (!agent.isResourceIgnored(cfg, hreq)) { StringBuffer sb = new StringBuffer(hreq.getRequestURI()); if (hreq.getQueryString() != null) { sb.append('?'); sb.append(hreq.getQueryString()); } agent.setAttribute(hreq, hres, WebAccessControlUtil.KEY_JOSSO_SAVED_REQUEST_URI, sb.toString()); } } if (log.isDebugEnabled()) log.debug("SSO cookie is not present, checking for outbound relaying"); if (!(hreq.getRequestURI().endsWith(agent.getJossoSecurityCheckUri()) && hreq.getParameter("josso_assertion_id") != null)) { log.debug("SSO cookie not present and relaying was not requested, skipping"); filterChain.doFilter(hreq, hres); return; } } // ------------------------------------------------------------------ // Check if this URI is subject to SSO protection // ------------------------------------------------------------------ if (agent.isResourceIgnored(cfg, hreq)) { filterChain.doFilter(hreq, hres); return; } // This URI should be protected by SSO, go on ... if (log.isDebugEnabled()) log.debug("Session is: " + session); // ------------------------------------------------------------------ // Invoke the SSO Agent // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("Executing agent..."); // ------------------------------------------------------------------ // Check if a user has been authenitcated and should be checked by the agent. // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("Checking if its a josso_security_check for '" + hreq.getRequestURI() + "'"); if (hreq.getRequestURI().endsWith(agent.getJossoSecurityCheckUri()) && hreq.getParameter("josso_assertion_id") != null) { if (log.isDebugEnabled()) log.debug("josso_security_check received for uri '" + hreq.getRequestURI() + "' assertion id '" + hreq.getParameter("josso_assertion_id") ); String assertionId = hreq.getParameter(Constants.JOSSO_ASSERTION_ID_PARAMETER); GenericServletSSOAgentRequest relayRequest; if (log.isDebugEnabled()) log.debug("Outbound relaying requested for assertion id [" + assertionId + "]"); relayRequest = (GenericServletSSOAgentRequest) doMakeSSOAgentRequest( cfg.getId(), SSOAgentRequest.ACTION_RELAY, null, nodeId, localSession, assertionId, hreq, hres); SingleSignOnEntry entry = agent.processRequest(relayRequest); if (entry == null) { // This is wrong! We should have an entry here! log.debug("Outbound relaying failed for assertion id [" + assertionId + "], no Principal found."); // Throw an exception and let the container deal with it throw new ServletException("No Principal found. Verify your SSO Agent Configuration!"); } if (log.isDebugEnabled()) log.debug("Outbound relaying successful for assertion id [" + assertionId + "]"); if (log.isDebugEnabled()) log.debug("Assertion id [" + assertionId + "] mapped to SSO session id [" + entry.ssoId + "]"); // The cookie is valid to for the partner application only ... in the future each partner app may // store a different auth. token (SSO SESSION) value cookie = agent.newJossoCookie(hreq.getContextPath(), entry.ssoId, hreq.isSecure()); hres.addCookie(cookie); // Redirect the user to the original request URI (which will cause // the original request to be restored) String requestURI = getSavedSplashResource(hreq); if(requestURI == null) { requestURI = getSavedRequestURL(hreq); if (requestURI == null) { if (cfg.getDefaultResource() != null) { requestURI = cfg.getDefaultResource(); } else { // If no saved request is found, redirect to the partner app root : requestURI = hreq.getRequestURI().substring( 0, (hreq.getRequestURI().length() - agent.getJossoSecurityCheckUri().length())); } // If we're behind a reverse proxy, we have to alter the URL ... this was not necessary on tomcat 5.0 ?! String singlePointOfAccess = agent.getSinglePointOfAccess(); if (singlePointOfAccess != null) { requestURI = singlePointOfAccess + requestURI; } else { String reverseProxyHost = hreq.getHeader(org.josso.gateway.Constants.JOSSO_REVERSE_PROXY_HEADER); if (reverseProxyHost != null) { requestURI = reverseProxyHost + requestURI; } } if (log.isDebugEnabled()) log.debug("No saved request found, using : '" + requestURI + "'"); } } clearSavedRequestURLs(hreq, hres); agent.clearAutomaticLoginReferer(hreq, hres); agent.prepareNonCacheResponse(hres); // Check if we have a post login resource : String postAuthURI = cfg.getPostAuthenticationResource(); if (postAuthURI != null) { String postAuthURL = agent.buildPostAuthUrl(hres, requestURI, postAuthURI); if (log.isDebugEnabled()) log.debug("Redirecting to post-auth-resource '" + postAuthURL + "'"); hres.sendRedirect(postAuthURL); } else { if (!"".equals(requestURI)) { if (log.isDebugEnabled()) log.debug("Redirecting to original '" + requestURI + "'"); hres.sendRedirect(hres.encodeRedirectURL(requestURI)); } else { log.debug("Redirecting to application's root context '" + contextPath); hres.sendRedirect(hres.encodeRedirectURL(contextPath)); } } return; } SSOAgentRequest r = doMakeSSOAgentRequest(cfg.getId(), SSOAgentRequest.ACTION_ESTABLISH_SECURITY_CONTEXT, jossoSessionId, nodeId, localSession, null, hreq, hres); SingleSignOnEntry entry = agent.processRequest(r); if (log.isDebugEnabled()) log.debug("Executed agent."); // ------------------------------------------------------------------ // Has a valid user already been authenticated? // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("Process request for '" + hreq.getRequestURI() + "'"); if (entry != null) { if (log.isDebugEnabled()) log.debug("Principal '" + entry.principal + "' has already been authenticated"); // TODO : Not supported // (request).setAuthType(entry.authType); // (request).setUserPrincipal(entry.principal); } else { log.info("No Valid SSO Session, attempt an optional login?"); // This is a standard anonymous request! if (cookie != null) { // cookie is not valid cookie = agent.newJossoCookie(hreq.getContextPath(), "-", hreq.isSecure()); hres.addCookie(cookie); } if (cookie != null || (getSavedRequestURL(hreq) == null && agent.isAutomaticLoginRequired(hreq, hres))) { if (log.isDebugEnabled()) log.debug("SSO Session is not valid, attempting automatic login"); // Save current request, so we can come back to it later ... saveRequestURL(hreq, hres); String loginUrl = agent.buildLoginOptionalUrl(hreq); if (log.isDebugEnabled()) log.debug("Redirecting to option login url '" + loginUrl + "'"); //set non cache headers agent.prepareNonCacheResponse(hres); hres.sendRedirect(hres.encodeRedirectURL(loginUrl)); return; } else { if (log.isDebugEnabled()) log.debug("SSO cookie is not present, but login optional process is not required"); } } // propagate the login and logout URLs to // partner applications. hreq.setAttribute("org.josso.agent.gateway-login-url", agent.getGatewayLoginUrl() ); hreq.setAttribute("org.josso.agent.gateway-logout-url", agent.getGatewayLogoutUrl() ); hreq.setAttribute("org.josso.agent.ssoSessionid", jossoSessionId); // ------------------------------------------------------------------ // Invoke the next Valve in our pipeline // ------------------------------------------------------------------ if (log.isDebugEnabled()) log.debug("Servlet Agent execution END"); filterChain.doFilter(hreq, hres); } finally { if (log.isDebugEnabled()) log.debug("Processed : " + hreq.getContextPath()); } } public void destroy() { // Validate and update our current component state if (agent != null) { agent.stop(); agent = null; } } /** * Return the splash resource from session so that we can redirect the user to it * if (s)he was logged in using custom form * @param hreq current http request */ private String getSavedSplashResource(HttpServletRequest hreq){ return agent.getAttribute(hreq, Constants.JOSSO_SPLASH_RESOURCE_PARAMETER); } /** * Return the request URI (with the corresponding query string, if any) * from the saved request so that we can redirect to it. * * @param hreq current http request */ private String getSavedRequestURL(HttpServletRequest hreq) { return agent.getAttribute(hreq, WebAccessControlUtil.KEY_JOSSO_SAVED_REQUEST_URI); } /** * Creates a new request */ protected SSOAgentRequest doMakeSSOAgentRequest(String requester, int action, String sessionId, String nodeId, LocalSession session, String assertionId, HttpServletRequest hreq, HttpServletResponse hres) { GenericServletSSOAgentRequest r = new GenericServletSSOAgentRequest(requester, action, sessionId, session, assertionId, nodeId); r.setRequest(hreq); r.setResponse(hres); return r; } /** * Saves the original request URL into our session. * * @param hreq The request to be saved * @param hres The http servlet response associated to the request */ private void saveRequestURL(HttpServletRequest hreq, HttpServletResponse hres) { StringBuffer sb = new StringBuffer(hreq.getRequestURI()); if (hreq.getQueryString() != null) { String q = hreq.getQueryString(); if (!q.startsWith("?")) sb.append('?'); sb.append(q); } agent.setAttribute(hreq, hres, WebAccessControlUtil.KEY_JOSSO_SAVED_REQUEST_URI, sb.toString()); } /** * Save referer URI into our session for later use. * * This method is used so agent can know from which * public resource (page) user requested login * * @deprecated. * * @param request http request * @param session current session * @param overrideSavedResource true if saved resource should be overridden, false otherwise */ protected void saveLoginBackToURL(HttpServletRequest request, HttpSession session, boolean overrideSavedResource) { saveLoginBackToURL(request, null, session, overrideSavedResource); } /** * Save referer URI into our session for later use. * * This method is used so agent can know from which * public resource (page) user requested login. * * @param request http request * @param response http response * @param session current session * @param overrideSavedResource true if saved resource should be overridden, false otherwise */ protected void saveLoginBackToURL(HttpServletRequest request, HttpServletResponse response, HttpSession session, boolean overrideSavedResource) { String referer = request.getHeader("referer"); if ((getSavedRequestURL(request) == null || overrideSavedResource) && referer != null && !referer.equals("")) { agent.setAttribute(request, response, WebAccessControlUtil.KEY_JOSSO_SAVED_REQUEST_URI, referer); } } /** * Remove saved request URLs from session * to avoid mixing up resources from previous operations * (logins, logouts) with the current one. * * @param hreq http request * @param hres http response */ protected void clearSavedRequestURLs(HttpServletRequest hreq, HttpServletResponse hres) { agent.removeAttribute(hreq, hres, WebAccessControlUtil.KEY_JOSSO_SAVED_REQUEST_URI); agent.removeAttribute(hreq, hres, Constants.JOSSO_SPLASH_RESOURCE_PARAMETER); } }