/*
* 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.liferay5.agent;
import com.liferay.portal.servlet.filters.BasePortalFilter;
import com.liferay.portal.util.PortalUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.josso.agent.*;
import org.josso.agent.http.HttpSSOAgent;
import org.josso.agent.http.WebAccessControlUtil;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* JOSSO Servlet Filter for Generic SSO Agent, this replaces the Valve in tomcat or other container specific components.
* The filter will handle web logic to authenticate, login and logout users.
* <p/>
*
* @author <a href="mailto:dfisic@josso.org">Dusan Fisic</a>
*/
public class LiferaySSOAgentFilter extends BasePortalFilter {
private static final String KEY_SESSION_MAP = "org.josso.servlet.agent.sessionMap";
private static final String LIFERAY_PORTAL_LOGIN_URI = "/c/portal/login";
private static final String LIFERAY_PORTAL_LOGOUT_URI = "/c/portal/logout";
private static final String LIFERAY_GROUP_URI = "/group";
private static final String LIFERAY_USER_URI = "/user";
private static final String LIFERAY_WEB_URI = "/web";
private static final String JOSSO_SECURITY_CHECK_URI = "/josso_security_check";
/**
* One agent instance for all applications.
*/
private HttpSSOAgent _agent;
/**
* Logger
*/
private static final Log log = LogFactory.getLog(LiferaySSOAgentFilter.class);
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
// Validate and update our current component state
ServletContext ctx = filterConfig.getServletContext();
ctx.setAttribute(KEY_SESSION_MAP, new HashMap());
if (_agent == null) {
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
filterConfig.getServletContext().setAttribute("org.josso.agent", _agent);
} catch (Exception e) {
throw new ServletException("Error starting SSO Agent : " + e.getMessage(), e);
}
}
}
@Override
protected void processFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest hreq =
(HttpServletRequest) request;
HttpServletResponse hres =
(HttpServletResponse) response;
// URI pattern matching is implemented programmatically in case this filter is bound to the root web context
// (i.e. '/*' url pattern) required for intercepting locale-prefixed URLs.
if (!hreq.getRequestURI().contains(LIFERAY_PORTAL_LOGIN_URI) &&
!hreq.getRequestURI().contains(LIFERAY_PORTAL_LOGOUT_URI) &&
!hreq.getRequestURI().contains(LIFERAY_GROUP_URI) &&
!hreq.getRequestURI().contains(LIFERAY_USER_URI) &&
!hreq.getRequestURI().contains(LIFERAY_WEB_URI) &&
!hreq.getRequestURI().contains(JOSSO_SECURITY_CHECK_URI)
) {
filterChain.doFilter(hreq, hres);
return;
}
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();
long companyId = PortalUtil.getCompanyId(request);
// 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 Liferay application required its login form [/c/portal/login]
// ------------------------------------------------------------------
if (JossoLiferayProps.isEnabled(companyId) &&
hreq.getRequestURI().endsWith(LIFERAY_PORTAL_LOGIN_URI)) {
if (log.isDebugEnabled())
log.debug("Requested liferay login: '" + 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 Liferay application required its logout form [/c/portal/logout]
// ------------------------------------------------------------------
if (JossoLiferayProps.isEnabled(companyId) &&
hreq.getRequestURI().endsWith(LIFERAY_PORTAL_LOGOUT_URI)) {
if (log.isDebugEnabled())
log.debug("Requested liferay logout: '" + 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();
LiferayLocalSession localSession = new LiferayLocalSession(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() + "'");
}
LiferaySSOAgentRequest customAuthRequest = (LiferaySSOAgentRequest) doMakeSSOAgentRequest(cfg.getId(), SSOAgentRequest.ACTION_CUSTOM_AUTHENTICATION, jossoSessionId, 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");
}
}
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);
LiferaySSOAgentRequest relayRequest;
if (log.isDebugEnabled())
log.debug("Outbound relaying requested for assertion id [" + assertionId + "]");
relayRequest = (LiferaySSOAgentRequest) 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!
log.error("Outbound relaying failed for assertion id [" + assertionId + "], no Principal found.");
// Throw an exception and let the container send the INERNAL SERVER ERROR
throw new ServletException("No Principal found. Verify your SSO Agent Configuration!");
}
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 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 (log.isDebugEnabled())
log.debug("Redirecting to original '" + requestURI + "'");
hres.sendRedirect(hres.encodeRedirectURL(requestURI));
}
return;
}
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.");
// Get session map for this servlet context.
Map sessionMap = (Map) hreq.getSession().getServletContext().getAttribute(KEY_SESSION_MAP);
if (sessionMap.get(localSession.getWrapped()) == null) {
// 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, localSession);
}
// ------------------------------------------------------------------
// 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 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");
}
}
// 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
// ------------------------------------------------------------------
filterChain.doFilter(hreq, hres);
} finally {
if (log.isDebugEnabled())
log.debug("Processed : " + hreq.getContextPath());
}
}
public void destroy() {
super.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, LocalSession session, String assertionId,
HttpServletRequest hreq, HttpServletResponse hres) {
LiferaySSOAgentRequest r = new LiferaySSOAgentRequest(requester, action, sessionId, session, assertionId);
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.
* <p/>
* This method is used so agent can know from which
* public resource (page) user requested login
*
* @param request http request
* @param session current session
* @param overrideSavedResource true if saved resource should be overridden, false otherwise
* @deprecated.
*/
protected void saveLoginBackToURL(HttpServletRequest request, HttpSession session, boolean overrideSavedResource) {
saveLoginBackToURL(request, null, session, overrideSavedResource);
}
/**
* Save referer URI into our session for later use.
* <p/>
* 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);
}
}