/*
* JBoss, Home of Professional Open Source Copyright 2008, Red Hat Middleware
* LLC, and individual contributors 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.internal.soa.esb.services.security;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Set;
import javax.security.auth.Subject;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.assertion.AssertArgument;
import org.jboss.security.RunAsIdentity;
import org.jboss.security.SecurityAssociation;
import org.jboss.security.SecurityConstants;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.security.SecurityContextFactory;
import org.jboss.soa.esb.common.Configuration;
import org.jboss.soa.esb.services.security.SecurityConfig;
import org.jboss.soa.esb.services.security.SecurityContext;
import org.jboss.soa.esb.services.security.principals.User;
import org.jboss.soa.esb.util.ClassUtil;
/**
* JBoss Application Server(AS) specifiec security context propagator.
*
* @author <a href="mailto:dbevenius@jboss.com">Daniel Bevenius</a>
*/
public final class JBossASContextPropagator implements SecurityContextPropagator
{
/**
* Logger for this class.
*/
private static final Logger LOGGER = Logger.getLogger(JBossASContextPropagator.class) ;
/**
* The default implementation of the security context operations.
*/
private static final JBossASSecurityContextOperations OPERATIONS ;
static
{
JBossASSecurityContextOperations operations = null ;
final String implementation = Configuration.getJBossASSecurityContextOperationsImplementationClass() ;
if (implementation != null)
{
try
{
final Class<?> implementationClass = ClassUtil.forName(implementation, JBossASContextPropagator.class) ;
operations = (JBossASSecurityContextOperations)implementationClass.newInstance() ;
}
catch (final Throwable th)
{
LOGGER.error("Unexpected exception creating security context operations implementation, falling back to default", th) ;
}
}
if (operations == null)
{
operations = (isSecurityContextAssociationPresent() ? new AS5SecurityContextOperations() : new AS4SecurityContextOperations()) ;
}
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Default JBossASSecurityContextOperations initialised to " + operations.getClass().getName()) ;
}
OPERATIONS = operations ;
}
/**
* Pushed the subject by calling {@link SecurityAssociation#pushSubjectContext(Subject, Principal, Object)}.
*
* @param context The SecurityContext holding the subject to push/propagate. May not be null.
* @param config The SecurityConfig which contains the information specified from the security configuration element. Can be null.
*/
public void pushSecurityContext(final SecurityContext context, Set<?> authCredentials, final SecurityConfig config)
{
pushSecurityContext(context, authCredentials, config, OPERATIONS) ;
}
/**
* Pushed the subject by calling {@link SecurityAssociation#pushSubjectContext(Subject, Principal, Object)}.
*
* @param context The SecurityContext holding the subject to push/propagate. May not be null.
* @param config The SecurityConfig which contains the information specified from the security configuration element. Can be null.
* @param operations The implementation of the security context operations or null.
*/
public void pushSecurityContext(final SecurityContext context, Set<?> authCredentials, final SecurityConfig config, final JBossASSecurityContextOperations operations)
{
AssertArgument.isNotNull(context, "context");
final Subject subject = context.getSubject();
final Principal principal = getPrincipal(subject);
// associate the subject with jboss security
final Object credential ;
if (authCredentials != null && authCredentials.isEmpty() == false)
{
credential = authCredentials.iterator().next();
}
else
{
credential = null ;
}
final JBossASSecurityContextOperations ops = getSecurityContextOperations(operations) ;
final String domain = context.getDomain() ;
if (System.getSecurityManager() == null)
{
ops.pushSecurityContext(principal, credential, subject, domain, config) ;
}
else
{
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
ops.pushSecurityContext(principal, credential, subject, domain, config) ;
return null ;
}
}) ;
}
}
/**
* Pops the subject by calling {@link SecurityAssociation#pushSubjectContext(Subject, Principal, Object)}.
*
* @param context The SecurityContext holding the subject to push/propagate. Can be null.
* @param config The SecurityConfig which contains the information specified from the security configuration element. Can be null.
*/
public void popSecurityContext(final SecurityContext context, final SecurityConfig config)
{
popSecurityContext(context, config, OPERATIONS) ;
}
/**
* Pops the subject by calling {@link SecurityAssociation#pushSubjectContext(Subject, Principal, Object)}.
*
* @param context The SecurityContext holding the subject to push/propagate. Can be null.
* @param config The SecurityConfig which contains the information specified from the security configuration element. Can be null.
* @param operations The implementation of the security context operations or null.
*/
public void popSecurityContext(final SecurityContext context, final SecurityConfig config, final JBossASSecurityContextOperations operations)
{
final JBossASSecurityContextOperations ops = getSecurityContextOperations(operations) ;
if (System.getSecurityManager() == null)
{
ops.popSecurityContext(config) ;
}
else
{
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
ops.popSecurityContext(config) ;
return null ;
}
}) ;
}
}
/**
* Get the implementation of the security context operations.
* @param operations The specified implementation or null.
* @return The specified implementation or default implementation.
*/
private JBossASSecurityContextOperations getSecurityContextOperations(final JBossASSecurityContextOperations operations)
{
return (operations != null ? operations : OPERATIONS) ;
}
private Principal getPrincipal( final Subject subject)
{
for (Principal principal : subject.getPrincipals())
{
return principal;
}
return new User("NullPrincipal");
}
/**
* The security context operations interface.
* @author kevin
*/
public interface JBossASSecurityContextOperations
{
public void pushSecurityContext(final Principal principal, final Object credential,
final Subject subject, final String domain, final SecurityConfig securityConfig) ;
public void popSecurityContext(final SecurityConfig securityConfig) ;
}
/**
* The AS4 implementation of the security context operations.
* @author kevin
*/
public static class AS4SecurityContextOperations implements JBossASSecurityContextOperations
{
public void pushSecurityContext(final Principal principal, final Object credential,
final Subject subject, final String domain, final SecurityConfig securityConfig)
{
try
{
SecurityAssociation.pushSubjectContext(subject, principal, credential) ;
if ((securityConfig != null) && securityConfig.hasRunAs())
{
SecurityAssociation.pushRunAsIdentity(new RunAsIdentity(securityConfig.getRunAs(), principal.getName()));
}
}
catch (final Exception ex)
{
throw new RuntimeException("Unexpected exception creating security context", ex) ;
}
}
public void popSecurityContext(final SecurityConfig securityConfig)
{
if ((securityConfig != null) && securityConfig.hasRunAs())
{
SecurityAssociation.popRunAsIdentity() ;
}
SecurityAssociation.popSubjectContext() ;
}
}
/**
* The AS5 implementation of the security context operations.
* @author kevin
*/
public static class AS5SecurityContextOperations implements JBossASSecurityContextOperations
{
public void pushSecurityContext(final Principal principal, final Object credential,
final Subject subject, final String domain, final SecurityConfig securityConfig)
{
final String securityDomain = domain == null ? SecurityConstants.DEFAULT_APPLICATION_POLICY : domain ;
try
{
final org.jboss.security.SecurityContext securityContext =
SecurityContextFactory.createSecurityContext(principal, credential, subject, securityDomain) ;
SecurityContextAssociation.setSecurityContext(securityContext) ;
if ((securityConfig != null) && securityConfig.hasRunAs())
{
securityContext.setOutgoingRunAs(new RunAsIdentity(securityConfig.getRunAs(), principal.getName()));
}
}
catch (final Exception ex)
{
throw new RuntimeException("Unexpected exception creating security context", ex) ;
}
}
public void popSecurityContext(final SecurityConfig securityConfig)
{
final org.jboss.security.SecurityContext securityContext = SecurityContextAssociation.getSecurityContext() ;
if(securityContext != null)
{
if ((securityConfig != null) && securityConfig.hasRunAs())
{
securityContext.setOutgoingRunAs(null) ;
}
SecurityContextAssociation.clearSecurityContext() ;
}
}
}
private static boolean isSecurityContextAssociationPresent()
{
try
{
ClassUtil.forName("org.jboss.security.SecurityContextAssociation", JBossASContextPropagator.class) ;
return true ;
}
catch (final ClassNotFoundException cnfe) {} // ignore
catch (final Throwable th)
{
LOGGER.debug("Exception checking for SecurityContextAssociation", th) ;
}
return false ;
}
}