    1   package groovy.security;
    2
    3   import groovy.lang.Binding;
    4   import groovy.lang.GroovyClassLoader;
    5   import groovy.lang.GroovyCodeSource;
    6   import groovy.lang.Script;
    7   import groovy.util.GroovyTestCase;
    8
    9   import java.io.File;
    10  import java.io.FileNotFoundException;
    11  import java.io.PrintStream;
    12  import java.security.AccessControlException;
    13  import java.security.AccessController;
    14  import java.security.Permission;
    15  import java.security.Policy;
    16  import java.security.PrivilegedAction;
    17  import java.util.Enumeration;
    18
    19  import junit.framework.TestCase;
    20  import junit.framework.TestFailure;
    21  import junit.framework.TestResult;
    22  import junit.framework.TestSuite;
    23  import junit.textui.ResultPrinter;
    24
    25  import org.codehaus.groovy.runtime.InvokerHelper;
    26
    27  /***
    28   * @author Steve Goetze
    29   */
    30  public class SecurityTestSupport extends GroovyTestCase {
    31
    32  	private static int counter = 0;
    33  	private static boolean securityDisabled;
    34  	private static boolean securityAvailable;
    35  	private static boolean securityChecked = false;
    36
    37  	static {
    38  		if (System.getProperty("groovy.security.disabled") != null) {
    39  			securityAvailable = false;
    40  			securityDisabled = true;
    41  		} else {
    42  			securityDisabled = false;
    43  			String groovyLibDir = System.getProperty("groovy.lib");
    44  			if (groovyLibDir == null) {
    45  				//Try to find maven repository in the default user.home location
    46  				groovyLibDir = System.getProperty("user.home") + "/" + ".maven/repository";
    47  			}
    48  			if (groovyLibDir == null) {
    49  				//Try at user.dir/lib
    50  				groovyLibDir = "lib";
    51  			}
    52  			if (new File(groovyLibDir).exists()) {
    53  				securityAvailable = true;
    54  				System.setProperty("groovy.lib", groovyLibDir);
    55  				System.setProperty("java.security.policy", "=security/groovy.policy");
    56  			} else {
    57  				securityAvailable = false;
    58  			}
    59  		}
    60  	}
    61
    62  	public static boolean isSecurityAvailable() {
    63  		return securityAvailable;
    64  	}
    65
    66  	public static boolean isSecurityDisabled() {
    67  		return securityDisabled;
    68  	}
    69
    70  	public static void resetSecurityPolicy(String policyFileURL) {
    71  		System.setProperty("java.security.policy", policyFileURL);
    72  		Policy.getPolicy().refresh();
    73  	}
    74
    75  	protected class SecurityTestResultPrinter extends ResultPrinter {
    76
    77  		public SecurityTestResultPrinter(PrintStream stream) {
    78  			super(stream);
    79  		}
    80  		public void print(TestResult result) {
    81  			getWriter().println("Security testing on a groovy test failed:");
    82  		    printErrors(result);
    83  		    printFailures(result);
    84  		    printFooter(result);
    85  		}
    86  	}
    87
    88      protected GroovyClassLoader loader = (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
    89  		public Object run() {
    90  			return new GroovyClassLoader(SecurityTestSupport.class.getClassLoader());
    91  		}
    92  	});
    93
    94  	private SecurityManager securityManager;
    95      private ClassLoader currentClassLoader;
    96
    97  	public SecurityTestSupport() {
    98  	}
    99
    100 	/*
    101 	 * Check SecuritySupport to see if security is properly configured.  If not, fail the first
    102 	 * test that runs.  All remaining tests will run, but not do any security checking.
    103 	 */
    104 	private boolean checkSecurity() {
    105 		if (!securityChecked) {
    106 			securityChecked = true;
    107 			if (!isSecurityAvailable()) {
    108 				fail("Security is not available - skipping security tests.  Ensure that groovy.lib is set and points to the groovy dependency jars.");
    109 			}
    110 		}
    111 		return isSecurityAvailable();
    112 	}
    113
    114 	//Prepare for each security test.  First, check to see if groovy.lib can be determined via
    115 	//a call to checkSecurity().  If not, fail() the first test.  Establish a security manager
    116 	//and make the GroovyClassLoader the initiating class loader (ala GroovyShell) to compile AND
    117 	//invoke the test scripts.  This handles cases where multiple .groovy scripts are involved in a
    118 	//test case: a.groovy depends on b.groovy; a.groovy is parsed (and in the process the gcl
    119 	//loads b.groovy via findClass).  Note that b.groovy is only available in the groovy class loader.
    120 	//See
    121 	protected void setUp() {
    122 		if (checkSecurity()) {
    123 			securityManager = System.getSecurityManager();
    124 			if (securityManager == null) {
    125 	    		System.setSecurityManager(new SecurityManager());
    126 			}
    127 		}
    128 		currentClassLoader = Thread.currentThread().getContextClassLoader();
    129 		AccessController.doPrivileged(new PrivilegedAction() {
    130             public Object run() {
    131                 Thread.currentThread().setContextClassLoader(loader);
    132                 return null;
    133             }
    134         });
    135     }
    136
    137 	protected void tearDown() {
    138         AccessController.doPrivileged(new PrivilegedAction() {
    139             public Object run() {
    140                 System.setSecurityManager(securityManager);
    141                 Thread.currentThread().setContextClassLoader(currentClassLoader);
    142                 return null;
    143             }
    144         });
    145 	}
    146
    147     protected synchronized String generateClassName() {
    148         return "testSecurity" + (++counter);
    149     }
    150
    151 	/*
    152 	 * Execute the groovy script contained in file.  If missingPermission
    153 	 * is non-null, then this invocation expects an AccessControlException with missingPermission
    154 	 * as the reason.  If missingPermission is null, the script is expected to execute successfully.
    155 	 */
    156 	protected Class parseClass(File file) {
    157 		GroovyCodeSource gcs = null;
    158 		try {
    159 			gcs = new GroovyCodeSource(file);
    160 		} catch (FileNotFoundException fnfe) {
    161 			fail(fnfe.toString());
    162 		}
    163 		return parseClass(gcs);
    164 	}
    165
    166     /*
    167 	 * Parse the Groovy code contained in the GroovyCodeSource as a privileged operation (i.e. do not
    168 	 * require the code source to have specific compile time permissions) and return the resulting class.
    169 	 */
    170 	protected Class parseClass(final GroovyCodeSource gcs) {
    171 		Class clazz = null;
    172 		try {
    173 			clazz = loader.parseClass(gcs);
    174 		} catch (Exception e) {
    175 			fail(e.toString());
    176 		}
    177 		return clazz;
    178 	}
    179
    180     /*
    181 	 * Parse the script contained in the GroovyCodeSource as a privileged operation (i.e. do not
    182 	 * require the code source to have specific compile time permissions).  If the class produced is a
    183 	 * TestCase, run the test in a suite and evaluate against the missingPermission.
    184 	 * Otherwise, run the class as a groovy script and evaluate against the missingPermission.
    185 	 */
    186 	private void parseAndExecute(final GroovyCodeSource gcs, Permission missingPermission) {
    187 		Class clazz = null;
    188 		try {
    189 			clazz = loader.parseClass(gcs);
    190 		} catch (Exception e) {
    191 			fail(e.toString());
    192 		}
    193 		if (TestCase.class.isAssignableFrom(clazz)) {
    194 			executeTest(clazz, missingPermission);
    195 		} else {
    196 			executeScript(clazz, missingPermission);
    197 		}
    198 	}
    199
    200 	protected void executeTest(Class test, Permission missingPermission) {
    201 		TestSuite suite = new TestSuite();
    202 		suite.addTestSuite(test);
    203         TestResult result = new TestResult();
    204         suite.run(result);
    205         if (result.wasSuccessful()) {
    206         	if (missingPermission == null) {
    207         		return;
    208         	} else {
    209         		fail("Security test expected an AccessControlException on " + missingPermission + ", but did not receive one");
    210         	}
    211         } else {
    212         	if (missingPermission == null) {
    213         		new SecurityTestResultPrinter(System.out).print(result);
    214         		fail("Security test was expected to run successfully, but failed (results on System.out)");
    215         	} else {
    216         		//There may be more than 1 failure:  iterate to ensure that they all match the missingPermission.
    217         		boolean otherFailure = false;
    218         		for (Enumeration e = result.errors(); e.hasMoreElements(); ) {
    219         			TestFailure failure = (TestFailure) e.nextElement();
    220         			if (failure.thrownException() instanceof AccessControlException) {
    221         				AccessControlException ace = (AccessControlException) failure.thrownException();
    222         				if (missingPermission.implies(ace.getPermission())) {
    223         					continue;
    224         				}
    225         			}
    226         			otherFailure = true;
    227         		}
    228         		if (otherFailure) {
    229         			new SecurityTestResultPrinter(System.out).print(result);
    230         			fail("Security test expected an AccessControlException on " + missingPermission + ", but  failed for other reasons (results on System.out)");
    231         		}
    232         	}
    233         }
    234     }
    235
    236 	protected void executeScript(Class scriptClass, Permission missingPermission) {
    237 		try {
    238 			Script script = InvokerHelper.createScript(scriptClass, new Binding());
    239 			script.run();
    240 			//InvokerHelper.runScript(scriptClass, null);
    241 		} catch (AccessControlException ace) {
    242 			if (missingPermission != null && missingPermission.implies(ace.getPermission())) {
    243 				return;
    244 			} else {
    245 				fail(ace.toString());
    246 			}
    247 		}
    248 		if (missingPermission != null) {
    249 			fail("Should catch an AccessControlException");
    250 		}
    251 	}
    252
    253 	/*
    254 	 * Execute the groovy script contained in file.  If missingPermission
    255 	 * is non-null, then this invocation expects an AccessControlException with missingPermission
    256 	 * as the reason.  If missingPermission is null, the script is expected to execute successfully.
    257 	 */
    258 	protected void assertExecute(File file, Permission missingPermission) {
    259 		if (!isSecurityAvailable()) {
    260 			return;
    261 		}
    262 		GroovyCodeSource gcs = null;
    263 		try {
    264 			gcs = new GroovyCodeSource(file);
    265 		} catch (FileNotFoundException fnfe) {
    266 			fail(fnfe.toString());
    267 		}
    268 		parseAndExecute(gcs, missingPermission);
    269 	}
    270
    271 	/*
    272 	 * Execute the script represented by scriptStr using the supplied codebase.  If missingPermission
    273 	 * is non-null, then this invocation expects an AccessControlException with missingPermission
    274 	 * as the reason.  If missingPermission is null, the script is expected to execute successfully.
    275 	 */
    276 	protected void assertExecute(String scriptStr, String codeBase, Permission missingPermission) {
    277 		if (!isSecurityAvailable()) {
    278 			return;
    279 		}
    280 		if (codeBase == null) {
    281 			codeBase = "/groovy/security/test";
    282 		}
    283 		parseAndExecute(new GroovyCodeSource(scriptStr, generateClassName(), codeBase), missingPermission);
    284 	}
    285 }