SecurityAssociation.java |
/* * JBoss, the OpenSource J2EE webOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.security; import java.security.Principal; import java.util.ArrayList; import java.util.HashMap; import javax.security.auth.Subject; import org.jboss.logging.Logger; /** * The SecurityAssociation class maintains the security principal and * credentials. This can be done on either a singleton basis or a thread local * basis depending on the server property. When the server property has been set * to true, the security information is maintained in thread local storage. The * type of thread local storage depends on the org.jboss.security.SecurityAssociation.ThreadLocal * property. If this property is true, then the thread local storage object is * of type java.lang.ThreadLocal which results in the current thread's security * information NOT being propagated to child threads. * * When the property is false or does not exist, the thread local storage object * is of type java.lang.InheritableThreadLocal, and any threads spawned by the * current thread will inherit the security information of the current thread. * Subseqent changes to the current thread's security information are NOT * propagated to any previously spawned child threads. * * When the server property is false, security information is maintained in * class variables which makes the information available to all threads within * the current VM. * * Note that this is not a public API class. Its an implementation detail that * is subject to change without notice. * * @author Daniel O'Connor (docodan@nycap.rr.com) * @author Scott.Stark@jboss.org * @version $Revision: 1.19.2.5 $ */ public final class SecurityAssociation { private static Logger log = Logger.getLogger(SecurityAssociation.class); /** * A flag indicating if trace level logging should be performed */ private static boolean trace; /** * A flag indicating if security information is global or thread local */ private static boolean server; /** * The SecurityAssociation principal used when the server flag is false */ private static Principal principal; /** * The SecurityAssociation credential used when the server flag is false */ private static Object credential; /** * The SecurityAssociation principal used when the server flag is true */ private static ThreadLocal threadPrincipal; /** * The SecurityAssociation credential used when the server flag is true */ private static ThreadLocal threadCredential; /** * The SecurityAssociation HashMap<String, Object> */ private static ThreadLocal threadContextMap; /** * Thread local stacks of run-as principal roles used to implement J2EE * run-as identity propagation */ private static RunAsThreadLocalStack threadRunAsStacks = new RunAsThreadLocalStack(); /** * Thread local stacks of authenticated subject used to control the current * caller security context */ private static SubjectThreadLocalStack threadSubjectStacks = new SubjectThreadLocalStack(); /** * The permission required to access getPrincpal, getCredential */ private static final RuntimePermission getPrincipalInfoPermission = new RuntimePermission("org.jboss.security.SecurityAssociation.getPrincipalInfo"); /** * The permission required to access getSubject */ private static final RuntimePermission getSubjectPermission = new RuntimePermission("org.jboss.security.SecurityAssociation.getSubject"); /** * The permission required to access setPrincpal, setCredential, setSubject * pushSubjectContext, popSubjectContext */ private static final RuntimePermission setPrincipalInfoPermission = new RuntimePermission("org.jboss.security.SecurityAssociation.setPrincipalInfo"); /** * The permission required to access setServer */ private static final RuntimePermission setServerPermission = new RuntimePermission("org.jboss.security.SecurityAssociation.setServer"); /** * The permission required to access pushRunAsIdentity/popRunAsIdentity */ private static final RuntimePermission setRunAsIdentity = new RuntimePermission("org.jboss.security.SecurityAssociation.setRunAsRole"); /** * The permission required to get the current security context info */ private static final RuntimePermission getContextInfo = new RuntimePermission("org.jboss.security.SecurityAssociation.accessContextInfo", "get"); /** * The permission required to set the current security context info */ private static final RuntimePermission setContextInfo = new RuntimePermission("org.jboss.security.SecurityAssociation.accessContextInfo", "set"); static { boolean useThreadLocal = true; try { useThreadLocal = Boolean.getBoolean("org.jboss.security.SecurityAssociation.ThreadLocal"); } catch (SecurityException e) { // Ignore and use the default } trace = log.isTraceEnabled(); if (useThreadLocal) { threadPrincipal = new ThreadLocal(); threadCredential = new ThreadLocal(); threadContextMap = new ThreadLocal() { protected Object initialValue() { return new HashMap(); } }; } else { threadPrincipal = new InheritableThreadLocal(); threadCredential = new InheritableThreadLocal(); threadContextMap = new InheritableThreadLocal() { protected Object initialValue() { return new HashMap(); } }; } } /** * Get the current principal information. If a security manager is present, * then this method calls the security manager's <code>checkPermission</code> * method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.getPrincipalInfo") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. * @return Principal, the current principal identity. */ public static Principal getPrincipal() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(getPrincipalInfoPermission); if (peekRunAsIdentity() != null) return peekRunAsIdentity(); if (server) return (Principal) threadPrincipal.get(); else return principal; } /** * Get the caller's principal information. If a security manager is present, * then this method calls the security manager's <code>checkPermission</code> * method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.getPrincipalInfo") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. * @return Principal, the current principal identity. */ public static Principal getCallerPrincipal() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(getPrincipalInfoPermission); if (peekRunAsIdentity(1) != null) return peekRunAsIdentity(1); if (server) return (Principal) threadPrincipal.get(); else return principal; } /** * Get the current principal credential information. This can be of any type * including: a String password, a char[] password, an X509 cert, etc. If a * security manager is present, then this method calls the security manager's * <code>checkPermission</code> method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.getPrincipalInfo") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. * @return Object, the credential that proves the principal identity. */ public static Object getCredential() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(getPrincipalInfoPermission); if (peekRunAsIdentity() != null) return peekRunAsIdentity().getCredential(); if (server) return threadCredential.get(); else return credential; } /** * Get the current Subject information. If a security manager is present, * then this method calls the security manager's checkPermission method with * a RuntimePermission("org.jboss.security.SecurityAssociation.getSubject") * permission to ensure it's ok to access principal information. If not, a * SecurityException will be thrown. Note that this method does not consider * whether or not a run-as identity exists. For access to this information * see the JACC PolicyContextHandler registered under the key * "javax.security.auth.Subject.container" * @return Subject, the current Subject identity. * @see javax.security.jacc.PolicyContext#getContext(String) */ public static Subject getSubject() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(getSubjectPermission); SubjectContext sc = threadSubjectStacks.peek(); Subject subject = null; if( sc != null ) subject = sc.getSubject(); return subject; } /** * Set the current principal information. If a security manager is present, * then this method calls the security manager's <code>checkPermission</code> * method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.setPrincipalInfo") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. * @param principal - the current principal identity. */ public static void setPrincipal(Principal principal) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setPrincipalInfoPermission); if (trace) log.trace("setPrincipal, p=" + principal + ", server=" + server); if (server) threadPrincipal.set(principal); else SecurityAssociation.principal = principal; // Integrate with the new SubjectContext SubjectContext sc = threadSubjectStacks.peek(); if( sc == null ) { // There is no active security context sc = new SubjectContext(); threadSubjectStacks.push(sc); } else if( (sc.getFlags() & SubjectContext.PRINCIPAL_WAS_SET) != 0 ) { // The current security context has its principal set sc = new SubjectContext(); threadSubjectStacks.push(sc); } sc.setPrincipal(principal); } /** * Set the current principal credential information. This can be of any type * including: a String password, a char[] password, an X509 cert, etc. * * If a security manager is present, then this method calls the security * manager's <code>checkPermission</code> method with a <code> * RuntimePermission("org.jboss.security.SecurityAssociation.setPrincipalInfo") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. * @param credential - the credential that proves the principal identity. */ public static void setCredential(Object credential) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setPrincipalInfoPermission); if (server) threadCredential.set(credential); else SecurityAssociation.credential = credential; // Integrate with the new SubjectContext SubjectContext sc = threadSubjectStacks.peek(); if( sc == null ) { // There is no active security context sc = new SubjectContext(); threadSubjectStacks.push(sc); } else if( (sc.getFlags() & SubjectContext.CREDENTIAL_WAS_SET) != 0 ) { // The current security context has its principal set sc = new SubjectContext(); threadSubjectStacks.push(sc); } sc.setCredential(credential); } /** * Set the current Subject information. If a security manager is present, * then this method calls the security manager's <code>checkPermission</code> * method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.setPrincipalInfo") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. * @param subject - the current identity. */ public static void setSubject(Subject subject) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setPrincipalInfoPermission); if (trace) log.trace("setSubject, s=" + subject + ", server=" + server); // Integrate with the new SubjectContext SubjectContext sc = threadSubjectStacks.peek(); if( sc == null ) { // There is no active security context sc = new SubjectContext(); threadSubjectStacks.push(sc); } else if( (sc.getFlags() & SubjectContext.SUBJECT_WAS_SET) != 0 ) { // The current security context has its subject set sc = new SubjectContext(); threadSubjectStacks.push(sc); } sc.setSubject(subject); } /** * Get the current thread context info. If a security manager is present, * then this method calls the security manager's <code>checkPermission</code> * method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.accessContextInfo", * "get") </code> permission to ensure it's ok to access context information. * If not, a <code>SecurityException</code> will be thrown. * @param key - the context key * @return the mapping for the key in the current thread context */ public static Object getContextInfo(Object key) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(getContextInfo); HashMap contextInfo = (HashMap) threadContextMap.get(); return contextInfo.get(key); } /** * Set the current thread context info. If a security manager is present, * then this method calls the security manager's <code>checkPermission</code> * method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.accessContextInfo", * "set") </code> permission to ensure it's ok to access context information. * If not, a <code>SecurityException</code> will be thrown. * @param key - the context key * @param value - the context value to associate under key * @return the previous mapping for the key if one exists */ public static Object setContextInfo(Object key, Object value) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setContextInfo); HashMap contextInfo = (HashMap) threadContextMap.get(); return contextInfo.put(key, value); } /** * Push the current authenticated context. This sets the authenticated subject * along with the principal and proof of identity that was used to validate * the subject. This context is used for authorization checks. Typically * just the subject as seen by getSubject() is input into the authorization. * When run under a security manager this requires the * RuntimePermission("org.jboss.security.SecurityAssociation.setPrincipalInfo") * permission. * @param subject - the authenticated subject * @param principal - the principal that was input into the authentication * @param credential - the credential that was input into the authentication */ public static void pushSubjectContext(Subject subject, Principal principal, Object credential) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setPrincipalInfoPermission); if (trace) log.trace("pushSubjectContext, subject=" + subject + ", principal="+principal); // Set the legacy single-value access points if (server) { threadPrincipal.set(principal); threadCredential.set(credential); } else { SecurityAssociation.principal = principal; SecurityAssociation.credential = credential; } // Push the subject context SubjectContext sc = new SubjectContext(subject, principal, credential); threadSubjectStacks.push(sc); } /** * Pop the current SubjectContext from the previous pushSubjectContext call * and return the pushed SubjectContext ig there was one. * When run under a security manager this requires the * RuntimePermission("org.jboss.security.SecurityAssociation.setPrincipalInfo") * permission. * @return the SubjectContext pushed previously by a pushSubjectContext call */ public static SubjectContext popSubjectContext() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setPrincipalInfoPermission); SubjectContext sc = threadSubjectStacks.pop(); return sc; } /** * Clear all principal information. If a security manager is present, then * this method calls the security manager's <code>checkPermission</code> * method with a <code> RuntimePermission("org.jboss.security.SecurityAssociation.setPrincipalInfo") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. */ public static void clear() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setPrincipalInfoPermission); if (trace) log.trace("clear, server=" + server); if (server == true) { threadPrincipal.set(null); threadCredential.set(null); } else { SecurityAssociation.principal = null; SecurityAssociation.credential = null; } // threadSubjectStacks.clear(); } /** * Push the current thread of control's run-as identity. */ public static void pushRunAsIdentity(RunAsIdentity runAs) { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setRunAsIdentity); if (trace) log.trace("pushRunAsIdentity, runAs=" + runAs); threadRunAsStacks.push(runAs); } /** * Pop the current thread of control's run-as identity. */ public static RunAsIdentity popRunAsIdentity() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setRunAsIdentity); RunAsIdentity runAs = threadRunAsStacks.pop(); if (trace) log.trace("popRunAsIdentity, runAs=" + runAs); return runAs; } /** * Look at the current thread of control's run-as identity on the top of the * stack. */ public static RunAsIdentity peekRunAsIdentity() { return peekRunAsIdentity(0); } /** * Look at the current thread of control's run-as identity at the indicated * depth. Typically depth is either 0 for the identity the current caller * run-as that will be assumed, or 1 for the active run-as the previous * caller has assumed. * @return RunAsIdentity depth frames up. */ public static RunAsIdentity peekRunAsIdentity(int depth) { RunAsIdentity runAs = threadRunAsStacks.peek(depth); return runAs; } /** * Set the server mode of operation. When the server property has been set to * true, the security information is maintained in thread local storage. This * should be called to enable property security semantics in any * multi-threaded environment where more than one thread requires that * security information be restricted to the thread's flow of control. * * If a security manager is present, then this method calls the security * manager's <code>checkPermission</code> method with a <code> * RuntimePermission("org.jboss.security.SecurityAssociation.setServer") * </code> permission to ensure it's ok to access principal information. If * not, a <code>SecurityException</code> will be thrown. */ public static void setServer() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(setServerPermission); server = true; } /** * A subclass of ThreadLocal that implements a value stack using an ArrayList * and implements push, pop and peek stack operations on the thread local * ArrayList. */ private static class RunAsThreadLocalStack extends ThreadLocal { protected Object initialValue() { return new ArrayList(); } void push(RunAsIdentity runAs) { ArrayList stack = (ArrayList) super.get(); stack.add(runAs); } RunAsIdentity pop() { ArrayList stack = (ArrayList) super.get(); RunAsIdentity runAs = null; int lastIndex = stack.size() - 1; if (lastIndex >= 0) runAs = (RunAsIdentity) stack.remove(lastIndex); return runAs; } /** * Look for the first non-null run-as identity on the stack starting * with the value at depth. * @return The run-as identity if one exists, null otherwise. */ RunAsIdentity peek(int depth) { ArrayList stack = (ArrayList) super.get(); RunAsIdentity runAs = null; final int stackSize = stack.size(); do { int index = stackSize - 1 - depth; if( index >= 0 ) runAs = (RunAsIdentity) stack.get(index); depth ++; } while (runAs == null && depth <= stackSize - 1); return runAs; } } /** * The encapsulation of the authenticated subject */ public static class SubjectContext { public static final int SUBJECT_WAS_SET = 1; public static final int PRINCIPAL_WAS_SET = 2; public static final int CREDENTIAL_WAS_SET = 4; private Subject subject; private Principal principal; private Object credential; private int flags; public SubjectContext() { this.flags = 0; } public SubjectContext(Subject s, Principal p, Object cred) { this.subject = s; this.principal = p; this.credential = cred; this.flags = SUBJECT_WAS_SET | PRINCIPAL_WAS_SET | CREDENTIAL_WAS_SET; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; this.flags |= SUBJECT_WAS_SET; } public Principal getPrincipal() { return principal; } public void setPrincipal(Principal principal) { this.principal = principal; this.flags |= PRINCIPAL_WAS_SET; } public Object getCredential() { return credential; } public void setCredential(Object credential) { this.credential = credential; this.flags |= CREDENTIAL_WAS_SET; } public int getFlags() { return this.flags; } } private static class SubjectThreadLocalStack extends ThreadLocal { protected Object initialValue() { return new ArrayList(); } void push(SubjectContext context) { ArrayList stack = (ArrayList) super.get(); stack.add(context); } SubjectContext pop() { ArrayList stack = (ArrayList) super.get(); SubjectContext context = null; int lastIndex = stack.size() - 1; if (lastIndex >= 0) context = (SubjectContext) stack.remove(lastIndex); return context; } /** * Look for the first non-null run-as identity on the stack starting * with the value at depth. * @return The run-as identity if one exists, null otherwise. */ SubjectContext peek() { ArrayList stack = (ArrayList) super.get(); SubjectContext context = null; int lastIndex = stack.size() - 1; if (lastIndex >= 0) context = (SubjectContext) stack.get(lastIndex); return context; } /** * Remove all SubjectContext from the current thread stack */ void clear() { ArrayList stack = (ArrayList) super.get(); stack.clear(); } } }
SecurityAssociation.java |