/*
* 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);
}
}