From d5d3104b7b13ce249bf9c877f7160cd2137ec209 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Thu, 6 Aug 2009 16:34:39 +0000 Subject: [PATCH] + interaction with user code uses now dedicated privileged when running under a security manager --- .../beans/BeanWrapperImpl.java | 88 ++++++- .../AbstractAutowireCapableBeanFactory.java | 128 +++++++--- .../factory/support/AbstractBeanFactory.java | 122 ++++++++-- .../factory/support/ConstructorResolver.java | 54 ++++- .../support/DefaultListableBeanFactory.java | 33 ++- .../support/DisposableBeanAdapter.java | 46 +++- .../support/FactoryBeanRegistrySupport.java | 98 +++++--- .../support/SecurityContextProvider.java | 33 +++ .../support/SimpleInstantiationStrategy.java | 22 +- .../SimpleSecurityContextProvider.java | 57 +++++ .../security/CallbacksSecurityTest.java | 223 ++++++++++++++++++ .../factory/support/security/callbacks.xml | 48 ++++ .../beans/factory/support/security/policy.all | 3 + .../security/support/ConstructorBean.java | 30 +++ .../security/support/CustomCallbackBean.java | 30 +++ .../security/support/CustomFactoryBean.java | 39 +++ .../support/security/support/DestroyBean.java | 28 +++ .../support/security/support/FactoryBean.java | 36 +++ .../support/security/support/InitBean.java | 28 +++ .../security/support/PropertyBean.java | 30 +++ .../ApplicationContextAwareProcessor.java | 41 +++- 21 files changed, 1099 insertions(+), 118 deletions(-) create mode 100644 org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java create mode 100644 org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTest.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/policy.all create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomFactoryBean.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.java create mode 100644 org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.java diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 828ca22ab2..44510cc0bb 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -22,6 +22,10 @@ import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -31,7 +35,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.util.Assert; @@ -102,6 +105,9 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra */ private Map nestedBeanWrappers; + /** The security context used for invoking the property methods */ + private AccessControlContext acc; + /** * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. @@ -198,6 +204,16 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra setIntrospectionClass(object.getClass()); } + /** + * Set the security context used during the invocation of the wrapped instance methods. + * Can be null. + * + * @param acc + */ + public void setSecurityContext(AccessControlContext acc) { + this.acc = acc; + } + public final Object getWrappedInstance() { return this.object; } @@ -539,12 +555,29 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra if (pd == null || pd.getReadMethod() == null) { throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName); } - Method readMethod = pd.getReadMethod(); + final Method readMethod = pd.getReadMethod(); try { if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } - Object value = readMethod.invoke(this.object, (Object[]) null); + + Object value = null; + + if (System.getSecurityManager() != null) { + try { + value = AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return readMethod.invoke(object, (Object[]) null); + } + },acc); + } catch (PrivilegedActionException pae) { + throw pae.getException(); + } + } + else { + value = readMethod.invoke(object, (Object[]) null); + } + if (tokens.keys != null) { // apply indexes and map keys for (int i = 0; i < tokens.keys.length; i++) { @@ -602,7 +635,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Getter for property '" + actualName + "' threw exception", ex); } - catch (IllegalAccessException ex) { + + catch(IllegalAccessException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Illegal attempt to get property '" + actualName + "' threw exception", ex); } @@ -614,6 +648,10 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Invalid index in property path '" + propertyName + "'", ex); } + catch (Exception ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Invalid index in property path '" + propertyName + "'", ex); + } } @Override @@ -813,14 +851,26 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } else { if (isExtractOldValueForEditor() && pd.getReadMethod() != null) { - Method readMethod = pd.getReadMethod(); + final Method readMethod = pd.getReadMethod(); if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } try { - oldValue = readMethod.invoke(this.object); + if (System.getSecurityManager() != null) { + oldValue = AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return readMethod.invoke(object); + } + },acc); + } + else { + oldValue = readMethod.invoke(object); + } } catch (Exception ex) { + if (ex instanceof PrivilegedActionException) { + ex = ((PrivilegedActionException) ex).getException(); + } if (logger.isDebugEnabled()) { logger.debug("Could not read previous value of property '" + this.nestedPath + propertyName + "'", ex); @@ -831,11 +881,28 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); } - Method writeMethod = pd.getWriteMethod(); + final Method writeMethod = pd.getWriteMethod(); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } - writeMethod.invoke(this.object, valueToApply); + final Object value = valueToApply; + + if (System.getSecurityManager() != null) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + writeMethod.invoke(object, value); + return null; + } + },acc); + } catch (PrivilegedActionException ex) { + throw ex.getException(); + } + } + else { + writeMethod.invoke(object, value); + } + } catch (InvocationTargetException ex) { PropertyChangeEvent propertyChangeEvent = @@ -862,6 +929,11 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); throw new MethodInvocationException(pce, ex); } + catch (Exception ex) { + PropertyChangeEvent pce = + new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); + throw new MethodInvocationException(pce, ex); + } } } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 182cd1c145..ff499b1395 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -24,6 +24,8 @@ import java.lang.reflect.Modifier; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -330,13 +332,27 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { // Use non-singleton bean definition, to avoid registering bean as dependent bean. - RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); + final RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); if (bd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR) { return autowireConstructor(beanClass.getName(), bd, null, null).getWrappedInstance(); } else { - Object bean = getInstantiationStrategy().instantiate(bd, null, this); + Object bean = null; + final BeanFactory parent = this; + + if (System.getSecurityManager() != null) { + bean = AccessController.doPrivileged(new PrivilegedAction() { + + public Object run() { + return getInstantiationStrategy().instantiate(bd, null, parent); + } + }, getAccessControlContext()); + } + else { + bean = getInstantiationStrategy().instantiate(bd, null, parent); + } + populateBean(beanClass.getName(), bd, new BeanWrapperImpl(bean)); return bean; } @@ -403,9 +419,6 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { - AccessControlContext acc = AccessController.getContext(); - return AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { if (logger.isDebugEnabled()) { logger.debug("Creating instance of bean '" + beanName + "'"); } @@ -438,8 +451,6 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac logger.debug("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; - } - }, acc); } /** @@ -904,9 +915,22 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * @param mbd the bean definition for the bean * @return BeanWrapper for the new instance */ - protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) { + protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) { try { - Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this); + Object beanInstance = null; + final BeanFactory parent = this; + if (System.getSecurityManager() != null) { + beanInstance = AccessController.doPrivileged(new PrivilegedAction() { + + public Object run() { + return getInstantiationStrategy().instantiate(mbd, beanName, parent); + } + }, getAccessControlContext()); + } + else { + beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent); + } + BeanWrapper bw = new BeanWrapperImpl(beanInstance); initBeanWrapper(bw); return bw; @@ -1229,6 +1253,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac MutablePropertyValues mpvs = null; List original; + + if (System.getSecurityManager()!= null) { + if (bw instanceof BeanWrapperImpl) { + ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); + } + } if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; @@ -1337,19 +1367,20 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * @see #invokeInitMethods * @see #applyBeanPostProcessorsAfterInitialization */ - protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { - if (bean instanceof BeanNameAware) { - ((BeanNameAware) bean).setBeanName(beanName); - } - - if (bean instanceof BeanClassLoaderAware) { - ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader()); + protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { + + if (System.getSecurityManager() != null) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + invokeAwareMethods(beanName, bean); + return null; + } + }, getAccessControlContext()); } - - if (bean instanceof BeanFactoryAware) { - ((BeanFactoryAware) bean).setBeanFactory(this); + else { + invokeAwareMethods(beanName, bean); } - + Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); @@ -1369,6 +1400,20 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } return wrappedBean; } + + private void invokeAwareMethods(final String beanName, final Object bean) { + if (bean instanceof BeanNameAware) { + ((BeanNameAware) bean).setBeanName(beanName); + } + + if (bean instanceof BeanClassLoaderAware) { + ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader()); + } + + if (bean instanceof BeanFactoryAware) { + ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this); + } + } /** * Give a bean a chance to react now all its properties are set, @@ -1382,7 +1427,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * @throws Throwable if thrown by init methods or by the invocation process * @see #invokeCustomInitMethod */ - protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) + protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable { boolean isInitializingBean = (bean instanceof InitializingBean); @@ -1390,7 +1435,22 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac if (logger.isDebugEnabled()) { logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } - ((InitializingBean) bean).afterPropertiesSet(); + + if (System.getSecurityManager() != null) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + ((InitializingBean) bean).afterPropertiesSet(); + return null; + } + },getAccessControlContext()); + } catch (PrivilegedActionException pae) { + throw pae.getException(); + } + } + else { + ((InitializingBean) bean).afterPropertiesSet(); + } } if (mbd != null) { @@ -1413,9 +1473,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * @param enforceInitMethod indicates whether the defined init method needs to exist * @see #invokeInitMethods */ - protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd) throws Throwable { + protected void invokeCustomInitMethod(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable { String initMethodName = mbd.getInitMethodName(); - Method initMethod = (mbd.isNonPublicAccessAllowed() ? + final Method initMethod = (mbd.isNonPublicAccessAllowed() ? BeanUtils.findMethod(bean.getClass(), initMethodName) : ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName)); if (initMethod == null) { @@ -1437,11 +1497,23 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac logger.debug("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); } ReflectionUtils.makeAccessible(initMethod); - try { - initMethod.invoke(bean, (Object[]) null); + if (System.getSecurityManager() != null) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + initMethod.invoke(bean, (Object[]) null); + return null; + } + }, getAccessControlContext()); + } + catch (PrivilegedActionException pae) { + InvocationTargetException ex = (InvocationTargetException) pae.getException(); + throw ex.getTargetException(); + } } - catch (InvocationTargetException ex) { - throw ex.getTargetException(); + else { + initMethod.invoke(bean, (Object[]) null); } } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 55801c8ad8..70c3d4f292 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -17,6 +17,11 @@ package org.springframework.beans.factory.support; import java.beans.PropertyEditor; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -151,6 +156,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp private final ThreadLocal prototypesCurrentlyInCreation = new NamedThreadLocal("Prototype beans currently in creation"); + /** security context used when running with a Security Manager */ + private volatile SecurityContextProvider securityProvider = new SimpleSecurityContextProvider(); + /** * Create a new AbstractBeanFactory. */ @@ -196,6 +204,16 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp return doGetBean(name, requiredType, args, false); } + protected T doGetBean( + final String name, final Class requiredType, final Object[] args, final boolean typeCheckOnly) + throws BeansException { + return AccessController.doPrivileged(new PrivilegedAction() { + + public T run() { + return doGetBeanRaw(name, requiredType, args, typeCheckOnly); + } + }); + } /** * Return an instance, which may be shared or independent, of the specified bean. * @param name the name of the bean to retrieve @@ -208,7 +226,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * @throws BeansException if the bean could not be created */ @SuppressWarnings("unchecked") - protected T doGetBean( + private T doGetBeanRaw( final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { @@ -409,9 +427,19 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp return false; } if (isFactoryBean(beanName, mbd)) { - FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); - return ((factoryBean instanceof SmartFactoryBean && ((SmartFactoryBean) factoryBean).isPrototype()) || - !factoryBean.isSingleton()); + final FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged(new PrivilegedAction() { + public Boolean run() { + return Boolean.valueOf(((factoryBean instanceof SmartFactoryBean && ((SmartFactoryBean) factoryBean).isPrototype()) || + !factoryBean.isSingleton())); + } + }, getAccessControlContext()).booleanValue(); + } + else { + return ((factoryBean instanceof SmartFactoryBean && ((SmartFactoryBean) factoryBean).isPrototype()) || + !factoryBean.isSingleton()); + } } else { return false; @@ -861,7 +889,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * @param mbd the merged bean definition */ protected void destroyBean(String beanName, Object beanInstance, RootBeanDefinition mbd) { - new DisposableBeanAdapter(beanInstance, beanName, mbd, getBeanPostProcessors()).destroy(); + new DisposableBeanAdapter(beanInstance, beanName, mbd, getBeanPostProcessors(), getAccessControlContext()).destroy(); } public void destroyScopedBean(String beanName) { @@ -1136,34 +1164,54 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * @return the resolved bean class (or null if none) * @throws CannotLoadBeanClassException if we failed to load the class */ - protected Class resolveBeanClass(RootBeanDefinition mbd, String beanName, Class[] typesToMatch) + protected Class resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class[] typesToMatch) throws CannotLoadBeanClassException { try { if (mbd.hasBeanClass()) { return mbd.getBeanClass(); } - if (typesToMatch != null) { - ClassLoader tempClassLoader = getTempClassLoader(); - if (tempClassLoader != null) { - if (tempClassLoader instanceof DecoratingClassLoader) { - DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader; - for (Class typeToMatch : typesToMatch) { - dcl.excludeClass(typeToMatch.getName()); - } + + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + + public Class run() throws Exception { + return doResolveBeanClass(mbd, typesToMatch); } - String className = mbd.getBeanClassName(); - return (className != null ? ClassUtils.forName(className, tempClassLoader) : null); - } + }, getAccessControlContext()); + } + else { + return doResolveBeanClass(mbd, typesToMatch); } - return mbd.resolveBeanClass(getBeanClassLoader()); + } + catch (PrivilegedActionException pae) { + ClassNotFoundException ex = (ClassNotFoundException) pae.getException(); + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), (ClassNotFoundException) ex); } catch (ClassNotFoundException ex) { - throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex); + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), (ClassNotFoundException) ex); } catch (LinkageError err) { throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err); } } + + private Class doResolveBeanClass(final RootBeanDefinition mbd, final Class[] typesToMatch) + throws ClassNotFoundException { + if (typesToMatch != null) { + ClassLoader tempClassLoader = getTempClassLoader(); + if (tempClassLoader != null) { + if (tempClassLoader instanceof DecoratingClassLoader) { + DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader; + for (Class typeToMatch : typesToMatch) { + dcl.excludeClass(typeToMatch.getName()); + } + } + String className = mbd.getBeanClassName(); + return (className != null ? ClassUtils.forName(className, tempClassLoader) : null); + } + } + return mbd.resolveBeanClass(getBeanClassLoader()); + } /** * Evaluate the given String as contained in a bean definition, @@ -1363,13 +1411,14 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * @see #registerDependentBean */ protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) { + AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null); if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) { if (mbd.isSingleton()) { // Register a DisposableBean implementation that performs all destruction // work for the given bean: DestructionAwareBeanPostProcessors, // DisposableBean interface, custom destroy method. registerDisposableBean(beanName, - new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors())); + new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc)); } else { // A bean with a custom scope... @@ -1378,11 +1427,41 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'"); } scope.registerDestructionCallback(beanName, - new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors())); + new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc)); } } } + + /** + * {@inheritDoc} + * + * Delegate the creation of the security context to {@link #getSecurityContextProvider()}. + */ + @Override + protected AccessControlContext getAccessControlContext() { + SecurityContextProvider provider = getSecurityContextProvider(); + return (provider != null ? provider.getAccessControlContext(): null); + } + /** + * Return the security context provider for this bean factory. + * + * @return + */ + public SecurityContextProvider getSecurityContextProvider() { + return securityProvider; + } + + /** + * Set the security context provider for this bean factory. If a security manager + * is set, interaction with the user code will be executed using the privileged + * of the provided security context. + * + * @param securityProvider + */ + public void setSecurityContextProvider(SecurityContextProvider securityProvider) { + this.securityProvider = securityProvider; + } //--------------------------------------------------------------------- // Abstract methods to be implemented by subclasses @@ -1442,5 +1521,4 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp */ protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException; - } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 81e278ebca..57e3602635 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -21,6 +21,9 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -99,7 +102,7 @@ class ConstructorResolver { * @return a BeanWrapper for the new instance */ public BeanWrapper autowireConstructor( - String beanName, RootBeanDefinition mbd, Constructor[] chosenCtors, Object[] explicitArgs) { + final String beanName, final RootBeanDefinition mbd, Constructor[] chosenCtors, final Object[] explicitArgs) { BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); @@ -256,8 +259,25 @@ class ConstructorResolver { } try { - Object beanInstance = this.beanFactory.getInstantiationStrategy().instantiate( - mbd, beanName, this.beanFactory, constructorToUse, argsToUse); + Object beanInstance = null; + + if (System.getSecurityManager() != null) { + final Constructor ctorToUse = constructorToUse; + final Object[] argumentsToUse = argsToUse; + + beanInstance = AccessController.doPrivileged(new PrivilegedAction() { + + public Object run() { + return beanFactory.getInstantiationStrategy().instantiate( + mbd, beanName, beanFactory, ctorToUse, argumentsToUse); + } + }, beanFactory.getAccessControlContext()); + } + else { + beanInstance = beanFactory.getInstantiationStrategy().instantiate( + mbd, beanName, beanFactory, constructorToUse, argsToUse); + } + bw.setWrappedInstance(beanInstance); return bw; } @@ -311,7 +331,7 @@ class ConstructorResolver { * method, or null if none (-> use constructor argument values from bean definition) * @return a BeanWrapper for the new instance */ - public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, Object[] explicitArgs) { + public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final RootBeanDefinition mbd, final Object[] explicitArgs) { BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); @@ -491,8 +511,27 @@ class ConstructorResolver { } try { - Object beanInstance = this.beanFactory.getInstantiationStrategy().instantiate( - mbd, beanName, this.beanFactory, factoryBean, factoryMethodToUse, argsToUse); + + Object beanInstance = null; + + if (System.getSecurityManager() != null) { + final Object fb = factoryBean; + final Method factoryMethod = factoryMethodToUse; + final Object[] args = argsToUse; + + beanInstance = AccessController.doPrivileged(new PrivilegedAction() { + + public Object run() { + return beanFactory.getInstantiationStrategy().instantiate( + mbd, beanName, beanFactory, fb, factoryMethod, args); + } + }, beanFactory.getAccessControlContext()); + } + else { + beanInstance = beanFactory.getInstantiationStrategy().instantiate( + mbd, beanName, beanFactory, factoryBean, factoryMethodToUse, argsToUse); + } + if (beanInstance == null) { return null; } @@ -808,5 +847,4 @@ class ConstructorResolver { } } } - -} +} \ No newline at end of file diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index fc1d65322d..3fac0d0324 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -21,6 +21,8 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -179,10 +181,21 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto * when deciding whether a bean definition should be considered as a * candidate for autowiring. */ - public void setAutowireCandidateResolver(AutowireCandidateResolver autowireCandidateResolver) { + public void setAutowireCandidateResolver(final AutowireCandidateResolver autowireCandidateResolver) { Assert.notNull(autowireCandidateResolver, "AutowireCandidateResolver must not be null"); if (autowireCandidateResolver instanceof BeanFactoryAware) { - ((BeanFactoryAware) autowireCandidateResolver).setBeanFactory(this); + if (System.getSecurityManager() != null) { + final BeanFactory target = this; + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + ((BeanFactoryAware) autowireCandidateResolver).setBeanFactory(target); + return null; + } + }, getAccessControlContext()); + } + else { + ((BeanFactoryAware) autowireCandidateResolver).setBeanFactory(this); + } } this.autowireCandidateResolver = autowireCandidateResolver; } @@ -493,8 +506,20 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { - FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); - if (factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit()) { + final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + boolean isEagerInit = false; + + if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { + isEagerInit = AccessController.doPrivileged(new PrivilegedAction() { + public Boolean run() { + return Boolean.valueOf(((SmartFactoryBean) factory).isEagerInit()); + } + }, getAccessControlContext()).booleanValue(); + } + else { + isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit(); + } + if (isEagerInit) { getBean(beanName); } } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index 9d2a42c0a6..5f3df5050b 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -19,6 +19,10 @@ package org.springframework.beans.factory.support; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.List; @@ -66,6 +70,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { private List beanPostProcessors; + private final AccessControlContext acc; /** * Create a new DisposableBeanAdapter for the given bean. @@ -76,7 +81,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { * (potentially DestructionAwareBeanPostProcessor), if any */ public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition, - List postProcessors) { + List postProcessors, AccessControlContext acc) { Assert.notNull(bean, "Bean must not be null"); this.bean = bean; @@ -84,6 +89,8 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { this.invokeDisposableBean = (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy")); this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed(); + this.acc = acc; + String destroyMethodName = beanDefinition.getDestroyMethodName(); if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) && !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { @@ -131,6 +138,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { this.nonPublicAccessAllowed = nonPublicAccessAllowed; this.destroyMethodName = destroyMethodName; this.beanPostProcessors = postProcessors; + this.acc = null; } @@ -169,7 +177,18 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'"); } try { - ((DisposableBean) this.bean).destroy(); + if (System.getSecurityManager() != null) { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + ((DisposableBean) bean).destroy(); + return null; + } + }, acc); + } + else { + ((DisposableBean) bean).destroy(); + } } catch (Throwable ex) { String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'"; @@ -199,9 +218,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { * for a method with a single boolean argument (passing in "true", * assuming a "force" parameter), else logging an error. */ - private void invokeCustomDestroyMethod(Method destroyMethod) { + private void invokeCustomDestroyMethod(final Method destroyMethod) { Class[] paramTypes = destroyMethod.getParameterTypes(); - Object[] args = new Object[paramTypes.length]; + final Object[] args = new Object[paramTypes.length]; if (paramTypes.length == 1) { args[0] = Boolean.TRUE; } @@ -211,9 +230,22 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { } ReflectionUtils.makeAccessible(destroyMethod); try { - destroyMethod.invoke(this.bean, args); - } - catch (InvocationTargetException ex) { + if (System.getSecurityManager() != null) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + destroyMethod.invoke(bean, args); + return null; + } + }, acc); + } catch (PrivilegedActionException pax) { + throw (InvocationTargetException) pax.getException(); + } + } + else { + destroyMethod.invoke(bean, args); + } + } catch (InvocationTargetException ex) { String msg = "Invocation of destroy method '" + this.destroyMethodName + "' failed on bean with name '" + this.beanName + "'"; if (logger.isDebugEnabled()) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java index 8b600e9b6e..5702fa9a11 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java @@ -19,6 +19,8 @@ package org.springframework.beans.factory.support; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -50,9 +52,18 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg * @return the FactoryBean's object type, * or null if the type cannot be determined yet */ - protected Class getTypeForFactoryBean(FactoryBean factoryBean) { + protected Class getTypeForFactoryBean(final FactoryBean factoryBean) { try { - return factoryBean.getObjectType(); + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged(new PrivilegedAction() { + public Class run() { + return factoryBean.getObjectType(); + } + }, getAccessControlContext()); + } + else { + return factoryBean.getObjectType(); + } } catch (Throwable ex) { // Thrown from the FactoryBean's getObjectType implementation. @@ -112,40 +123,51 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg final FactoryBean factory, final String beanName, final boolean shouldPostProcess) throws BeanCreationException { - AccessControlContext acc = AccessController.getContext(); - return AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - Object object; - + + + Object object; + try { + if (System.getSecurityManager() != null) { + AccessControlContext acc = getAccessControlContext(); try { - object = factory.getObject(); - } - catch (FactoryBeanNotInitializedException ex) { - throw new BeanCurrentlyInCreationException(beanName, ex.toString()); - } - catch (Throwable ex) { - throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); - } - - // Do not accept a null value for a FactoryBean that's not fully - // initialized yet: Many FactoryBeans just return null then. - if (object == null && isSingletonCurrentlyInCreation(beanName)) { - throw new BeanCurrentlyInCreationException( - beanName, "FactoryBean which is currently in creation returned null from getObject"); + object = AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return factory.getObject(); + } + }, acc); + } catch (PrivilegedActionException pae) { + throw pae.getException(); } + } + else { + object = factory.getObject(); + } + } + catch (FactoryBeanNotInitializedException ex) { + throw new BeanCurrentlyInCreationException(beanName, ex.toString()); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); + } - if (object != null && shouldPostProcess) { - try { - object = postProcessObjectFromFactoryBean(object, beanName); - } - catch (Throwable ex) { - throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex); - } - } + + // Do not accept a null value for a FactoryBean that's not fully + // initialized yet: Many FactoryBeans just return null then. + if (object == null && isSingletonCurrentlyInCreation(beanName)) { + throw new BeanCurrentlyInCreationException( + beanName, "FactoryBean which is currently in creation returned null from getObject"); + } - return object; + if (object != null && shouldPostProcess) { + try { + object = postProcessObjectFromFactoryBean(object, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex); } - }, acc); + } + + return object; } /** @@ -185,5 +207,15 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg super.removeSingleton(beanName); this.factoryBeanObjectCache.remove(beanName); } - -} + + /** + * Returns the security context for this bean factory. If a security manager + * is set, interaction with the user code will be executed using the privileged + * of the security context returned by this method. + * + * @return + */ + protected AccessControlContext getAccessControlContext() { + return AccessController.getContext(); + } +} \ No newline at end of file diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java new file mode 100644 index 0000000000..aae9cd79c2 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support; + +import java.security.AccessControlContext; + +/** + * Provider of the security context of the code running inside the bean factory. + * + * @author Costin Leau + */ +public interface SecurityContextProvider { + + /** + * Provides a security access control context relevant to a bean factory. + * + * @return bean factory security control context + */ + AccessControlContext getAccessControlContext(); +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java index fd0a1dfea0..7f20da06b1 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java @@ -19,6 +19,9 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; @@ -46,12 +49,17 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { if (beanDefinition.getMethodOverrides().isEmpty()) { Constructor constructorToUse = (Constructor) beanDefinition.resolvedConstructorOrFactoryMethod; if (constructorToUse == null) { - Class clazz = beanDefinition.getBeanClass(); + final Class clazz = beanDefinition.getBeanClass(); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { - constructorToUse = clazz.getDeclaredConstructor((Class[]) null); + constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction() { + + public Constructor run() throws Exception { + return clazz.getDeclaredConstructor((Class[]) null); + } + }); beanDefinition.resolvedConstructorOrFactoryMethod = constructorToUse; } catch (Exception ex) { @@ -107,11 +115,17 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { public Object instantiate( RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, - Object factoryBean, Method factoryMethod, Object[] args) { + Object factoryBean, final Method factoryMethod, Object[] args) { try { // It's a static method if the target is null. - ReflectionUtils.makeAccessible(factoryMethod); + AccessController.doPrivileged(new PrivilegedAction() { + + public Object run() { + ReflectionUtils.makeAccessible(factoryMethod); + return null; + } + }); return factoryMethod.invoke(factoryBean, args); } catch (IllegalArgumentException ex) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java new file mode 100644 index 0000000000..56dea40a46 --- /dev/null +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support; + +import java.security.AccessControlContext; +import java.security.AccessController; + +/** + * Simple #SecurityContextProvider implementation. + * + * @author Costin Leau + */ +public class SimpleSecurityContextProvider implements SecurityContextProvider { + + private final AccessControlContext acc; + + /** + * Constructs a new SimpleSecurityContextProvider instance. + * + * The security context will be retrieved on each call from the current + * thread. + */ + public SimpleSecurityContextProvider() { + this(null); + } + + /** + * Constructs a new SimpleSecurityContextProvider instance. + * + * If the given control context is null, the security context will be + * retrieved on each call from the current thread. + * + * @see AccessController#getContext() + * @param acc + * access control context (can be null) + */ + public SimpleSecurityContextProvider(AccessControlContext acc) { + this.acc = acc; + } + + public AccessControlContext getAccessControlContext() { + return (acc == null ? AccessController.getContext() : acc); + } +} diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTest.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTest.java new file mode 100644 index 0000000000..1f3ffe5cf2 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTest.java @@ -0,0 +1,223 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security; + +import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.util.PropertyPermission; + +import junit.framework.TestCase; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.support.AbstractBeanFactory; +import org.springframework.beans.factory.support.SecurityContextProvider; +import org.springframework.beans.factory.support.security.support.ConstructorBean; +import org.springframework.beans.factory.support.security.support.CustomCallbackBean; +import org.springframework.beans.factory.xml.XmlBeanFactory; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; + +/** + * @author Costin Leau + */ +public class CallbacksSecurityTest extends TestCase { + + private XmlBeanFactory beanFactory; + private SecurityContextProvider provider; + + public CallbacksSecurityTest() { + // setup security + if (System.getSecurityManager() == null) { + Policy policy = Policy.getPolicy(); + URL policyURL = getClass().getResource("/org/springframework/beans/factory/support/security/policy.all"); + System.setProperty("java.security.policy", policyURL.toString()); + System.setProperty("policy.allowSystemProperty", "true"); + policy.refresh(); + + System.setSecurityManager(new SecurityManager()); + } + } + + @Override + protected void setUp() throws Exception { + + final ProtectionDomain empty = new ProtectionDomain(null, new Permissions()); + + provider = new SecurityContextProvider() { + private final AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] { empty }); + + public AccessControlContext getAccessControlContext() { + return acc; + } + }; + + DefaultResourceLoader drl = new DefaultResourceLoader(); + Resource config = drl.getResource("/org/springframework/beans/factory/support/security/callbacks.xml"); + beanFactory = new XmlBeanFactory(config); + + beanFactory.setSecurityContextProvider(provider); + } + + public void testSecuritySanity() throws Exception { + AccessControlContext acc = provider.getAccessControlContext(); + try { + acc.checkPermission(new PropertyPermission("*", "read")); + fail("Acc should not have any permissions"); + } catch (SecurityException se) { + // expected + } + + final CustomCallbackBean bean = new CustomCallbackBean(); + final Method method = bean.getClass().getMethod("destroy", null); + method.setAccessible(true); + + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + method.invoke(bean, null); + return null; + } + }, acc); + fail("expected security exception"); + } catch (Exception ex) { + } + + final Class cl = ConstructorBean.class; + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + return cl.newInstance(); + } + }, acc); + fail("expected security exception"); + } catch (Exception ex) { + } + } + + public void testSpringInitBean() throws Exception { + try { + beanFactory.getBean("spring-init"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + assertTrue(ex.getCause() instanceof SecurityException); + } + } + + public void testCustomInitBean() throws Exception { + try { + beanFactory.getBean("custom-init"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + assertTrue(ex.getCause() instanceof SecurityException); + } + } + + public void testSpringDestroyBean() throws Exception { + beanFactory.getBean("spring-destroy"); + beanFactory.destroySingletons(); + assertNull(System.getProperty("security.destroy")); + } + + public void testCustomDestroyBean() throws Exception { + beanFactory.getBean("custom-destroy"); + beanFactory.destroySingletons(); + assertNull(System.getProperty("security.destroy")); + } + + public void testCustomFactoryObject() throws Exception { + try { + beanFactory.getBean("spring-factory"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + assertTrue(ex.getCause() instanceof SecurityException); + } + + } + + public void testCustomFactoryType() throws Exception { + assertNull(beanFactory.getType("spring-factory")); + assertNull(System.getProperty("factory.object.type")); + } + + public void testCustomStaticFactoryMethod() throws Exception { + try { + beanFactory.getBean("custom-static-factory-method"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + assertTrue(ex.getMostSpecificCause() instanceof SecurityException); + } + } + + public void testCustomInstanceFactoryMethod() throws Exception { + try { + beanFactory.getBean("custom-factory-method"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + assertTrue(ex.getMostSpecificCause() instanceof SecurityException); + } + } + + public void testTrustedFactoryMethod() throws Exception { + try { + beanFactory.getBean("trusted-factory-method"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + assertTrue(ex.getMostSpecificCause() instanceof SecurityException); + } + } + + public void testConstructor() throws Exception { + try { + beanFactory.getBean("constructor"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + // expected + assertTrue(ex.getMostSpecificCause() instanceof SecurityException); + } + } + + public void testContainerPriviledges() throws Exception { + AccessControlContext acc = provider.getAccessControlContext(); + + AccessController.doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws Exception { + beanFactory.getBean("working-factory-method"); + beanFactory.getBean("container-execution"); + return null; + } + }, acc); + } + + public void testPropertyInjection() throws Exception { + try { + beanFactory.getBean("property-injection"); + fail("expected security exception"); + } catch (BeanCreationException ex) { + assertTrue(ex.getMessage().contains("security")); + } + + beanFactory.getBean("working-property-injection"); + } +} \ No newline at end of file diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml new file mode 100644 index 0000000000..16315db6b3 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/policy.all b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/policy.all new file mode 100644 index 0000000000..a59033804b --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/policy.all @@ -0,0 +1,3 @@ +grant { + permission java.security.AllPermission; +}; \ No newline at end of file diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java new file mode 100644 index 0000000000..1934157b94 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/ConstructorBean.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security.support; + +/** + * @author Costin Leau + */ +public class ConstructorBean { + + public ConstructorBean() { + System.getProperties(); + } + + public ConstructorBean(Object obj) { + System.out.println("Received object " + obj); + } +} diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.java new file mode 100644 index 0000000000..c64000093b --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security.support; + +/** + * @author Costin Leau + */ +public class CustomCallbackBean { + + public void init() { + System.getProperties(); + } + + public void destroy() { + System.setProperty("security.destroy", "true"); + } +} diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomFactoryBean.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomFactoryBean.java new file mode 100644 index 0000000000..231c5d1d08 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomFactoryBean.java @@ -0,0 +1,39 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security.support; + +import java.util.Properties; + +import org.springframework.beans.factory.FactoryBean; + +/** + * @author Costin Leau + */ +public class CustomFactoryBean implements FactoryBean { + + public Object getObject() throws Exception { + return System.getProperties(); + } + + public Class getObjectType() { + System.setProperty("factory.object.type", "true"); + return Properties.class; + } + + public boolean isSingleton() { + return true; + } +} diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.java new file mode 100644 index 0000000000..da25e799b7 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.java @@ -0,0 +1,28 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security.support; + +import org.springframework.beans.factory.DisposableBean; + +/** + * @author Costin Leau + */ +public class DestroyBean implements DisposableBean { + + public void destroy() throws Exception { + System.setProperty("security.destroy", "true"); + } +} diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.java new file mode 100644 index 0000000000..80871674f5 --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security.support; + +/** + * @author Costin Leau + */ +public class FactoryBean { + + public static Object makeStaticInstance() { + System.getProperties(); + return new Object(); + } + + protected static Object protectedStaticInstance() { + return "protectedStaticInstance"; + } + + public Object makeInstance() { + System.getProperties(); + return new Object(); + } +} diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.java new file mode 100644 index 0000000000..0c3f71e63e --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.java @@ -0,0 +1,28 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security.support; + +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Costin Leau + */ +public class InitBean implements InitializingBean { + + public void afterPropertiesSet() throws Exception { + System.getProperties(); + } +} diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.java new file mode 100644 index 0000000000..494187c20f --- /dev/null +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory.support.security.support; + +/** + * @author Costin Leau + */ +public class PropertyBean { + + public void setSecurityProperty(Object property) { + System.getProperties(); + } + + public void setProperty(Object property) { + + } +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java index e904facbd1..d44f52cf53 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java @@ -16,11 +16,18 @@ package org.springframework.context.support; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.MessageSourceAware; import org.springframework.context.ResourceLoaderAware; @@ -56,7 +63,35 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { } - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException { + AccessControlContext acc = null; + + if (System.getSecurityManager() != null) { + if (applicationContext instanceof ConfigurableApplicationContext) { + ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); + if (factory instanceof AbstractBeanFactory) { + acc = ((AbstractBeanFactory) factory).getSecurityContextProvider().getAccessControlContext(); + } + } + // optimize - check the bean class before creating the inner class + native call + if (bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware + || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + doProcess(bean); + return null; + } + }, acc); + } + } + else { + doProcess(bean); + } + + return bean; + } + + private void doProcess(Object bean) { if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } @@ -69,11 +104,9 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } - return bean; } public Object postProcessAfterInitialization(Object bean, String name) { return bean; } - -} +} \ No newline at end of file