[jboss-cvs] JBossAS SVN: r74501 - projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth.
jboss-cvs-commits at lists.jboss.org
jboss-cvs-commits at lists.jboss.org
Thu Jun 12 16:01:47 EDT 2008
Author: mmoyses
Date: 2008-06-12 16:01:47 -0400 (Thu, 12 Jun 2008)
New Revision: 74501
Added:
projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/SynchronizedJaasSecurityManagerBase.java
Modified:
projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/JaasSecurityManagerBase.java
Log:
SECURITY-237
Modified: projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/JaasSecurityManagerBase.java
===================================================================
--- projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/JaasSecurityManagerBase.java 2008-06-12 19:41:37 UTC (rev 74500)
+++ projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/JaasSecurityManagerBase.java 2008-06-12 20:01:47 UTC (rev 74501)
@@ -73,10 +73,10 @@
{
private static Logger log = Logger.getLogger(DomainInfo.class);
private static boolean trace = log.isTraceEnabled();
- private LoginContext loginCtx;
- private Subject subject;
- private Object credential;
- private Principal callerPrincipal;
+ protected LoginContext loginCtx;
+ protected Subject subject;
+ protected Object credential;
+ protected Principal callerPrincipal;
private long expirationTime;
/** Is there an active authentication in process */
private boolean needsDestroy;
Added: projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/SynchronizedJaasSecurityManagerBase.java
===================================================================
--- projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/SynchronizedJaasSecurityManagerBase.java (rev 0)
+++ projects/security/security-jboss-sx/trunk/jbosssx/src/main/org/jboss/security/plugins/auth/SynchronizedJaasSecurityManagerBase.java 2008-06-12 20:01:47 UTC (rev 74501)
@@ -0,0 +1,615 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt 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.auth;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.jboss.logging.Logger;
+import org.jboss.security.AuthenticationManager;
+import org.jboss.security.AuthorizationManager;
+import org.jboss.security.RealmMapping;
+import org.jboss.security.SecurityConstants;
+import org.jboss.security.SecurityContext;
+import org.jboss.security.SecurityContextAssociation;
+import org.jboss.security.SecurityUtil;
+import org.jboss.security.SubjectSecurityManager;
+import org.jboss.security.auth.callback.SecurityAssociationHandler;
+import org.jboss.security.plugins.auth.JaasSecurityManagerBase.DomainInfo;
+import org.jboss.util.CachePolicy;
+import org.jboss.util.TimedCachePolicy;
+
+/** The JaasSecurityManager is responsible both for authenticating credentials
+ associated with principals and for role mapping. This implementation relies
+ on the JAAS LoginContext/LoginModules associated with the security
+ domain name associated with the class for authentication,
+ and the context JAAS Subject object for role mapping.
+
+ @see #isValid(Principal, Object, Subject)
+ @see #getPrincipal(Principal)
+ @see #doesUserHaveRole(Principal, Set)
+
+ @author <a href="on at ibis.odessa.ua">Oleg Nitz</a>
+ @author Scott.Stark at jboss.org
+ @author Anil.Saldhana at jboss.org
+ @author <a href="mmoyses at redhat.com">Marcus Moyses</a>
+ @version $Revision: 62860 $
+*/
+public class SynchronizedJaasSecurityManagerBase implements SubjectSecurityManager, RealmMapping
+{
+ /** The name of the domain this instance is securing. It is used as
+ the appName into the SecurityPolicy.
+ */
+ private String securityDomain;
+
+ /** A cache of DomainInfo objects keyd by Principal. This is now
+ always set externally by our security manager service.
+ */
+ private CachePolicy domainCache;
+
+ /** The JAAS callback handler to use in defaultLogin */
+ private CallbackHandler handler;
+
+ /** The setSecurityInfo(Principal, Object) method of the handler obj */
+ private transient Method setSecurityInfo;
+
+ /** The flag to indicate that the Subject sets need to be deep copied*/
+ private boolean deepCopySubjectOption = false;
+
+ /** The log4j category for the security manager domain
+ */
+ protected Logger log;
+
+ protected boolean trace;
+
+ /** Creates a default JaasSecurityManager for with a securityDomain
+ name of 'other'.
+ */
+ public SynchronizedJaasSecurityManagerBase()
+ {
+ this("other", new SecurityAssociationHandler());
+ }
+
+ /** Creates a JaasSecurityManager for with a securityDomain
+ name of that given by the 'securityDomain' argument.
+ @param securityDomain the name of the security domain
+ @param handler the JAAS callback handler instance to use
+ @exception UndeclaredThrowableException thrown if handler does not
+ implement a setSecurityInfo(Princpal, Object) method
+ */
+ public SynchronizedJaasSecurityManagerBase(String securityDomain, CallbackHandler handler)
+ {
+ this.securityDomain = securityDomain;
+ this.handler = handler;
+ String categoryName = getClass().getName() + '.' + securityDomain;
+ this.log = Logger.getLogger(categoryName);
+ this.trace = log.isTraceEnabled();
+
+ // Get the setSecurityInfo(Principal principal, Object credential) method
+ Class<?>[] sig =
+ {Principal.class, Object.class};
+ try
+ {
+ setSecurityInfo = handler.getClass().getMethod("setSecurityInfo", sig);
+ }
+ catch (Exception e)
+ {
+ String msg = "Failed to find setSecurityInfo(Princpal, Object) method in handler";
+ throw new UndeclaredThrowableException(e, msg);
+ }
+ log.debug("CallbackHandler: " + handler);
+ }
+
+ /** The domainCache is typically a shared object that is populated
+ by the login code(LoginModule, etc.) and read by this class in the
+ isValid() method.
+ @see #isValid(Principal, Object, Subject)
+ */
+ public void setCachePolicy(CachePolicy domainCache)
+ {
+ this.domainCache = domainCache;
+ log.debug("CachePolicy set to: " + domainCache);
+ }
+
+ /**
+ * Flag to specify if deep copy of subject sets needs to be
+ * enabled
+ *
+ * @param flag
+ */
+ public void setDeepCopySubjectOption(Boolean flag)
+ {
+ log.debug("setDeepCopySubjectOption=" + flag);
+ this.deepCopySubjectOption = (flag == Boolean.TRUE);
+ }
+
+ /** Not really used anymore as the security manager service manages the
+ security domain authentication caches.
+ */
+ public void flushCache()
+ {
+ if (domainCache != null)
+ domainCache.flush();
+ }
+
+ /** Get the name of the security domain associated with this security mgr.
+ @return Name of the security manager security domain.
+ */
+ public String getSecurityDomain()
+ {
+ return securityDomain;
+ }
+
+ /** Get the currently authenticated Subject. This is a thread local
+ property shared across all JaasSecurityManager instances.
+ @return The Subject authenticated in the current thread if one
+ exists, null otherwise.
+ */
+ public Subject getActiveSubject()
+ {
+ /* This does not use SubjectActions.getActiveSubject since the caller
+ must have the correct permissions to access the
+ SecurityAssociation.getSubject method.
+ */
+ //return SecurityAssociation.getSubject();
+ Subject subj = null;
+ SecurityContext sc = SecurityContextAssociation.getSecurityContext();
+ if (sc != null)
+ {
+ subj = sc.getUtil().getSubject();
+ }
+ return subj;
+ }
+
+ /** Validate that the given credential is correct for principal. This
+ returns the value from invoking isValid(principal, credential, null).
+ @param principal - the security domain principal attempting access
+ @param credential - the proof of identity offered by the principal
+ @return true if the principal was authenticated, false otherwise.
+ */
+ public boolean isValid(Principal principal, Object credential)
+ {
+ return isValid(principal, credential, null);
+ }
+
+ /** Validate that the given credential is correct for principal. This first
+ will check the current CachePolicy object if one exists to see if the
+ user's cached credentials match the given credential. If there is no
+ credential cache or the cache information is invalid or does not match,
+ the user is authenticated against the JAAS login modules configured for
+ the security domain.
+ @param principal - the security domain principal attempting access
+ @param credential the proof of identity offered by the principal
+ @param activeSubject - if not null, a Subject that will be populated with
+ the state of the authenticated Subject.
+ @return true if the principal was authenticated, false otherwise.
+ */
+ public boolean isValid(Principal principal, Object credential, Subject activeSubject)
+ {
+ // Check the cache first
+ DomainInfo cacheInfo = getCacheInfo(principal, true);
+ if (trace)
+ log.trace("Begin isValid, principal:" + principal + ", cache info: " + cacheInfo);
+
+ boolean isValid = false;
+ if (principal != null && principal.getName() != null)
+ {
+ synchronized (principal.getName().intern())
+ {
+ if (cacheInfo != null)
+ {
+ isValid = validateCache(cacheInfo, credential, activeSubject);
+ cacheInfo.release();
+ }
+ if (isValid == false)
+ isValid = authenticate(principal, credential, activeSubject);
+ }
+ }
+ else
+ {
+ if (cacheInfo != null)
+ cacheInfo.release();
+ isValid = authenticate(principal, credential, activeSubject);
+ }
+ if (trace)
+ log.trace("End isValid, " + isValid);
+ return isValid;
+ }
+
+ /** Map the argument principal from the deployment environment principal
+ to the developer environment. This is called by the EJB context
+ getCallerPrincipal() to return the Principal as described by
+ the EJB developer domain.
+ @return a Principal object that is valid in the deployment environment
+ if one exists. If no Subject exists or the Subject has no principals
+ then the argument principal is returned.
+ */
+ public Principal getPrincipal(Principal principal)
+ {
+ if (domainCache == null)
+ return principal;
+ Principal result = principal;
+ // Get the CallerPrincipal group member
+ synchronized (domainCache)
+ {
+ DomainInfo info = getCacheInfo(principal, false);
+ if (trace)
+ log.trace("getPrincipal, cache info: " + info);
+ if (info != null)
+ {
+ result = info.callerPrincipal;
+ // If the mapping did not have a callerPrincipal just use principal
+ if (result == null)
+ result = principal;
+ info.release();
+ }
+ }
+
+ return result;
+ }
+
+ /** Does the current Subject have a role(a Principal) that equates to one
+ of the role names. This method obtains the Group named 'Roles' from
+ the principal set of the currently authenticated Subject as determined
+ by the SecurityAssociation.getSubject() method and then creates a
+ SimplePrincipal for each name in roleNames. If the role is a member of the
+ Roles group, then the user has the role. This requires that the caller
+ establish the correct SecurityAssociation subject prior to calling this
+ method. In the past this was done as a side-effect of an isValid() call,
+ but this is no longer the case.
+
+ @param principal - ignored. The current authenticated Subject determines
+ the active user and assigned user roles.
+ @param rolePrincipals - a Set of Principals for the roles to check.
+
+ @see java.security.acl.Group;
+ @see Subject#getPrincipals()
+ */
+ public boolean doesUserHaveRole(Principal principal, Set<Principal> rolePrincipals)
+ {
+ AuthorizationManager am = SecurityUtil.getAuthorizationManager(securityDomain,
+ SecurityConstants.JAAS_CONTEXT_ROOT);
+ return am.doesUserHaveRole(principal, rolePrincipals);
+ }
+
+ /** Return the set of domain roles the current active Subject 'Roles' group
+ found in the subject Principals set.
+
+ @param principal - ignored. The current authenticated Subject determines
+ the active user and assigned user roles.
+ @return The Set<Principal> for the application domain roles that the
+ principal has been assigned.
+ */
+ @SuppressWarnings("deprecation")
+ public Set<Principal> getUserRoles(Principal principal)
+ {
+ AuthorizationManager am = SecurityUtil.getAuthorizationManager(securityDomain,
+ SecurityConstants.JAAS_CONTEXT_ROOT);
+ return am.getUserRoles(principal);
+ }
+
+ /**
+ * @see AuthenticationManager#getTargetPrincipal(Principal,Map)
+ */
+ public Principal getTargetPrincipal(Principal anotherDomainPrincipal, Map<String, Object> contextMap)
+ {
+ throw new RuntimeException("Not implemented yet");
+ }
+
+ /** Currently this simply calls defaultLogin() to do a JAAS login using the
+ security domain name as the login module configuration name.
+
+ * @param principal - the user id to authenticate
+ * @param credential - an opaque credential.
+ * @return false on failure, true on success.
+ */
+ private boolean authenticate(Principal principal, Object credential, Subject theSubject)
+ {
+ Subject subject = null;
+ boolean authenticated = false;
+ LoginException authException = null;
+
+ try
+ {
+ // Validate the principal using the login configuration for this domain
+ LoginContext lc = defaultLogin(principal, credential);
+ subject = lc.getSubject();
+
+ // Set the current subject if login was successful
+ if (subject != null)
+ {
+ // Copy the current subject into theSubject
+ if (theSubject != null)
+ {
+ SubjectActions.copySubject(subject, theSubject, false, this.deepCopySubjectOption);
+ }
+ else
+ {
+ theSubject = subject;
+ }
+
+ authenticated = true;
+ // Build the Subject based DomainInfo cache value
+ updateCache(lc, subject, principal, credential);
+ }
+ }
+ catch (LoginException e)
+ {
+ // Don't log anonymous user failures unless trace level logging is on
+ if (principal != null && principal.getName() != null || trace)
+ log.trace("Login failure", e);
+ authException = e;
+ }
+ // Set the security association thread context info exception
+ SubjectActions.setContextInfo("org.jboss.security.exception", authException);
+
+ return authenticated;
+ }
+
+ /** Pass the security info to the login modules configured for
+ this security domain using our SecurityAssociationHandler.
+ @return The authenticated Subject if successful.
+ @exception LoginException throw if login fails for any reason.
+ */
+ private LoginContext defaultLogin(Principal principal, Object credential) throws LoginException
+ {
+ /* We use our internal CallbackHandler to provide the security info. A
+ copy must be made to ensure there is a unique handler per active
+ login since there can be multiple active logins.
+ */
+ Object[] securityInfo =
+ {principal, credential};
+ CallbackHandler theHandler = null;
+ try
+ {
+ theHandler = (CallbackHandler) handler.getClass().newInstance();
+ setSecurityInfo.invoke(theHandler, securityInfo);
+ }
+ catch (Throwable e)
+ {
+ if (trace)
+ log.trace("Failed to create/setSecurityInfo on handler", e);
+ LoginException le = new LoginException("Failed to setSecurityInfo on handler");
+ le.initCause(e);
+ throw le;
+ }
+ Subject subject = new Subject();
+ LoginContext lc = null;
+ if (trace)
+ log.trace("defaultLogin, principal=" + principal);
+ lc = SubjectActions.createLoginContext(securityDomain, subject, theHandler);
+ lc.login();
+ if (trace)
+ log.trace("defaultLogin, lc=" + lc + ", subject=" + SubjectActions.toString(subject));
+ return lc;
+ }
+
+ /** Validate the cache credential value against the provided credential
+ */
+ @SuppressWarnings("unchecked")
+ private boolean validateCache(DomainInfo info, Object credential, Subject theSubject)
+ {
+ if (trace)
+ {
+ StringBuffer tmp = new StringBuffer("Begin validateCache, info=");
+ tmp.append(info.toString());
+ tmp.append(";credential.class=");
+ if (credential != null)
+ {
+ Class c = credential.getClass();
+ tmp.append(c.getName());
+ tmp.append('@');
+ tmp.append(System.identityHashCode(c));
+ }
+ else
+ {
+ tmp.append("null");
+ }
+ log.trace(tmp.toString());
+ }
+
+ Object subjectCredential = info.credential;
+ boolean isValid = false;
+ // Check for a null credential as can be the case for an anonymous user
+ if (credential == null || subjectCredential == null)
+ {
+ // Both credentials must be null
+ isValid = (credential == null) && (subjectCredential == null);
+ }
+ // See if the credential is assignable to the cache value
+ else if (subjectCredential.getClass().isAssignableFrom(credential.getClass()))
+ {
+ /* Validate the credential by trying Comparable, char[], byte[],
+ Object[], and finally Object.equals()
+ */
+ if (subjectCredential instanceof Comparable)
+ {
+ Comparable c = (Comparable) subjectCredential;
+ isValid = c.compareTo(credential) == 0;
+ }
+ else if (subjectCredential instanceof char[])
+ {
+ char[] a1 = (char[]) subjectCredential;
+ char[] a2 = (char[]) credential;
+ isValid = Arrays.equals(a1, a2);
+ }
+ else if (subjectCredential instanceof byte[])
+ {
+ byte[] a1 = (byte[]) subjectCredential;
+ byte[] a2 = (byte[]) credential;
+ isValid = Arrays.equals(a1, a2);
+ }
+ else if (subjectCredential.getClass().isArray())
+ {
+ Object[] a1 = (Object[]) subjectCredential;
+ Object[] a2 = (Object[]) credential;
+ isValid = Arrays.equals(a1, a2);
+ }
+ else
+ {
+ isValid = subjectCredential.equals(credential);
+ }
+ }
+ else if (subjectCredential instanceof char[] && credential instanceof String)
+ {
+ char[] a1 = (char[]) subjectCredential;
+ char[] a2 = ((String) credential).toCharArray();
+ isValid = Arrays.equals(a1, a2);
+ }
+ else if (subjectCredential instanceof String && credential instanceof char[])
+ {
+ char[] a1 = ((String) subjectCredential).toCharArray();
+ char[] a2 = (char[]) credential;
+ isValid = Arrays.equals(a1, a2);
+ }
+
+ // If the credentials match, set the thread's active Subject
+ if (isValid)
+ {
+ // Copy the current subject into theSubject
+ if (theSubject != null)
+ {
+ SubjectActions.copySubject(info.subject, theSubject, false, this.deepCopySubjectOption);
+ }
+ }
+ if (trace)
+ log.trace("End validateCache, isValid=" + isValid);
+
+ return isValid;
+ }
+
+ /** An accessor method that synchronizes access on the domainCache
+ to avoid a race condition that can occur when the cache entry expires
+ in the presence of multi-threaded access. The allowRefresh flag should
+ be true for authentication accesses and false for other accesses.
+ Previously the other accesses included authorization and caller principal
+ mapping. Now the only use of the
+
+ @param principal - the caller identity whose cached credentials are to
+ be accessed.
+ @param allowRefresh - a flag indicating if the cache access should flush
+ any expired entries.
+ */
+ private DomainInfo getCacheInfo(Principal principal, boolean allowRefresh)
+ {
+ if (domainCache == null)
+ return null;
+
+ DomainInfo cacheInfo = null;
+ synchronized (domainCache)
+ {
+ if (allowRefresh == true)
+ cacheInfo = (DomainInfo) domainCache.get(principal);
+ else
+ cacheInfo = (DomainInfo) domainCache.peek(principal);
+ if (cacheInfo != null)
+ cacheInfo.acquire();
+ }
+ return cacheInfo;
+ }
+
+ private Subject updateCache(LoginContext lc, Subject subject, Principal principal, Object credential)
+ {
+ // If we don't have a cache there is nothing to update
+ if (domainCache == null)
+ return subject;
+
+ long lifetime = 0;
+ if (domainCache instanceof TimedCachePolicy)
+ {
+ TimedCachePolicy cache = (TimedCachePolicy) domainCache;
+ lifetime = cache.getDefaultLifetime();
+ }
+ DomainInfo info = new DomainInfo(lifetime);
+ info.loginCtx = lc;
+ info.subject = new Subject();
+ SubjectActions.copySubject(subject, info.subject, true, this.deepCopySubjectOption);
+ info.credential = credential;
+
+ if (trace)
+ {
+ log.trace("updateCache, inputSubject=" + SubjectActions.toString(subject) + ", cacheSubject="
+ + SubjectActions.toString(info.subject));
+ }
+
+ /* Get the Subject callerPrincipal by looking for a Group called
+ 'CallerPrincipal'
+ */
+ Set<Group> subjectGroups = subject.getPrincipals(Group.class);
+ Iterator<Group> iter = subjectGroups.iterator();
+ while (iter.hasNext())
+ {
+ Group grp = iter.next();
+ String name = grp.getName();
+ if (name.equals("CallerPrincipal"))
+ {
+ Enumeration<? extends Principal> members = grp.members();
+ if (members.hasMoreElements())
+ info.callerPrincipal = members.nextElement();
+ }
+ }
+
+ /* Handle null principals with no callerPrincipal. This is an indication
+ of an user that has not provided any authentication info, but
+ has been authenticated by the domain login module stack. Here we look
+ for the first non-Group Principal and use that.
+ */
+ if (principal == null && info.callerPrincipal == null)
+ {
+ Set<Principal> subjectPrincipals = subject.getPrincipals(Principal.class);
+ Iterator<? extends Principal> iterPrincipals = subjectPrincipals.iterator();
+ while (iterPrincipals.hasNext())
+ {
+ Principal p = iterPrincipals.next();
+ if ((p instanceof Group) == false)
+ info.callerPrincipal = p;
+ }
+ }
+
+ /* If the user already exists another login is active. Currently
+ only one is allowed so remove the old and insert the new. Synchronize
+ on the domainCache to ensure the removal and addition are an atomic
+ operation so that getCacheInfo cannot see stale data.
+ */
+ synchronized (domainCache)
+ {
+ if (domainCache.peek(principal) != null)
+ domainCache.remove(principal);
+ domainCache.insert(principal, info);
+ if (trace)
+ log.trace("Inserted cache info: " + info);
+ }
+ return info.subject;
+ }
+}
\ No newline at end of file
More information about the jboss-cvs-commits
mailing list