/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * 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.jboss.security.plugins; import java.beans.PropertyEditorManager; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.security.AccessController; import java.security.Principal; import java.security.PrivilegedAction; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.naming.CommunicationException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NameClassPair; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.StringRefAddr; import javax.naming.spi.ObjectFactory; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.jacc.PolicyContext; import org.jboss.logging.Logger; import org.jboss.security.AuthenticationManager; import org.jboss.security.SecurityAssociation; import org.jboss.security.SecurityConstants; import org.jboss.security.SecurityDomain; import org.jboss.security.SecurityProxyFactory; import org.jboss.security.auth.callback.CallbackHandlerPolicyContextHandler; import org.jboss.security.auth.callback.JBossCallbackHandler; import org.jboss.security.config.SecurityConfiguration; import org.jboss.security.integration.JNDIBasedSecurityManagement; import org.jboss.security.integration.SecurityConstantsBridge; import org.jboss.security.jacc.SubjectPolicyContextHandler; import org.jboss.security.propertyeditor.PrincipalEditor; import org.jboss.security.propertyeditor.SecurityDomainEditor; import org.jboss.system.ServiceMBeanSupport; import org.jboss.util.CachePolicy; import org.jboss.util.TimedCachePolicy; /** * This is a JMX service which manages JAAS based SecurityManagers. * JAAS SecurityManagers are responsible for validating credentials * associated with principals. The service defaults to the * org.jboss.security.plugins.JaasSecurityManager implementation but * this can be changed via the securityManagerClass property. * * @see JaasSecurityManager * @see org.jboss.security.SubjectSecurityManager * * @author <a href="on@ibis.odessa.ua">Oleg Nitz</a> * @author <a href="rickard@telkel.com">Rickard Oberg</a> * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a> * @author <a href="mailto:Anil.Saldhana@jboss.org">Anil Saldhana</a> * @version $Revision: 96172 $ */ public class JaasSecurityManagerService extends ServiceMBeanSupport implements JaasSecurityManagerServiceMBean { private static final String SECURITY_MGR_PATH = "java:/jaas"; private static final String DEFAULT_CACHE_POLICY_PATH = "java:/timedCacheFactory"; /** The log4j interface */ private static Logger log; /** The class that provides the security manager implementation */ private static String securityMgrClassName = "org.jboss.security.plugins.JaasSecurityManager"; /** The loaded securityMgrClassName */ private static Class securityMgrClass = JaasSecurityManager.class; /** The JAAS CallbackHandler interface implementation to use */ private static String callbackHandlerClassName = "org.jboss.security.auth.callback.JBossCallbackHandler"; private static Class callbackHandlerClass = org.jboss.security.auth.callback.JBossCallbackHandler.class; /** 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; private static int defaultCacheTimeout = 30*60; private static int defaultCacheResolution = 60; /** The class that provides the SecurityProxyFactory implementation */ private static String securityProxyFactoryClassName = "org.jboss.security.SubjectSecurityProxyFactory"; private static Class securityProxyFactoryClass = org.jboss.security.SubjectSecurityProxyFactory.class; /** A mapping from security domain name to a SecurityDomainContext object */ private static ConcurrentHashMap securityDomainCtxMap = new ConcurrentHashMap(); private static NameParser parser; /** A flag indicating if the SecurityAssociation.setServer should be called */ private boolean serverMode = true; /** A flag indicating if the Deep Copy of Subject Sets should be enabled in the security managers */ private static boolean deepCopySubjectMode = false; /** The default unauthenticated principal */ private static String defaultUnauthenticatedPrincipal = "Unauthenticated Principal"; /** Frequency of the thread cleaning the authentication cache of expired entries */ private static int defaultCacheFlushPeriod = 60*60; private static JNDIBasedSecurityManagement securityManagement = SecurityConstantsBridge.getSecurityManagement(); static { // Get a log interface, required for some statics below // can not use instance field inherited from ServiceMBeanSupport log = Logger.getLogger(JaasSecurityManagerService.class); } /** The constructor does nothing as the security manager is created on each lookup into java:/jaas/xxx. This is also why all variables in this class are static. */ public JaasSecurityManagerService() { } public boolean getServerMode() { return serverMode; } public void setServerMode(boolean mode) { this.serverMode = mode; } public String getSecurityManagerClassName() { return securityMgrClassName; } public void setSecurityManagerClassName(String className) throws ClassNotFoundException, ClassCastException { securityMgrClassName = className; ClassLoader loader = getContextClassLoader(); securityMgrClass = loader.loadClass(securityMgrClassName); if( AuthenticationManager.class.isAssignableFrom(securityMgrClass) == false ) throw new ClassCastException(securityMgrClass+" does not implement "+AuthenticationManager.class); } public String getSecurityProxyFactoryClassName() { return securityProxyFactoryClassName; } public void setSecurityProxyFactoryClassName(String className) throws ClassNotFoundException { securityProxyFactoryClassName = className; ClassLoader loader = getContextClassLoader(); securityProxyFactoryClass = loader.loadClass(securityProxyFactoryClassName); } /** Get the default CallbackHandler implementation class name * * @return The fully qualified classname of the */ public String getCallbackHandlerClassName() { return JaasSecurityManagerService.callbackHandlerClassName; } /** Set the default CallbackHandler implementation class name * @see javax.security.auth.callback.CallbackHandler */ public void setCallbackHandlerClassName(String className) throws ClassNotFoundException { callbackHandlerClassName = className; ClassLoader loader = getContextClassLoader(); callbackHandlerClass = loader.loadClass(callbackHandlerClassName); } /** Get the jndi name under which the authentication cache policy is found */ public String getAuthenticationCacheJndiName() { return cacheJndiName; } /** Set the jndi name under which the authentication cache policy is found */ public void setAuthenticationCacheJndiName(String jndiName) { cacheJndiName = jndiName; } /** Get the default timed cache policy timeout. @return the default cache timeout in seconds. */ public int getDefaultCacheTimeout() { return defaultCacheTimeout; } /** Set the default timed cache policy timeout. This has no affect if the AuthenticationCacheJndiName has been changed from the default value. @param timeoutInSecs - the cache timeout in seconds. */ public void setDefaultCacheTimeout(int timeoutInSecs) { defaultCacheTimeout = timeoutInSecs; SecurityConstantsBridge.defaultCacheTimeout = timeoutInSecs; } /** Get the default timed cache policy resolution. */ public int getDefaultCacheResolution() { return defaultCacheResolution; } /** Set the default timed cache policy resolution. This has no affect if the AuthenticationCacheJndiName has been changed from the default value. @param resInSecs - resolution of timeouts in seconds. */ public void setDefaultCacheResolution(int resInSecs) { defaultCacheResolution = resInSecs; SecurityConstantsBridge.defaultCacheResolution = resInSecs; } /** * @see JaasSecurityManagerServiceMBean#getDeepCopySubjectMode() */ public boolean getDeepCopySubjectMode() { return deepCopySubjectMode; } /** * @see JaasSecurityManagerServiceMBean#getDeepCopySubjectMode() */ public void setDeepCopySubjectMode(boolean flag) { log.debug("setDeepCopySubjectMode="+flag); deepCopySubjectMode = flag; //Update the security managers if already present if(securityDomainCtxMap.isEmpty() == false) { Iterator iter = securityDomainCtxMap.keySet().iterator(); while(iter.hasNext()) { String securityDomainName = (String)iter.next(); SecurityDomainContext sdc = (SecurityDomainContext)securityDomainCtxMap.get(securityDomainName); setDeepCopySubjectOption(sdc.securityMgr, flag); } } SecurityConfiguration.setDeepCopySubjectMode(flag); } /** Set the indicated security domain cache timeout. This only has an effect if the security domain is using the default jboss TimedCachePolicy implementation. @param securityDomain the name of the security domain cache @param timeoutInSecs - the cache timeout in seconds. @param resInSecs - resolution of timeouts in seconds. */ public void setCacheTimeout(String securityDomain, int timeoutInSecs, int resInSecs) { CachePolicy cache = getCachePolicy(securityDomain); if( cache != null && cache instanceof TimedCachePolicy ) { TimedCachePolicy tcp = (TimedCachePolicy) cache; synchronized( tcp ) { tcp.setDefaultLifetime(timeoutInSecs); tcp.setResolution(resInSecs); } } else { log.warn("Failed to find cache policy for securityDomain='" + securityDomain + "'"); } //Set the CacheTimeOut on JNDIBasedSecurityManagement JNDIBasedSecurityManagement.setCacheTimeout(securityDomain, timeoutInSecs, resInSecs); } /** * Get the authentication cache flush period * @return period in seconds */ public int getDefaultCacheFlushPeriod() { return defaultCacheFlushPeriod; } /** * Set the authentication cache flush period * * @param flushPeriodInSecs */ public void setDefaultCacheFlushPeriod(int flushPeriodInSecs) { this.defaultCacheFlushPeriod = flushPeriodInSecs; JNDIBasedSecurityManagement.setDefaultCacheFlushPeriod(flushPeriodInSecs); } /** flush the cache policy for the indicated security domain if one exists. * @param securityDomain the name of the security domain cache */ public void flushAuthenticationCache(String securityDomain) { CachePolicy cache = getCachePolicy(securityDomain); if( cache != null ) { cache.flush(); } else { log.warn("Failed to find cache policy for securityDomain='" + securityDomain + "'"); } } /** Flush a principal's authentication cache entry associated with the * given securityDomain. * * @param securityDomain the name of the security domain cache * @param user the principal of the user to flush */ public void flushAuthenticationCache(String securityDomain, Principal user) { CachePolicy cache = getCachePolicy(securityDomain); if( cache != null ) { cache.remove(user); } else { log.warn("Failed to find cache policy for securityDomain='" + securityDomain + "'"); } } /** Return the active principals in the indicated security domain auth cache. * @param securityDomain the name of the security to lookup the cache for * @return List<Principal> of active keys found in the auth cache if * the cache exists and is accessible, null otherwise. */ public List getAuthenticationCachePrincipals(String securityDomain) { CachePolicy cache = getCachePolicy(securityDomain); List validPrincipals = null; if( cache instanceof TimedCachePolicy ) { TimedCachePolicy tcache = (TimedCachePolicy) cache; validPrincipals = tcache.getValidKeys(); } return validPrincipals; } // Begin SecurityManagerMBean interface methods public boolean isValid(String securityDomain, Principal principal, Object credential) { boolean isValid = false; try { SecurityDomainContext sdc = lookupSecurityDomain(securityDomain); isValid = sdc.getSecurityManager().isValid(principal, credential, null); } catch(NamingException e) { log.debug("isValid("+securityDomain+") failed", e); } return isValid; } public Principal getPrincipal(String securityDomain, Principal principal) { Principal realmPrincipal = null; try { SecurityDomainContext sdc = lookupSecurityDomain(securityDomain); realmPrincipal = sdc.getRealmMapping().getPrincipal(principal); } catch(NamingException e) { log.debug("getPrincipal("+securityDomain+") failed", e); } return realmPrincipal; } public boolean doesUserHaveRole(String securityDomain, Principal principal, Object credential, Set roles) { boolean doesUserHaveRole = false; try { SecurityDomainContext sdc = lookupSecurityDomain(securityDomain); // Must first validate the user Subject subject = new Subject(); boolean isValid = sdc.getSecurityManager().isValid(principal, credential, subject); if( isValid ) { // Now can query if the authenticated Subject has the role SubjectActions.pushSubjectContext(principal, credential, subject, sdc.getSecurityManager().getSecurityDomain()); doesUserHaveRole = sdc.getRealmMapping().doesUserHaveRole(principal, roles); SubjectActions.popSubjectContext(); } } catch(NamingException e) { log.debug("doesUserHaveRole("+securityDomain+") failed", e); } return doesUserHaveRole; } public Set getUserRoles(String securityDomain, Principal principal, Object credential) { Set userRoles = null; try { SecurityDomainContext sdc = lookupSecurityDomain(securityDomain); // Must first validate the user Subject subject = new Subject(); boolean isValid = sdc.getSecurityManager().isValid(principal, credential, subject); // Now can query if the authenticated Subject has the role if( isValid ) { SubjectActions.pushSubjectContext(principal, credential, subject, sdc.getSecurityManager().getSecurityDomain() ); userRoles = sdc.getRealmMapping().getUserRoles(principal); SubjectActions.popSubjectContext(); } } catch(NamingException e) { log.debug("getUserRoles("+securityDomain+") failed", e); } return userRoles; } // End SecurityManagerMBean interface methods protected void startService() throws Exception { // use thread-local principal and credential propagation if (serverMode) SecurityAssociation.setServer(); // Register the default active Subject PolicyContextHandler SubjectPolicyContextHandler handler = new SubjectPolicyContextHandler(); PolicyContext.registerHandler(SecurityConstants.SUBJECT_CONTEXT_KEY, handler, true); // Register the JAAS CallbackHandler JACC PolicyContextHandlers CallbackHandlerPolicyContextHandler chandler = new CallbackHandlerPolicyContextHandler(); PolicyContext.registerHandler(CallbackHandlerPolicyContextHandler.CALLBACK_HANDLER_KEY, chandler, true); Context ctx = new InitialContext(); parser = ctx.getNameParser(""); RefAddr refAddr = new StringRefAddr("nns", "JSMCachePolicy"); String factoryName = DefaultCacheObjectFactory.class.getName(); Reference ref = new Reference("javax.naming.Context", refAddr, factoryName, null); ctx.rebind(DEFAULT_CACHE_POLICY_PATH, ref); log.debug("cachePolicyCtxPath="+cacheJndiName); // Bind the default SecurityProxyFactory instance under java:/SecurityProxyFactory SecurityProxyFactory proxyFactory = (SecurityProxyFactory) securityProxyFactoryClass.newInstance(); ctx.bind("java:/SecurityProxyFactory", proxyFactory); log.debug("SecurityProxyFactory="+proxyFactory); //Handler custom callbackhandler if(callbackHandlerClass != JBossCallbackHandler.class) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { System.setProperty(JNDIBasedSecurityManagement.CBH, callbackHandlerClassName); return null; } }); CallbackHandler callbackHandler = null; callbackHandler = (CallbackHandler) callbackHandlerClass.newInstance(); if (callbackHandler != null) securityManagement.setCallBackHandler(callbackHandler); } // Set AuthenticationManager class securityManagement.setAuthenticationMgrClass(securityMgrClassName); // Register the Principal property editor PropertyEditorManager.registerEditor(Principal.class, PrincipalEditor.class); PropertyEditorManager.registerEditor(SecurityDomain.class, SecurityDomainEditor.class); log.debug("Registered PrincipalEditor, SecurityDomainEditor"); log.debug("ServerMode="+this.serverMode); log.debug("SecurityMgrClass="+JaasSecurityManagerService.securityMgrClass); log.debug("CallbackHandlerClass="+JaasSecurityManagerService.callbackHandlerClass); } protected void stopService() throws Exception { InitialContext ic = new InitialContext(); try { ic.unbind(SECURITY_MGR_PATH); } catch(CommunicationException e) { // Do nothing, the naming services is already stopped } finally { ic.close(); } } /** Register a SecurityDomain implmentation. This is synchronized to ensure * that the binding of the security domain and cache population is atomic. * @param securityDomain the name of the security domain * @param instance the SecurityDomain instance to bind */ public synchronized void registerSecurityDomain(String securityDomain, SecurityDomain instance) { log.debug("Added "+securityDomain+", "+instance+" to map"); CachePolicy authCache = lookupCachePolicy(securityDomain); SecurityDomainContext sdc = new SecurityDomainContext(instance, authCache); securityDomainCtxMap.put(securityDomain, sdc); // See if the security mgr supports an externalized cache policy setSecurityDomainCache(instance, authCache); } /** Access the CachePolicy for the securityDomain. * @param securityDomain the name of the security domain * @return The CachePolicy if found, null otherwise. */ private static CachePolicy getCachePolicy(String securityDomain) { if( securityDomain.startsWith(SECURITY_MGR_PATH) ) securityDomain = securityDomain.substring(SECURITY_MGR_PATH.length()+1); CachePolicy cache = null; try { SecurityDomainContext sdc = lookupSecurityDomain(securityDomain); if( sdc != null ) cache = sdc.getAuthenticationCache(); } catch(NamingException e) { log.debug("getCachePolicy("+securityDomain+") failure", e); } return cache; } /** 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. */ 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) { log.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. * @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 = securityMgrClass.getMethod("setCachePolicy", setCachePolicyTypes); Object[] setCachePolicyArgs = {cachePolicy}; m.invoke(securityMgr, setCachePolicyArgs); log.debug("setCachePolicy, c="+setCachePolicyArgs[0]); } catch(Exception e2) { if(log.isTraceEnabled()) log.trace("Optional setCachePolicy failed" + e2.getLocalizedMessage()); } } /** Use reflection to attempt to set the DeepCopySubject on the * securityMgr argument. * @param securityMgr the security manager * @param flag deep copy subject option */ private static void setDeepCopySubjectOption(AuthenticationManager securityMgr, boolean flag) { Boolean bValue = flag ? Boolean.TRUE : Boolean.FALSE; try { Class[] setDeepCopySubjTypes = {Boolean.class}; Method m = securityMgrClass.getMethod("setDeepCopySubjectOption", setDeepCopySubjTypes); Object[] setDeepCopySubjectOptionArgs = {bValue}; m.invoke(securityMgr, setDeepCopySubjectOptionArgs); log.debug("setDeepCopySubjectOption, c="+setDeepCopySubjectOptionArgs[0]); } catch(Exception e2) { // No setDeepCopySubjectOption support, this is ok log.debug("setDeepCopySubjectOption failed", e2); } } /** Lookup or create the SecurityDomainContext for securityDomain. * @param securityDomain * @return the SecurityDomainContext for securityDomain * @throws NamingException */ private synchronized static SecurityDomainContext lookupSecurityDomain(String securityDomain) throws NamingException { SecurityDomainContext securityDomainCtx = (SecurityDomainContext) securityDomainCtxMap.get(securityDomain); if( securityDomainCtx == null ) { securityDomainCtx = (SecurityDomainContext) new InitialContext().lookup( SecurityConstants.JAAS_CONTEXT_ROOT + "/" + securityDomain + "/domainContext"); securityDomainCtxMap.put(securityDomain, securityDomainCtx); log.debug("Added "+securityDomain+", "+securityDomainCtx+" to map"); } return securityDomainCtx; } /** * Get the default unauthenticated principal. * @return The principal name */ public String getDefaultUnauthenticatedPrincipal() { return defaultUnauthenticatedPrincipal; } /** * Set the default unauthenticated principal. * @param principal The principal name */ public void setDefaultUnauthenticatedPrincipal(String principal) { defaultUnauthenticatedPrincipal = principal; } /** * @see JaasSecurityManagerServiceMBean#getJCAInformation() */ public String displayJCAInformation() { String[] sarr = new String[]{"Cipher","Signature","KeyFactory", "SecretKeyFactory","AlgorithmParameters", "MessageDigest","Mac"}; StringBuilder sb = new StringBuilder(); JCASecurityInfo jsi = new JCASecurityInfo(); sb.append("JCA Providers=").append(jsi.getJCAProviderInfo()); sb.append("JCA Service/Algorithms="); for(String serviceName:sarr) { sb.append(jsi.getJCAAlgorithms(serviceName)); } return sb.toString(); } static class DomainEnumeration implements NamingEnumeration { Enumeration domains; Map ctxMap; DomainEnumeration(Enumeration domains, Map ctxMap) { this.domains = domains; this.ctxMap = ctxMap; } public void close() { } public boolean hasMoreElements() { return domains.hasMoreElements(); } public boolean hasMore() { return domains.hasMoreElements(); } public Object next() { String name = (String) domains.nextElement(); Object value = ctxMap.get(name); String className = value.getClass().getName(); NameClassPair pair = new NameClassPair(name, className); return pair; } public Object nextElement() { return domains.nextElement(); } } /** java:/timedCacheFactory ObjectFactory implementation */ public static class DefaultCacheObjectFactory implements InvocationHandler, ObjectFactory { /** Object factory implementation. This method returns a Context proxy that is only able to handle a lookup operation for an atomic name of a security domain. */ public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class[] interfaces = {Context.class}; Context ctx = (Context) Proxy.newProxyInstance(loader, interfaces, this); return ctx; } /** This is the InvocationHandler callback for the Context interface that was created by out getObjectInstance() method. All this does is create a new TimedCache instance. */ public Object invoke(Object obj, Method method, Object[] args) throws Throwable { TimedCachePolicy cachePolicy = new TimedCachePolicy(defaultCacheTimeout, true, defaultCacheResolution); cachePolicy.create(); cachePolicy.start(); return cachePolicy; } } static ClassLoader getContextClassLoader() { return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); } }