blob: e50c05e7faa8416480b16f1105eb0af3ffbcf119 [file]
/*
*
* Derby - Class org.apache.derbyTesting.junit.SecurityManagerSetup
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
package org.apache.derbyTesting.junit;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Enumeration;
import java.util.Properties;
import junit.extensions.TestSetup;
import junit.framework.Test;
import org.apache.derbyTesting.functionTests.util.PrivilegedFileOpsForTests;
/**
* Configures the wrapped test to be run with the specified security policy.
* <p>
* This setup class normally installs the default policy file. This can be
* overridden by specifying {@literal java.security.policy=<NONE>} (see
* {@linkplain #NO_POLICY}), and can also be overridden by installing a
* security manager explicitly before the default security manager is installed.
* <p>
* Individual tests/suites can be configured to be run without a security
* manager, with a specific policy file, or with a specific policy file merged
* with the default policy file. The last option is useful when you only need
* to extend the default policy with a few extra permissions to run a test.
*/
public final class SecurityManagerSetup extends TestSetup {
/** Constant used to indicate that no security policy is to be installed. */
static final String NO_POLICY = "<NONE>";
// codebase variables used in derby_tests.policy
public static final String DERBY_ENGINE = "derbyTesting.engine";
public static final String DERBY_CLIENT = "derbyTesting.client";
public static final String DERBY_SHARED = "derbyTesting.shared";
public static final String DERBY_NETSERVER = "derbyTesting.netserver";
public static final String DERBY_TOOLS = "derbyTesting.tools";
public static final String DERBY_OPTIONAL_TOOLS = "derbyTesting.optionaltools";
public static final String DERBY_TESTING = "derbyTesting.testing";
public static final String TESTING_JUNIT = "derbyTesting.junit";
public static final String TESTING_ANTJUNIT = "derbyTesting.antjunit";
public static final String TESTING_ANT = "derbyTesting.ant";
public static final String LUCENE_CORE = "derbyTesting.lucene.core";
public static final String JAXP = "derbyTesting.jaxpjar";
public static final String PACKAGE_PRIVATE_CLASSES = "derbyTesting.ppcodeclasses";
/**
* Does the JVM support Subjects for
* authorization through Java security manager.
* J2ME/CDC/Foundation 1.1 does not support Subjects.
*/
public static final boolean JVM_HAS_SUBJECT_AUTHORIZATION;
static {
JVM_HAS_SUBJECT_AUTHORIZATION = JDBC.haveClass("javax.security.auth.Subject");
}
private static final Properties classPathSet = new Properties();
/**
* True if the classes are loaded from jars.
*/
static boolean isJars;
/**
* True if a security manager was installed outside of the
* control of this class and BaseTestCase.
*/
private static final boolean externalSecurityManagerInstalled;
static {
// Determine what the set of properties
// describing the environment is.
externalSecurityManagerInstalled = determineClasspath();
}
static {
// Work around bug in weme6.2 (DERBY-5558 and DERBY-6079).
if (BaseTestCase.isJ9Platform()) {
BaseTestCase.setSystemProperty("emma.active", "");
BaseTestCase.setSystemProperty("jacoco.active", "");
}
}
static final boolean jacocoEnabled = checkIfJacocoIsRunning();
private static boolean checkIfJacocoIsRunning() {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
try {
// Check if some arbitrary class from jacocoagent.jar
// is available.
Class.forName("org.jacoco.agent.rt.RT");
// If we got here, it means the tests are running
// under JaCoCo. Set the jacoco.active property to
// the empty string in order to activate the
// JaCoCo-specific permissions in derby_tests.policy,
// and return true.
System.setProperty("jacoco.active", "");
return true;
} catch (ClassNotFoundException e) {
return false;
} catch (LinkageError e) {
return false;
}
}
});
}
private final String decoratorPolicyResource;
/** An additional policy to install (may be {@code null}). */
private final String additionalPolicyResource;
private SecurityManager decoratorSecurityManager = null;
public SecurityManagerSetup(Test test, String policyResource) {
this(test, policyResource, false);
}
/**
* Installs a new security policy.
*
* @param test the test to wrap
* @param policyResource the policy to install
* @param mergePolicies if {@code false} the specified policy will be the
* only policy installed, if {@code true} the specified policy will be
* merged with the default test policy for the test framework
*/
public SecurityManagerSetup(Test test, String policyResource,
boolean mergePolicies) {
super(test);
if (mergePolicies) {
// By choice, only support merging with the default test policy.
this.decoratorPolicyResource = getDefaultPolicy();
this.additionalPolicyResource = policyResource;
} else {
this.decoratorPolicyResource = policyResource != null ?
policyResource : getDefaultPolicy();
this.additionalPolicyResource = null;
}
}
/**
* Use custom policy and SecurityManager
*
* @param test - Test to wrap
* @param policyResource - policy resource. If null use default testing policy
* @param securityManager - Custom SecurityManager if null use the system security manager
*/
public SecurityManagerSetup(Test test, String policyResource, SecurityManager securityManager)
{
this(test, policyResource, false);
this.decoratorSecurityManager = securityManager;
}
/**
* Get a decorator that will ensure no security manger
* is installed to run a test. Not supported for suites.
* <BR>
* An empty suite is returned if a security manager was installed
* externally, i.e. not under the control of the BaseTestCase
* and this code. In this case the code can not support the
* mode of no security manager as it may not have enough information
* to re-install the security manager. So the passed in test
* will be skipped.
*
* @param test Test to run without a security manager. Note that
* this must be an instance of BaseTestCase as this call depends
* on setup code in that class. Arbitrary Test instances cannot be passed in.
*/
public static Test noSecurityManager(Test test)
{
if (externalSecurityManagerInstalled) {
return new BaseTestSuite(
"skipped due to external security manager "
+ test.toString());
}
return new SecurityManagerSetup(test, NO_POLICY);
}
/**
* "Install" no security manager.
*
*/
static void noSecurityManager()
{
installSecurityManager(NO_POLICY);
}
/**
* Install specific policy file with the security manager
* including the special case of no security manager.
*/
protected void setUp()
throws IOException {
String resource = getEffectivePolicyResource(
decoratorPolicyResource, additionalPolicyResource);
installSecurityManager(resource, decoratorSecurityManager);
}
protected void tearDown() throws Exception
{
if (NO_POLICY.equals(decoratorPolicyResource))
BaseTestCase.setSystemProperty("java.security.policy", "");
else if ( !externalSecurityManagerInstalled )
{
uninstallSecurityManager();
}
}
/**
* Return the name of the default policy.
*/
private static String getDefaultPolicy()
{
return "org/apache/derbyTesting/functionTests/util/derby_tests.policy";
}
/**
* Install a SecurityManager with the default test policy
* file:
* org/apache/derbyTesting/functionTests/util/derby_tests.policy
*
*/
static void installSecurityManager()
{
installSecurityManager( getDefaultPolicy() );
}
private static void installSecurityManager(String policyFile) {
installSecurityManager(policyFile, System.getSecurityManager());
}
private static void installSecurityManager(String policyFile, final SecurityManager sm)
{
if (externalSecurityManagerInstalled)
return;
Properties set = new Properties(classPathSet);
setSecurityPolicy(set, policyFile);
for (Enumeration e = set.propertyNames(); e.hasMoreElements();) {
String key = (String) e.nextElement();
}
SecurityManager currentsm = System.getSecurityManager();
if (currentsm != null) {
// SecurityManager installed, see if it has the same settings.
String newPolicyProperty = set.getProperty("java.security.policy" );
if ( newPolicyProperty == null ) { newPolicyProperty = ""; }
String oldPolicyProperty = BaseTestCase.getSystemProperty("java.security.policy");
SecurityManager oldSecMan = System.getSecurityManager();
if ( oldPolicyProperty == null ) { oldPolicyProperty = ""; }
if ( newPolicyProperty.equals( oldPolicyProperty ) &&
oldSecMan == sm) { return; }
// Uninstall the current manager.
uninstallSecurityManager();
}
// Set the system properties from the desired set.
for (Enumeration e = set.propertyNames(); e.hasMoreElements();) {
String key = (String) e.nextElement();
BaseTestCase.setSystemProperty(key, set.getProperty(key));
}
// Check indicator for no security manager
if (NO_POLICY.equals(set.getProperty("java.security.policy")))
return;
// and install
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (sm == null)
System.setSecurityManager(new SecurityManager());
else
System.setSecurityManager(sm);
Policy.getPolicy().refresh();
return null;
}
});
println("installed policy " + policyFile);
}
private static void setSecurityPolicy(Properties set,
String policyResource)
{
if (NO_POLICY.equals(policyResource)) {
set.setProperty("java.security.policy", policyResource);
return;
}
try {
URL policyURL = getResourceURL(policyResource);
set.setProperty("java.security.policy", policyURL.toExternalForm());
} catch (MalformedURLException mue) {
BaseTestCase.alarm("Unreadable policy URL: " + policyResource);
}
}
/**
* Determine the settings of the classpath in order to configure
* the variables used in the testing policy files.
* Looks for three items:
*
* Location of derbyTesting.jar via this class
* Location of derby.jar via org.apache.derby.jdbc.EmbeddedDataSource
* Location of derbyclient.jar via org.apache.derby.jdbc.ClientDataSource
*
* Two options are supported, either all are in jar files or
* all are on the classpath. Properties are set as follows:
*
* <P>
* Classpath:
* <BR>
* derbyTesting.codeclasses set to URL of classes folder
* <BR>
* derbyTesting.ppcodeclasses set to URL of the 'classes.pptesting' folder
* if it exists on the classpath. The existence of the package private tests
* is determined via org.apache.derby.PackagePrivateTestSuite
* <P>
* Jar files:
* <BR>
* derbyTesting.codejar - URL of derby.jar, derbyshared.jar,
* derbynet.jar and derbytools.jar, all assumed to be in the
* same location.
* <BR>
* derbyTesting.clientjar - URL of derbyclient.jar
* <BR>
* derbyTesting.testjar - URL of derbyTesting.jar
* <BR>
* derbyTesting.testjarpath - File system path to derbyTesting.jar
* if the jar has a URL with a file protocol.
*
*/
private static boolean determineClasspath()
{
// Security manager already installed, assume that
// it is set up correctly.
if (System.getSecurityManager() != null) {
return true;
}
//We need the junit classes to instantiate this class, so the
//following should not cause runtime errors.
setCodebase(TESTING_JUNIT, getURL(junit.framework.Test.class));
// Load indirectly so we don't need ant-junit.jar at compile time.
setCodebase(TESTING_ANTJUNIT, "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner");
setCodebase(TESTING_ANT, "org.apache.tools.ant.Task");
// variables for lucene jar files
URL luceneCore = getURL( "org.apache.lucene.store.FSDirectory" );
if ( luceneCore != null )
{
setCodebase(LUCENE_CORE, luceneCore);
classPathSet.setProperty( "derbyTesting.lucene.core.jar.file", luceneCore.getFile() );
}
// Load indirectly, normally no EMMA jars in the classpath.
// This property is needed to set correct permissions in policy files.
URL emma = getURL("com.vladium.emma.EMMAException");
if (emma != null) {
classPathSet.setProperty("emma.active", "");
}
/* When inserting XML values that use external DTD's, the JAXP
* parser needs permission to read the DTD files. So here we set
* a property to hold the location of the JAXP implementation
* jar file. We can then grant the JAXP impl the permissions
* needed for reading the DTD files.
*/
setCodebase(JAXP, XML.getJAXPParserLocation());
URL testing = getURL(SecurityManagerSetup.class);
URL ppTesting = null;
// Only try to load PackagePrivateTestSuite if the running JVM is
// Java 1.5 or newer (class version 49 = Java 1.5).
if (BaseTestCase.getClassVersionMajor() >= 49) {
ppTesting = getURL("org.apache.derby.PackagePrivateTestSuite");
}
boolean isClasspath = testing.toExternalForm().endsWith("/");
if (isClasspath) {
// ppTesting can be null, for instance if 'classes.pptesting' is
// not on the classpath.
setCodebase(PACKAGE_PRIVATE_CLASSES, ppTesting);
isJars = false;
}
else
{
isJars = true;
}
setCodebase(DERBY_TESTING, testing);
if (testing.getProtocol().equals("file")) {
File f = new File(testing.getPath());
classPathSet.setProperty("derbyTesting.testjarpath", f.getAbsolutePath());
}
setCodebase(DERBY_ENGINE, "org.apache.derby.database.Database");
setCodebase(DERBY_CLIENT, "org.apache.derby.client.ClientAutoloadedDriver");
setCodebase(DERBY_SHARED, "org.apache.derby.shared.common.error.MessageUtils");
setCodebase(DERBY_NETSERVER, "org.apache.derby.drda.NetworkServerControl");
setCodebase(DERBY_TOOLS, "org.apache.derby.tools.ij");
setCodebase(DERBY_OPTIONAL_TOOLS, "org.apache.derby.optional.utils.ToolUtilities");
return false;
}
/**
* Set the System property which identifies the
* location of a codebase referenced by derby_tests.policy.
*
* @param codebaseProperty Name of codebase variable.
* @param className Name of class used to locate the codebase URL.
*/
private static void setCodebase
(
String codebaseProperty,
String className
)
{
if (className != null)
{
setCodebase(codebaseProperty, getURL(className));
}
}
/**
* Set the System property which identifies the
* location of a codebase referenced by derby_tests.policy.
*
* @param codebaseProperty Name of codebase variable.
* @param codebaseURL URL of codebase
*/
private static void setCodebase
(
String codebaseProperty,
URL codebaseURL
)
{
if (codebaseURL != null)
{
classPathSet.setProperty(codebaseProperty, codebaseURL.toExternalForm());
}
}
/**
* Return the policy file system properties for use
* by the old test harness. This ensures a consistent
* approach to setting the properties. There are the
* properties used to define the jar file location in
* any policy files.
*/
public static Properties getPolicyFilePropertiesForOldHarness()
{
return classPathSet;
}
/**
* Get the URL of the code base from a class name.
* If the class cannot be loaded, null is returned.
*/
public static URL getURL(String className) {
try {
return getURL(Class.forName(className));
} catch (ClassNotFoundException e) {
return null;
} catch (NoClassDefFoundError e) {
return null;
}
}
/**
* Get the URL of the code base from a class.
*/
static URL getURL(final Class cl)
{
return AccessController.doPrivileged(new PrivilegedAction<URL>() {
public URL run() {
/* It's possible that the class does not have a "codeSource"
* associated with it (ex. if it is embedded within the JVM,
* as can happen with Xalan and/or a JAXP parser), so in that
* case we just return null.
*/
if (cl.getProtectionDomain().getCodeSource() == null)
return null;
return cl.getProtectionDomain().getCodeSource().getLocation();
}
});
}
/**
* Remove the security manager.
*/
private static void uninstallSecurityManager()
{
AccessController.doPrivileged
(
new PrivilegedAction<Void>()
{
public Void run() {
System.setSecurityManager(null);
return null;
}
}
);
}
/**
* Returns the location of the effective policy resource.
* <p>
* If two valid policy resources from different locations are specified,
* they will be merged into one policy file.
*
* @param policy1 first policy
* @param policy2 second policy (may be {@code null})
* @return The location of a policy resource, or {@linkplain #NO_POLICY}.
* @throws IOException if reading or writing a policy resource fails
*/
private static String getEffectivePolicyResource(String policy1,
String policy2)
throws IOException {
String resource = policy1;
if (!NO_POLICY.equals(resource)) {
URL url1 = getResourceURL(policy1);
resource = url1.toExternalForm();
if (policy2 != null) {
URL url2 = getResourceURL(policy2);
// Don't use URL.equals - it blocks and goes onto the network.
if (!url1.toExternalForm().equals(url2.toExternalForm())) {
resource = mergePolicies(url1, url2);
}
}
}
return resource;
}
/**
* Returns a URL for the given policy resource.
*
* @param policy the policy resource
* @return A {@code URL} denoting the policy resource.
* @throws MalformedURLException if the resource string not a valid URL
*/
private static URL getResourceURL(final String policy)
throws MalformedURLException {
URL url = BaseTestCase.getTestResource(policy);
if (url == null) {
// Assume the policy is expressed as an URL already, probably
// as a file.
url = new URL(policy);
}
return url;
}
/**
* Merges the two specified policy resources (typically files), and writes
* the combined policy to a new file.
*
* @param policy1 the first policy
* @param policy2 the second policy
* @return The resource location string for a policy file.
* @throws IOException if reading or writing to one of the resources fails
*/
private static String mergePolicies(URL policy1, URL policy2)
throws IOException {
// Create target directory for the merged policy files.
String sytemHome =
BaseTestCase.getSystemProperty("derby.system.home");
File sysDir = new File(sytemHome == null ? "system" : sytemHome);
File varDir = new File(sysDir, "var");
// If running as the first test the system directory may not exist.
// This situation looks a little bit suspicious - investigate?
mkdir(sysDir);
mkdir(varDir);
// Read the contents of both policy files and write them out to
// a new policy file. Construct a somewhat informative file name.
final File mergedPF = new File(varDir,
new File(policy2.getPath()).getName() +
"-MERGED_WITH-" +
new File(policy1.getPath()).getName());
OutputStream o =
PrivilegedFileOpsForTests.getFileOutputStream(mergedPF);
byte[] buf = new byte[1024];
int read;
InputStream i1 = openStream(policy1);
while ((read = i1.read(buf)) != -1) {
o.write(buf, 0, read);
}
i1.close();
InputStream i2 = openStream(policy2);
while ((read = i2.read(buf)) != -1) {
o.write(buf, 0, read);
}
i2.close();
o.close();
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<String>() {
public String run() throws MalformedURLException {
return mergedPF.toURI().toURL().toExternalForm();
}
});
} catch (PrivilegedActionException pae) {
throw (MalformedURLException)pae.getException();
}
}
/** Opens the resource stream in a privileged block. */
private static InputStream openStream(final URL resource)
throws IOException {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<InputStream>(){
public InputStream run() throws IOException {
return resource.openStream();
}
}
);
} catch (PrivilegedActionException pae) {
throw (IOException)pae.getException();
}
}
/** Creates the specified directory if it doesn't exist. */
private static void mkdir(final File dir) {
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
public Void run() {
if (!dir.exists() && !dir.mkdir()) {
fail("failed to create directory: " + dir.getPath());
}
return null;
}
}
);
}
/** Prints a debug message if debugging is enabled. */
private static void println(String msg) {
BaseTestCase.println("{SecurityManagerSetup} " + msg);
}
}