1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.logging.security; 19 20 import java.io.PrintWriter; 21 import java.io.StringWriter; 22 import java.lang.reflect.Field; 23 import java.lang.reflect.Method; 24 import java.security.AllPermission; 25 import java.util.Hashtable; 26 27 import junit.framework.Test; 28 import junit.framework.TestCase; 29 30 import org.apache.commons.lang3.JavaVersion; 31 import org.apache.commons.lang3.SystemUtils; 32 import org.apache.commons.logging.Log; 33 import org.apache.commons.logging.LogFactory; 34 import org.apache.commons.logging.PathableClassLoader; 35 import org.apache.commons.logging.PathableTestSuite; 36 37 /** 38 * Tests for logging with a security policy that allows JCL access to everything. 39 * <p> 40 * This test cannot run on Java 21 and up: {@code java.lang.UnsupportedOperationException: The Security Manager is deprecated and will be removed in a future release}. 41 * </p> 42 * This class has only one unit test, as we are (in part) checking behavior in the static block of the LogFactory class. As that class cannot be unloaded after 43 * being loaded into a class loader, the only workaround is to use the PathableClassLoader approach to ensure each test is run in its own class loader, and use 44 * a separate test class for each test. 45 * </p> 46 */ 47 public class SecurityAllowedTestCase extends TestCase { 48 49 /** 50 * Dummy special Hashtable, so we can tell JCL to use this instead of the standard one. 51 */ 52 public static class CustomHashtable<K, V> extends Hashtable<K, V> { 53 54 /** 55 * Generated serial version ID. 56 */ 57 private static final long serialVersionUID = 8941017300059246720L; 58 } 59 60 /** 61 * Return the tests included in this test suite. 62 */ 63 public static Test suite() throws Exception { 64 final PathableClassLoader parent = new PathableClassLoader(null); 65 parent.useExplicitLoader("junit.", Test.class.getClassLoader()); 66 parent.addLogicalLib("commons-logging"); 67 parent.addLogicalLib("testclasses"); 68 parent.addLogicalLib("commons-lang3"); 69 70 final Class<?> testClass = parent.loadClass("org.apache.commons.logging.security.SecurityAllowedTestCase"); 71 return new PathableTestSuite(testClass, parent); 72 } 73 74 private SecurityManager oldSecMgr; 75 76 @Override 77 public void setUp() { 78 // Ignore on Java 21 and up 79 // TODO Port tests to JUnit 5 80 if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_21)) { 81 return; 82 } 83 // save security manager so it can be restored in tearDown 84 oldSecMgr = System.getSecurityManager(); 85 } 86 87 @Override 88 public void tearDown() { 89 // Ignore on Java 21 and up 90 // TODO Port tests to JUnit 5 91 if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_21)) { 92 return; 93 } 94 // Restore, so other tests don't get stuffed up if a test 95 // sets a custom security manager. 96 // Java 21 and up: java.lang.UnsupportedOperationException: The Security Manager is deprecated and will be removed in a future release 97 System.setSecurityManager(oldSecMgr); 98 } 99 100 /** 101 * Test what happens when JCL is run with all permissions enabled. Custom overrides should take effect. 102 */ 103 public void testAllAllowed() { 104 // Ignore on Java 21 and up 105 // TODO Port tests to JUnit 5 106 if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_21)) { 107 return; 108 } 109 System.setProperty(LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY, CustomHashtable.class.getName()); 110 final MockSecurityManager mySecurityManager = new MockSecurityManager(); 111 mySecurityManager.addPermission(new AllPermission()); 112 // Java 22: java.lang.UnsupportedOperationException: The Security Manager is deprecated and will be removed in a future release 113 System.setSecurityManager(mySecurityManager); 114 115 try { 116 // Use reflection so that we can control exactly when the static 117 // initializer for the LogFactory class is executed. 118 final Class<?> c = this.getClass().getClassLoader().loadClass("org.apache.commons.logging.LogFactory"); 119 final Method m = c.getMethod("getLog", Class.class); 120 final Log log = (Log) m.invoke(null, this.getClass()); 121 122 // Check whether we had any security exceptions so far (which were 123 // caught by the code). We should not, as every secure operation 124 // should be wrapped in an AccessController. Any security exceptions 125 // indicate a path that is missing an appropriate AccessController. 126 // 127 // We don't wait until after the log.info call to get this count 128 // because java.util.logging tries to load a resource bundle, which 129 // requires permission accessClassInPackage. JCL explicitly does not 130 // wrap calls to log methods in AccessControllers because writes to 131 // a log file *should* only be permitted if the original caller is 132 // trusted to access that file. 133 final int untrustedCodeCount = mySecurityManager.getUntrustedCodeCount(); 134 log.info("testing"); 135 136 // check that the default map implementation was loaded, as JCL was 137 // forbidden from reading the HASHTABLE_IMPLEMENTATION_PROPERTY property. 138 System.setSecurityManager(null); 139 final Field factoryField = c.getDeclaredField("factories"); 140 factoryField.setAccessible(true); 141 final Object factoryTable = factoryField.get(null); 142 assertNotNull(factoryTable); 143 assertEquals(CustomHashtable.class.getName(), factoryTable.getClass().getName()); 144 145 // we better compare that we have no security exception during the call to log 146 // IBM JVM tries to load bundles during the invoke call, which increase the count 147 assertEquals("Untrusted code count", untrustedCodeCount, mySecurityManager.getUntrustedCodeCount()); 148 } catch (final Throwable t) { 149 // Restore original security manager so output can be generated; the 150 // PrintWriter constructor tries to read the line.separator 151 // system property. 152 System.setSecurityManager(oldSecMgr); 153 final StringWriter sw = new StringWriter(); 154 final PrintWriter pw = new PrintWriter(sw); 155 t.printStackTrace(pw); 156 fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString()); 157 } 158 } 159 }