View Javadoc
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.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.commons.logging.PathableClassLoader;
33  import org.apache.commons.logging.PathableTestSuite;
34  
35  /**
36   * Tests for logging with a security policy that allows JCL access to everything.
37   * <p>
38   * This class has only one unit test, as we are (in part) checking behavior in
39   * the static block of the LogFactory class. As that class cannot be unloaded after
40   * being loaded into a class loader, the only workaround is to use the
41   * PathableClassLoader approach to ensure each test is run in its own
42   * class loader, and use a separate test class for each test.
43   */
44  public class SecurityAllowedTestCase extends TestCase {
45  
46      // Dummy special hashtable, so we can tell JCL to use this instead of
47      // the standard one.
48      public static class CustomHashtable extends Hashtable {
49  
50          /**
51           * Generated serial version ID.
52           */
53          private static final long serialVersionUID = 8941017300059246720L;
54      }
55  
56      /**
57       * Return the tests included in this test suite.
58       */
59      public static Test suite() throws Exception {
60          final PathableClassLoader parent = new PathableClassLoader(null);
61          parent.useExplicitLoader("junit.", Test.class.getClassLoader());
62          parent.addLogicalLib("commons-logging");
63          parent.addLogicalLib("testclasses");
64  
65          final Class testClass = parent.loadClass(
66              "org.apache.commons.logging.security.SecurityAllowedTestCase");
67          return new PathableTestSuite(testClass, parent);
68      }
69  
70      private SecurityManager oldSecMgr;
71  
72      @Override
73      public void setUp() {
74          // save security manager so it can be restored in tearDown
75          oldSecMgr = System.getSecurityManager();
76      }
77  
78      @Override
79      public void tearDown() {
80          // Restore, so other tests don't get stuffed up if a test
81          // sets a custom security manager.
82          System.setSecurityManager(oldSecMgr);
83      }
84  
85      /**
86       * Test what happens when JCL is run with all permissions enabled. Custom
87       * overrides should take effect.
88       */
89      public void testAllAllowed() {
90          // Ignore on Java 21
91          if (System.getProperty("java.version").startsWith("21.")) {
92              return;
93          }
94          System.setProperty(
95                  LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY,
96                  CustomHashtable.class.getName());
97          final MockSecurityManager mySecurityManager = new MockSecurityManager();
98          mySecurityManager.addPermission(new AllPermission());
99          System.setSecurityManager(mySecurityManager);
100 
101         try {
102             // Use reflection so that we can control exactly when the static
103             // initializer for the LogFactory class is executed.
104             final Class c = this.getClass().getClassLoader().loadClass(
105                     "org.apache.commons.logging.LogFactory");
106             final Method m = c.getMethod("getLog", Class.class);
107             final Log log = (Log) m.invoke(null, this.getClass());
108 
109             // Check whether we had any security exceptions so far (which were
110             // caught by the code). We should not, as every secure operation
111             // should be wrapped in an AccessController. Any security exceptions
112             // indicate a path that is missing an appropriate AccessController.
113             //
114             // We don't wait until after the log.info call to get this count
115             // because java.util.logging tries to load a resource bundle, which
116             // requires permission accessClassInPackage. JCL explicitly does not
117             // wrap calls to log methods in AccessControllers because writes to
118             // a log file *should* only be permitted if the original caller is
119             // trusted to access that file.
120             final int untrustedCodeCount = mySecurityManager.getUntrustedCodeCount();
121             log.info("testing");
122 
123             // check that the default map implementation was loaded, as JCL was
124             // forbidden from reading the HASHTABLE_IMPLEMENTATION_PROPERTY property.
125             System.setSecurityManager(null);
126             final Field factoryField = c.getDeclaredField("factories");
127             factoryField.setAccessible(true);
128             final Object factoryTable = factoryField.get(null);
129             assertNotNull(factoryTable);
130             assertEquals(CustomHashtable.class.getName(), factoryTable.getClass().getName());
131 
132             // we better compare that we have no security exception during the call to log
133             // IBM JVM tries to load bundles during the invoke call, which increase the count
134             assertEquals("Untrusted code count", untrustedCodeCount, mySecurityManager.getUntrustedCodeCount());
135         } catch (final Throwable t) {
136             // Restore original security manager so output can be generated; the
137             // PrintWriter constructor tries to read the line.separator
138             // system property.
139             System.setSecurityManager(oldSecMgr);
140             final StringWriter sw = new StringWriter();
141             final PrintWriter pw = new PrintWriter(sw);
142             t.printStackTrace(pw);
143             fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString());
144         }
145     }
146 }