/*
* RHQ Management Platform
* Copyright (C) 2005-2012 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.jndi.test;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.fail;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.testng.Assert;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.rhq.bindings.StandardScriptPermissions;
import org.rhq.enterprise.server.AllowRhqServerInternalsAccessPermission;
import org.rhq.enterprise.server.test.StrippedDownStartupBean;
import org.rhq.enterprise.server.test.StrippedDownStartupBeanPreparation;
import org.rhq.scripting.javascript.JsEngineProvider;
import org.rhq.scripting.python.PythonScriptEngineProvider;
/**
*
* @author Lukas Krejci
*/
@Test
public class JndiAccessTest extends Arquillian {
private static final Log JNP_SERVER_LOG = LogFactory.getLog("Test JNP Server");
private static final String GROUP_FOR_NORMAL_TESTS = "JndiAccessTest";
private Process testServerProcess;
private Thread testServerStdErrReader;
private Thread testServerStdOutReader;
@Deployment
public static EnterpriseArchive TestEar() {
JavaArchive testEjb = ShrinkWrap.create(JavaArchive.class, "test-ejb.jar").addClass(TestEjb.class)
.addClass(TestEjbBean.class).addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
Collection thirdPartyDeps = new ArrayList();
thirdPartyDeps.add("jboss:jnp-client");
thirdPartyDeps.add("org.rhq:rhq-scripting-api");
thirdPartyDeps.add("org.rhq:rhq-scripting-javascript");
thirdPartyDeps.add("org.rhq:rhq-scripting-python");
Collection<JavaArchive> deps = Arrays.asList(Maven.resolver().loadPomFromFile("pom.xml")
.resolve(thirdPartyDeps).withTransitivity().as(JavaArchive.class));
//we need to pull in the naming hack classes from the server jar so that our EAR behaves the same
testEjb.addPackages(true, "org.rhq.enterprise.server.naming").addClass(
AllowRhqServerInternalsAccessPermission.class);
//we also need to pull in the special startup beans from the server/itests-2 that will initialize the naming
//subsystem
testEjb.addClasses(StrippedDownStartupBean.class, StrippedDownStartupBeanPreparation.class);
//to work around https://issues.jboss.org/browse/ARQ-659
//we need to include this test class in the EAR manually
//Instead of pulling the whole rhq-script-bindings (which has a lot of deps), we're just picking the
//StandardScriptPermissions from there so that it can be used in the tests
JavaArchive classes = ShrinkWrap.create(JavaArchive.class, "test-class.jar").addClasses(JndiAccessTest.class,
StandardScriptPermissions.class);
return ShrinkWrap.create(EnterpriseArchive.class, "test-ear.ear").addAsModule(testEjb).addAsLibraries(deps)
.addAsLibrary(classes);
}
@RunAsClient
@Parameters({ "test.server.jar.path", "jnp.port", "jnp.rmiPort" })
public void startTestJnpServer(String testServerJar, int jnpPort, int rmiPort) throws Exception {
ProcessBuilder bld = new ProcessBuilder("java", "-Djnp.port=" + jnpPort, "-Djnp.rmiPort=" + rmiPort, "-jar",
testServerJar);
testServerProcess = bld.start();
testServerStdErrReader = new Thread(new Runnable() {
@Override
public void run() {
BufferedReader rdr = new BufferedReader(new InputStreamReader(testServerProcess.getErrorStream()));
try {
String line;
while ((line = rdr.readLine()) != null) {
JNP_SERVER_LOG.warn(line);
}
} catch (IOException e) {
JNP_SERVER_LOG.error("Reading test JNP server error output failed.", e);
} finally {
try {
rdr.close();
} catch (IOException e) {
JNP_SERVER_LOG.error("Failed to close the test server error stream.", e);
}
}
}
});
testServerStdErrReader.start();
testServerStdOutReader = new Thread(new Runnable() {
@Override
public void run() {
BufferedReader rdr = new BufferedReader(new InputStreamReader(testServerProcess.getInputStream()));
try {
String line;
while ((line = rdr.readLine()) != null) {
JNP_SERVER_LOG.debug(line);
}
} catch (IOException e) {
JNP_SERVER_LOG.error("Reading test JNP server standard output failed.", e);
} finally {
try {
rdr.close();
} catch (IOException e) {
JNP_SERVER_LOG.error("Failed to close the test server standard output stream.", e);
}
}
}
});
testServerStdOutReader.start();
//give the JNP server some time to start up
Thread.sleep(5000);
}
@RunAsClient
@Test(dependsOnGroups = GROUP_FOR_NORMAL_TESTS, alwaysRun = true)
public void stopTestJnpServer() throws Exception {
testServerProcess.destroy();
testServerStdErrReader.join();
testServerStdOutReader.join();
}
@Test(dependsOnMethods = "startTestJnpServer", groups = GROUP_FOR_NORMAL_TESTS)
@Parameters("jnp.port")
public void testRemoteConnectionWorkingFromJava(int jnpPort) throws Exception {
Properties env = new Properties();
env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
env.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
env.put("java.naming.provider.url", "jnp://localhost:" + jnpPort);
InitialContext ctx = new InitialContext(env);
Object kachny = ctx.lookup("kachny");
assertNotNull(kachny);
}
@Test(dependsOnMethods = "startTestJnpServer", groups = GROUP_FOR_NORMAL_TESTS)
@Parameters("jnp.port")
public void testRemoteConnectionWorkingFromScripts_javascript(int jnpPort) throws Exception {
getJavascriptScriptEngine().eval("" //
+ "var env = new java.util.Properties;" //
+ "env.put('java.naming.factory.initial', 'org.jnp.interfaces.NamingContextFactory');" //
+ "env.put('java.naming.factory.url.pkgs', 'org.jboss.naming:org.jnp.interfaces');" //
+ "env.put('java.naming.provider.url', 'jnp://localhost:" + jnpPort + "');" //
+ "var ctx = new javax.naming.InitialContext(env);" //
+ "var kachny = ctx.lookup('kachny');" //
+ "if (kachny == null) throw 'The object retrieved from the remote server should not be null';");
}
@Test(dependsOnMethods = "startTestJnpServer", groups = GROUP_FOR_NORMAL_TESTS)
@Parameters("jnp.port")
public void testRemoteConnectionWorkingFromScripts_python(int jnpPort) throws Exception {
getPythonScriptEngine()
.eval(
"" //
+ "import java.util as u\n" //
+ "import javax.naming as n\n" //
+ "env = u.Properties()\n" //
+ "env.put('java.naming.factory.initial', 'org.jnp.interfaces.NamingContextFactory')\n" //
+ "env.put('java.naming.factory.url.pkgs', 'org.jboss.naming:org.jnp.interfaces')\n" //
+ "env.put('java.naming.provider.url', 'jnp://localhost:"
+ jnpPort
+ "')\n" //
+ "ctx = n.InitialContext(env)\n" //
+ "kachny = ctx.lookup('kachny')\n" //
+ "if (kachny is None):\n raise Exception('The object retrieved from the remote server should not be null')\n");
}
@Test(dependsOnMethods = "startTestJnpServer", groups = GROUP_FOR_NORMAL_TESTS)
public void testLocalJNDILookupWorksFromJava() throws Exception {
InitialContext ctx = new InitialContext();
Object bean = ctx.lookup("java:app/test-ejb/TestEjbBean");
assertNotNull(bean);
}
@Test(dependsOnMethods = "startTestJnpServer", groups = GROUP_FOR_NORMAL_TESTS)
public void testLocalJNDILookupFailsFromScripts_javascript() throws Exception {
try {
getJavascriptScriptEngine().eval(
"var ctx = new javax.naming.InitialContext();\n"
+ "var bean = ctx.lookup('java:app/test-ejb/TestEjbBean');\n");
fail("Script was able to perform local JNDI lookup.");
} catch (Exception e) {
checkIsDesiredSecurityException(e);
}
}
@Test(dependsOnMethods = "startTestJnpServer", groups = GROUP_FOR_NORMAL_TESTS)
public void testLocalJNDILookupFailsFromScripts_python() throws Exception {
try {
getPythonScriptEngine().eval("import javax.naming as n\n" //
+ "ctx = n.InitialContext()\n" //
+ "bean = ctx.lookup('java:app/test-ejb/TestEjbBean')\n");
fail("Script was able to perform local JNDI lookup.");
} catch (Exception e) {
checkIsDesiredSecurityException(e);
}
}
private ScriptEngine getJavascriptScriptEngine() throws ScriptException {
return new JsEngineProvider().getInitializer().instantiate(Collections.<String> emptySet(),
new StandardScriptPermissions());
}
private ScriptEngine getPythonScriptEngine() throws ScriptException {
return new PythonScriptEngineProvider().getInitializer().instantiate(Collections.<String> emptySet(),
new StandardScriptPermissions());
}
private void checkIsDesiredSecurityException(Throwable t) {
String message = t.getMessage();
String permissionTrace = "org.rhq.allow.server.internals.access";
if (!message.contains(permissionTrace)) {
Assert
.fail(
"The script exception doesn't seem to be caused by the AllowRhqServerInternalsAccessPermission security exception. ",
t);
}
}
}