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 }