/*
* 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.jb32.agent;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.naming.Util;
import org.jboss.security.AuthenticationManager;
import org.jboss.security.RealmMapping;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SubjectSecurityManager;
import org.jboss.security.plugins.JaasSecurityManager;
import org.jboss.util.CachePolicy;
import org.jboss.web.tomcat.security.JBossSecurityMgrRealm;
import org.jboss.web.tomcat.security.SecurityAssociationValve;
import org.josso.gateway.identity.SSOUser;
import org.josso.tc50.agent.jaas.CatalinaSSOUser;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.LinkRef;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import java.lang.reflect.Method;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
/**
* JBoss Realm proxy that does mainly the following two things :
*
* <p>
* 1. Before invoking the overriden Realm methods, it creates a "java:comp/env/security" JNDI context
* needed by the JBossSecurityMgrRealm to retrieve the configured JBoss Security Manager.
* The "java:comp/env/security" context is only created by Catalina for built-in authenticators
* and web applications contexts. The Context where the Agent Valve is associated to does not have
* an ENC at all so we must build one for it.
* <p>
* 2. Completely overrides the user authentication method so that the current Principal is not
* the SSO Session Id Principal but the SSOUser Principal.
*
* <p>
* All Realm operations that require a SecurityContext were overriden so that there is a chance
* for our Realm to prepare the "java:comp/env/security" JNDI Context.
* <p>
*
* @author <a href="mailto:gbrigand@josso.org">Gianluca Brigandi</a>
* @version CVS $Id: JBossCatalinaRealm.java 974 2009-01-14 00:39:45Z sgonzalez $
*/
public class JBossCatalinaRealm extends JBossSecurityMgrRealm {
private static final Log logger = LogFactory.getLog(JBossCatalinaRealm.class);
/** The fixed JOSSO JBoss Security Domain Name */
private static final String JOSSO_SECURITY_DOMAIN = "java:/jaas/josso";
private static final String DEFAULT_CACHE_POLICY_PATH = "java:/timedCacheFactory";
/** The location of the security credential cache policy. This is first treated
as a ObjectFactory location that is capable of returning CachePolicy instances
on a per security domain basis by appending a '/security-domain-name' string
to this name when looking up the CachePolicy for a domain. If this fails then
the location is treated as a single CachePolicy for all security domains.
*/
private static String cacheJndiName = DEFAULT_CACHE_POLICY_PATH;
/** HashMap<UserPrincipal, AuthPrincipal> */
private HashMap _userPrincipalMap = new HashMap();
/** HashMap<SSOUserPrincipal, JossoSessionIdPrincipal> */
/* private HashMap _userSessionMap = new HashMap();*/
private SessionMappingCachePolicy _cachePolicy;
/**
*
* @param domain the security domain name
* @return true if this is a SSO security domain.
*/
protected boolean isSSODomain(String domain) {
boolean isSSODomain = "josso".equals(domain);
if (logger.isDebugEnabled())
logger.debug(" JBoss Security Domain ["+domain+"] is" + (isSSODomain ? "" : " not") + " under SSO Control");
return isSSODomain;
}
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return null.
*
* The method was completely rewritten since the overriden operation,
* on succesfull authentication, sets as the authenticated Principal
* a SimplePrincipal instantiated using the provided username.
* The problem is that in JOSSO the username is a SSO Session Id, not
* a username. So we need to set the SSOUser returned by the JAAS Gateway
* Login Module as the authenticatd Principal.
* Since the JaasSecurityManager caches the authenticated user using the
* Principal referring to a JOSSO Session Id, we will need to map, for
* example when roles are checked against the realm, a user Principal
* back to its JOSSO Session Identifier Principal. This way the the user
* and its roles can be retrieved correctly by the JaasSecurityManager.
*
* @param username Username of the Principal to look up
* @param credentials Password or other credentials to use in
* authenticating this username
*/
public Principal authenticate(String username, String credentials) {
logger.debug("Begin authenticate, username=" + username);
Principal principal = null;
SSOUser ssoUser = null;
Principal caller = (Principal) SecurityAssociationValve.userPrincipal.get();
if (caller == null && username == null && credentials == null)
return null;
try {
Context securityCtx = null;
securityCtx = prepareENC();
if (securityCtx == null) {
logger.error("No security context for authenticate(String, String)");
return null;
}
// Get the JBoss security manager from the ENC context
SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
if (!isSSODomain(securityMgr.getSecurityDomain())) {
// This is not a SSO Security domain, let JBoss realm handle this ...
return super.authenticate(username, credentials);
}
principal = new SimplePrincipal(username);
char[] passwordChars = null;
if (credentials != null)
passwordChars = credentials.toCharArray();
Subject subject = new Subject();
if (securityMgr.isValid(principal, passwordChars, subject)) {
logger.debug("User: " + username + " is authenticated");
// Get the authorized subject set by the isValid() call on succesful
// authentication.
// Subject activeSubject = securityMgr.getActiveSubject();
// logger.debug("Authenticated Subject: " + activeSubject);
logger.debug("Authenticated Subject: " + subject);
Set principals = subject.getPrincipals(SSOUser.class);
Iterator i = principals.iterator();
while (i.hasNext()) {
ssoUser = (SSOUser) i.next();
break;
}
// Make the cache aware of the user-session association so that
// it can handle correctly cache entry lookups.
//_cachePolicy.attachSessionToUser(principal, ssoUser);
// Instead of associating the Principal used for authenticating (which is a
// session id), sets the authenticated principal to the SSOUser part of the
// Subject returned by the Gateway.
JBossSecurityAssociationActions.setPrincipalInfo(ssoUser, passwordChars, subject);
// Get the CallerPrincipal mapping
RealmMapping realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
Principal oldPrincipal = ssoUser;
principal = realmMapping.getPrincipal(oldPrincipal);
logger.debug("Mapped from input principal: " + oldPrincipal
+ "to: " + principal);
if (principal.equals(oldPrincipal) == false)
{
_userPrincipalMap.put(principal, oldPrincipal);
}
} else {
principal = null;
logger.debug("User: " + username + " is NOT authenticated");
}
} catch (NamingException e) {
principal = null;
logger.error("Error during authenticate", e);
}
logger.debug("End authenticate, principal=" + ssoUser);
return ssoUser;
}
/**
* Return <code>true</code> if the specified Principal has the specified
* security role, within the context of this Realm; otherwise return
* <code>false</code>.
*
* Since the Principal, in the JaasSecurityManager, has been stored in its cache
* using the JOSSO Single Sign-On Session Identifier Principal (see isValid method),
* when roles are checked , the Principal to be submitted to the overriden
* operation is not the user principal but the JOSSO Session Id Principal.
*
* @param principal Principal for whom the role is to be checked
* @param role Security role to be checked
*/
public boolean hasRole(Principal principal, String role) {
boolean hasRole = false;
try {
Context securityCtx = null;
securityCtx = prepareENC();
if (securityCtx == null) {
logger.error("No security context for authenticate(String, String)");
return false;
}
// Get the JBoss security manager from the ENC context
SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
if (!isSSODomain(securityMgr.getSecurityDomain())) {
// This is not a SSO Security domain, let JBoss realm handle this ...
return super.hasRole(principal, role);
}
Subject activeSubject = securityMgr.getActiveSubject();
logger.debug("Authenticated Subject: " + activeSubject);
CatalinaSSOUser ssoUser = CatalinaSSOUser.newInstance(this, activeSubject);
hasRole = super.hasRole(ssoUser, role);
} catch (NamingException e) {
principal = null;
logger.error("Error during authenticate", e);
}
return hasRole;
}
/**
* Return the Principal associated with the specified chain of X509
* client certificates. If there is none, return <code>null</code>.
*
* Before invoking the overriden operation it creates the security JNDI context
* in case one was not found.
*
* @param certs Array of client certificates, with the first one in
* the array being the certificate of the client itself.
*/
public Principal authenticate(X509Certificate[] certs) {
logger.debug("authenticate(X509Certificate[]), Begin");
try {
prepareENC();
return super.authenticate(certs);
} catch (NamingException ne) {
// Error creating ENC Context
logger.error("Cannot create ENC Context");
}
logger.debug("authenticate(), Emd");
return null;
}
/** This creates a java:comp/env/security context that contains a
securityMgr binding pointing to an AuthenticationManager implementation
and a realmMapping binding pointing to a RealmMapping implementation.
*/
protected Context prepareENC()
throws NamingException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InitialContext iniCtx = new InitialContext();
boolean securityContextExists = false;
boolean isJaasSecurityManager = false;
try {
Context envCtx = (Context) iniCtx.lookup("java:comp/env");
Context securityCtx = (Context) envCtx.lookup("security");
securityContextExists = true;
AuthenticationManager securityMgr = (AuthenticationManager)
securityCtx.lookup("securityMgr");
// If the Security Manager set in the web application ENC is not
// a JaasSecurityManager, unbind the Security context and rebind it
// with the JaasSecurityManager associated with the JOSSO Security Domain.
// Note: the jboss-web.xml file of the partner application MUST not have an
// entry referring to a security domain.
if (!(securityMgr instanceof JaasSecurityManager)) {
Util.unbind(envCtx, "security");
} else
isJaasSecurityManager = true;
} catch (NamingException e) {
// No Security Context found
}
// If we do not have a SecurityContext create it
Context envCtx = null;
if (!securityContextExists) {
Thread currentThread = Thread.currentThread();
logger.debug("Creating ENC using ClassLoader: " + loader);
ClassLoader parent = loader.getParent();
while (parent != null) {
logger.debug(".." + parent);
parent = parent.getParent();
}
envCtx = (Context) iniCtx.lookup("java:comp");
envCtx = envCtx.createSubcontext("env");
} else
envCtx = (Context) iniCtx.lookup("java:comp/env");
// If the Security Manager binded is not a JaasSecurityManager, rebind using
// the Security Manager associated with the JOSSO Security Domain.
if (!isJaasSecurityManager) {
// Prepare the Security JNDI subcontext
logger.debug("Linking security/securityMgr to JNDI name: " + JOSSO_SECURITY_DOMAIN);
Util.bind(envCtx, "security/securityMgr", new LinkRef(JOSSO_SECURITY_DOMAIN));
Util.bind(envCtx, "security/realmMapping", new LinkRef(JOSSO_SECURITY_DOMAIN));
Util.bind(envCtx, "security/security-domain", new LinkRef(JOSSO_SECURITY_DOMAIN));
Util.bind(envCtx, "security/subject", new LinkRef(JOSSO_SECURITY_DOMAIN + "/subject"));
}
logger.debug("JBossCatalinaRealm.prepareENC, End");
return (Context) iniCtx.lookup("java:comp/env/security");
}
/** Lookup the authentication CachePolicy object for a security domain. This
method first treats the cacheJndiName as a ObjectFactory location that is
capable of returning CachePolicy instances on a per security domain basis
by appending a '/security-domain-name' string to the cacheJndiName when
looking up the CachePolicy for a domain. If this fails then the cacheJndiName
location is treated as a single CachePolicy for all security domains.
@deprecated No longer used for JBoss 3.2.6 support
*/
private static CachePolicy lookupCachePolicy(String securityDomain) {
CachePolicy authCache = null;
String domainCachePath = cacheJndiName + '/' + securityDomain;
try {
InitialContext iniCtx = new InitialContext();
authCache = (CachePolicy) iniCtx.lookup(domainCachePath);
} catch (Exception e) {
// Failed, treat the cacheJndiName name as a global CachePolicy binding
try {
InitialContext iniCtx = new InitialContext();
authCache = (CachePolicy) iniCtx.lookup(cacheJndiName);
} catch (Exception e2) {
logger.warn("Failed to locate auth CachePolicy at: " + cacheJndiName
+ " for securityDomain=" + securityDomain);
}
}
return authCache;
}
/** Use reflection to attempt to set the authentication cache on the
* securityMgr argument.
*
* This is done this way to avoid dependency with JaasSecurityManager.
*
* @deprecated No longer used for JBoss 3.2.6 support
* @param securityMgr the security manager
* @param cachePolicy the cache policy implementation
*/
private static void setSecurityDomainCache(AuthenticationManager securityMgr,
CachePolicy cachePolicy) {
try {
Class[] setCachePolicyTypes = {CachePolicy.class};
Method m = securityMgr.getClass().getMethod("setCachePolicy", setCachePolicyTypes);
Object[] setCachePolicyArgs = {cachePolicy};
m.invoke(securityMgr, setCachePolicyArgs);
logger.debug("setCachePolicy, c=" + setCachePolicyArgs[0]);
} catch (Exception e2) { // No cache policy support, this is ok
logger.debug("setCachePolicy failed", e2);
}
}
}