diff --git a/spring-context/src/test/java/org/springframework/context/support/EnvironmentSecurityManagerIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/support/EnvironmentSecurityManagerIntegrationTests.java new file mode 100644 index 00000000000..42647392832 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/support/EnvironmentSecurityManagerIntegrationTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2013 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.context.support; + +import static java.lang.String.format; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.security.AccessControlException; +import java.security.Permission; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.core.env.StandardEnvironmentTests; +import org.springframework.stereotype.Component; + + +/** + * Tests integration between Environment and SecurityManagers. See SPR-9970. + * + * @author Chris Beams + */ +public class EnvironmentSecurityManagerIntegrationTests { + + private SecurityManager originalSecurityManager; + private Map env; + + @Before + public void setUp() { + originalSecurityManager = System.getSecurityManager(); + env = StandardEnvironmentTests.getModifiableSystemEnvironment(); + env.put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "p1"); + } + + @After + public void tearDown() { + env.remove(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME); + System.setSecurityManager(originalSecurityManager); + } + + @Test + public void securityManagerDisallowsAccessToSystemEnvironmentButAllowsAccessToIndividualKeys() { + SecurityManager securityManager = new SecurityManager() { + @Override + public void checkPermission(Permission perm) { + // disallowing access to System#getenv means that our + // ReadOnlySystemAttributesMap will come into play. + if ("getenv.*".equals(perm.getName())) { + throw new AccessControlException( + "Accessing the system environment is disallowed"); + } + } + }; + System.setSecurityManager(securityManager); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(bf); + reader.register(C1.class); + assertThat(bf.containsBean("c1"), is(true)); + } + + @Test + public void securityManagerDisallowsAccessToSystemEnvironmentAndDisallowsAccessToIndividualKey() { + SecurityManager securityManager = new SecurityManager() { + @Override + public void checkPermission(Permission perm) { + // disallowing access to System#getenv means that our + // ReadOnlySystemAttributesMap will come into play. + if ("getenv.*".equals(perm.getName())) { + throw new AccessControlException( + "Accessing the system environment is disallowed"); + } + // disallowing access to the spring.profiles.active property means that + // the BeanDefinitionReader won't be able to determine which profiles are + // active. We should see an INFO-level message in the console about this + // and as a result, any components marked with a non-default profile will + // be ignored. + if (("getenv."+AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME).equals(perm.getName())) { + throw new AccessControlException( + format("Accessing system environment variable [%s] is disallowed", + AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME)); + } + } + }; + System.setSecurityManager(securityManager); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(bf); + reader.register(C1.class); + assertThat(bf.containsBean("c1"), is(false)); + } + + @Component("c1") + @Profile("p1") + static class C1 { + } +} diff --git a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java index df310c6002b..5ded8cbbc5e 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java +++ b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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. @@ -17,17 +17,21 @@ package org.springframework.core.env; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Set; import org.springframework.util.Assert; /** - * Read-only {@code Map} implementation that is backed by system properties or environment - * variables. + * Read-only {@code Map} implementation that is backed by system + * properties or environment variables. * - *

Used by {@link AbstractApplicationContext} when a {@link SecurityManager} prohibits access to {@link - * System#getProperties()} or {@link System#getenv()}. + *

Used by {@link AbstractApplicationContext} when a {@link SecurityManager} prohibits + * access to {@link System#getProperties()} or {@link System#getenv()}. It is for this + * reason that the implementations of {@link #keySet()}, {@link #entrySet()}, and + * {@link #values()} always return empty even though {@link #get(Object)} may in fact + * return non-null if the current security manager allows access to individual keys. * * @author Arjen Poutsma * @author Chris Beams @@ -85,7 +89,7 @@ abstract class ReadOnlySystemAttributesMap implements Map { } public Set keySet() { - throw new UnsupportedOperationException(); + return Collections.emptySet(); } public void putAll(Map m) { @@ -93,11 +97,11 @@ abstract class ReadOnlySystemAttributesMap implements Map { } public Collection values() { - throw new UnsupportedOperationException(); + return Collections.emptySet(); } public Set> entrySet() { - throw new UnsupportedOperationException(); + return Collections.emptySet(); } } diff --git a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java index 66eeb1e5bb0..09082aad5c2 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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. @@ -76,7 +76,7 @@ public class SystemEnvironmentPropertySource extends MapPropertySource { */ @Override public boolean containsProperty(String name) { - return resolvePropertyName(name) != null; + return getProperty(name) != null; } /** @@ -102,8 +102,8 @@ public class SystemEnvironmentPropertySource extends MapPropertySource { /** * Check to see if this property source contains a property with the given name, or - * any underscore / uppercase variation thereof. Return the resolved name or - * {@code null} if none found. + * any underscore / uppercase variation thereof. Return the resolved name if one is + * found or otherwise the original name. Never returns {@code null}. */ private String resolvePropertyName(String name) { if (super.containsProperty(name)) { @@ -127,6 +127,6 @@ public class SystemEnvironmentPropertySource extends MapPropertySource { } } - return null; + return name; } } diff --git a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java index 8cec8f813d5..131917c8ccf 100644 --- a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -456,7 +456,7 @@ public class StandardEnvironmentTests { } @SuppressWarnings("unchecked") - private static Map getModifiableSystemEnvironment() { + public static Map getModifiableSystemEnvironment() { // for os x / linux Class[] classes = Collections.class.getDeclaredClasses(); Map env = System.getenv();