/* * 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.jaspi.agent; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.message.AuthException; import javax.security.auth.message.AuthStatus; import javax.security.auth.message.MessageInfo; import javax.security.auth.message.MessagePolicy; 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; import org.josso.gateway.identity.SSORole; import java.util.*; /** * JOSSO server auth module. */ public class JASPISSOAuthModule extends JOSSOServerAuthModule { public static final String KEY_SESSION_MAP = "org.josso.servlet.agent.sessionMap"; private static final Log log = LogFactory.getLog(JASPISSOAuthModule.class); private static HttpSSOAgent _agent; protected String delegatingLoginContextName = null; /** * <p> * Creates an instance of {@code JASPISSOAuthModule}. * </p> */ public JASPISSOAuthModule() { this(null); } /** * <p> * Creates an instance of {@code HTTPFormServerAuthModule} with the specified delegating login context name. * </p> * * @param delegatingLoginContextName the name of the login context configuration that contains the JAAS modules that * are to be called by this module. */ public JASPISSOAuthModule(String delegatingLoginContextName) { super(); this.delegatingLoginContextName = delegatingLoginContextName; } @Override public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler, Map options) throws AuthException { if (_agent == null) { synchronized(this) { try { if (_agent == null) { Lookup lookup = Lookup.getInstance(); lookup.init("josso-agent-config.xml"); _agent = (HttpSSOAgent) lookup.lookupSSOAgent(); if (log.isDebugEnabled()) { _agent.setDebug(1); } _agent.start(); if (log.isDebugEnabled()) log.debug("Agent started"); } } catch (Exception e) { log.error("Error starting SSO Agent : " + e.getMessage(), e); throw new RuntimeException("Error starting SSO Agent : " + e.getMessage(), e); } } } super.initialize(requestPolicy, responsePolicy, handler, options); } @Override public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException { throw new RuntimeException("Not Applicable"); //return AuthStatus.SEND_SUCCESS; } @Override public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException { HttpServletRequest hreq = (HttpServletRequest) messageInfo.getRequestMessage(); HttpServletResponse hres = (HttpServletResponse) messageInfo.getResponseMessage(); if (log.isDebugEnabled()) { log.debug("Processing : " + hreq.getContextPath() + " ["+hreq.getRequestURL()+"]"); } try { // ------------------------------------------------------------------ // Check with the agent if this context should be processed. // ------------------------------------------------------------------ String contextPath = hreq.getContextPath(); String vhost = hreq.getServerName(); // In catalina, the empty context is considered the root context if ("".equals(contextPath)) { contextPath = "/"; } if (!_agent.isPartnerApp(vhost, contextPath)) { if (log.isDebugEnabled()) { log.debug("Context is not a josso partner app : " + hreq.getContextPath()); } AuthStatus status = AuthStatus.SUCCESS; return status; } // ------------------------------------------------------------------ // 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)); // Request is authorized for this URI return AuthStatus.SEND_CONTINUE; } // ------------------------------------------------------------------ // 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)); // Request is authorized for this URI return AuthStatus.SEND_CONTINUE; } // ------------------------------------------------------------------ // 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); } } } LocalSession localSession = (LocalSession) sessionMap.get(session.getId()); if (localSession == null) { localSession = new JASPILocalSession(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); } // ------------------------------------------------------------------ // 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() + "'"); } JASPISSOAgentRequest customAuthRequest = (JASPISSOAgentRequest) doMakeSSOAgentRequest(cfg.getId(), SSOAgentRequest.ACTION_CUSTOM_AUTHENTICATION, jossoSessionId, localSession, null, hreq, hres); _agent.processRequest(customAuthRequest); // Request is authorized return AuthStatus.SEND_CONTINUE; } 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 = this.getSavedRequestURL(hreq); _agent.prepareNonCacheResponse(hres); hres.sendRedirect(hres.encodeRedirectURL(requestURI)); AuthStatus status = AuthStatus.SEND_CONTINUE; return status; } // This is a standard anonymous request! if (!hreq.getRequestURI().endsWith(_agent.getJossoSecurityCheckUri())) { // If saved request is NOT null, we're in the middle of another process ... 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)); //hreq.getRequestDispatcher(loginUrl).forward(hreq, hres); AuthStatus status = AuthStatus.SEND_CONTINUE; return status; } else { if (log.isDebugEnabled()) { log.debug("SSO cookie is not present, but login optional process is not required"); } } } 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"); AuthStatus status = AuthStatus.SUCCESS; return status; } } // ------------------------------------------------------------------ // Check if this URI is subject to SSO protection // ------------------------------------------------------------------ if (_agent.isResourceIgnored(cfg, hreq)) { // Ignored resources are authorized return AuthStatus.SUCCESS; } // 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 authenticated 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); JASPISSOAgentRequest relayRequest; if (log.isDebugEnabled()) { log.debug("Outbound relaying requested for assertion id [" + assertionId + "]"); } relayRequest = (JASPISSOAgentRequest) doMakeSSOAgentRequest(cfg.getId(), SSOAgentRequest.ACTION_RELAY, null, localSession, assertionId, hreq, hres); SingleSignOnEntry entry = _agent.processRequest(relayRequest); if (entry == null) { // This is wrong! We should have an entry here! if (log.isDebugEnabled()) { log.debug("Outbound relaying failed for assertion id [" + assertionId + "], no Principal found."); } // Throw an exception, we will handle it below ! throw new RuntimeException("Outbound relaying failed. No Principal found. Verify your SSO Agent Configuration!"); } else { // Add the SSOUser as a Principal if (!clientSubject.getPrincipals().contains(entry.principal)) { clientSubject.getPrincipals().add(entry.principal); } SSORole[] ssoRolePrincipals = _agent.getRoleSets(cfg.getId(), entry.ssoId, relayRequest.getNodeId()); List<String> rolesList = new ArrayList<String>(); for (int i=0; i < ssoRolePrincipals.length; i++) { if (clientSubject.getPrincipals().contains(ssoRolePrincipals[i])) { continue; } rolesList.add(ssoRolePrincipals[i].getName()); clientSubject.getPrincipals().add(ssoRolePrincipals[i]); log.debug("Added SSORole Principal to the Subject : " + ssoRolePrincipals [i]); } registerWithCallbackHandler(entry.principal, entry.principal.getName(), entry.ssoId, rolesList.toArray(new String[rolesList.size()])); } if (log.isDebugEnabled()) { log.debug("Outbound relaying succesfull 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 user to the saved splash resource (in case of auth request) or to request URI otherwise 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 + "'"); } } _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 (log.isDebugEnabled()) { log.debug("Redirecting to original '" + requestURI + "'"); } hres.sendRedirect(hres.encodeRedirectURL(requestURI)); } AuthStatus status = AuthStatus.SEND_SUCCESS; return status; } if (log.isDebugEnabled()) { log.debug("Creating Security Context for Session [" + session + "]"); } SSOAgentRequest r = doMakeSSOAgentRequest(cfg.getId(), SSOAgentRequest.ACTION_ESTABLISH_SECURITY_CONTEXT, jossoSessionId, 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"); } // Add the SSOUser as a Principal if (!clientSubject.getPrincipals().contains(entry.principal)) { clientSubject.getPrincipals().add(entry.principal); } SSORole[] ssoRolePrincipals = _agent.getRoleSets(cfg.getId(), entry.ssoId, r.getNodeId()); List<String> rolesList = new ArrayList<String>(); for (int i=0; i < ssoRolePrincipals.length; i++) { if (clientSubject.getPrincipals().contains(ssoRolePrincipals[i])) { continue; } rolesList.add(ssoRolePrincipals[i].getName()); clientSubject.getPrincipals().add(ssoRolePrincipals[i]); log.debug("Added SSORole Principal to the Subject : " + ssoRolePrincipals [i]); } registerWithCallbackHandler(entry.principal, entry.principal.getName(), entry.ssoId, rolesList.toArray(new String[rolesList.size()])); } else { log.debug("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 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)); // Request is authorized for this URI return AuthStatus.SEND_CONTINUE; } 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); clearSavedRequestURLs(hreq, hres); AuthStatus status = AuthStatus.SUCCESS; return status; } catch (Throwable t) { log.warn(t.getMessage(), t); throw new AuthException(t.getMessage()); //return AuthStatus.FAILURE; } finally { if (log.isDebugEnabled()) { log.debug("Processed : " + hreq.getContextPath() + " ["+hreq.getRequestURL()+"]"); } } } /** * Creates a new SSO Agent request. * * @return SSO Agent request */ protected SSOAgentRequest doMakeSSOAgentRequest(String requester, int action, String sessionId, LocalSession session, String assertionId, HttpServletRequest hreq, HttpServletResponse hres) { JASPISSOAgentRequest r = new JASPISSOAgentRequest(requester, action, sessionId, session, assertionId); r.setRequest(hreq); r.setResponse(hres); return r; } /** * 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); } /** * 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); } /** * 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. * * @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); } }