001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */ 
019     
020    package org.apache.commons.logging.security;
021    
022    import java.io.PrintWriter;
023    import java.io.StringWriter;
024    import java.lang.reflect.Field;
025    import java.lang.reflect.Method;
026    import java.util.Hashtable;
027    
028    import junit.framework.Test;
029    import junit.framework.TestCase;
030    
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    import org.apache.commons.logging.PathableClassLoader;
034    import org.apache.commons.logging.PathableTestSuite;
035    
036    /**
037     * Tests for logging with a security policy that forbids JCL access to anything.
038     * <p>
039     * Performing tests with security permissions disabled is tricky, as building error
040     * messages on failure requires certain security permissions. If the security manager
041     * blocks these, then the test can fail without the error messages being output.
042     * <p>
043     * This class has only one unit test, as we are (in part) checking behaviour in
044     * the static block of the LogFactory class. As that class cannot be unloaded after
045     * being loaded into a classloader, the only workaround is to use the 
046     * PathableClassLoader approach to ensure each test is run in its own
047     * classloader, and use a separate testcase class for each test.
048     */
049    public class SecurityForbiddenTestCase extends TestCase
050    {
051        private SecurityManager oldSecMgr;
052    
053        // Dummy special hashtable, so we can tell JCL to use this instead of
054        // the standard one.
055        public static class CustomHashtable extends Hashtable {
056        }
057    
058        /**
059         * Return the tests included in this test suite.
060         */
061        public static Test suite() throws Exception {
062            PathableClassLoader parent = new PathableClassLoader(null);
063            parent.useExplicitLoader("junit.", Test.class.getClassLoader());
064            parent.addLogicalLib("commons-logging");
065            parent.addLogicalLib("testclasses");
066    
067            Class testClass = parent.loadClass(
068                "org.apache.commons.logging.security.SecurityForbiddenTestCase");
069            return new PathableTestSuite(testClass, parent);
070        }
071    
072        public void setUp() {
073            // save security manager so it can be restored in tearDown
074            oldSecMgr = System.getSecurityManager();
075        }
076        
077        public void tearDown() {
078            // Restore, so other tests don't get stuffed up if a test
079            // sets a custom security manager.
080            System.setSecurityManager(oldSecMgr);
081        }
082    
083        /**
084         * Test what happens when JCL is run with absolutely no security
085         * priveleges at all, including reading system properties. Everything
086         * should fall back to the built-in defaults.
087         */
088        public void testAllForbidden() {
089            System.setProperty(
090                    LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY,
091                    CustomHashtable.class.getName());
092            MockSecurityManager mySecurityManager = new MockSecurityManager();
093            System.setSecurityManager(mySecurityManager);
094    
095            try {
096                // Use reflection so that we can control exactly when the static
097                // initialiser for the LogFactory class is executed.
098                Class c = this.getClass().getClassLoader().loadClass(
099                        "org.apache.commons.logging.LogFactory");
100                Method m = c.getMethod("getLog", new Class[] {Class.class});
101                Log log = (Log) m.invoke(null, new Object[] {this.getClass()});
102                log.info("testing");
103                
104                // check that the default map implementation was loaded, as JCL was
105                // forbidden from reading the HASHTABLE_IMPLEMENTATION_PROPERTY property.
106                //
107                // The default is either the java Hashtable class (java < 1.2) or the
108                // JCL WeakHashtable (java >= 1.3).
109                System.setSecurityManager(oldSecMgr);
110                Field factoryField = c.getDeclaredField("factories");
111                factoryField.setAccessible(true);
112                Object factoryTable = factoryField.get(null); 
113                assertNotNull(factoryTable);
114                String ftClassName = factoryTable.getClass().getName();
115                assertTrue("Custom hashtable unexpectedly used", 
116                        !CustomHashtable.class.getName().equals(ftClassName));
117    
118                assertEquals(0, mySecurityManager.getUntrustedCodeCount());
119            } catch(Throwable t) {
120                // Restore original security manager so output can be generated; the
121                // PrintWriter constructor tries to read the line.separator
122                // system property.
123                System.setSecurityManager(oldSecMgr);
124                StringWriter sw = new StringWriter();
125                PrintWriter pw = new PrintWriter(sw);
126                t.printStackTrace(pw);
127                fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString());
128            }
129        }
130    }