diff --git a/src/test/org/apache/commons/logging/security/MockSecurityManager.java b/src/test/org/apache/commons/logging/security/MockSecurityManager.java index 5a4c601..81eaca9 100644 --- a/src/test/org/apache/commons/logging/security/MockSecurityManager.java +++ b/src/test/org/apache/commons/logging/security/MockSecurityManager.java @@ -18,68 +18,118 @@ package org.apache.commons.logging.security; import java.io.FilePermission; import java.security.Permission; -import java.util.PropertyPermission; +import java.security.Permissions; /** * Custom implementation of a security manager, so we can control the * security environment for tests in this package. - *
- * Note that we don't want to refuse permission to any junit method; otherwise
- * any call to an assert will not be able to output its data!
*/
public class MockSecurityManager extends SecurityManager {
+
+ private Permissions permissions = new Permissions();
+ private static final Permission setSecurityManagerPerm =
+ new RuntimePermission("setSecurityManager");
+
+ private int untrustedCodeCount = 0;
+
+ public MockSecurityManager() {
+ permissions.add(setSecurityManagerPerm);
+ }
+
+ /**
+ * Define the set of permissions to be granted to classes in the o.a.c.l package,
+ * but NOT to unit-test classes in o.a.c.l.security package.
+ */
+ public void addPermission(Permission p) {
+ permissions.add(p);
+ }
+
+ /**
+ * This returns the number of times that a check of a permission failed
+ * due to stack-walking tracing up into untrusted code. Any non-zero
+ * value indicates a bug in JCL, ie a situation where code was not
+ * correctly wrapped in an AccessController block. The result of such a
+ * bug is that signing JCL is not sufficient to allow JCL to perform
+ * the operation; the caller would need to be signed too.
+ */
+ public int getUntrustedCodeCount() {
+ return untrustedCodeCount;
+ }
+
public void checkPermission(Permission p) throws SecurityException {
- // System.out.println("\n\ntesting permission:" + p.getClass() + ":"+ p);
-
- // allow read-only access to files, as this is needed to load classes!
- if (p instanceof FilePermission) {
- FilePermission fp = (FilePermission) p;
- if (fp.getActions().equals("read")) {
- return;
- }
+ if (setSecurityManagerPerm.implies(p)) {
+ // ok, allow this; we don't want to block any calls to setSecurityManager
+ // otherwise this custom security manager cannot be reset to the original.
+ // System.out.println("setSecurityManager: granted");
+ return;
}
+ // Allow read-only access to files, as this is needed to load classes!
+ // Ideally, we would limit this to just .class and .jar files.
+ if (p instanceof FilePermission) {
+ FilePermission fp = (FilePermission) p;
+ if (fp.getActions().equals("read")) {
+ // System.out.println("Permit read of files");
+ return;
+ }
+ }
+
+ System.out.println("\n\ntesting permission:" + p.getClass() + ":"+ p);
+
Exception e = new Exception();
e.fillInStackTrace();
StackTraceElement[] stack = e.getStackTrace();
- boolean isControlled = false;
+ // scan the call stack from most recent to oldest.
// start at 1 to skip the entry in the stack for this method
for(int i=1; i
+ * Performing tests with security permissions disabled is tricky, as building error
+ * messages on failure requires certain security permissions. If the security manager
+ * blocks these, then the test can fail without the error messages being output.
+ *
+ * This class has only one unit test, as we are (in part) checking behaviour in
+ * the static block of the LogFactory class. As that class cannot be unloaded after
+ * being loaded into a classloader, the only workaround is to use the
+ * PathableClassLoader approach to ensure each test is run in its own
+ * classloader, and use a separate testcase class for each test.
+ */
+public class SecurityTestCaseForbidden extends TestCase
+{
+ private SecurityManager oldSecMgr;
+
+ // Dummy special hashtable, so we can tell JCL to use this instead of
+ // the standard one.
+ public static class CustomHashtable extends Hashtable {
+ }
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useSystemLoader("junit.");
+ parent.addLogicalLib("commons-logging");
+ parent.addLogicalLib("testclasses");
+
+ Class testClass = parent.loadClass(
+ "org.apache.commons.logging.security.SecurityTestCaseForbidden");
+ return new PathableTestSuite(testClass, parent);
+ }
+
+ public void setUp() {
+ // save security manager so it can be restored in tearDown
+ oldSecMgr = System.getSecurityManager();
+ }
+
+ public void tearDown() {
+ // Restore, so other tests don't get stuffed up if a test
+ // sets a custom security manager.
+ System.setSecurityManager(oldSecMgr);
+ }
+
+ /**
+ * Test what happens when JCL is run with absolutely no security
+ * priveleges at all, including reading system properties. Everything
+ * should fall back to the built-in defaults.
+ */
+ public void testAllForbidden() {
+ System.setProperty(
+ LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY,
+ CustomHashtable.class.getName());
+ MockSecurityManager mySecurityManager = new MockSecurityManager();
+ System.setSecurityManager(mySecurityManager);
+
+ try {
+ // Use reflection so that we can control exactly when the static
+ // initialiser for the LogFactory class is executed.
+ Class c = this.getClass().getClassLoader().loadClass(
+ "org.apache.commons.logging.LogFactory");
+ Method m = c.getMethod("getLog", new Class[] {Class.class});
+ Log log = (Log) m.invoke(null, new Object[] {this.getClass()});
+ log.info("testing");
+
+ // check that the default map implementation was loaded, as JCL was
+ // forbidden from reading the HASHTABLE_IMPLEMENTATION_PROPERTY property.
+ //
+ // The default is either the java Hashtable class (java < 1.2) or the
+ // JCL WeakHashtable (java >= 1.3).
+ System.setSecurityManager(oldSecMgr);
+ Field factoryField = c.getDeclaredField("factories");
+ factoryField.setAccessible(true);
+ Object factoryTable = factoryField.get(null);
+ assertNotNull(factoryTable);
+ String ftClassName = factoryTable.getClass().getName();
+ assertTrue("Custom hashtable unexpectedly used",
+ !CustomHashtable.class.getName().equals(ftClassName));
+
+ assertEquals(0, mySecurityManager.getUntrustedCodeCount());
+ } catch(Throwable t) {
+ // Restore original security manager so output can be generated; the
+ // PrintWriter constructor tries to read the line.separator
+ // system property.
+ System.setSecurityManager(oldSecMgr);
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString());
+ }
+ }
+}