dev/null |binary modules/enterprise/binding/pom.xml | 2 modules/enterprise/binding/src/main/java/org/rhq/bindings/SandboxedScriptEngine.java | 3 modules/enterprise/binding/src/main/java/org/rhq/bindings/ScriptEngineFactory.java | 60 + modules/enterprise/binding/src/main/java/org/rhq/bindings/StandardScriptPermissions.java | 6 modules/enterprise/binding/src/test/java/org/rhq/bindings/ScriptEngineTest.java | 9 modules/enterprise/server/client-api/pom.xml | 67 -- modules/enterprise/server/client-api/src/main/java/org/rhq/enterprise/client/LocalClientProxy.java | 20 modules/enterprise/server/container-lib/pom.xml | 6 modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java | 252 ++++++++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java.rej | 122 --- modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AllowRhqServerInternalsAccessPermission.java | 36 + modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java | 110 +++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java | 264 ++++++++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java | 75 ++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingDirContextDecorator.java | 187 +++++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventContextDecorator.java | 69 ++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventDirContextDecorator.java | 96 +++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingLdapContextDecorator.java | 85 ++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecorator.java | 33 + modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java | 222 +++++++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorHelper.java | 76 ++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java | 71 ++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringDirContextDecorator.java | 185 +++++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventContextDecorator.java | 81 ++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventDirContextDecorator.java | 104 +++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringLdapContextDecorator.java | 94 ++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java | 45 + modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstallerMBean.java | 32 + modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java | 55 + modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java | 195 ++++++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java | 59 + modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java | 145 ++++ modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java | 180 +++++ modules/enterprise/server/container/src/main/resources/jbossas/server/default/conf/jboss-service.xml | 7 modules/enterprise/server/itests/pom.xml | 51 + modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java | 196 ++++++ modules/enterprise/server/itests/src/test/resources/embedded-jboss-beans.xml | 10 modules/enterprise/server/itests/src/test/resources/security.policy | 10 modules/enterprise/server/jar/pom.xml | 7 modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/auth/SessionManager.java | 36 - modules/enterprise/server/jar/src/test/resources/embedded-jboss-beans.xml | 10 modules/enterprise/server/plugins/alert-cli/src/main/java/org/rhq/enterprise/server/plugins/alertCli/CliSender.java | 9 modules/enterprise/server/pom.xml | 2 modules/integration-tests/jndi-access/jndi-access-test/pom.xml | 315 ++++++++++ modules/integration-tests/jndi-access/jndi-access-test/src/test/java/org/rhq/jndi/test/JndiAccessTest.java | 193 ++++++ modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/hibernate.properties | 26 modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/log4j.xml | 78 ++ modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/security.policy | 10 modules/integration-tests/jndi-access/pom.xml | 21 modules/integration-tests/jndi-access/remote-server/pom.xml | 72 ++ modules/integration-tests/jndi-access/remote-server/src/main/java/org/rhq/jndi/test/Server.java | 79 ++ modules/integration-tests/jndi-access/remote-server/src/main/resources/jndi.properties | 2 modules/integration-tests/jndi-access/remote-server/src/main/resources/log4j.properties | 5 modules/integration-tests/pom.xml | 1 55 files changed, 4002 insertions(+), 184 deletions(-)
New commits: commit 94b7a562f4efb9e4d9922e090ceca49deef5c1fd Merge: 3bf2fbf 7d14f74 Author: Lukas Krejci lkrejci@redhat.com Date: Fri Jan 13 11:38:26 2012 +0100
Merge branch 'lkrejci/dissalow-alert-scripts-from-accessing-local-slsbs'
commit 3bf2fbfd118d30e2c0c9b616ff12ba7bc9e9492c Author: Lukas Krejci lkrejci@redhat.com Date: Fri Jan 13 11:38:18 2012 +0100
Putting back the original version of secured JNDI access so that the latest batch of fixes can be merged into it.
This reverts commit 0e36c3ee8f1954486b1c12ee22ddd4b90cf7e685.
diff --git a/modules/enterprise/binding/src/main/java/org/rhq/bindings/StandardScriptPermissions.java b/modules/enterprise/binding/src/main/java/org/rhq/bindings/StandardScriptPermissions.java index 894c953..5822d44 100644 --- a/modules/enterprise/binding/src/main/java/org/rhq/bindings/StandardScriptPermissions.java +++ b/modules/enterprise/binding/src/main/java/org/rhq/bindings/StandardScriptPermissions.java @@ -20,6 +20,7 @@ package org.rhq.bindings;
import java.io.FilePermission; +import java.io.SerializablePermission; import java.lang.reflect.ReflectPermission; import java.net.SocketPermission; import java.security.Permission; @@ -83,6 +84,11 @@ public class StandardScriptPermissions extends PermissionCollection { add(new PropertyPermission("*", "read"));
add(new ReflectPermission("suppressAccessChecks")); + + //these 2 are required for server-side scripts to be able to + //invoke remote EJBs. + add(new SerializablePermission("enableSubclassImplementation")); + add(new RuntimePermission("reflectionFactoryAccess")); }
public void add(Permission permission) { diff --git a/modules/enterprise/server/client-api/pom.xml b/modules/enterprise/server/client-api/pom.xml index e777e21..4dfd109 100644 --- a/modules/enterprise/server/client-api/pom.xml +++ b/modules/enterprise/server/client-api/pom.xml @@ -17,15 +17,35 @@
<properties> <persistence-api.version>1.0</persistence-api.version> + <rhq.server.datasource>java:/RHQDS</rhq.server.datasource> + <rhq.server.ds-mapping>PostgreSQL</rhq.server.ds-mapping> + + <!-- dependency versions --> + <jboss-embeddable-ejb3.version>1.0.0.Alpha9</jboss-embeddable-ejb3.version> + + <clean.db>true</clean.db> </properties>
<dependencies> + + <!-- Note, the test deps are intentionally placed above the other scoped deps because of classpath + reasons. Maven orders the [test] classpath in the order listed in the pom. We specifically + need the embeddable-ejb3 jar above the standard ejb3 jars because we need the embeddble packages + loaded when testing. --> + <dependency> + <groupId>jboss.jboss-embeddable-ejb3</groupId> + <artifactId>jboss-ejb3-all</artifactId> + <version>${jboss-embeddable-ejb3.version}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.rhq</groupId> <artifactId>rhq-script-bindings</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> + <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> @@ -40,20 +60,333 @@ section. --> <scope>provided</scope> </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-ejb3x</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>provided</scope> <!-- by JBossAS --> + </dependency> + + <!-- Test deps - this insane list of deps is needed to get the embedded JBoss server with RHQ server deployed running --> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>test-utils</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>rhq-enterprise-server</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>rhq-enterprise-server</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>rhq-container-lib</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>hibernate</groupId> + <artifactId>hibernate3</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>hibernate-entitymanager</groupId> + <artifactId>hibernate-entitymanager</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-javamail_1.3.1_spec</artifactId> + <!-- The Sun javamail jar isn't available from a public repo due to licensing issues, + so use the Geronimo one instead. --> + <version>1.3</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>2.4</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>jsp-api</artifactId> + <version>2.0</version> + <scope>test</scope> + </dependency> + + + <dependency> + <groupId>org.opensymphony.quartz</groupId> + <artifactId>quartz</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.opensymphony.quartz</groupId> + <artifactId>quartz-oracle</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-annotations-ejb3</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-cache</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-common</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <!-- includes the org.jboss.ejb3.StrictMaxPool class, which is needed by the PoolClass annotation used on some + of our SLSB's --> + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-ejb3</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-j2ee</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-jmx</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-system</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jbosssx</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jbpm</artifactId> + <version>3.1.1</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>antlr</groupId> + <artifactId>antlr</artifactId> + <version>2.7.7</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>javassist</groupId> + <artifactId>javassist</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>trove</groupId> + <artifactId>trove</artifactId> + <version>1.0.2</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>xerces</groupId> + <artifactId>xercesImpl</artifactId> + <version>2.8.1</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>net.sf.opencsv</groupId> + <artifactId>opencsv</artifactId> + <version>1.8</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>commons-jxpath</groupId> + <artifactId>commons-jxpath</artifactId> + <version>1.3</version> + <scope>test</scope> + </dependency> + + <!-- for the transaction interrupt EJB3 interceptor --> + <dependency> + <groupId>org.jboss.transaction</groupId> + <artifactId>jboss-jta</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>tomcat</groupId> + <artifactId>catalina</artifactId> + <version>5.5.20</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>tomcat</groupId> + <artifactId>tomcat-jk</artifactId> + <version>4.1.31</version> + <scope>test</scope> + </dependency> + + <!-- Needed by com.jboss.jbossnetwork.apl.actions.xml.XPathProcessor; TODO: Remove once APL has been excised. --> + <dependency> + <groupId>xalan</groupId> + <artifactId>xalan</artifactId> + <version>2.5.1</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>com.jcraft</groupId> + <artifactId>jsch</artifactId> + <version>0.1.29</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.jboss.resteasy</groupId> + <artifactId>resteasy-jaxrs</artifactId> + <version>${resteasy.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.jboss.resteasy</groupId> + <artifactId>resteasy-jettison-provider</artifactId> + <version>${resteasy.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.jboss.resteasy</groupId> + <artifactId>resteasy-links</artifactId> + <version>${resteasy.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.jboss.el</groupId> + <artifactId>jboss-el</artifactId> + <version>2.0.1.GA</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.freemarker</groupId> + <artifactId>freemarker</artifactId> + <version>2.3.18</version> + <scope>test</scope> + </dependency> + </dependencies>
<build> + <testResources> + <testResource> + <directory>src/test/resources</directory> + <filtering>true</filtering> + </testResource> + </testResources> + <plugins>
- <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <excludedGroups>${rhq.testng.excludedGroups}</excludedGroups> - <!-- <argLine>-Xdebug -Xnoagent -Djava.compiler=NONE - -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y</argLine> --> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <!-- Everything but the web service tests, this is the standard test execution --> + <configuration> + <excludedGroups>${rhq.testng.excludedGroups}</excludedGroups> + <groups>${rhq.testng.includedGroups}</groups> + <systemPropertyVariables> + <embeddedDeployment>true</embeddedDeployment> + <deploymentDirectory>target/test-classes</deploymentDirectory> + <hibernate.dialect>${rhq.test.ds.hibernate-dialect}</hibernate.dialect> + <clean.db>${clean.db}</clean.db> + </systemPropertyVariables> + <argLine>-Djava.security.manager -Djava.security.policy==target/test-classes/security.policy</argLine> + <additionalClasspathElements> + <!-- The below is required for tests to run against Oracle. --> + <additionalClasspathElement>${settings.localRepository}/com/oracle/ojdbc5/${ojdbc5.version}/ojdbc5-${ojdbc5.version}.jar</additionalClasspathElement> + </additionalClasspathElements> </configuration> - </plugin> + </plugin> + + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + + <!-- in order to get JMS to work properly in embedded test container, extract jms-rs.rar classes --> + <execution> + <id>Extract JMS classes from RAR needed for JMS tests</id> + <phase>process-classes</phase> + <configuration> + <tasks> + <unzip src="src/test/resources/jms-ra.rar" dest="target"> + <patternset> + <include name="jms-ra.jar"/> + </patternset> + </unzip> + <unzip src="target/jms-ra.jar" dest="target/test-classes"> + <patternset> + <include name="org/**"/> + </patternset> + </unzip> + </tasks> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution>
+ </executions> + </plugin> </plugins> </build>
diff --git a/modules/enterprise/server/client-api/src/main/java/org/rhq/enterprise/client/LocalClientProxy.java b/modules/enterprise/server/client-api/src/main/java/org/rhq/enterprise/client/LocalClientProxy.java index 5f3ff90..e9f1e4d 100644 --- a/modules/enterprise/server/client-api/src/main/java/org/rhq/enterprise/client/LocalClientProxy.java +++ b/modules/enterprise/server/client-api/src/main/java/org/rhq/enterprise/client/LocalClientProxy.java @@ -20,6 +20,8 @@ package org.rhq.enterprise.client;
import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; import java.util.Arrays;
import org.apache.commons.logging.Log; @@ -54,11 +56,21 @@ public class LocalClientProxy extends AbstractRhqFacadeProxy<LocalClient> { } }
- protected Object doInvoke(Object proxy, Method originalMethod, java.lang.Class<?>[] argTypes, Object[] args) throws Throwable { + protected Object doInvoke(Object proxy, Method originalMethod, java.lang.Class<?>[] argTypes, final Object[] args) throws Throwable { try { - Method realMethod = localSLSB.getClass().getMethod(originalMethod.getName(), argTypes); - - return realMethod.invoke(localSLSB, args); + final Method realMethod = localSLSB.getClass().getMethod(originalMethod.getName(), argTypes); + + //run this through the privileged block to elevate the privs of the script + //the scripts don't have the AllowEjbAccessPermission but this code has + //all perms (or at least all perms assigned to it by the current context, + //which at the time of writing is defined by the rhq-server.policy file + //which gives all code all permissions). + return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + return realMethod.invoke(localSLSB, args); + } + }); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Method [" + originalMethod + "] does not have a desimplified counterpart with arguments " + Arrays.asList(argTypes) + ".", e); } diff --git a/modules/enterprise/server/client-api/src/test/java/org/rhq/enterprise/client/security/test/EjbAccessTest.java b/modules/enterprise/server/client-api/src/test/java/org/rhq/enterprise/client/security/test/EjbAccessTest.java new file mode 100644 index 0000000..dc1fc83 --- /dev/null +++ b/modules/enterprise/server/client-api/src/test/java/org/rhq/enterprise/client/security/test/EjbAccessTest.java @@ -0,0 +1,198 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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.enterprise.client.security.test; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.SerializablePermission; +import java.security.PermissionCollection; +import java.util.Collections; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import org.rhq.bindings.SandboxedScriptEngine; +import org.rhq.bindings.ScriptEngineFactory; +import org.rhq.bindings.StandardBindings; +import org.rhq.bindings.StandardScriptPermissions; +import org.rhq.bindings.util.PackageFinder; +import org.rhq.core.domain.auth.Subject; +import org.rhq.enterprise.client.LocalClient; +import org.rhq.enterprise.server.test.AbstractEJB3Test; +import org.rhq.enterprise.server.util.LookupUtil; +import org.rhq.jndi.AllowRhqServerInternalsAccessPermission; + +/** + * + * + * @author Lukas Krejci + */ +@Test +public class EjbAccessTest extends AbstractEJB3Test { + + public void testEjbsAccessibleThroughPrivilegedCode() { + LookupUtil.getSubjectManager().getOverlord(); + } + + public void testEjbsAccessibleThroughLocalClient() throws ScriptException, IOException { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + engine.eval("SubjectManager.getSubjectByName('rhqadmin');"); + } + + public void testLocalEjbsInaccessibleThroughJndiLookup() throws ScriptException, IOException { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "subjectManager = context.lookup('SubjectManagerBean/local');\n" + + "subjectManager.getOverlord();"); + + Assert.fail("The script shouldn't have been able to call local SLSB method."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + public void testRemoteEjbsInaccessibleThroughJndiLookup() throws ScriptException, IOException { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "subjectManager = context.lookup('SubjectManagerBean/remote');\n" + + "subjectManager.getSubjectByName('rhqadmin');"); + + Assert.fail("The script shouldn't have been able to call remote SLSB method directly."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + public void testScriptCantUseSessionManagerMethods() throws Exception { + + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + final ScriptEngine engine = getEngine(overlord); + + class G { + private String sessionManager = "" + + "org.rhq.enterprise.server.auth.SessionManager.getInstance()."; + + public void testInvoke(String methodCall) throws ScriptException { + String code = sessionManager + methodCall; + + try { + engine.eval(code); + Assert.fail("The script shouldn't have been able to call a method on a SessionManager: " + methodCall); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + }; + G manager = new G(); + + manager.testInvoke("getlastAccess(0);"); + manager.testInvoke("getOverlord()"); + manager.testInvoke("getSubject(2);"); + manager.testInvoke("invalidate(0);"); + manager.testInvoke("invalidate("");"); + manager.testInvoke("put(new org.rhq.core.domain.auth.Subject());"); + manager.testInvoke("put(new org.rhq.core.domain.auth.Subject(), 0);"); + } + + public void testScriptCantObtainRawJDBCConnectionsWithoutCredentials() throws Exception { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "datasource = context.lookup('java:/RHQDS');\n" + + "con = datasource.getConnection();"); + + Assert.fail("The script shouldn't have been able to obtain the datasource from the JNDI."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + public void testScriptCantUseEntityManager() throws Exception { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "entityManagerFactory = context.lookup('java:/RHQEntityManagerFactory');\n" + + "entityManager = entityManagerFactory.createEntityManager();\n" + + "entityManager.find(java.lang.Class.forName('org.rhq.core.domain.resource.Resource'), java.lang.Integer.valueOf('10001'));"); + + Assert.fail("The script shouldn't have been able to use the EntityManager."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + + //try harder with manually specifying the initial context factory + try { + engine.eval("" + + "env = new java.util.Hashtable();" + + "env.put('java.naming.factory.initial', 'org.jnp.interfaces.LocalOnlyContextFactory');" + + "env.put('java.naming.factory.url.pkgs', 'org.jboss.naming:org.jnp.interfaces');" + + "context = new javax.naming.InitialContext(env);\n" + + "entityManagerFactory = context.lookup('java:/RHQEntityManagerFactory');\n" + + "entityManager = entityManagerFactory.createEntityManager();\n" + + "entityManager.find(java.lang.Class.forName('org.rhq.core.domain.resource.Resource'), java.lang.Integer.valueOf('10001'));"); + + Assert.fail("The script shouldn't have been able to use the EntityManager even using custom initial context factory."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + private ScriptEngine getEngine(Subject subject) throws ScriptException, IOException { + StandardBindings bindings = new StandardBindings(new PrintWriter(System.out), new LocalClient(subject)); + ScriptEngine engine = ScriptEngineFactory.getScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), bindings); + + PermissionCollection perms = new StandardScriptPermissions(); + perms.add(new SerializablePermission("enableSubclassImplementation")); + + return new SandboxedScriptEngine(engine, perms); + } + + private static void checkIsDesiredSecurityException(ScriptException e) { + String message = e.getMessage(); + String permissionTrace = AllowRhqServerInternalsAccessPermission.class.getName(); + + Assert.assertTrue(message.contains(permissionTrace), "The script exception doesn't seem to be caused by the AllowRhqServerInternalsAccessPermission security exception. " + message); + } +} diff --git a/modules/enterprise/server/client-api/src/test/resources/hibernate.properties b/modules/enterprise/server/client-api/src/test/resources/hibernate.properties new file mode 100644 index 0000000..1951b84 --- /dev/null +++ b/modules/enterprise/server/client-api/src/test/resources/hibernate.properties @@ -0,0 +1,26 @@ +# FOR SOME STRANGE REASON, THIS FILE NEEDS TO BE HERE FOR THE HIBERNATE TO CORRECTLY +# INITIALIZE. I DON'T KNOW WHY THE STANDARD default.persistence.properties FILE DOESN'T +# WORK IN THIS MODULE. + +hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup +#hibernate.connection.release_mode=after_statement +#hibernate.transaction.flush_before_completion=false +#hibernate.transaction.auto_close_session=false +#hibernate.query.factory_class=org.hibernate.hql.ast.ASTQueryTranslatorFactory +#hibernate.hbm2ddl.auto=create-drop +#hibernate.hbm2ddl.auto=create +hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider +# Clustered cache with TreeCache +#hibernate.cache.provider_class=org.jboss.ejb3.entity.TreeCacheProviderHook +#hibernate.treecache.mbean.object_name=jboss.cache:service=EJB3EntityTreeCache +#hibernate.dialect=org.hibernate.dialect.HSQLDialect +hibernate.jndi.java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory +hibernate.jndi.java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces +hibernate.bytecode.use_reflection_optimizer=false +# I don't think this is honored, but EJB3Deployer uses it +hibernate.bytecode.provider=javassist +hibernate.jdbc.use_streams_for_binary=true +hibernate.show_sql=false +hibernate.format_sql=true +hibernate.default_batch_fetch_size=16 +hibernate.jdbc.batch_size=20 diff --git a/modules/enterprise/server/client-api/src/test/resources/security.policy b/modules/enterprise/server/client-api/src/test/resources/security.policy new file mode 100644 index 0000000..8860b47 --- /dev/null +++ b/modules/enterprise/server/client-api/src/test/resources/security.policy @@ -0,0 +1,10 @@ +// We need the SecurityManager installed to enable sandboxing of CLI scripts +// but we don't define any other security measures on the RHQ server itself. +// +// Granting all permissions allows us to run the RHQ server as if no security +// manager was in place (which is assumed by default by JBoss AS) but be able +// to use it when we need it for our own purposes. + +grant { + permission java.security.AllPermission; +}; diff --git a/modules/enterprise/server/container-lib/pom.xml b/modules/enterprise/server/container-lib/pom.xml index b832b06..0974431 100644 --- a/modules/enterprise/server/container-lib/pom.xml +++ b/modules/enterprise/server/container-lib/pom.xml @@ -39,6 +39,12 @@ <scope>provided</scope> <!-- by JBossAS --> </dependency>
+ <dependency> + <groupId>jboss</groupId> + <artifactId>jnpserver</artifactId> + <version>4.2.2.GA</version> + <scope>provided</scope> + </dependency> </dependencies>
<profiles> diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java new file mode 100644 index 0000000..3046af7 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java @@ -0,0 +1,230 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.event.EventContext; +import javax.naming.event.EventDirContext; +import javax.naming.ldap.LdapContext; +import javax.naming.spi.InitialContextFactory; +import javax.naming.spi.InitialContextFactoryBuilder; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jnp.interfaces.NamingContextFactory; + +import org.rhq.jndi.context.AccessCheckingContextDecorator; +import org.rhq.jndi.context.AccessCheckingContextDecoratorSetContext; +import org.rhq.jndi.context.ContextDecorator; +import org.rhq.jndi.context.URLPreferringContextDecoratorSetContext; +import org.rhq.jndi.util.DecoratorPicker; + +/** + * This initial context factory builder is installed early on during the RHQ server startup + * and is later on used for obtaining the {@link Context}s for all JNDI lookups in the + * RHQ server. + * <p> + * We use a custom initial context factory builder to prevent the potential malicious 3rd party + * code (like CLI alert scripts) from supplying custom environment variables to {@link InitialContext} + * that would modify the JNDI lookup to skip our security access checks. + * <p> + * By using a builder we effectively take control of the initial context creation process + * and are free to ignore whatever the script is trying to supply. + * <p> + * This builder makes sure to install the RHQ server's security access checks to whatever + * initial context that is configured by the standard environment variables + * ({@link Context#INITIAL_CONTEXT_FACTORY}, etc.) + * <p> + * This class is heavily inspired by the implementation of a similar builder in JBoss AS 7. + * + * @see AllowRhqServerInternalsAccessPermission + * + * @author Lukas Krejci + */ +public class AccessCheckingInitialContextFactoryBuilder implements InitialContextFactoryBuilder { + private static final Log LOG = LogFactory.getLog(AccessCheckingInitialContextFactoryBuilder.class); + + /** + * The list of JNDI name schemes that should be checked for security permissions + * (in addition to the names with no scheme). + * + * @see AccessCheckingContextDecorator + */ + private static final String[] CHECKED_SCHEMES = { "java" }; + + private static final Set<Class<? extends Context>> SUPPORTED_CONTEXT_INTERFACES; + + static { + SUPPORTED_CONTEXT_INTERFACES = new HashSet<Class<? extends Context>>(); + SUPPORTED_CONTEXT_INTERFACES.add(Context.class); + SUPPORTED_CONTEXT_INTERFACES.add(DirContext.class); + SUPPORTED_CONTEXT_INTERFACES.add(EventContext.class); + SUPPORTED_CONTEXT_INTERFACES.add(EventDirContext.class); + SUPPORTED_CONTEXT_INTERFACES.add(LdapContext.class); + } + + private static final Set<InetAddress> SERVER_BIND_IPS; + static { + SERVER_BIND_IPS = new HashSet<InetAddress>(); + + try { + String bindingAddressString = System.getProperty("jboss.bind.address"); + InetAddress bindingAddress = InetAddress.getByName(bindingAddressString); + + if (bindingAddress.isAnyLocalAddress()) { + Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); + while (ifaces.hasMoreElements()) { + NetworkInterface iface = ifaces.nextElement(); + SERVER_BIND_IPS.addAll(Collections.list(iface.getInetAddresses())); + } + } else { + SERVER_BIND_IPS.add(bindingAddress); + } + } catch (SocketException e) { + LOG.error("Could not obtain the list of local IPs", e); + } catch (UnknownHostException e) { + LOG.error("Failed to get the binding address of the RHQ server.", e); + } + } + + private static final int JNP_PORT = Integer.parseInt(System.getProperty("rhq.server.startup.namingservice.port", + "2099")); + + /** + * This is the default initial context factory that is returned when no other is + * configured using the environment variables. + * <p> + * It uses {@link NamingContextFactory} as the underlying mechanism - the same + * as the default configuration in JBoss 4. + */ + private static final InitialContextFactory DEFAULT_FACTORY = new InitialContextFactory() { + public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException { + return createSecureWrapper(new NamingContextFactory(), environment).getInitialContext(environment); + } + }; + + /** + * Create a InitialContext factory. If the environment does not override the factory class it will use the + * default context factory. + * + * @param environment The environment + * @return An initial context factory + * @throws NamingException If an error occurs loading the factory class. + */ + public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException { + final String factoryClassName = (String) environment.get(Context.INITIAL_CONTEXT_FACTORY); + if (factoryClassName == null) { + return DEFAULT_FACTORY; + } + final ClassLoader classLoader = getContextClassLoader(); + try { + final Class<?> factoryClass = Class.forName(factoryClassName, true, classLoader); + InitialContextFactory configuredFactory = (InitialContextFactory) factoryClass.newInstance(); + return createSecureWrapper(configuredFactory, environment); + } catch (Exception e) { + throw new NamingException("Failed instantiate InitialContextFactory " + factoryClassName + + " from classloader " + classLoader); + } + } + + private ClassLoader getContextClassLoader() { + return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + } + + private static InitialContextFactory + createSecureWrapper(InitialContextFactory factory, Hashtable<?, ?> environment) { + String providerUrl = (String) environment.get(Context.PROVIDER_URL); + + if (providerUrl == null) { + return getAccessCheckingFactory(factory); + } else { + try { + URI uri = new URI(providerUrl); + InetAddress providerHost = InetAddress.getByName(uri.getHost()); + + //check if we are accessing the RHQ server through some remoting + //interface. + if (uri.getPort() == JNP_PORT && SERVER_BIND_IPS.contains(providerHost)) { + return getAccessCheckingFactory(factory); + } else { + return getURLPreferringFactory(factory); + } + } catch (URISyntaxException e) { + return getAccessCheckingFactory(factory); + } catch (UnknownHostException e) { + //let the factory deal with the unknown host... + //this most probably shouldn't be secured because localhost addresses + //should be resolvable. + return getURLPreferringFactory(factory); + } + } + } + + private static InitialContextFactory getAccessCheckingFactory(InitialContextFactory original) { + ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context,ContextDecorator>>(); + pickers.add(getURLPreferringDecoratorPicker()); + pickers.add(getAccessCheckingDecoratorPicker()); + + return new DecoratingInitialContextFactory(original, pickers); + } + + private static InitialContextFactory getURLPreferringFactory(InitialContextFactory original) { + ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context,ContextDecorator>>(); + pickers.add(getURLPreferringDecoratorPicker()); + + return new DecoratingInitialContextFactory(original, pickers); + } + + private static DecoratorPicker<Context, ContextDecorator> getAccessCheckingDecoratorPicker() { + DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>(); + ret.setContext(new AccessCheckingContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES, CHECKED_SCHEMES)); + + return ret; + } + + private static DecoratorPicker<Context, ContextDecorator> getURLPreferringDecoratorPicker() { + DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>(); + ret.setContext(new URLPreferringContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES)); + + return ret; + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java.rej b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java.rej deleted file mode 100644 index 445fa3a..0000000 --- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java.rej +++ /dev/null @@ -1,122 +0,0 @@ ---- modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java -+++ modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java -@@ -27,7 +27,7 @@ - import java.net.UnknownHostException; - import java.security.AccessController; - import java.security.PrivilegedAction; --import java.util.ArrayList; -+import java.util.Arrays; - import java.util.Collections; - import java.util.Enumeration; - import java.util.HashSet; -@@ -37,10 +37,6 @@ - import javax.naming.Context; - import javax.naming.InitialContext; - import javax.naming.NamingException; --import javax.naming.directory.DirContext; --import javax.naming.event.EventContext; --import javax.naming.event.EventDirContext; --import javax.naming.ldap.LdapContext; - import javax.naming.spi.InitialContextFactory; - import javax.naming.spi.InitialContextFactoryBuilder; - -@@ -49,10 +45,16 @@ - import org.jnp.interfaces.NamingContextFactory; - - import org.rhq.jndi.context.AccessCheckingContextDecorator; --import org.rhq.jndi.context.AccessCheckingContextDecoratorSetContext; --import org.rhq.jndi.context.ContextDecorator; --import org.rhq.jndi.context.URLPreferringContextDecoratorSetContext; --import org.rhq.jndi.util.DecoratorPicker; -+import org.rhq.jndi.context.AccessCheckingDirContextDecorator; -+import org.rhq.jndi.context.AccessCheckingEventContextDecorator; -+import org.rhq.jndi.context.AccessCheckingEventDirContextDecorator; -+import org.rhq.jndi.context.AccessCheckingLdapContextDecorator; -+import org.rhq.jndi.context.ContextDecoratorPicker; -+import org.rhq.jndi.context.URLPreferringContextDecorator; -+import org.rhq.jndi.context.URLPreferringDirContextDecorator; -+import org.rhq.jndi.context.URLPreferringEventContextDecorator; -+import org.rhq.jndi.context.URLPreferringEventDirContextDecorator; -+import org.rhq.jndi.context.URLPreferringLdapContextDecorator; - - /** - * This initial context factory builder is installed early on during the RHQ server startup -@@ -87,17 +89,6 @@ - */ - private static final String[] CHECKED_SCHEMES = { "java" }; - -- private static final Set<Class<? extends Context>> SUPPORTED_CONTEXT_INTERFACES; -- -- static { -- SUPPORTED_CONTEXT_INTERFACES = new HashSet<Class<? extends Context>>(); -- SUPPORTED_CONTEXT_INTERFACES.add(Context.class); -- SUPPORTED_CONTEXT_INTERFACES.add(DirContext.class); -- SUPPORTED_CONTEXT_INTERFACES.add(EventContext.class); -- SUPPORTED_CONTEXT_INTERFACES.add(EventDirContext.class); -- SUPPORTED_CONTEXT_INTERFACES.add(LdapContext.class); -- } -- - private static final Set<InetAddress> SERVER_BIND_IPS; - static { - SERVER_BIND_IPS = new HashSet<InetAddress>(); -@@ -200,31 +191,42 @@ - } - - private static InitialContextFactory getAccessCheckingFactory(InitialContextFactory original) { -- ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context,ContextDecorator>>(); -- pickers.add(getURLPreferringDecoratorPicker()); -- pickers.add(getAccessCheckingDecoratorPicker()); -- -- return new DecoratingInitialContextFactory(original, pickers); -+ return new DecoratingInitialContextFactory(original, Arrays.asList( -+ getURLPreferringDecoratorPicker(), getAccessCheckingDecoratorPicker())); - } -- -+ - private static InitialContextFactory getURLPreferringFactory(InitialContextFactory original) { -- ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context,ContextDecorator>>(); -- pickers.add(getURLPreferringDecoratorPicker()); -- -- return new DecoratingInitialContextFactory(original, pickers); -+ return new DecoratingInitialContextFactory(original, Arrays.asList( -+ getURLPreferringDecoratorPicker())); - } - -- private static DecoratorPicker<Context, ContextDecorator> getAccessCheckingDecoratorPicker() { -- DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>(); -- ret.setContext(new AccessCheckingContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES, CHECKED_SCHEMES)); -- -+ private static ContextDecoratorPicker getAccessCheckingDecoratorPicker() { -+ ContextDecoratorPicker ret = new ContextDecoratorPicker(); -+ -+ ret.setConstructorParameters(new Object[] { CHECKED_SCHEMES }); -+ ret.setConstructorParameterTypes(new Class<?>[] { String[].class }); -+ -+ ret.getPossibleDecorators().add(AccessCheckingContextDecorator.class); -+ ret.getPossibleDecorators().add(AccessCheckingDirContextDecorator.class); -+ ret.getPossibleDecorators().add(AccessCheckingEventContextDecorator.class); -+ ret.getPossibleDecorators().add(AccessCheckingEventDirContextDecorator.class); -+ ret.getPossibleDecorators().add(AccessCheckingLdapContextDecorator.class); -+ - return ret; - } - -- private static DecoratorPicker<Context, ContextDecorator> getURLPreferringDecoratorPicker() { -- DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>(); -- ret.setContext(new URLPreferringContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES)); -- -+ private static ContextDecoratorPicker getURLPreferringDecoratorPicker() { -+ ContextDecoratorPicker ret = new ContextDecoratorPicker(); -+ -+ ret.setConstructorParameters(null); -+ ret.setConstructorParameterTypes(null); -+ -+ ret.getPossibleDecorators().add(URLPreferringContextDecorator.class); -+ ret.getPossibleDecorators().add(URLPreferringDirContextDecorator.class); -+ ret.getPossibleDecorators().add(URLPreferringEventContextDecorator.class); -+ ret.getPossibleDecorators().add(URLPreferringEventDirContextDecorator.class); -+ ret.getPossibleDecorators().add(URLPreferringLdapContextDecorator.class); -+ - return ret; - } - } diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AllowRhqServerInternalsAccessPermission.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AllowRhqServerInternalsAccessPermission.java new file mode 100644 index 0000000..aa807d6 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AllowRhqServerInternalsAccessPermission.java @@ -0,0 +1,36 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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; + +import java.security.BasicPermission; + +/** + * + * + * @author Lukas Krejci + */ +public class AllowRhqServerInternalsAccessPermission extends BasicPermission { + + private static final long serialVersionUID = 1L; + + public AllowRhqServerInternalsAccessPermission() { + super("org.rhq.allow.server.internals.access"); + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java new file mode 100644 index 0000000..19b30ea --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java @@ -0,0 +1,100 @@ +/* + * 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; + +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.spi.InitialContextFactory; + +import org.rhq.jndi.context.ContextDecorator; +import org.rhq.jndi.util.DecoratorPicker; +import org.rhq.jndi.util.DecoratingInvocationHandler; + +/** + * This class implements an initial context factory that decorates the contexts + * returned from a "wrapped" initial factory passed to this class in the constructor. + * <p> + * The contexts returned from the wrapped initial factory are hidden behind a proxy + * that implements the intersection of interfaces from the <code>supportedContextInterfaces</code> + * constructor parameter and the actual interfaces the wrapped context implements. + * <p> + * The proxy method calls are handled using the {@link DecoratingInvocationHandler} which is initialized + * with the list of {@link DecoratorPicker pickers} that are used to intercept the method + * calls on the wrapped context. + * + * @see DecoratorPicker + * @see DecoratingInvocationHandler + * + * @author Lukas Krejci + */ +public class DecoratingInitialContextFactory implements InitialContextFactory { + + List<DecoratorPicker<Context, ContextDecorator>> pickers; + private InitialContextFactory factory; + private Set<Class<? extends Context>> supportedContextInterfaces; + + public DecoratingInitialContextFactory(InitialContextFactory factory, List<DecoratorPicker<Context, ContextDecorator>> decoratorPickers) { + this.factory = factory; + this.pickers = decoratorPickers; + this.supportedContextInterfaces = new HashSet<Class<? extends Context>>(); + for(DecoratorPicker<Context, ContextDecorator> picker : pickers) { + supportedContextInterfaces.addAll(picker.getContext().getSupportedInterfaces()); + } + } + + public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException { + Context ctx = factory.getInitialContext(environment); + + Set<Class<?>> implementedIfaces = getAllImplementedInterfaces(ctx.getClass()); + Class<?>[] ii = new Class<?>[implementedIfaces.size()]; + implementedIfaces.toArray(ii); + + return (Context) Proxy.newProxyInstance(ctx.getClass().getClassLoader(), ii, new DecoratingInvocationHandler<Context, ContextDecorator>(pickers, ctx)); + } + + private Set<Class<?>> getAllImplementedInterfaces(Class<?> cls) { + HashSet<Class<?>> ret = new HashSet<Class<?>>(); + getAllImplementedInterfaces(cls, ret); + + ret.retainAll(supportedContextInterfaces); + + return ret; + } + + private static void getAllImplementedInterfaces(Class<?> cls, Set<Class<?>> output) { + Class<?>[] ifaces = cls.getInterfaces(); + + for (Class<?> iface : Arrays.asList(ifaces)) { + output.add(iface); + getAllImplementedInterfaces(iface, output); + } + + if (cls.getSuperclass() != null) { + getAllImplementedInterfaces(cls.getSuperclass(), output); + } + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java new file mode 100644 index 0000000..6bfec7e --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java @@ -0,0 +1,254 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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.context; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +import org.rhq.jndi.AllowRhqServerInternalsAccessPermission; + +/** + * This is the "meat" of the RHQ's secured JNDI access. This {@link Context} decorator + * applied security checks in each method (lookups, (un)bindings, etc). + * <p> + * The security check consists of checking if the current callstack has the {@link AllowRhqServerInternalsAccessPermission}. + * <p> + * This decorator applies the security check on any JNDI name without a scheme and + * on any name that has a scheme listed in the {@link #checkedSchemes} list supplied + * in the constructor. + * + * @author Lukas Krejci + */ +public class AccessCheckingContextDecorator implements Context, ContextDecorator, Serializable { + + private static final long serialVersionUID = 1L; + + private static final AllowRhqServerInternalsAccessPermission PERM = new AllowRhqServerInternalsAccessPermission(); + private Context original; + private List<String> checkedSchemes; + + public AccessCheckingContextDecorator(String... checkedSchemes) { + this.checkedSchemes = Arrays.asList(checkedSchemes); + } + + public AccessCheckingContextDecorator(Context original, String... checkedSchemes) { + this.original = original; + this.checkedSchemes = Arrays.asList(checkedSchemes); + } + + public void init(Context ctx) { + this.original = ctx; + } + + protected Context getOriginal() { + return original; + } + + protected static void check() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) sm.checkPermission(PERM); + } + + private void checkScheme(String scheme) { + if (scheme == null || checkedSchemes.contains(scheme)) { + check(); + } + } + + protected void check(String name) { + checkScheme(getURLScheme(name)); + } + + protected void check(Name name) { + if (name.size() == 0) { + check(); + } else { + String first = name.get(0); + checkScheme(getURLScheme(first)); + } + } + + public Object lookup(Name name) throws NamingException { + check(name); + return original.lookup(name); + } + + public Object lookup(String name) throws NamingException { + check(name); + return original.lookup(name); + } + + public void bind(Name name, Object obj) throws NamingException { + check(name); + original.bind(name, obj); + } + + public void bind(String name, Object obj) throws NamingException { + check(name); + original.bind(name, obj); + } + + public void rebind(Name name, Object obj) throws NamingException { + check(name); + original.rebind(name, obj); + } + + public void rebind(String name, Object obj) throws NamingException { + check(name); + original.rebind(name, obj); + } + + public void unbind(Name name) throws NamingException { + check(name); + original.unbind(name); + } + + public void unbind(String name) throws NamingException { + check(name); + original.unbind(name); + } + + public void rename(Name oldName, Name newName) throws NamingException { + check(oldName); + check(newName); + original.rename(oldName, newName); + } + + public void rename(String oldName, String newName) throws NamingException { + check(oldName); + check(newName); + original.rename(oldName, newName); + } + + public NamingEnumeration<NameClassPair> list(Name name) throws NamingException { + check(name); + return original.list(name); + } + + public NamingEnumeration<NameClassPair> list(String name) throws NamingException { + check(name); + return original.list(name); + } + + public NamingEnumeration<Binding> listBindings(Name name) throws NamingException { + check(name); + return original.listBindings(name); + } + + public NamingEnumeration<Binding> listBindings(String name) throws NamingException { + check(name); + return original.listBindings(name); + } + + public void destroySubcontext(Name name) throws NamingException { + check(name); + original.destroySubcontext(name); + } + + public void destroySubcontext(String name) throws NamingException { + check(name); + original.destroySubcontext(name); + } + + public Context createSubcontext(Name name) throws NamingException { + check(name); + return original.createSubcontext(name); + } + + public Context createSubcontext(String name) throws NamingException { + check(name); + return original.createSubcontext(name); + } + + public Object lookupLink(Name name) throws NamingException { + check(name); + return original.lookupLink(name); + } + + public Object lookupLink(String name) throws NamingException { + check(name); + return original.lookupLink(name); + } + + public NameParser getNameParser(Name name) throws NamingException { + check(name); + return original.getNameParser(name); + } + + public NameParser getNameParser(String name) throws NamingException { + check(name); + return original.getNameParser(name); + } + + public Name composeName(Name name, Name prefix) throws NamingException { + check(name); + return original.composeName(name, prefix); + } + + public String composeName(String name, String prefix) throws NamingException { + check(name); + return original.composeName(name, prefix); + } + + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + check(); + return original.addToEnvironment(propName, propVal); + } + + public Object removeFromEnvironment(String propName) throws NamingException { + check(); + return original.removeFromEnvironment(propName); + } + + public Hashtable<?, ?> getEnvironment() throws NamingException { + check(); + return original.getEnvironment(); + } + + public void close() throws NamingException { + check(); + original.close(); + } + + public String getNameInNamespace() throws NamingException { + check(); + return original.getNameInNamespace(); + } + + //copied from InitialContext + private static String getURLScheme(String str) { + int colon_posn = str.indexOf(':'); + int slash_posn = str.indexOf('/'); + + if (colon_posn > 0 && (slash_posn == -1 || colon_posn < slash_posn)) + return str.substring(0, colon_posn); + return null; + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java new file mode 100644 index 0000000..284fd0c --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecoratorSetContext.java @@ -0,0 +1,75 @@ +/* + * 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.context; + +import java.lang.reflect.Constructor; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.naming.Context; + +import org.rhq.jndi.util.DecoratorSetContext; + +/** + * + * + * @author Lukas Krejci + */ +public class AccessCheckingContextDecoratorSetContext implements DecoratorSetContext<Context, ContextDecorator> { + + private static final Set<Class<? extends ContextDecorator>> DECORATOR_CLASSES; + static { + HashSet<Class<? extends ContextDecorator>> tmp = new HashSet<Class<? extends ContextDecorator>>(); + tmp.add(AccessCheckingContextDecorator.class); + tmp.add(AccessCheckingDirContextDecorator.class); + tmp.add(AccessCheckingEventContextDecorator.class); + tmp.add(AccessCheckingEventDirContextDecorator.class); + tmp.add(AccessCheckingLdapContextDecorator.class); + + DECORATOR_CLASSES = Collections.unmodifiableSet(tmp); + } + + private Set<Class<? extends Context>> supportedInterfaces; + private String[] checkedSchemes; + + public AccessCheckingContextDecoratorSetContext(Set<Class<? extends Context>> supportedInterfaces, String... checkedSchemes) { + this.supportedInterfaces = supportedInterfaces; + this.checkedSchemes = checkedSchemes; + } + + public ContextDecorator instantiate(Class<? extends ContextDecorator> decoratorClass) throws Exception { + Constructor<? extends ContextDecorator> ctor = decoratorClass.getConstructor(String[].class); + + return ctor.newInstance((Object)checkedSchemes); + } + + public Set<Class<? extends Context>> getSupportedInterfaces() { + return supportedInterfaces; + } + + public Set<Class<? extends ContextDecorator>> getDecoratorClasses() { + return DECORATOR_CLASSES; + } + + public void init(ContextDecorator decorator, Context object) throws Exception { + decorator.init(object); + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingDirContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingDirContextDecorator.java new file mode 100644 index 0000000..1614180 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingDirContextDecorator.java @@ -0,0 +1,187 @@ +/* + * 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.context; + +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +/** + * A decorator of {@link DirContext}. + * + * @author Lukas Krejci + */ +public class AccessCheckingDirContextDecorator extends AccessCheckingContextDecorator implements DirContext { + + private static final long serialVersionUID = 1L; + + public AccessCheckingDirContextDecorator(String... checkedSchemes) { + super(checkedSchemes); + } + + public AccessCheckingDirContextDecorator(DirContext original, String... checkedSchemes) { + super(original, checkedSchemes); + } + + @Override + protected DirContext getOriginal() { + return (DirContext) super.getOriginal(); + } + + public Attributes getAttributes(Name name) throws NamingException { + check(name); + return getOriginal().getAttributes(name); + } + + public Attributes getAttributes(String name) throws NamingException { + check(name); + return getOriginal().getAttributes(name); + } + + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + check(name); + return getOriginal().getAttributes(name, attrIds); + } + + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + check(name); + return getOriginal().getAttributes(name, attrIds); + } + + public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException { + check(name); + getOriginal().modifyAttributes(name, mod_op, attrs); + } + + public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException { + check(name); + getOriginal().modifyAttributes(name, mod_op, attrs); + } + + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException { + check(name); + getOriginal().modifyAttributes(name, mods); + } + + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { + check(name); + getOriginal().modifyAttributes(name, mods); + } + + public void bind(Name name, Object obj, Attributes attrs) throws NamingException { + check(name); + getOriginal().bind(name, obj, attrs); + } + + public void bind(String name, Object obj, Attributes attrs) throws NamingException { + check(name); + getOriginal().bind(name, obj, attrs); + } + + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException { + check(name); + getOriginal().rebind(name, obj, attrs); + } + + public void rebind(String name, Object obj, Attributes attrs) throws NamingException { + check(name); + getOriginal().rebind(name, obj, attrs); + } + + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + check(name); + return getOriginal().createSubcontext(name, attrs); + } + + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + check(name); + return getOriginal().createSubcontext(name, attrs); + } + + public DirContext getSchema(Name name) throws NamingException { + check(name); + return getOriginal().getSchema(name); + } + + public DirContext getSchema(String name) throws NamingException { + check(name); + return getOriginal().getSchema(name); + } + + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + check(name); + return getOriginal().getSchemaClassDefinition(name); + } + + public DirContext getSchemaClassDefinition(String name) throws NamingException { + check(name); + return getOriginal().getSchema(name); + } + + public NamingEnumeration<SearchResult> + search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + check(name); + return getOriginal().search(name, matchingAttributes, attributesToReturn); + } + + public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + check(name); + return getOriginal().search(name, matchingAttributes, attributesToReturn); + } + + public NamingEnumeration<SearchResult> search(Name name, Attributes matchingAttributes) throws NamingException { + check(name); + return getOriginal().search(name, matchingAttributes); + } + + public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes) throws NamingException { + check(name); + return getOriginal().search(name, matchingAttributes); + } + + public NamingEnumeration<SearchResult> search(Name name, String filter, SearchControls cons) throws NamingException { + check(name); + return getOriginal().search(name, filter, cons); + } + + public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons) + throws NamingException { + check(name); + return getOriginal().search(name, filter, cons); + } + + public NamingEnumeration<SearchResult> + search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + check(name); + return getOriginal().search(name, filterExpr, filterArgs, cons); + } + + public NamingEnumeration<SearchResult> search(String name, String filterExpr, Object[] filterArgs, + SearchControls cons) throws NamingException { + check(name); + return getOriginal().search(name, filterExpr, filterArgs, cons); + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventContextDecorator.java new file mode 100644 index 0000000..67a9527 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventContextDecorator.java @@ -0,0 +1,69 @@ +/* + * 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.context; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.event.EventContext; +import javax.naming.event.NamingListener; + +/** + * + * + * @author Lukas Krejci + */ +public class AccessCheckingEventContextDecorator extends AccessCheckingContextDecorator implements EventContext { + + private static final long serialVersionUID = 1L; + + public AccessCheckingEventContextDecorator(String... checkedSchemes) { + super(checkedSchemes); + } + + public AccessCheckingEventContextDecorator(EventContext original, String... checkedSchemes) { + super(original, checkedSchemes); + } + + @Override + protected EventContext getOriginal() { + return (EventContext) super.getOriginal(); + } + + public void addNamingListener(Name target, int scope, NamingListener l) throws NamingException { + check(target); + getOriginal().addNamingListener(target, scope, l); + } + + public void addNamingListener(String target, int scope, NamingListener l) throws NamingException { + check(target); + getOriginal().addNamingListener(target, scope, l); + } + + public void removeNamingListener(NamingListener l) throws NamingException { + check(); + getOriginal().removeNamingListener(l); + } + + public boolean targetMustExist() throws NamingException { + check(); + return getOriginal().targetMustExist(); + } + +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventDirContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventDirContextDecorator.java new file mode 100644 index 0000000..ed7a848 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingEventDirContextDecorator.java @@ -0,0 +1,96 @@ +/* + * 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.context; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.SearchControls; +import javax.naming.event.EventDirContext; +import javax.naming.event.NamingListener; + +/** + * + * + * @author Lukas Krejci + */ +public class AccessCheckingEventDirContextDecorator extends AccessCheckingDirContextDecorator implements + EventDirContext { + + private static final long serialVersionUID = 1L; + + public AccessCheckingEventDirContextDecorator(String... checkedSchemes) { + super(checkedSchemes); + } + + public AccessCheckingEventDirContextDecorator(EventDirContext original, String... checkedSchemes) { + super(original, checkedSchemes); + } + + @Override + protected EventDirContext getOriginal() { + return (EventDirContext) super.getOriginal(); + } + + public void addNamingListener(Name target, int scope, NamingListener l) throws NamingException { + check(target); + getOriginal().addNamingListener(target, scope, l); + } + + public void addNamingListener(String target, int scope, NamingListener l) throws NamingException { + check(target); + getOriginal().addNamingListener(target, scope, l); + } + + public void removeNamingListener(NamingListener l) throws NamingException { + check(); + getOriginal().removeNamingListener(l); + } + + public boolean targetMustExist() throws NamingException { + check(); + return getOriginal().targetMustExist(); + } + + public void addNamingListener(Name target, String filter, SearchControls ctls, NamingListener l) + throws NamingException { + check(target); + getOriginal().addNamingListener(target, filter, ctls, l); + } + + public void addNamingListener(String target, String filter, SearchControls ctls, NamingListener l) + throws NamingException { + check(target); + getOriginal().addNamingListener(target, filter, ctls, l); + } + + public void + addNamingListener(Name target, String filter, Object[] filterArgs, SearchControls ctls, NamingListener l) + throws NamingException { + check(target); + getOriginal().addNamingListener(target, filter, filterArgs, ctls, l); + } + + public void addNamingListener(String target, String filter, Object[] filterArgs, SearchControls ctls, + NamingListener l) throws NamingException { + check(target); + getOriginal().addNamingListener(target, filter, filterArgs, ctls, l); + } + +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingLdapContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingLdapContextDecorator.java new file mode 100644 index 0000000..e361db8 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingLdapContextDecorator.java @@ -0,0 +1,85 @@ +/* + * 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.context; + +import javax.naming.NamingException; +import javax.naming.ldap.Control; +import javax.naming.ldap.ExtendedRequest; +import javax.naming.ldap.ExtendedResponse; +import javax.naming.ldap.LdapContext; + +/** + * + * + * @author Lukas Krejci + */ +public class AccessCheckingLdapContextDecorator extends AccessCheckingDirContextDecorator implements LdapContext { + + private static final long serialVersionUID = 1L; + + public AccessCheckingLdapContextDecorator(String... checkedSchemes) { + super(checkedSchemes); + } + + public AccessCheckingLdapContextDecorator(LdapContext original, String... checkedSchemes) { + super(original, checkedSchemes); + } + + @Override + protected LdapContext getOriginal() { + return (LdapContext) super.getOriginal(); + } + + public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { + check(); + return getOriginal().extendedOperation(request); + } + + public LdapContext newInstance(Control[] requestControls) throws NamingException { + check(); + return getOriginal().newInstance(requestControls); + } + + public void reconnect(Control[] connCtls) throws NamingException { + check(); + getOriginal().reconnect(connCtls); + } + + public Control[] getConnectControls() throws NamingException { + check(); + return getOriginal().getConnectControls(); + } + + public void setRequestControls(Control[] requestControls) throws NamingException { + check(); + getOriginal().setRequestControls(requestControls); + } + + public Control[] getRequestControls() throws NamingException { + check(); + return getOriginal().getRequestControls(); + } + + public Control[] getResponseControls() throws NamingException { + check(); + return getOriginal().getResponseControls(); + } + +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecorator.java new file mode 100644 index 0000000..5755ebd --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/ContextDecorator.java @@ -0,0 +1,33 @@ +/* + * 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.context; + +import javax.naming.Context; +import javax.naming.NamingException; + +/** + * + * + * @author Lukas Krejci + */ +public interface ContextDecorator extends Context { + + void init(Context context) throws NamingException; +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java new file mode 100644 index 0000000..20077de --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java @@ -0,0 +1,212 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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.context; + +import java.io.Serializable; +import java.util.Hashtable; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +import org.rhq.jndi.AccessCheckingInitialContextFactoryBuilder; + +/** + * This is a wrapper class around another {@link Context} implementation that + * prefers to use an URL context for some operation if the JNDI name contains + * a scheme rather than the original. This is the behavior of {@link InitialContext} + * which we need to restore in the contexts created by the {@link AccessCheckingInitialContextFactoryBuilder} + * (which an {@link InitialContext} uses exclusively if the builder is set). + * <p> + * This is important because RHQ server has its own initial context factory + * builder that creates factories that in turn create contexts. If the default + * {@link InitialContext} implementation was used, we'd never be able to lookup + * scheme-based names because the default implementation of the {@link InitialContext} + * always uses the default context of the builder if one is installed no matter + * the scheme in the name. + * <p> + * The {@link AccessCheckingInitialContextFactoryBuilder} wraps the context returned + * by the factory in an instance of this class and thus is restoring the original + * intended behavior of the {@link InitialContext}. It looks at the name being looked + * up (bound or whatever) and prefers to use the URL context factories if the name + * contains the scheme (as does the {@link InitialContext} if no builder is installed). + * If the name doesn't contain a scheme, the provided default context factory is used to + * look up the name. + * + * @author Lukas Krejci + */ +public class URLPreferringContextDecorator implements Context, ContextDecorator, Serializable { + + private static final long serialVersionUID = 1L; + + private Context original; + + public URLPreferringContextDecorator() { + + } + + public URLPreferringContextDecorator(Context ctx) { + original = ctx; + } + + public void init(Context context) { + original = context; + } + + protected Context getOriginal() throws NamingException { + return original; + } + + protected Context getURLOrDefaultInitCtx(Name name) throws NamingException { + @SuppressWarnings("unchecked") + Context urlContext = URLPreferringContextDecoratorHelper.getURLContext(name, (Hashtable<Object, Object>) getEnvironment()); + return urlContext == null ? getOriginal() : urlContext; + } + + protected Context getURLOrDefaultInitCtx(String name) throws NamingException { + @SuppressWarnings("unchecked") + Context urlContext = URLPreferringContextDecoratorHelper.getURLContext(name, (Hashtable<Object, Object>) getEnvironment()); + return urlContext == null ? getOriginal() : urlContext; + } + + public Object lookup(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).lookup(name); + } + + public Object lookup(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).lookup(name); + } + + public void bind(Name name, Object obj) throws NamingException { + getURLOrDefaultInitCtx(name).bind(name, obj); + } + + public void bind(String name, Object obj) throws NamingException { + getURLOrDefaultInitCtx(name).bind(name, obj); + } + + public void rebind(Name name, Object obj) throws NamingException { + getURLOrDefaultInitCtx(name).rebind(name, obj); + } + + public void rebind(String name, Object obj) throws NamingException { + getURLOrDefaultInitCtx(name).rebind(name, obj); + } + + public void unbind(Name name) throws NamingException { + getURLOrDefaultInitCtx(name).unbind(name); + } + + public void unbind(String name) throws NamingException { + getURLOrDefaultInitCtx(name).unbind(name); + } + + public void rename(Name oldName, Name newName) throws NamingException { + getURLOrDefaultInitCtx(oldName).rename(oldName, newName); + } + + public void rename(String oldName, String newName) throws NamingException { + getURLOrDefaultInitCtx(oldName).rename(oldName, newName); + } + + public NamingEnumeration<NameClassPair> list(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).list(name); + } + + public NamingEnumeration<NameClassPair> list(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).list(name); + } + + public NamingEnumeration<Binding> listBindings(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).listBindings(name); + } + + public NamingEnumeration<Binding> listBindings(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).listBindings(name); + } + + public void destroySubcontext(Name name) throws NamingException { + getURLOrDefaultInitCtx(name).destroySubcontext(name); + } + + public void destroySubcontext(String name) throws NamingException { + getURLOrDefaultInitCtx(name).destroySubcontext(name); + } + + public Context createSubcontext(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).createSubcontext(name); + } + + public Context createSubcontext(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).createSubcontext(name); + } + + public Object lookupLink(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).lookupLink(name); + } + + public Object lookupLink(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).lookupLink(name); + } + + public NameParser getNameParser(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).getNameParser(name); + } + + public NameParser getNameParser(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).getNameParser(name); + } + + public Name composeName(Name name, Name prefix) throws NamingException { + return getOriginal().composeName(name, prefix); + } + + public String composeName(String name, String prefix) throws NamingException { + return getOriginal().composeName(name, prefix); + } + + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + return getOriginal().addToEnvironment(propName, propVal); + } + + public Object removeFromEnvironment(String propName) throws NamingException { + return getOriginal().removeFromEnvironment(propName); + } + + public Hashtable<?, ?> getEnvironment() throws NamingException { + return getOriginal().getEnvironment(); + } + + public void close() throws NamingException { + if (getOriginal() != null) { + getOriginal().close(); + original = null; + } + } + + public String getNameInNamespace() throws NamingException { + return getOriginal().getNameInNamespace(); + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorHelper.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorHelper.java new file mode 100644 index 0000000..6e7debc --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorHelper.java @@ -0,0 +1,76 @@ +/* + * 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.context; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.spi.NamingManager; + +/** + * + * + * @author Lukas Krejci + */ +public class URLPreferringContextDecoratorHelper { + + private URLPreferringContextDecoratorHelper() { + + } + + public static Context getURLContext(String name, Hashtable<Object, Object> env) throws NamingException { + String scheme = getURLScheme(name); + if (scheme != null) { + Context ctx = NamingManager.getURLContext(scheme, env); + if (ctx != null) { + return ctx; + } + } + + return null; + } + + public static Context getURLContext(Name name, Hashtable<Object, Object> env) throws NamingException { + if (name.size() > 0) { + String first = name.get(0); + String scheme = getURLScheme(first); + if (scheme != null) { + Context ctx = NamingManager.getURLContext(scheme, env); + if (ctx != null) { + return ctx; + } + } + } + + return null; + } + + //copied from InitialContext + private static String getURLScheme(String str) { + int colon_posn = str.indexOf(':'); + int slash_posn = str.indexOf('/'); + + if (colon_posn > 0 && (slash_posn == -1 || colon_posn < slash_posn)) + return str.substring(0, colon_posn); + return null; + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java new file mode 100644 index 0000000..828cd74 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecoratorSetContext.java @@ -0,0 +1,71 @@ +/* + * 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.context; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.naming.Context; + +import org.rhq.jndi.util.DecoratorSetContext; + +/** + * + * + * @author Lukas Krejci + */ +public class URLPreferringContextDecoratorSetContext implements DecoratorSetContext<Context, ContextDecorator> { + + private static final Set<Class<? extends ContextDecorator>> DECORATOR_CLASSES; + static { + HashSet<Class<? extends ContextDecorator>> tmp = new HashSet<Class<? extends ContextDecorator>>(); + tmp.add(URLPreferringContextDecorator.class); + tmp.add(URLPreferringDirContextDecorator.class); + tmp.add(URLPreferringEventContextDecorator.class); + tmp.add(URLPreferringEventDirContextDecorator.class); + tmp.add(URLPreferringLdapContextDecorator.class); + + DECORATOR_CLASSES = Collections.unmodifiableSet(tmp); + } + + private Set<Class<? extends Context>> supportedInterfaces; + + public URLPreferringContextDecoratorSetContext(Set<Class<? extends Context>> supportedInterfaces) { + this.supportedInterfaces = supportedInterfaces; + } + + public Set<Class<? extends Context>> getSupportedInterfaces() { + return supportedInterfaces; + } + + public Set<Class<? extends ContextDecorator>> getDecoratorClasses() { + return DECORATOR_CLASSES; + } + + public ContextDecorator instantiate(Class<? extends ContextDecorator> decoratorClass) throws Exception { + return decoratorClass.newInstance(); + } + + public void init(ContextDecorator decorator, Context object) throws Exception { + decorator.init(object); + } + +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringDirContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringDirContextDecorator.java new file mode 100644 index 0000000..6a49942 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringDirContextDecorator.java @@ -0,0 +1,185 @@ +/* + * 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.context; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; +import javax.naming.NotContextException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +/** + * Akin to {@link URLPreferringContextDecorator} this class implements the similar logic + * for {@link DirContext}s. + * + * @author Lukas Krejci + */ +public class URLPreferringDirContextDecorator extends URLPreferringContextDecorator implements DirContext { + + private static final long serialVersionUID = 1L; + + public URLPreferringDirContextDecorator() { + super(null); + } + + public URLPreferringDirContextDecorator(DirContext ctx) { + super(ctx); + } + + protected DirContext checkAndCast(Context ctx) throws NamingException { + if (!(ctx instanceof DirContext)) { + if (ctx == null) { + throw new NoInitialContextException(); + } else { + throw new NotContextException( + "Not an instance of DirContext"); + } + } + + return (DirContext) ctx; + } + + @Override + protected DirContext getURLOrDefaultInitCtx(Name name) throws NamingException { + Context ctx = super.getURLOrDefaultInitCtx(name); + return checkAndCast(ctx); + } + + @Override + protected DirContext getURLOrDefaultInitCtx(String name) throws NamingException { + Context ctx = super.getURLOrDefaultInitCtx(name); + return checkAndCast(ctx); + } + + public Attributes getAttributes(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).getAttributes(name); + } + + public Attributes getAttributes(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).getAttributes(name); + } + + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + return getURLOrDefaultInitCtx(name).getAttributes(name, attrIds); + } + + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + return getURLOrDefaultInitCtx(name).getAttributes(name, attrIds); + } + + public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException { + getURLOrDefaultInitCtx(name).modifyAttributes(name, mod_op, attrs); + } + + public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException { + getURLOrDefaultInitCtx(name).modifyAttributes(name, mod_op, attrs); + } + + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException { + getURLOrDefaultInitCtx(name).modifyAttributes(name, mods); + } + + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { + getURLOrDefaultInitCtx(name).modifyAttributes(name, mods); + } + + public void bind(Name name, Object obj, Attributes attrs) throws NamingException { + getURLOrDefaultInitCtx(name).bind(name, obj, attrs); + } + + public void bind(String name, Object obj, Attributes attrs) throws NamingException { + getURLOrDefaultInitCtx(name).bind(name, obj, attrs); + } + + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException { + getURLOrDefaultInitCtx(name).rebind(name, obj, attrs); + } + + public void rebind(String name, Object obj, Attributes attrs) throws NamingException { + getURLOrDefaultInitCtx(name).rebind(name, obj, attrs); + } + + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + return getURLOrDefaultInitCtx(name).createSubcontext(name, attrs); + } + + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + return getURLOrDefaultInitCtx(name).createSubcontext(name, attrs); + } + + public DirContext getSchema(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).getSchema(name); + } + + public DirContext getSchema(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).getSchema(name); + } + + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + return getURLOrDefaultInitCtx(name).getSchemaClassDefinition(name); + } + + public DirContext getSchemaClassDefinition(String name) throws NamingException { + return getURLOrDefaultInitCtx(name).getSchemaClassDefinition(name); + } + + public NamingEnumeration<SearchResult> + search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, matchingAttributes, attributesToReturn); + } + + public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, matchingAttributes, attributesToReturn); + } + + public NamingEnumeration<SearchResult> search(Name name, Attributes matchingAttributes) throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, matchingAttributes); + } + + public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes) throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, matchingAttributes); + } + + public NamingEnumeration<SearchResult> search(Name name, String filter, SearchControls cons) throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, filter, cons); + } + + public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons) + throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, filter, cons); + } + + public NamingEnumeration<SearchResult> + search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, filterExpr, filterArgs, cons); + } + + public NamingEnumeration<SearchResult> search(String name, String filterExpr, Object[] filterArgs, + SearchControls cons) throws NamingException { + return getURLOrDefaultInitCtx(name).search(name, filterExpr, filterArgs, cons); + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventContextDecorator.java new file mode 100644 index 0000000..e3298a0 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventContextDecorator.java @@ -0,0 +1,81 @@ +/* + * 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.context; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; +import javax.naming.NotContextException; +import javax.naming.event.EventContext; +import javax.naming.event.NamingListener; + +/** + * + * + * @author Lukas Krejci + */ +public class URLPreferringEventContextDecorator extends URLPreferringContextDecorator implements EventContext { + + private static final long serialVersionUID = 1L; + + public URLPreferringEventContextDecorator() { + super(null); + } + + public URLPreferringEventContextDecorator(EventContext ctx) { + super(ctx); + } + + protected EventContext checkAndCast(Context ctx) throws NamingException { + if (!(ctx instanceof EventContext)) { + if (ctx == null) { + throw new NoInitialContextException(); + } else { + throw new NotContextException( + "Not an instance of EventContext"); + } + } + + return (EventContext) ctx; + } + + @Override + protected EventContext getOriginal() throws NamingException { + return checkAndCast(super.getOriginal()); + } + + public void addNamingListener(Name target, int scope, NamingListener l) throws NamingException { + getOriginal().addNamingListener(target, scope, l); + } + + public void addNamingListener(String target, int scope, NamingListener l) throws NamingException { + getOriginal().addNamingListener(target, scope, l); + } + + public void removeNamingListener(NamingListener l) throws NamingException { + getOriginal().removeNamingListener(l); + } + + public boolean targetMustExist() throws NamingException { + return getOriginal().targetMustExist(); + } + +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventDirContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventDirContextDecorator.java new file mode 100644 index 0000000..4925297 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringEventDirContextDecorator.java @@ -0,0 +1,104 @@ +/* + * 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.context; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; +import javax.naming.NotContextException; +import javax.naming.directory.SearchControls; +import javax.naming.event.EventDirContext; +import javax.naming.event.NamingListener; + +/** + * + * + * @author Lukas Krejci + */ +public class URLPreferringEventDirContextDecorator extends URLPreferringDirContextDecorator implements EventDirContext { + + private static final long serialVersionUID = 1L; + + public URLPreferringEventDirContextDecorator() { + super(null); + } + + public URLPreferringEventDirContextDecorator(EventDirContext ctx) { + super(ctx); + } + + @Override + protected EventDirContext checkAndCast(Context ctx) throws NamingException { + if (!(ctx instanceof EventDirContext)) { + if (ctx == null) { + throw new NoInitialContextException(); + } else { + throw new NotContextException( + "Not an instance of EventDirContext"); + } + } + + return (EventDirContext) ctx; + } + + @Override + protected EventDirContext getOriginal() throws NamingException { + return checkAndCast(super.getOriginal()); + } + + public void addNamingListener(Name target, int scope, NamingListener l) throws NamingException { + getOriginal().addNamingListener(target, scope, l); + } + + public void addNamingListener(String target, int scope, NamingListener l) throws NamingException { + getOriginal().addNamingListener(target, scope, l); + } + + public void removeNamingListener(NamingListener l) throws NamingException { + getOriginal().removeNamingListener(l); + } + + public boolean targetMustExist() throws NamingException { + return getOriginal().targetMustExist(); + } + + public void addNamingListener(Name target, String filter, SearchControls ctls, NamingListener l) + throws NamingException { + getOriginal().addNamingListener(target, filter, ctls, l); + } + + public void addNamingListener(String target, String filter, SearchControls ctls, NamingListener l) + throws NamingException { + getOriginal().addNamingListener(target, filter, ctls, l); + } + + public void + addNamingListener(Name target, String filter, Object[] filterArgs, SearchControls ctls, NamingListener l) + throws NamingException { + getOriginal().addNamingListener(target, filter, filterArgs, ctls, l); + } + + public void addNamingListener(String target, String filter, Object[] filterArgs, SearchControls ctls, + NamingListener l) throws NamingException { + getOriginal().addNamingListener(target, filter, filterArgs, ctls, l); + } + +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringLdapContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringLdapContextDecorator.java new file mode 100644 index 0000000..c74df39 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringLdapContextDecorator.java @@ -0,0 +1,94 @@ +/* + * 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.context; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; +import javax.naming.NotContextException; +import javax.naming.ldap.Control; +import javax.naming.ldap.ExtendedRequest; +import javax.naming.ldap.ExtendedResponse; +import javax.naming.ldap.LdapContext; + +/** + * + * + * @author Lukas Krejci + */ +public class URLPreferringLdapContextDecorator extends URLPreferringDirContextDecorator implements LdapContext { + + private static final long serialVersionUID = 1L; + + public URLPreferringLdapContextDecorator() { + super(null); + } + + public URLPreferringLdapContextDecorator(LdapContext original) { + super(original); + } + + @Override + protected LdapContext checkAndCast(Context ctx) throws NamingException { + if (!(ctx instanceof LdapContext)) { + if (ctx == null) { + throw new NoInitialContextException(); + } else { + throw new NotContextException("Not an instance of LdapContext"); + } + } + + return (LdapContext) ctx; + } + + @Override + protected LdapContext getOriginal() throws NamingException { + return checkAndCast(super.getOriginal()); + } + + public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { + return getOriginal().extendedOperation(request); + } + + public LdapContext newInstance(Control[] requestControls) throws NamingException { + return new URLPreferringLdapContextDecorator(getOriginal().newInstance(requestControls)); + } + + public void reconnect(Control[] connCtls) throws NamingException { + getOriginal().reconnect(connCtls); + } + + public Control[] getConnectControls() throws NamingException { + return getOriginal().getConnectControls(); + } + + public void setRequestControls(Control[] requestControls) throws NamingException { + getOriginal().setRequestControls(requestControls); + } + + public Control[] getRequestControls() throws NamingException { + return getOriginal().getRequestControls(); + } + + public Control[] getResponseControls() throws NamingException { + return getOriginal().getResponseControls(); + } + +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java new file mode 100644 index 0000000..d938c50 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java @@ -0,0 +1,45 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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.mbean; + +import javax.naming.spi.NamingManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.rhq.jndi.AccessCheckingInitialContextFactoryBuilder; + +/** + * + * + * @author Lukas Krejci + */ +public class AccessCheckingInitialContextFactoryBuilderInstaller implements AccessCheckingInitialContextFactoryBuilderInstallerMBean { + private static final Log LOG = LogFactory.getLog(AccessCheckingInitialContextFactoryBuilder.class); + + public void start() throws Exception { + LOG.info("Installing RHQ's access permission checking initial context factory builder"); + + NamingManager.setInitialContextFactoryBuilder(new AccessCheckingInitialContextFactoryBuilder()); + } + + public void stop() throws Exception { + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstallerMBean.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstallerMBean.java new file mode 100644 index 0000000..87d19ab --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstallerMBean.java @@ -0,0 +1,32 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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.mbean; + +/** + * + * + * @author Lukas Krejci + */ +public interface AccessCheckingInitialContextFactoryBuilderInstallerMBean { + + void start() throws Exception; + + void stop() throws Exception; +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java new file mode 100644 index 0000000..356a6e9 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java @@ -0,0 +1,46 @@ +/* + * 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.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.List; + +public class DecoratingInvocationHandler<Type, Decorator extends Type> implements InvocationHandler { + + private final List<DecoratorPicker<Type, Decorator>> pickers; + private Type object; + + public DecoratingInvocationHandler(List<DecoratorPicker<Type, Decorator>> pickers, Type object) { + this.pickers = pickers; + this.object = object; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Type target = object; + Class<?> methodClass = method.getDeclaringClass(); + + for(DecoratorPicker<Type, Decorator> picker : pickers) { + target = picker.decorate(target, methodClass); + } + + return method.invoke(target, args); + } +} \ No newline at end of file diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java new file mode 100644 index 0000000..017d29f --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorPicker.java @@ -0,0 +1,195 @@ +/* + * 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.util; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * Given as set of decorators extending given type, this class can pick + * the most appropriate set of decorators for a class or a method call. + * <p> + * To configure the decorator, one has to provide a {@link DecoratorSetContext} that + * is then used to obtain the list of + * {@link DecoratorSetContext#getSupportedInterfaces() supported interfaces}, which are + * all the interfaces that should be used for decorator resolution (i.e. all other interfaces + * that a class might implement are ignored during decorator resolution), the list of + * {@link DecoratorSetContext#getDecoratorClasses() decorator classes}, which is a list + * of decorators the picker can choose from and is also used to instantiate and initialize + * the decorators. + * + * @author Lukas Krejci + */ +public class DecoratorPicker<Type, Decorator extends Type> { + + private DecoratorSetContext<Type, Decorator> context; + + public DecoratorSetContext<Type, Decorator> getContext() { + return context; + } + + public void setContext(DecoratorSetContext<Type, Decorator> decoratorSetContext) { + this.context = decoratorSetContext; + } + + /** + * Returns a set of decorators applicable for given method. The set is established based + * on the declaring class of the method. + * + * @param method the method to inspect + * @return the set of decorators that can be used to wrap a method call + * @throws Exception + */ + public Set<Decorator> getDecoratorsForMethod(Method method) throws Exception { + return getDecoratorsForClass_Private(method.getDeclaringClass()); + } + + /** + * Returns a set of decorators that can be used on instances of given class. + * @param cls the class to inspect + * @return + * @throws Exception + */ + public Set<Decorator> getDecoratorsForClass(Class<? extends Type> cls) throws Exception { + return getDecoratorsForClass_Private(cls); + } + + /** + * This method first establishes the set of decorators to use based on the class of the supplied + * object and then chains the decorators (in arbitrary order) with the supplied object at the + * "root" of the chain. + * <p> + * If a method is then called on the returned object, the methods of all the decorators are called + * in chain (each supposedly calling the next) and finally, at the end of the chain, the method on + * the original object (the one supplied to this method) is called. + * <p> + * Note that the above is only an intended behavior and actually depends on the implementation of + * the decorators that are resposinble for the chaining. Each decorator is initialized + * (@{link {@link DecoratorSetContext#init(Object, Object)} which should set it up for such chaining. + * + * @param object + * @return + * @throws Exception + */ + public Type decorate(Type object) throws Exception { + Set<Decorator> decs = getDecoratorsForClass_Private(object.getClass()); + Type ret = object; + for(Decorator d : decs) { + context.init(d, ret); + ret = d; + } + + return ret; + } + + /** + * Similar to {@link #decorate(Object)} but instead of the class of the object itself, + * uses the significantSuperClass as the basis for the decorator resolution. + * <p> + * This is important, because if the object implements two mutually incompatible sub-interfaces of <code>Type</code>, + * the chained decorators might fail to execute a method later on if the decorator depends on the upper part + * of the chain to implement certain sub-interface of <code>Type</code>. + * + * @param object the object to wrap in decorators + * @param significantSuperClass the class to base the decorator resolution on + * @return + * @throws Exception + */ + public Type decorate(Type object, Class<?> significantSuperClass) throws Exception { + Set<Decorator> decs = getDecoratorsForClass_Private(significantSuperClass); + Type ret = object; + for(Decorator d : decs) { + context.init(d, ret); + ret = d; + } + + return ret; + } + + private Set<Decorator> getDecoratorsForClass_Private(Class<?> cls) throws Exception { + Set<Class<? extends Type>> ifaces = getNearestApplicableInterfaces(cls); + + HashSet<Decorator> ret = new HashSet<Decorator>(); + + for (Class<? extends Type> iface : ifaces) { + for (Class<? extends Decorator> decClass : getMatch(iface)) { + ret.add(context.instantiate(decClass)); + } + } + + return ret; + } + + private Set<Class<? extends Type>> getNearestApplicableInterfaces(Class<?> cls) { + List<Class<? extends Type>> ifaces = new ArrayList<Class<? extends Type>>(getAllApplicableInterfaces(cls)); + + //now compact the set to only contain the most concrete interfaces + + Iterator<Class<? extends Type>> it = ifaces.iterator(); + while (it.hasNext()) { + Class<? extends Type> c = it.next(); + + for (int i = 0; i < ifaces.size(); ++i) { + Class<? extends Type> nextC = ifaces.get(i); + if (!c.equals(nextC) && c.isAssignableFrom(nextC)) { + it.remove(); + break; + } + } + } + + return new HashSet<Class<? extends Type>>(ifaces); + } + + private Set<Class<? extends Type>> getAllApplicableInterfaces(Class<?> cls) { + Set<Class<? extends Type>> ifaces = new HashSet<Class<? extends Type>>(); + + for (Class<? extends Type> iface : context.getSupportedInterfaces()) { + if (iface.isAssignableFrom(cls)) { + ifaces.add(iface); + } + } + + if (ifaces.isEmpty()) { + throw new IllegalArgumentException("Class " + cls + + " doesn't implement any of the applicable interfaces. Cannot find decorators for it."); + } + + return ifaces; + } + + private Set<Class<? extends Decorator>> getMatch(Class<?> targetIface) { + + Set<Class<? extends Decorator>> ret = new HashSet<Class<? extends Decorator>>(); + + for (Class<? extends Decorator> cls : context.getDecoratorClasses()) { + if (Arrays.asList(cls.getInterfaces()).contains(targetIface)) { + ret.add(cls); + } + } + + return ret; + } +} diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java new file mode 100644 index 0000000..d7b1676 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratorSetContext.java @@ -0,0 +1,59 @@ +/* + * 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.util; + +import java.util.Set; + +/** + * Implementations of this interface provide a context to the {@link DecoratorPicker}. + * + * @author Lukas Krejci + */ +public interface DecoratorSetContext<Type, Decorator> { + + /** + * @return the set of interfaces that the decorators are able support. + * Usually this should be just a union of all interfaces the decorators implement + * but can be trimmed down. + */ + Set<Class<? extends Type>> getSupportedInterfaces(); + + /** + * @return the set of all decorator classes in this set + */ + Set<Class<? extends Decorator>> getDecoratorClasses(); + + /** + * Instantiates a new decorator of given class. + * @param decoratorClass + * @return + * @throws Exception + */ + Decorator instantiate(Class<? extends Decorator> decoratorClass) throws Exception; + + /** + * Initializes the decorator to decorate given object. + * + * @param decorator + * @param object + * @throws Exception on error + */ + void init(Decorator decorator, Type object) throws Exception; +} diff --git a/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java new file mode 100644 index 0000000..45ba114 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratingInvocationHandlerTest.java @@ -0,0 +1,145 @@ +/* + * 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.context; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Set; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.event.EventContext; +import javax.naming.event.NamingListener; +import javax.naming.spi.InitialContextFactory; +import javax.naming.spi.NamingManager; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import org.rhq.jndi.AccessCheckingInitialContextFactoryBuilder; + +/** + * + * + * @author Lukas Krejci + */ +@Test +public class DecoratingInvocationHandlerTest { + private static final Set<String> INVOKED_METHODS = new HashSet<String>(); + + private static final InvocationHandler NOTE_TAKING_HANDLER = new InvocationHandler() { + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + INVOKED_METHODS.add(method.getName()); + + if ("hashCode".equals(method.getName())) { + return 0; + } else if ("equals".equals(method.getName())) { + return false; + } + + return null; + } + }; + + private static Class<?>[] CONTEXT_INTERFACES; + + public static class Factory implements InitialContextFactory { + public Context getInitialContext(Hashtable<?, ?> environment) + throws NamingException { + + return (Context) Proxy.newProxyInstance(DecoratingInvocationHandlerTest.class.getClassLoader(), CONTEXT_INTERFACES, NOTE_TAKING_HANDLER); + } + } + + private static class DummyInitialEventContext extends InitialContext implements EventContext { + + /** + * @param environment + * @throws NamingException + */ + public DummyInitialEventContext(Hashtable<?, ?> environment) throws NamingException { + super(environment); + } + + public void addNamingListener(Name target, int scope, NamingListener l) throws NamingException { + ((EventContext)getURLOrDefaultInitCtx(target)).addNamingListener(target, scope, l); + } + + public void addNamingListener(String target, int scope, NamingListener l) throws NamingException { + ((EventContext)getURLOrDefaultInitCtx(target)).addNamingListener(target, scope, l); + } + + public void removeNamingListener(NamingListener l) throws NamingException { + ((EventContext)getDefaultInitCtx()).removeNamingListener(l); + } + + public boolean targetMustExist() throws NamingException { + return ((EventContext)getDefaultInitCtx()).targetMustExist(); + } + + + } + + @BeforeClass + public void setBuilder() throws Exception { + NamingManager.setInitialContextFactoryBuilder(new AccessCheckingInitialContextFactoryBuilder()); + } + + public void testSimpleDispatch() throws Exception { + INVOKED_METHODS.clear(); + Properties env = new Properties(); + env.put(Context.INITIAL_CONTEXT_FACTORY, Factory.class.getName()); + + CONTEXT_INTERFACES = new Class<?>[] { Context.class }; + + InitialContext ctx = new InitialContext(env); + + ctx.lookup("asdf"); + + assert INVOKED_METHODS.contains("lookup") : "The lookup doesn't seem to have propagated to the actual context to be used."; + } + + public void testMultiInterfaceDispatch() throws Exception { + INVOKED_METHODS.clear(); + Properties env = new Properties(); + env.put(Context.INITIAL_CONTEXT_FACTORY, Factory.class.getName()); + + CONTEXT_INTERFACES = new Class<?>[] { EventContext.class, DirContext.class }; + + InitialContext ctx = new InitialContext(env); + + ctx.lookup("asdf"); + + DummyInitialEventContext ectx = new DummyInitialEventContext(env); + + ectx.addNamingListener("hodiny", 0, null); + + assert INVOKED_METHODS.contains("lookup") : "The lookup doesn't seem to have propagated to the actual context to be used."; + assert INVOKED_METHODS.contains("addNamingListener") : "The addNamingListener doesn't seem to have propagated to the actual context to be used."; + } +} diff --git a/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java new file mode 100644 index 0000000..e2d7429 --- /dev/null +++ b/modules/enterprise/server/container-lib/src/test/java/org/rhq/jndi/context/DecoratorPickerTest.java @@ -0,0 +1,180 @@ +/* + * 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.context; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import javax.naming.event.EventContext; +import javax.naming.ldap.LdapContext; + +import org.testng.annotations.Test; + +import org.rhq.jndi.util.DecoratorPicker; +import org.rhq.jndi.util.DecoratorSetContext; + +/** + * @author Lukas Krejci + */ +@Test +public class DecoratorPickerTest { + + private static final InvocationHandler DUMMY_HANDLER = new InvocationHandler() { + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("hashCode".equals(method.getName())) { + return 0; + } else if ("equals".equals(method.getName())) { + return false; + } + + return null; + } + }; + + private static final Class<?> TEST_OBJECT_CLASS1 = createProxyClass(Context.class); + private static final Class<?> TEST_OBJECT_CLASS2 = createProxyClass(DirContext.class); + private static final Class<?> TEST_OBJECT_CLASS3 = createProxyClass(LdapContext.class); + private static final Class<?> TEST_OBJECT_CLASS4 = createProxyClass(LdapContext.class, EventContext.class); + private static final Class<?> DECORATOR_CLASS1 = createProxyClass(Context.class); + private static final Class<?> DECORATOR_CLASS2 = createProxyClass(EventContext.class); + private static final Class<?> DECORATOR_CLASS3 = createProxyClass(LdapContext.class); + private static final Class<?> DECORATOR_CLASS4 = createProxyClass(DirContext.class); + + public void testSimpleDecoratorIdentifiedByClass() throws Exception { + DecoratorPicker<Object, Object> picker = createTestPicker(); + + Set<Object> contextDecorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS1); + assertEquals(contextDecorators.size(), 1, "Expected exactly one decorator for Context class"); + assertEquals(contextDecorators.iterator().next().getClass().getInterfaces()[0], Context.class); + } + + public void testSuperClassDecoratorHasPrecedenceOverSubClassDecorator() throws Exception { + DecoratorPicker<Object, Object> picker = createTestPicker(); + + //this tests that the LdapContext isn't returned even though it subclasses the DirContext + Set<Object> contextDecorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS2); + assertEquals(contextDecorators.size(), 1, "Expected exactly one decorator for DirContext class"); + assertEquals(contextDecorators.iterator().next().getClass().getInterfaces()[0], DirContext.class); + } + + public void testSubClassDecoratorCorrectlyIdentified() throws Exception { + DecoratorPicker<Object, Object> picker = createTestPicker(); + + Set<Object> contextDecorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS3); + assertEquals(contextDecorators.size(), 1, "Expected exactly one decorator for LdapContext class"); + assertEquals(contextDecorators.iterator().next().getClass().getInterfaces()[0], LdapContext.class); + } + + public void testMultipleDecoratorsDetectable() throws Exception { + DecoratorPicker<Object, Object> picker = createTestPicker(); + + Set<Object> decorators = picker.getDecoratorsForClass(TEST_OBJECT_CLASS4); + assertEquals(decorators.size(), 2, + "Exactly 2 decorators should have been found for a class implementing 2 interfaces."); + + boolean ldapContextDecoratorFound = false; + boolean eventContextDecoratorFound = false; + + for (Object d : decorators) { + if (LdapContext.class.isAssignableFrom(d.getClass())) { + ldapContextDecoratorFound = true; + continue; //just to make sure that somehow the decorator doesn't implement both + } + if (EventContext.class.isAssignableFrom(d.getClass())) { + eventContextDecoratorFound = true; + } + } + + assertTrue(ldapContextDecoratorFound && eventContextDecoratorFound, + "The found decorators don't implement the desired interfaces."); + } + + public void testDecoratorsIdentifiedByMethod() throws Exception { + DecoratorPicker<Object, Object> picker = createTestPicker(); + + Set<Object> decorators = + picker.getDecoratorsForMethod(LdapContext.class.getMethod("getConnectControls", (Class<?>[]) null)); + assertEquals(decorators.size(), 1, + "Expected exactly one decorator for method 'getConnectControls()' from LdapContext class"); + assertEquals(decorators.iterator().next().getClass().getInterfaces()[0], LdapContext.class); + } + + public void testMethodFromSubclassMatchesSubclassDecorator() throws Exception { + DecoratorPicker<Object, Object> picker = createTestPicker(); + + //this is a method from the DirContext but we're asking for it from a class + //that implements also an LdapContext + //The LdapContext decorator also inherits from the DirContext decorator + //(by the virtue of LdapContext interface inheriting from the DirContext) + //The picker should therefore match the LdapContext decorator because its + //the "closest" one to the actual class. + Set<Object> decorators = + picker.getDecoratorsForMethod(TEST_OBJECT_CLASS3.getMethod("getSchemaClassDefinition", + new Class<?>[] { String.class })); + assertEquals(decorators.size(), 1, + "Expected exactly one decorator for method 'getSchemaClassDefinition(String)' from LdapContext class"); + assertEquals(decorators.iterator().next().getClass().getInterfaces()[0], LdapContext.class); + } + + private static Class<?> createProxyClass(Class<?>... ifaces) { + return Proxy.getProxyClass(DecoratorPickerTest.class.getClassLoader(), ifaces); + } + + private static DecoratorPicker<Object, Object> createTestPicker() { + DecoratorPicker<Object, Object> picker = new DecoratorPicker<Object, Object>(); + + DecoratorSetContext<Object, Object> decSet = new DecoratorSetContext<Object, Object>() { + + public Object instantiate(Class<? extends Object> decoratorClass) throws Exception { + Constructor<? extends Object> ctor = decoratorClass.getConstructor(InvocationHandler.class); + return ctor.newInstance(DUMMY_HANDLER); + } + + public void init(Object decorator, Object object) throws Exception { + } + + @SuppressWarnings("unchecked") + public Set<Class<? extends Object>> getSupportedInterfaces() { + return new HashSet<Class<? extends Object>>(Arrays.asList(Context.class, EventContext.class, + LdapContext.class, DirContext.class)); + } + + @SuppressWarnings("unchecked") + public Set<Class<? extends Object>> getDecoratorClasses() { + return new HashSet<Class<? extends Object>>(Arrays.asList(DECORATOR_CLASS1, DECORATOR_CLASS2, DECORATOR_CLASS3, DECORATOR_CLASS4)); + } + }; + + picker.setContext(decSet); + + return picker; + } +} diff --git a/modules/enterprise/server/container/src/main/resources/jbossas/server/default/conf/jboss-service.xml b/modules/enterprise/server/container/src/main/resources/jbossas/server/default/conf/jboss-service.xml index 859b552..f231f15 100644 --- a/modules/enterprise/server/container/src/main/resources/jbossas/server/default/conf/jboss-service.xml +++ b/modules/enterprise/server/container/src/main/resources/jbossas/server/default/conf/jboss-service.xml @@ -225,6 +225,13 @@ <!-- JNDI --> <!-- ==================================================================== -->
+ <!-- This installs a custom initial context factory builder into the JVM + that will ensure that all the default InitialContexts are going to check + for the permission to access the RHQ internals. --> + <mbean code="org.rhq.jndi.mbean.AccessCheckingInitialContextFactoryBuilderInstaller" + name="org.rhq:service=AccessCheckingInitialContextFactoryBuilderInstaller"> + </mbean> + <!-- A simple mbean wrapper around the jndi Naming object. This only handles an in memory instance. The NamingService uses this as the JNDI store and exposes it remotely. diff --git a/modules/enterprise/server/itests/pom.xml b/modules/enterprise/server/itests/pom.xml index 8753e6f..6898621 100644 --- a/modules/enterprise/server/itests/pom.xml +++ b/modules/enterprise/server/itests/pom.xml @@ -56,6 +56,12 @@
<dependency> <groupId>org.rhq</groupId> + <artifactId>rhq-container-lib</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.rhq</groupId> <artifactId>rhq-enterprise-server</artifactId> <version>${project.version}</version> <scope>test</scope> diff --git a/modules/enterprise/server/jar/pom.xml b/modules/enterprise/server/jar/pom.xml index 6eaf304..92f4cf1 100644 --- a/modules/enterprise/server/jar/pom.xml +++ b/modules/enterprise/server/jar/pom.xml @@ -78,6 +78,13 @@ <version>${project.version}</version> </dependency>
+ <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>rhq-container-lib</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>com.googlecode.java-diff-utils</groupId> <artifactId>diffutils</artifactId> diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/auth/SessionManager.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/auth/SessionManager.java index 39b6076..ecf2f6f 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/auth/SessionManager.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/auth/SessionManager.java @@ -26,29 +26,41 @@ import java.util.Random;
import org.rhq.core.domain.auth.Subject; import org.rhq.enterprise.server.util.LookupUtil; +import org.rhq.jndi.AllowRhqServerInternalsAccessPermission;
/** * This is the JON Server's own session ID generator. It is outside any container-provided session mechanism. Its sole * purpose is to provide session IDs to logged in {@link Subject}s. It will timeout those sessions regardless of any * container-provided session-timeout mechanism. - * + * <p> + * Because this is a very security-sensitive class, any public method requires the caller to + * have the {@link AllowEjbAccessPermission} as any other calls to the EJB layer. This is so that the + * malicious users can't trick the EJB layer into thinking that some users are logged in or log out other + * users. + * <p> + * Also, for security reasons, this class is final so that malicious code can't subclass it and modify its + * behavior. + * * <p>This object is a {@link #getInstance() singleton}.</p> */ -public class SessionManager { +public final class SessionManager { + + private static final AllowRhqServerInternalsAccessPermission ACCESS_PERMISSION = new AllowRhqServerInternalsAccessPermission(); + /** * Our source for random session IDs. */ - private static Random _random = new Random(); + private static final Random _random = new Random();
/** * Our session cache that is keyed on the session ID. */ - private static Map<Integer, AuthSession> _cache = new HashMap<Integer, AuthSession>(); + private static final Map<Integer, AuthSession> _cache = new HashMap<Integer, AuthSession>();
/** * The singleton instance */ - private static SessionManager _manager = new SessionManager(); + private static final SessionManager _manager = new SessionManager();
/** * The timeout for all user sessions. @@ -78,7 +90,7 @@ public class SessionManager {
/** * Return the singleton object. - * + * * @return the {@link SessionManager} */ public static SessionManager getInstance() { @@ -95,6 +107,7 @@ public class SessionManager { * sessionId will be assigned. */ public Subject put(Subject subject) { + checkPermission(); return put(subject, DEFAULT_TIMEOUT); }
@@ -108,6 +121,7 @@ public class SessionManager { * that Subject is overlord). The sessionId will be assigned. */ public synchronized Subject put(Subject subject, long timeout) { + checkPermission(); Integer key;
do { @@ -141,6 +155,7 @@ public class SessionManager { * @throws SessionTimeoutException */ public synchronized Subject getSubject(int sessionId) throws SessionNotFoundException, SessionTimeoutException { + checkPermission(); Integer id = new Integer(sessionId); AuthSession session = _cache.get(id);
@@ -162,6 +177,7 @@ public class SessionManager { * @param sessionId session id to invalidate */ public synchronized void invalidate(int sessionId) { + checkPermission(); _cache.remove(new Integer(sessionId));
// while we are here, let's go through the entire session cache and remove expired sessions @@ -187,6 +203,7 @@ public class SessionManager { * @param username username for the sessions to be invalidated */ public synchronized void invalidate(String username) { + checkPermission(); List<Integer> doomedSessionIds = new ArrayList<Integer>(_cache.size()); for (AuthSession s : _cache.values()) { if (username.equals(s.getSubject(false).getName())) { @@ -201,6 +218,7 @@ public class SessionManager { }
public long getlastAccess(int sessionId) { + checkPermission(); AuthSession session = _cache.get(sessionId); if (session == null) { return -1; @@ -209,6 +227,7 @@ public class SessionManager { }
public Subject getOverlord() { + checkPermission(); if (overlordSubject == null) { overlordSubject = LookupUtil.getSubjectManager().getSubjectById(OVERLORD_SUBJECT_ID);
@@ -256,4 +275,9 @@ public class SessionManager {
return copy; } + + private static void checkPermission() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) sm.checkPermission(ACCESS_PERMISSION); + } } \ No newline at end of file diff --git a/modules/enterprise/server/jar/src/test/resources/embedded-jboss-beans.xml b/modules/enterprise/server/jar/src/test/resources/embedded-jboss-beans.xml index 05edefa..85e2fb3 100644 --- a/modules/enterprise/server/jar/src/test/resources/embedded-jboss-beans.xml +++ b/modules/enterprise/server/jar/src/test/resources/embedded-jboss-beans.xml @@ -4,6 +4,14 @@ xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0">
+ <!-- This installs a custom initial context factory builder into the JVM + that will ensure that all the default InitialContexts are going to check + for the permission to access the RHQ internals. --> + <bean class="org.rhq.jndi.mbean.AccessCheckingInitialContextFactoryBuilderInstaller" + name="AccessCheckingInitialContextFactoryBuilderInstaller"> + </bean> + + <bean name="Naming" class="org.jnp.server.SingletonNamingServer"/>
<bean name="InitialContextProperties" class="java.util.Hashtable"> @@ -20,7 +28,7 @@ </entry> </map> </parameter> - </constructor> + </constructor> </bean>
<bean name="java:comp/Initializer" class="org.jboss.ejb3.embedded.JavaCompInitializer"> diff --git a/modules/integration-tests/jndi-access/jndi-access-test/pom.xml b/modules/integration-tests/jndi-access/jndi-access-test/pom.xml new file mode 100644 index 0000000..1991e0e --- /dev/null +++ b/modules/integration-tests/jndi-access/jndi-access-test/pom.xml @@ -0,0 +1,315 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd%22%3E + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>jndi-access-test-parent</artifactId> + <groupId>org.rhq</groupId> + <version>4.3.0-SNAPSHOT</version> + </parent> + + <groupId>org.rhq</groupId> + <artifactId>jndi-access-test</artifactId> + <packaging>jar</packaging> + + <name>JNDI access integration test</name> + <description>Tests for local and remote JNDI access from within serverside scripts.</description> + + <properties> + <rhq.server.datasource>java:/RHQDS</rhq.server.datasource> + <rhq.server.ds-mapping>PostgreSQL</rhq.server.ds-mapping> + <jboss-embeddable-ejb3.version>1.0.0.Alpha9</jboss-embeddable-ejb3.version> + <jnp.port>54987</jnp.port> + <jnp.rmiPort>54988</jnp.rmiPort> + </properties> + + <dependencies> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.14</version> + <scope>runtime</scope> + </dependency> + + <!--================ Test Deps ================ --> + + <!-- Note, the test deps are intentionally placed above the other + scoped deps because of classpath reasons. Maven orders the [test] classpath + in the order listed in the pom. We specifically need the embeddable-ejb3 + jar above the standard ejb3 jars because we need the embeddble packages loaded + when testing. --> + + <dependency> + <groupId>org.rhq</groupId> + <artifactId>test-utils</artifactId> + <version>${project.version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.testng</groupId> + <artifactId>testng</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>org.rhq</groupId> + <artifactId>rhq-core-domain</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.rhq</groupId> + <artifactId>rhq-enterprise-server</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.rhq</groupId> + <artifactId>rhq-enterprise-server</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>rhq-core-client-api</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>rhq-container-lib</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>rhq-server-client-api</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.rhq</groupId> + <artifactId>jndi-access-remote-server</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>jboss.jboss-embeddable-ejb3</groupId> + <artifactId>jboss-ejb3-all</artifactId> + <version>${jboss-embeddable-ejb3.version}</version> + <scope>test</scope> + </dependency> + + <!-- NOTE: The remaining test deps correspond to the classes contained + in hibernate-all.jar and thirdparty-all.jar. --> + + <dependency> + <groupId>javassist</groupId> + <artifactId>javassist</artifactId> + <scope>test</scope> + </dependency> + + <!-- needed by embedded ejb container --> + <dependency> + <groupId>trove</groupId> + <artifactId>trove</artifactId> + <version>1.0.2</version> + <scope>test</scope> + </dependency> + + <!-- needed by embedded ejb container --> + <dependency> + <groupId>xerces</groupId> + <artifactId>xercesImpl</artifactId> + <version>2.8.1</version> + <scope>test</scope> + </dependency> + + <!-- 3rd Party Deps --> + + <!-- required by RHQ server classes, as well as EJB3 Embedded --> + <dependency> + <groupId>dom4j</groupId> + <artifactId>dom4j</artifactId> + <version>1.6.1-jboss</version> + <scope>runtime</scope> + </dependency> + + <dependency> + <groupId>hibernate</groupId> + <artifactId>hibernate3</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement + section. --> + <scope>provided</scope> <!-- by JBossAS --> + </dependency> + + <dependency> + <groupId>hibernate-annotations</groupId> + <artifactId>hibernate-annotations</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement + section. --> + <scope>provided</scope> <!-- by JBossAS --> + </dependency> + + <dependency> + <groupId>hibernate-entitymanager</groupId> + <artifactId>hibernate-entitymanager</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>javax.mail</groupId> + <artifactId>mail</artifactId> + <version>1.4</version> + </dependency> + + <dependency> + <groupId>javax.persistence</groupId> + <artifactId>persistence-api</artifactId> + <version>1.0</version> + <scope>provided</scope> <!-- by JBossAS --> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-annotations-ejb3</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement + section. --> + <scope>provided</scope> <!-- by JBossAS --> + </dependency> + + <!-- includes the org.jboss.ejb3.StrictMaxPool class, which is needed + by the PoolClass annotation used on some of our SLSB's --> + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-ejb3</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement + section. --> + <scope>provided</scope> <!-- by JBossAS --> + </dependency> + + <!-- for the transaction interrupt EJB3 interceptor --> + <dependency> + <groupId>org.jboss.transaction</groupId> + <artifactId>jboss-jta</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement + section. --> + <scope>provided</scope> <!-- by JBossAS --> + </dependency> + + <dependency> + <groupId>org.opensymphony.quartz</groupId> + <artifactId>quartz</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement + section. --> + <scope>provided</scope> <!-- by JBossAS itself, which the container build has packaged with 1.6.5 --> + </dependency> + + <dependency> + <groupId>org.opensymphony.quartz</groupId> + <artifactId>quartz-oracle</artifactId> + <!-- NOTE: The version is defined in the root POM's dependencyManagement + section. --> + <scope>provided</scope> <!-- by JBossAS itself, which the container build has packaged with 1.6.5 --> + </dependency> + + <!-- This is needed cglib which is in turn needed by hibernate --> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymockclassextension</artifactId> + <version>2.2</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.freemarker</groupId> + <artifactId>freemarker</artifactId> + <version>2.3.18</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.jboss.resteasy</groupId> + <artifactId>resteasy-jaxrs</artifactId> + <version>${resteasy.version}</version> + </dependency> + </dependencies> + + <build> + <testResources> + <testResource> + <directory>src/test/resources</directory> + <filtering>true</filtering> + </testResource> + </testResources> + + <plugins> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <!-- in order to get JMS to work properly in embedded + test container, extract jms-rs.rar classes --> + <execution> + <id>Extract JMS classes from RAR needed for JMS tests</id> + <phase>process-classes</phase> + <configuration> + <tasks> + <unzip src="src/test/resources/jms-ra.rar" + dest="target"> + <patternset> + <include name="jms-ra.jar" /> + </patternset> + </unzip> + <unzip src="target/jms-ra.jar" + dest="target/test-classes"> + <patternset> + <include name="org/**" /> + </patternset> + </unzip> + </tasks> + </configuration> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <!-- Everything but the web service tests, this is the standard + test execution --> + <configuration> + <excludedGroups>${rhq.testng.excludedGroups}</excludedGroups> + <groups>${rhq.testng.includedGroups}</groups> + <systemPropertyVariables> + <embeddedDeployment>true</embeddedDeployment> + <deploymentDirectory>target/test-classes</deploymentDirectory> + <hibernate.dialect>${rhq.test.ds.hibernate-dialect}</hibernate.dialect> + <clean.db>${clean.db}</clean.db> + <test.server.jar.path>${settings.localRepository}/org/rhq/jndi-access-remote-server/${project.version}/jndi-access-remote-server-${project.version}.jar</test.server.jar.path> + <jnp.port>${jnp.port}</jnp.port> + <jnp.rmiPort>${jnp.rmiPort}</jnp.rmiPort> + </systemPropertyVariables> + <argLine>-Djava.security.manager -Djava.security.policy==target/test-classes/security.policy</argLine> + <additionalClasspathElements> + <!-- The below is required for tests to run against + Oracle. --> + <additionalClasspathElement>${settings.localRepository}/com/oracle/ojdbc5/${ojdbc5.version}/ojdbc5-${ojdbc5.version}.jar</additionalClasspathElement> + </additionalClasspathElements> + </configuration> + </plugin> + + </plugins> + </build> + +</project> diff --git a/modules/integration-tests/jndi-access/jndi-access-test/src/test/java/org/rhq/jndi/test/JndiAccessTest.java b/modules/integration-tests/jndi-access/jndi-access-test/src/test/java/org/rhq/jndi/test/JndiAccessTest.java new file mode 100644 index 0000000..f20d4f1 --- /dev/null +++ b/modules/integration-tests/jndi-access/jndi-access-test/src/test/java/org/rhq/jndi/test/JndiAccessTest.java @@ -0,0 +1,193 @@ +/* + * 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 java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.SerializablePermission; +import java.security.PermissionCollection; +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.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import org.rhq.bindings.SandboxedScriptEngine; +import org.rhq.bindings.ScriptEngineFactory; +import org.rhq.bindings.StandardBindings; +import org.rhq.bindings.StandardScriptPermissions; +import org.rhq.bindings.util.PackageFinder; +import org.rhq.core.domain.auth.Subject; +import org.rhq.enterprise.client.LocalClient; +import org.rhq.enterprise.server.test.AbstractEJB3Test; +import org.rhq.enterprise.server.util.LookupUtil; +import org.rhq.jndi.AllowRhqServerInternalsAccessPermission; + +/** + * + * + * @author Lukas Krejci + */ +@Test +public class JndiAccessTest extends AbstractEJB3Test { + private static final Log JNP_SERVER_LOG = LogFactory.getLog("Test JNP Server"); + + private Process testServerProcess; + private Thread testServerStdErrReader; + private Thread testServerStdOutReader; + + @BeforeClass + @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); + } + + @AfterClass + public void stopTestJnpServer() throws Exception { + testServerProcess.destroy(); + testServerStdErrReader.join(); + testServerStdOutReader.join(); + } + + @Parameters("jnp.port") + public void testRemoteConnectionWorkingFromJava(int jnpPort) throws Exception { + Properties env = new Properties(); + env.put("java.naming.factory.initial", "org.jboss.naming.NamingContextFactory"); + env.put("java.naming.provider.url", "jnp://localhost:" + jnpPort); + InitialContext ctx = new InitialContext(env); + Object kachny = ctx.lookup("kachny"); + + assert kachny != null; + } + + public void testLocalJNDILookupFailsFromScripts() throws Exception { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "entityManagerFactory = context.lookup('java:/RHQEntityManagerFactory');\n" + + "entityManager = entityManagerFactory.createEntityManager();\n" + + "entityManager.find(java.lang.Class.forName('org.rhq.core.domain.resource.Resource'), java.lang.Integer.valueOf('10001'));"); + + Assert.fail("The script shouldn't have been able to use the EntityManager."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + @Parameters("jnp.port") + public void testRemoteJNDILookupWorksFromScripts(int jnpPort) throws Exception { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "env = new java.util.Hashtable();" + + "env.put('java.naming.factory.initial', 'org.jboss.naming.NamingContextFactory');" + + "env.put('java.naming.provider.url', 'jnp://localhost:" + jnpPort + "');" + + "context = new javax.naming.InitialContext(env);\n" + + "kachny = context.lookup('kachny');\n" + + "assertNotNull(kachny);\n"); + } catch (ScriptException e) { + Assert.fail("The script should have been able to access a remote JNDI server.", e); + } + } + + private ScriptEngine getEngine(Subject subject) throws ScriptException, IOException { + StandardBindings bindings = new StandardBindings(new PrintWriter(System.out), new LocalClient(subject)); + ScriptEngine engine = ScriptEngineFactory.getScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), bindings); + + PermissionCollection perms = new StandardScriptPermissions(); + + return new SandboxedScriptEngine(engine, perms); + } + + private static void checkIsDesiredSecurityException(ScriptException e) { + String message = e.getMessage(); + String permissionTrace = AllowRhqServerInternalsAccessPermission.class.getName(); + + Assert.assertTrue(message.contains(permissionTrace), "The script exception doesn't seem to be caused by the AllowRhqServerInternalsAccessPermission security exception. " + message); + } +} diff --git a/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/hibernate.properties b/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/hibernate.properties new file mode 100644 index 0000000..1951b84 --- /dev/null +++ b/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/hibernate.properties @@ -0,0 +1,26 @@ +# FOR SOME STRANGE REASON, THIS FILE NEEDS TO BE HERE FOR THE HIBERNATE TO CORRECTLY +# INITIALIZE. I DON'T KNOW WHY THE STANDARD default.persistence.properties FILE DOESN'T +# WORK IN THIS MODULE. + +hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup +#hibernate.connection.release_mode=after_statement +#hibernate.transaction.flush_before_completion=false +#hibernate.transaction.auto_close_session=false +#hibernate.query.factory_class=org.hibernate.hql.ast.ASTQueryTranslatorFactory +#hibernate.hbm2ddl.auto=create-drop +#hibernate.hbm2ddl.auto=create +hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider +# Clustered cache with TreeCache +#hibernate.cache.provider_class=org.jboss.ejb3.entity.TreeCacheProviderHook +#hibernate.treecache.mbean.object_name=jboss.cache:service=EJB3EntityTreeCache +#hibernate.dialect=org.hibernate.dialect.HSQLDialect +hibernate.jndi.java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory +hibernate.jndi.java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces +hibernate.bytecode.use_reflection_optimizer=false +# I don't think this is honored, but EJB3Deployer uses it +hibernate.bytecode.provider=javassist +hibernate.jdbc.use_streams_for_binary=true +hibernate.show_sql=false +hibernate.format_sql=true +hibernate.default_batch_fetch_size=16 +hibernate.jdbc.batch_size=20 diff --git a/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/log4j.xml b/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/log4j.xml new file mode 100644 index 0000000..ec09ed7 --- /dev/null +++ b/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/log4j.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> + +<!-- ===================================================================== --> +<!-- --> +<!-- Log4j Configuration --> +<!-- --> +<!-- ===================================================================== --> + +<!-- $Id: log4j.xml 39945 2006-01-12 02:44:07Z bill $ --> + +<!-- + | For more configuration infromation and examples see the Jakarta Log4j + | owebsite: http://jakarta.apache.org/log4j + --> + +<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true"> + + <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> + <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/> + <param name="Target" value="System.out"/> + <param name="Threshold" value="WARN"/> + + <layout class="org.apache.log4j.PatternLayout"> + <!-- The default pattern: Date Priority [Category] Messagen --> + <!-- + <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/> + --> + <param name="ConversionPattern" value="%-5p %d{dd-MM HH:mm:ss,SSS} [%c] (%F:%M:%L) -%m%n"/> + </layout> + </appender> + + <appender name="FILE" class="org.apache.log4j.RollingFileAppender"> + <param name="File" value="target/server-jar-test.log"/> + <param name="Threshold" value="WARN"/> + <param name="Append" value="false"/> + + <layout class="org.apache.log4j.PatternLayout"> + <!-- The default pattern: Date Priority [Category] Messagen --> + <!-- + <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/> + --> + <param name="ConversionPattern" value="%-5p %d{dd-MM HH:mm:ss,SSS} [%c] (%F:%M:%L) -%m%n"/> + </layout> + </appender> + + <category name="Test JNP Server"> + <priority value="DEBUG"/> + </category> + + <!-- Hibernate logs WARNINGS frequent from this class, in test envs. --> + <category name="org.hibernate.hql.ast.QueryTranslatorImpl"> + <priority value="ERROR"/> + </category> + + <!-- hides the TIMER SERVICE IS NOT INSTALLED warning - we know embedded EJB3 container doesn't support timers --> + <category name="org.jboss.ejb3.timerservice.jboss.JBossTimerServiceFactory"> + <priority value="ERROR"/> + </category> + + <!-- hides the shutdown warnings - for some reason, the container spits out some warnings when shutting down --> + <category name="org.jboss.kernel.plugins.dependency.StartStopLifecycleAction"> + <priority value="ERROR"/> + </category> + + <!-- Hibernate SQL logs --> + <!-- + <category name="org.hibernate.SQL"> + <priority value="DEBUG"/> + </category> + --> + + <root> + <appender-ref ref="CONSOLE"/> + <appender-ref ref="FILE"/> + </root> + +</log4j:configuration> diff --git a/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/security.policy b/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/security.policy new file mode 100644 index 0000000..8860b47 --- /dev/null +++ b/modules/integration-tests/jndi-access/jndi-access-test/src/test/resources/security.policy @@ -0,0 +1,10 @@ +// We need the SecurityManager installed to enable sandboxing of CLI scripts +// but we don't define any other security measures on the RHQ server itself. +// +// Granting all permissions allows us to run the RHQ server as if no security +// manager was in place (which is assumed by default by JBoss AS) but be able +// to use it when we need it for our own purposes. + +grant { + permission java.security.AllPermission; +}; diff --git a/modules/integration-tests/jndi-access/pom.xml b/modules/integration-tests/jndi-access/pom.xml new file mode 100644 index 0000000..6e9c673 --- /dev/null +++ b/modules/integration-tests/jndi-access/pom.xml @@ -0,0 +1,21 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd%22%3E + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>rhq-integration-tests</artifactId> + <groupId>org.rhq</groupId> + <version>4.3.0-SNAPSHOT</version> + </parent> + + <groupId>org.rhq</groupId> + <artifactId>jndi-access-test-parent</artifactId> + <packaging>pom</packaging> + + <name>JNDI access tests</name> + <description>Tests for the secured JNDI access tests</description> + + <modules> + <module>remote-server</module> + <module>jndi-access-test</module> + </modules> +</project> diff --git a/modules/integration-tests/jndi-access/remote-server/pom.xml b/modules/integration-tests/jndi-access/remote-server/pom.xml new file mode 100644 index 0000000..9046e6f --- /dev/null +++ b/modules/integration-tests/jndi-access/remote-server/pom.xml @@ -0,0 +1,72 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd%22%3E + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>jndi-access-test-parent</artifactId> + <groupId>org.rhq</groupId> + <version>4.3.0-SNAPSHOT</version> + </parent> + + <groupId>org.rhq</groupId> + <artifactId>jndi-access-remote-server</artifactId> + <packaging>jar</packaging> + + <name>Test JNDI-enabled remote server</name> + <description> + A testing JNDI-enabled server to test the ability to connect to remote servers without security + checks from within the scripts running inside the RHQ server. + </description> + + <dependencies> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jnpserver</artifactId> + <version>4.2.2.GA</version> + </dependency> + + <dependency> + <groupId>jboss</groupId> + <artifactId>jboss-common</artifactId> + <version>4.2.2.GA</version> + </dependency> + + <dependency> + <groupId>oswego-concurrent</groupId> + <artifactId>concurrent</artifactId> + <version>1.3.4-jboss</version> + </dependency> + + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.14</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <id>package</id> + <phase>package</phase> + <goals><goal>single</goal></goals> + <configuration> + <archive> + <manifest> + <mainClass>org.rhq.jndi.test.Server</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + </execution> + </executions> + </plugin> + + </plugins> + </build> +</project> diff --git a/modules/integration-tests/jndi-access/remote-server/src/main/java/org/rhq/jndi/test/Server.java b/modules/integration-tests/jndi-access/remote-server/src/main/java/org/rhq/jndi/test/Server.java new file mode 100644 index 0000000..7bfb8e8 --- /dev/null +++ b/modules/integration-tests/jndi-access/remote-server/src/main/java/org/rhq/jndi/test/Server.java @@ -0,0 +1,79 @@ +package org.rhq.jndi.test; +import java.util.Properties; + +import javax.naming.CompoundName; + +import org.jnp.server.Main; +import org.jnp.server.NamingBeanImpl; + +import org.jboss.logging.Logger; + +/* + * 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. + */ + +/** + * + * + * @author Lukas Krejci + */ +public class Server { + private static final Logger LOG = Logger.getLogger(Server.class); + + private static Server INSTANCE; + + private Main jnpServer; + + public static void main(String[] args) throws Exception { + LOG.debug("System properties: " + System.getProperties()); + Server.start(); + } + + private Server() { + jnpServer = new Main("org.rhq.jndi.access.test.server"); + } + + public static synchronized Server getInstance() { + if (INSTANCE == null) { + INSTANCE = new Server(); + } + + return INSTANCE; + } + + public static void start() throws Exception { + LOG.debug("Initializing the JNP server"); + + NamingBeanImpl nbi = new NamingBeanImpl(); + getInstance().jnpServer.setNamingInfo(nbi); + nbi.start(); + + LOG.debug("Binding kachny"); + + nbi.getNamingInstance().bind(new CompoundName("kachny", new Properties()), "KACHNY!", String.class.getName()); + + LOG.debug("Starting the JNP server"); + + getInstance().jnpServer.start(); + } + + public static void stop() { + LOG.debug("Stopping the JNP server"); + getInstance().jnpServer.stop(); + } +} diff --git a/modules/integration-tests/jndi-access/remote-server/src/main/resources/jndi.properties b/modules/integration-tests/jndi-access/remote-server/src/main/resources/jndi.properties new file mode 100644 index 0000000..a45f2ce --- /dev/null +++ b/modules/integration-tests/jndi-access/remote-server/src/main/resources/jndi.properties @@ -0,0 +1,2 @@ +java.naming.factory.initial=org.jboss.naming.NamingContextFactory +java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces \ No newline at end of file diff --git a/modules/integration-tests/jndi-access/remote-server/src/main/resources/log4j.properties b/modules/integration-tests/jndi-access/remote-server/src/main/resources/log4j.properties new file mode 100644 index 0000000..2d41f83 --- /dev/null +++ b/modules/integration-tests/jndi-access/remote-server/src/main/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootCategory=TRACE, CONSOLE + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c] %m%n diff --git a/modules/integration-tests/pom.xml b/modules/integration-tests/pom.xml index 0290729..969b94a 100644 --- a/modules/integration-tests/pom.xml +++ b/modules/integration-tests/pom.xml @@ -62,6 +62,7 @@ <id>integration-tests</id> <modules> <module>apache-plugin-test</module> + <module>jndi-access</module> <!--<module>mod_cluster-plugin-test</module>--> </modules> </profile>
commit 7d14f7484ad758be037694e3599654e4fcb11a88 Author: Lukas Krejci lkrejci@redhat.com Date: Thu Jan 12 16:45:50 2012 +0100
Increasing the log level back to WARN now that the tests are working again.
diff --git a/modules/enterprise/server/itests/src/test/resources/log4j.xml b/modules/enterprise/server/itests/src/test/resources/log4j.xml index 1e37497..bc65329 100644 --- a/modules/enterprise/server/itests/src/test/resources/log4j.xml +++ b/modules/enterprise/server/itests/src/test/resources/log4j.xml @@ -19,7 +19,7 @@ <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/> <param name="Target" value="System.out"/> - <param name="Threshold" value="INFO"/> + <param name="Threshold" value="WARN"/>
<layout class="org.apache.log4j.PatternLayout"> <!-- The default pattern: Date Priority [Category] Messagen --> @@ -32,7 +32,7 @@
<appender name="FILE" class="org.apache.log4j.RollingFileAppender"> <param name="File" value="target/server-jar-test.log"/> - <param name="Threshold" value="INFO"/> + <param name="Threshold" value="WARN"/> <param name="Append" value="false"/>
<layout class="org.apache.log4j.PatternLayout">
commit 759b64ee6b76525650ebece39459bf9abe5c7dd4 Author: Lukas Krejci lkrejci@redhat.com Date: Thu Jan 12 16:45:20 2012 +0100
Making the script security work with Java 6u27 and up. Due to CVE-2011-3544 the Rhino script engine changed the way it applies security permissions which broke our original sandboxing approach.
diff --git a/modules/enterprise/binding/src/main/java/org/rhq/bindings/SandboxedScriptEngine.java b/modules/enterprise/binding/src/main/java/org/rhq/bindings/SandboxedScriptEngine.java index 7b34c6d..f994f09 100644 --- a/modules/enterprise/binding/src/main/java/org/rhq/bindings/SandboxedScriptEngine.java +++ b/modules/enterprise/binding/src/main/java/org/rhq/bindings/SandboxedScriptEngine.java @@ -40,6 +40,9 @@ import javax.script.ScriptEngineFactory; import javax.script.ScriptException;
/** + * <b>DO NOT USE THIS CLASS DIRECTLY!!!!</b> Use {@link org.rhq.bindings.ScriptEngineFactory#getSecuredScriptEngine(String, org.rhq.bindings.util.PackageFinder, StandardBindings, PermissionCollection)} + * method instead for a reliably secured script engine. + * <p> * This is a decorator class for any other {@link ScriptEngine} implementation * that runs any of the eval methods with the defined set of {@link Permission}s. * <p> diff --git a/modules/enterprise/binding/src/main/java/org/rhq/bindings/ScriptEngineFactory.java b/modules/enterprise/binding/src/main/java/org/rhq/bindings/ScriptEngineFactory.java index 168d2fb..5834922 100644 --- a/modules/enterprise/binding/src/main/java/org/rhq/bindings/ScriptEngineFactory.java +++ b/modules/enterprise/binding/src/main/java/org/rhq/bindings/ScriptEngineFactory.java @@ -25,6 +25,15 @@ import java.beans.Introspector; import java.beans.MethodDescriptor; import java.io.IOException; import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.security.cert.Certificate; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -79,7 +88,7 @@ public class ScriptEngineFactory { public static ScriptEngine getScriptEngine(String language, PackageFinder packageFinder, StandardBindings bindings) throws ScriptException, IOException { ScriptEngineInitializer initializer = getInitializer(language); - + if (initializer == null) { return null; } @@ -92,6 +101,55 @@ public class ScriptEngineFactory {
return engine; } + + /** + * This method is similar to the {@link #getScriptEngine(String, PackageFinder, StandardBindings)} method + * but additionally applies a security wrapper on the returned script engine so that the scripts execute + * with the provided java permissions. + * + * @see #getScriptEngine(String, PackageFinder, StandardBindings) + */ + public static ScriptEngine getSecuredScriptEngine(final String language, final PackageFinder packageFinder, final StandardBindings bindings, final PermissionCollection permissions) throws ScriptException, IOException { + CodeSource src = new CodeSource(new URL("http://rhq-project.org/scripting"), (Certificate[]) null); + ProtectionDomain scriptDomain = new ProtectionDomain(src, permissions); + AccessControlContext ctx = new AccessControlContext(new ProtectionDomain[] { scriptDomain }); + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction<ScriptEngine>() { + @Override + public ScriptEngine run() throws Exception { + //This might seem a bit excessive but is necessary due to the + //change in security handling in the rhino script engine + //that occured in Java6u27 (due to a CVE desribed here: + //https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2011-3544) + + //In Java 6u26 and earlier, it was enough to wrap a script engine + //in the sandbox and everything would work. + + //Java 6u27 introduced new behavior where the rhino script engine + //remembers the access control context with which it has been + //constructed and combines that with the callers protection domain + //when a script is executed. Because this class has all perms and + //all the code in RHQ that called ScriptEngine.eval* also + //had all perms, the scripts would never be sandboxed even if the call + //was pushed through the SandboxedScriptEngine. + + //This means that the below wrapping is necessary for the security + //to work in java6 pre u27 while the surrounding privileged block + //is necessary for the security to be applied in java6 u27 and later. + return new SandboxedScriptEngine(getScriptEngine(language, packageFinder, bindings), permissions); + } + }, ctx); + } catch (PrivilegedActionException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } else if (cause instanceof ScriptException) { + throw (ScriptException) cause; + } else { + throw new ScriptException(e); + } + } + }
/** * Injects the values provided in the bindings into the {@link ScriptContext#ENGINE_SCOPE engine scope} diff --git a/modules/enterprise/binding/src/test/java/org/rhq/bindings/ScriptEngineTest.java b/modules/enterprise/binding/src/test/java/org/rhq/bindings/ScriptEngineTest.java index 69874e4..15184c1 100644 --- a/modules/enterprise/binding/src/test/java/org/rhq/bindings/ScriptEngineTest.java +++ b/modules/enterprise/binding/src/test/java/org/rhq/bindings/ScriptEngineTest.java @@ -40,6 +40,7 @@ import org.rhq.bindings.util.PackageFinder; * * @author Lukas Krejci */ +@Test public class ScriptEngineTest {
private static StandardBindings EMPTY_BINDINGS = new StandardBindings(new PrintWriter(System.out), new FakeRhqFacade()); @@ -52,9 +53,7 @@ public class ScriptEngineTest {
@Test public void testSandbox() throws ScriptException, IOException { - ScriptEngine engine = getScriptEngine(); - - SandboxedScriptEngine sandbox = new SandboxedScriptEngine(engine, new StandardScriptPermissions()); + ScriptEngine sandbox = getSecuredScriptEngine();
try { sandbox.eval("java.lang.System.exit(1);"); @@ -90,6 +89,10 @@ public class ScriptEngineTest { return ScriptEngineFactory.getScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), EMPTY_BINDINGS); }
+ private ScriptEngine getSecuredScriptEngine() throws ScriptException, IOException { + return ScriptEngineFactory.getSecuredScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), EMPTY_BINDINGS, new StandardScriptPermissions()); + } + private void assertSecurityExceptionPresent(Throwable t) { boolean ok = false; while (t != null) { diff --git a/modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java b/modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java index ae848a8..de0d912 100644 --- a/modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java +++ b/modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java @@ -181,12 +181,10 @@ public class JndiAccessTest extends AbstractEJB3Test {
private ScriptEngine getEngine(Subject subject) throws ScriptException, IOException { StandardBindings bindings = new StandardBindings(new PrintWriter(System.out), new LocalClient(subject)); - ScriptEngine engine = ScriptEngineFactory.getScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), bindings);
PermissionCollection perms = new StandardScriptPermissions(); - perms.add(new SerializablePermission("enableSubclassImplementation"));
- return new SandboxedScriptEngine(engine, perms); + return ScriptEngineFactory.getSecuredScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), bindings, perms); }
private static void checkIsDesiredSecurityException(ScriptException e) { diff --git a/modules/enterprise/server/plugins/alert-cli/src/main/java/org/rhq/enterprise/server/plugins/alertCli/CliSender.java b/modules/enterprise/server/plugins/alert-cli/src/main/java/org/rhq/enterprise/server/plugins/alertCli/CliSender.java index 3aab6a2..43090c7 100644 --- a/modules/enterprise/server/plugins/alert-cli/src/main/java/org/rhq/enterprise/server/plugins/alertCli/CliSender.java +++ b/modules/enterprise/server/plugins/alert-cli/src/main/java/org/rhq/enterprise/server/plugins/alertCli/CliSender.java @@ -127,8 +127,6 @@ public class CliSender extends AlertSender<CliComponent> {
engine = getScriptEngine(alert, scriptOut, config);
- final SandboxedScriptEngine sandbox = new SandboxedScriptEngine(engine, new StandardScriptPermissions()); - InputStream packageBits = getPackageBits(config.packageId, config.repoId);
reader = new BufferedReader(new InputStreamReader(packageBits)); @@ -137,10 +135,11 @@ public class CliSender extends AlertSender<CliComponent> {
final ExceptionHolder exceptionHolder = new ExceptionHolder();
+ final ScriptEngine e = engine; Thread scriptRunner = new Thread(new Runnable() { public void run() { try { - sandbox.eval(rdr); + e.eval(rdr); } catch (ScriptException e) { exceptionHolder.scriptException = e; } @@ -414,8 +413,8 @@ public class CliSender extends AlertSender<CliComponent> { ScriptEngine engine = SCRIPT_ENGINES.poll();
if (engine == null) { - engine = ScriptEngineFactory.getScriptEngine(ENGINE_NAME, new PackageFinder(Collections - .<File> emptyList()), bindings); + engine = ScriptEngineFactory.getSecuredScriptEngine(ENGINE_NAME, new PackageFinder(Collections + .<File> emptyList()), bindings, new StandardScriptPermissions()); } else { ScriptEngineFactory.injectStandardBindings(engine, bindings, true); }
commit 40247b6c476b0a68476480fe5bca118c4db93489 Author: Lukas Krejci lkrejci@redhat.com Date: Thu Jan 12 16:28:10 2012 +0100
making sure that the tests that try to exit the jvm won't make the test suite appear to have succeeded.
diff --git a/modules/enterprise/binding/pom.xml b/modules/enterprise/binding/pom.xml index 0a9dd46..52def8e 100644 --- a/modules/enterprise/binding/pom.xml +++ b/modules/enterprise/binding/pom.xml @@ -209,6 +209,8 @@ <!-- <argLine>-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y</argLine> --> <argLine>-Djava.security.manager -Djava.security.policy==${project.build.testOutputDirectory}/allow-all.policy</argLine> + <!-- This is important, because some of the tests try to exit the JVM. --> + <failIfNoTests>true</failIfNoTests> </configuration> </plugin>
commit 6483f4edcf08478af30763af05df004b5840e350 Author: Lukas Krejci lkrejci@redhat.com Date: Wed Jan 11 15:03:38 2012 +0100
Explicitly list the container-lib in the list of submodules of the enterprise/server module with hope of resolving the mysterious Jenkins build failures. Also slightly increase the logging verbosity in the itests to see what might be going wrong.
diff --git a/modules/enterprise/server/itests/src/test/resources/log4j.xml b/modules/enterprise/server/itests/src/test/resources/log4j.xml index bc65329..1e37497 100644 --- a/modules/enterprise/server/itests/src/test/resources/log4j.xml +++ b/modules/enterprise/server/itests/src/test/resources/log4j.xml @@ -19,7 +19,7 @@ <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/> <param name="Target" value="System.out"/> - <param name="Threshold" value="WARN"/> + <param name="Threshold" value="INFO"/>
<layout class="org.apache.log4j.PatternLayout"> <!-- The default pattern: Date Priority [Category] Messagen --> @@ -32,7 +32,7 @@
<appender name="FILE" class="org.apache.log4j.RollingFileAppender"> <param name="File" value="target/server-jar-test.log"/> - <param name="Threshold" value="WARN"/> + <param name="Threshold" value="INFO"/> <param name="Append" value="false"/>
<layout class="org.apache.log4j.PatternLayout"> diff --git a/modules/enterprise/server/pom.xml b/modules/enterprise/server/pom.xml index 42d5c4b..8cf8acc 100644 --- a/modules/enterprise/server/pom.xml +++ b/modules/enterprise/server/pom.xml @@ -25,6 +25,7 @@ </activation> <modules> <module>xml-schemas</module> + <module>container-lib</module> <module>jar</module> <module>sars</module> <module>plugins</module> @@ -44,6 +45,7 @@ </activation> <modules> <module>xml-schemas</module> + <module>container-lib</module> <module>jar</module> <module>safe-invoker</module> </modules>
commit 9ffda9543216f1176b3fd5ba87d08df5f1bbec87 Author: Lukas Krejci lkrejci@redhat.com Date: Wed Jan 11 15:02:04 2012 +0100
Adding some logging to AccessCheckingInitialContextFactoryBuilder. Use the correct logger in the AccessCheckingInitialContextFactoryBuilderInstaller MBean.
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java index 3046af7..9abe13c 100644 --- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/AccessCheckingInitialContextFactoryBuilder.java @@ -147,14 +147,17 @@ public class AccessCheckingInitialContextFactoryBuilder implements InitialContex * @throws NamingException If an error occurs loading the factory class. */ public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException { - final String factoryClassName = (String) environment.get(Context.INITIAL_CONTEXT_FACTORY); + final String factoryClassName = (String) environment.get(Context.INITIAL_CONTEXT_FACTORY); if (factoryClassName == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No " + Context.INITIAL_CONTEXT_FACTORY + " set. Using the default factory."); + } return DEFAULT_FACTORY; } final ClassLoader classLoader = getContextClassLoader(); try { final Class<?> factoryClass = Class.forName(factoryClassName, true, classLoader); - InitialContextFactory configuredFactory = (InitialContextFactory) factoryClass.newInstance(); + InitialContextFactory configuredFactory = (InitialContextFactory) factoryClass.newInstance(); return createSecureWrapper(configuredFactory, environment); } catch (Exception e) { throw new NamingException("Failed instantiate InitialContextFactory " + factoryClassName @@ -175,6 +178,9 @@ public class AccessCheckingInitialContextFactoryBuilder implements InitialContex String providerUrl = (String) environment.get(Context.PROVIDER_URL);
if (providerUrl == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Wrapping " + factory + " of class " + factory.getClass() + " in an access checking wrapper. No provider URL detected."); + } return getAccessCheckingFactory(factory); } else { try { @@ -184,16 +190,32 @@ public class AccessCheckingInitialContextFactoryBuilder implements InitialContex //check if we are accessing the RHQ server through some remoting //interface. if (uri.getPort() == JNP_PORT && SERVER_BIND_IPS.contains(providerHost)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Wrapping " + factory + " of class " + factory.getClass() + " in an access checking wrapper. The provider URL points to this server."); + } return getAccessCheckingFactory(factory); } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Wrapping " + factory + " of class " + factory.getClass() + " in an URL preferring wrapper to enable remote connections."); + } return getURLPreferringFactory(factory); } } catch (URISyntaxException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("The " + Context.PROVIDER_URL + + " is not a valid URI. Falling back to using the access checking wrapper for the factory " + + factory + " of class " + factory.getClass() + ".", e); + } return getAccessCheckingFactory(factory); } catch (UnknownHostException e) { //let the factory deal with the unknown host... //this most probably shouldn't be secured because localhost addresses //should be resolvable. + if (LOG.isDebugEnabled()) { + LOG.debug("The " + Context.PROVIDER_URL + + " is not resolvable. Falling back to using the URL preferring wrapper for the factory " + + factory + " of class " + factory.getClass() + ".", e); + } return getURLPreferringFactory(factory); } } diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java index d938c50..a1ccb4f 100644 --- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/mbean/AccessCheckingInitialContextFactoryBuilderInstaller.java @@ -32,7 +32,7 @@ import org.rhq.jndi.AccessCheckingInitialContextFactoryBuilder; * @author Lukas Krejci */ public class AccessCheckingInitialContextFactoryBuilderInstaller implements AccessCheckingInitialContextFactoryBuilderInstallerMBean { - private static final Log LOG = LogFactory.getLog(AccessCheckingInitialContextFactoryBuilder.class); + private static final Log LOG = LogFactory.getLog(AccessCheckingInitialContextFactoryBuilderInstaller.class);
public void start() throws Exception { LOG.info("Installing RHQ's access permission checking initial context factory builder");
commit ca98fa5acf8c59902ff3dac98215605a4a455237 Author: Lukas Krejci lkrejci@redhat.com Date: Tue Jan 10 14:25:11 2012 +0100
Moving the JNDI access tests from rhq-server-client-api to rhq-server-itests to reduce the number of tests suites that set up the whole RHQ container.
Made sure to avoid multiple inclusion of the server jar classes on the test classpath (by explicitly excluding the module from the deps of rhq-script-bindings and rhq-server-client-api that the itests module is now dependent on). This caused some weird problems during the embedded container startup.
diff --git a/modules/enterprise/server/client-api/pom.xml b/modules/enterprise/server/client-api/pom.xml index 4dfd109..29ac29a 100644 --- a/modules/enterprise/server/client-api/pom.xml +++ b/modules/enterprise/server/client-api/pom.xml @@ -15,380 +15,32 @@ <name>RHQ Enterprise Server Client API</name> <description>The implementation of the client API when accessing the server locally</description>
- <properties> - <persistence-api.version>1.0</persistence-api.version> - <rhq.server.datasource>java:/RHQDS</rhq.server.datasource> - <rhq.server.ds-mapping>PostgreSQL</rhq.server.ds-mapping> - - <!-- dependency versions --> - <jboss-embeddable-ejb3.version>1.0.0.Alpha9</jboss-embeddable-ejb3.version> - - <clean.db>true</clean.db> - </properties> - - <dependencies> - - <!-- Note, the test deps are intentionally placed above the other scoped deps because of classpath - reasons. Maven orders the [test] classpath in the order listed in the pom. We specifically - need the embeddable-ejb3 jar above the standard ejb3 jars because we need the embeddble packages - loaded when testing. --> - <dependency> - <groupId>jboss.jboss-embeddable-ejb3</groupId> - <artifactId>jboss-ejb3-all</artifactId> - <version>${jboss-embeddable-ejb3.version}</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>org.rhq</groupId> - <artifactId>rhq-script-bindings</artifactId> - <version>${project.version}</version> - <scope>compile</scope> - </dependency> - - <dependency> - <groupId>javax.persistence</groupId> - <artifactId>persistence-api</artifactId> - <version>${persistence-api.version}</version> - <scope>provided</scope> - </dependency> - - <dependency> - <groupId>hibernate-annotations</groupId> - <artifactId>hibernate-annotations</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement - section. --> - <scope>provided</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jboss-ejb3x</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>provided</scope> <!-- by JBossAS --> - </dependency> - - <!-- Test deps - this insane list of deps is needed to get the embedded JBoss server with RHQ server deployed running --> - + <dependencies> <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>test-utils</artifactId> - <version>${project.version}</version> - <scope>test</scope> + <groupId>org.rhq</groupId> + <artifactId>rhq-container-lib</artifactId> + <version>${project.version}</version> </dependency>
<dependency> - <groupId>${project.groupId}</groupId> - <artifactId>rhq-enterprise-server</artifactId> - <version>${project.version}</version> - <type>test-jar</type> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>rhq-enterprise-server</artifactId> - <version>${project.version}</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>${project.groupId}</groupId> - <artifactId>rhq-container-lib</artifactId> - <version>${project.version}</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>hibernate</groupId> - <artifactId>hibernate3</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>hibernate-entitymanager</groupId> - <artifactId>hibernate-entitymanager</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>org.apache.geronimo.specs</groupId> - <artifactId>geronimo-javamail_1.3.1_spec</artifactId> - <!-- The Sun javamail jar isn't available from a public repo due to licensing issues, - so use the Geronimo one instead. --> - <version>1.3</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>javax.servlet</groupId> - <artifactId>servlet-api</artifactId> - <version>2.4</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>javax.servlet</groupId> - <artifactId>jsp-api</artifactId> - <version>2.0</version> - <scope>test</scope> - </dependency> - - - <dependency> - <groupId>org.opensymphony.quartz</groupId> - <artifactId>quartz</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>org.opensymphony.quartz</groupId> - <artifactId>quartz-oracle</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jboss-annotations-ejb3</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jboss-cache</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jboss-common</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <!-- includes the org.jboss.ejb3.StrictMaxPool class, which is needed by the PoolClass annotation used on some - of our SLSB's --> - <dependency> - <groupId>jboss</groupId> - <artifactId>jboss-ejb3</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> + <groupId>org.rhq</groupId> + <artifactId>rhq-enterprise-server</artifactId> + <version>${project.version}</version> </dependency>
<dependency> - <groupId>jboss</groupId> - <artifactId>jboss-j2ee</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jboss-jmx</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jboss-system</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jbosssx</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>jboss</groupId> - <artifactId>jbpm</artifactId> - <version>3.1.1</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>antlr</groupId> - <artifactId>antlr</artifactId> - <version>2.7.7</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>javassist</groupId> - <artifactId>javassist</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>trove</groupId> - <artifactId>trove</artifactId> - <version>1.0.2</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>xerces</groupId> - <artifactId>xercesImpl</artifactId> - <version>2.8.1</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>net.sf.opencsv</groupId> - <artifactId>opencsv</artifactId> - <version>1.8</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>commons-jxpath</groupId> - <artifactId>commons-jxpath</artifactId> - <version>1.3</version> - <scope>test</scope> - </dependency> - - <!-- for the transaction interrupt EJB3 interceptor --> - <dependency> - <groupId>org.jboss.transaction</groupId> - <artifactId>jboss-jta</artifactId> - <!-- NOTE: The version is defined in the root POM's dependencyManagement section. --> - <scope>test</scope> + <groupId>org.rhq</groupId> + <artifactId>rhq-script-bindings</artifactId> + <version>${project.version}</version> </dependency>
<dependency> - <groupId>tomcat</groupId> - <artifactId>catalina</artifactId> - <version>5.5.20</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>tomcat</groupId> - <artifactId>tomcat-jk</artifactId> - <version>4.1.31</version> - <scope>test</scope> - </dependency> - - <!-- Needed by com.jboss.jbossnetwork.apl.actions.xml.XPathProcessor; TODO: Remove once APL has been excised. --> - <dependency> - <groupId>xalan</groupId> - <artifactId>xalan</artifactId> - <version>2.5.1</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>com.jcraft</groupId> - <artifactId>jsch</artifactId> - <version>0.1.29</version> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>org.jboss.resteasy</groupId> - <artifactId>resteasy-jaxrs</artifactId> - <version>${resteasy.version}</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.jboss.resteasy</groupId> - <artifactId>resteasy-jettison-provider</artifactId> - <version>${resteasy.version}</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.jboss.resteasy</groupId> - <artifactId>resteasy-links</artifactId> - <version>${resteasy.version}</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.jboss.el</groupId> - <artifactId>jboss-el</artifactId> - <version>2.0.1.GA</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.freemarker</groupId> - <artifactId>freemarker</artifactId> - <version>2.3.18</version> - <scope>test</scope> - </dependency> - - </dependencies> - - <build> - <testResources> - <testResource> - <directory>src/test/resources</directory> - <filtering>true</filtering> - </testResource> - </testResources> - - <plugins> - - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <!-- Everything but the web service tests, this is the standard test execution --> - <configuration> - <excludedGroups>${rhq.testng.excludedGroups}</excludedGroups> - <groups>${rhq.testng.includedGroups}</groups> - <systemPropertyVariables> - <embeddedDeployment>true</embeddedDeployment> - <deploymentDirectory>target/test-classes</deploymentDirectory> - <hibernate.dialect>${rhq.test.ds.hibernate-dialect}</hibernate.dialect> - <clean.db>${clean.db}</clean.db> - </systemPropertyVariables> - <argLine>-Djava.security.manager -Djava.security.policy==target/test-classes/security.policy</argLine> - <additionalClasspathElements> - <!-- The below is required for tests to run against Oracle. --> - <additionalClasspathElement>${settings.localRepository}/com/oracle/ojdbc5/${ojdbc5.version}/ojdbc5-${ojdbc5.version}.jar</additionalClasspathElement> - </additionalClasspathElements> - </configuration> - </plugin> - - <plugin> - <artifactId>maven-antrun-plugin</artifactId> - <executions> - - <!-- in order to get JMS to work properly in embedded test container, extract jms-rs.rar classes --> - <execution> - <id>Extract JMS classes from RAR needed for JMS tests</id> - <phase>process-classes</phase> - <configuration> - <tasks> - <unzip src="src/test/resources/jms-ra.rar" dest="target"> - <patternset> - <include name="jms-ra.jar"/> - </patternset> - </unzip> - <unzip src="target/jms-ra.jar" dest="target/test-classes"> - <patternset> - <include name="org/**"/> - </patternset> - </unzip> - </tasks> - </configuration> - <goals> - <goal>run</goal> - </goals> - </execution> - - </executions> - </plugin> - </plugins> - </build> + <groupId>javax.persistence</groupId> + <artifactId>persistence-api</artifactId> + <version>1.0</version> + <scope>provided</scope> + </dependency> + </dependencies>
<profiles>
diff --git a/modules/enterprise/server/client-api/src/test/java/org/rhq/enterprise/client/security/test/EjbAccessTest.java b/modules/enterprise/server/client-api/src/test/java/org/rhq/enterprise/client/security/test/EjbAccessTest.java deleted file mode 100644 index dc1fc83..0000000 --- a/modules/enterprise/server/client-api/src/test/java/org/rhq/enterprise/client/security/test/EjbAccessTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * RHQ Management Platform - * Copyright (C) 2005-2011 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.enterprise.client.security.test; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.SerializablePermission; -import java.security.PermissionCollection; -import java.util.Collections; - -import javax.script.ScriptEngine; -import javax.script.ScriptException; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import org.rhq.bindings.SandboxedScriptEngine; -import org.rhq.bindings.ScriptEngineFactory; -import org.rhq.bindings.StandardBindings; -import org.rhq.bindings.StandardScriptPermissions; -import org.rhq.bindings.util.PackageFinder; -import org.rhq.core.domain.auth.Subject; -import org.rhq.enterprise.client.LocalClient; -import org.rhq.enterprise.server.test.AbstractEJB3Test; -import org.rhq.enterprise.server.util.LookupUtil; -import org.rhq.jndi.AllowRhqServerInternalsAccessPermission; - -/** - * - * - * @author Lukas Krejci - */ -@Test -public class EjbAccessTest extends AbstractEJB3Test { - - public void testEjbsAccessibleThroughPrivilegedCode() { - LookupUtil.getSubjectManager().getOverlord(); - } - - public void testEjbsAccessibleThroughLocalClient() throws ScriptException, IOException { - Subject overlord = LookupUtil.getSubjectManager().getOverlord(); - - ScriptEngine engine = getEngine(overlord); - - engine.eval("SubjectManager.getSubjectByName('rhqadmin');"); - } - - public void testLocalEjbsInaccessibleThroughJndiLookup() throws ScriptException, IOException { - Subject overlord = LookupUtil.getSubjectManager().getOverlord(); - - ScriptEngine engine = getEngine(overlord); - - try { - engine.eval("" - + "context = new javax.naming.InitialContext();\n" - + "subjectManager = context.lookup('SubjectManagerBean/local');\n" - + "subjectManager.getOverlord();"); - - Assert.fail("The script shouldn't have been able to call local SLSB method."); - } catch (ScriptException e) { - checkIsDesiredSecurityException(e); - } - } - - public void testRemoteEjbsInaccessibleThroughJndiLookup() throws ScriptException, IOException { - Subject overlord = LookupUtil.getSubjectManager().getOverlord(); - - ScriptEngine engine = getEngine(overlord); - - try { - engine.eval("" - + "context = new javax.naming.InitialContext();\n" - + "subjectManager = context.lookup('SubjectManagerBean/remote');\n" - + "subjectManager.getSubjectByName('rhqadmin');"); - - Assert.fail("The script shouldn't have been able to call remote SLSB method directly."); - } catch (ScriptException e) { - checkIsDesiredSecurityException(e); - } - } - - public void testScriptCantUseSessionManagerMethods() throws Exception { - - Subject overlord = LookupUtil.getSubjectManager().getOverlord(); - - final ScriptEngine engine = getEngine(overlord); - - class G { - private String sessionManager = "" - + "org.rhq.enterprise.server.auth.SessionManager.getInstance()."; - - public void testInvoke(String methodCall) throws ScriptException { - String code = sessionManager + methodCall; - - try { - engine.eval(code); - Assert.fail("The script shouldn't have been able to call a method on a SessionManager: " + methodCall); - } catch (ScriptException e) { - checkIsDesiredSecurityException(e); - } - } - }; - G manager = new G(); - - manager.testInvoke("getlastAccess(0);"); - manager.testInvoke("getOverlord()"); - manager.testInvoke("getSubject(2);"); - manager.testInvoke("invalidate(0);"); - manager.testInvoke("invalidate("");"); - manager.testInvoke("put(new org.rhq.core.domain.auth.Subject());"); - manager.testInvoke("put(new org.rhq.core.domain.auth.Subject(), 0);"); - } - - public void testScriptCantObtainRawJDBCConnectionsWithoutCredentials() throws Exception { - Subject overlord = LookupUtil.getSubjectManager().getOverlord(); - - ScriptEngine engine = getEngine(overlord); - - try { - engine.eval("" - + "context = new javax.naming.InitialContext();\n" - + "datasource = context.lookup('java:/RHQDS');\n" - + "con = datasource.getConnection();"); - - Assert.fail("The script shouldn't have been able to obtain the datasource from the JNDI."); - } catch (ScriptException e) { - checkIsDesiredSecurityException(e); - } - } - - public void testScriptCantUseEntityManager() throws Exception { - Subject overlord = LookupUtil.getSubjectManager().getOverlord(); - - ScriptEngine engine = getEngine(overlord); - - try { - engine.eval("" - + "context = new javax.naming.InitialContext();\n" - + "entityManagerFactory = context.lookup('java:/RHQEntityManagerFactory');\n" - + "entityManager = entityManagerFactory.createEntityManager();\n" - + "entityManager.find(java.lang.Class.forName('org.rhq.core.domain.resource.Resource'), java.lang.Integer.valueOf('10001'));"); - - Assert.fail("The script shouldn't have been able to use the EntityManager."); - } catch (ScriptException e) { - checkIsDesiredSecurityException(e); - } - - //try harder with manually specifying the initial context factory - try { - engine.eval("" - + "env = new java.util.Hashtable();" - + "env.put('java.naming.factory.initial', 'org.jnp.interfaces.LocalOnlyContextFactory');" - + "env.put('java.naming.factory.url.pkgs', 'org.jboss.naming:org.jnp.interfaces');" - + "context = new javax.naming.InitialContext(env);\n" - + "entityManagerFactory = context.lookup('java:/RHQEntityManagerFactory');\n" - + "entityManager = entityManagerFactory.createEntityManager();\n" - + "entityManager.find(java.lang.Class.forName('org.rhq.core.domain.resource.Resource'), java.lang.Integer.valueOf('10001'));"); - - Assert.fail("The script shouldn't have been able to use the EntityManager even using custom initial context factory."); - } catch (ScriptException e) { - checkIsDesiredSecurityException(e); - } - } - - private ScriptEngine getEngine(Subject subject) throws ScriptException, IOException { - StandardBindings bindings = new StandardBindings(new PrintWriter(System.out), new LocalClient(subject)); - ScriptEngine engine = ScriptEngineFactory.getScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), bindings); - - PermissionCollection perms = new StandardScriptPermissions(); - perms.add(new SerializablePermission("enableSubclassImplementation")); - - return new SandboxedScriptEngine(engine, perms); - } - - private static void checkIsDesiredSecurityException(ScriptException e) { - String message = e.getMessage(); - String permissionTrace = AllowRhqServerInternalsAccessPermission.class.getName(); - - Assert.assertTrue(message.contains(permissionTrace), "The script exception doesn't seem to be caused by the AllowRhqServerInternalsAccessPermission security exception. " + message); - } -} diff --git a/modules/enterprise/server/client-api/src/test/resources/hibernate.properties b/modules/enterprise/server/client-api/src/test/resources/hibernate.properties deleted file mode 100644 index 1951b84..0000000 --- a/modules/enterprise/server/client-api/src/test/resources/hibernate.properties +++ /dev/null @@ -1,26 +0,0 @@ -# FOR SOME STRANGE REASON, THIS FILE NEEDS TO BE HERE FOR THE HIBERNATE TO CORRECTLY -# INITIALIZE. I DON'T KNOW WHY THE STANDARD default.persistence.properties FILE DOESN'T -# WORK IN THIS MODULE. - -hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup -#hibernate.connection.release_mode=after_statement -#hibernate.transaction.flush_before_completion=false -#hibernate.transaction.auto_close_session=false -#hibernate.query.factory_class=org.hibernate.hql.ast.ASTQueryTranslatorFactory -#hibernate.hbm2ddl.auto=create-drop -#hibernate.hbm2ddl.auto=create -hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider -# Clustered cache with TreeCache -#hibernate.cache.provider_class=org.jboss.ejb3.entity.TreeCacheProviderHook -#hibernate.treecache.mbean.object_name=jboss.cache:service=EJB3EntityTreeCache -#hibernate.dialect=org.hibernate.dialect.HSQLDialect -hibernate.jndi.java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -hibernate.jndi.java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces -hibernate.bytecode.use_reflection_optimizer=false -# I don't think this is honored, but EJB3Deployer uses it -hibernate.bytecode.provider=javassist -hibernate.jdbc.use_streams_for_binary=true -hibernate.show_sql=false -hibernate.format_sql=true -hibernate.default_batch_fetch_size=16 -hibernate.jdbc.batch_size=20 diff --git a/modules/enterprise/server/client-api/src/test/resources/jms-ra.rar b/modules/enterprise/server/client-api/src/test/resources/jms-ra.rar deleted file mode 100644 index c4807c6..0000000 Binary files a/modules/enterprise/server/client-api/src/test/resources/jms-ra.rar and /dev/null differ diff --git a/modules/enterprise/server/client-api/src/test/resources/security.policy b/modules/enterprise/server/client-api/src/test/resources/security.policy deleted file mode 100644 index 8860b47..0000000 --- a/modules/enterprise/server/client-api/src/test/resources/security.policy +++ /dev/null @@ -1,10 +0,0 @@ -// We need the SecurityManager installed to enable sandboxing of CLI scripts -// but we don't define any other security measures on the RHQ server itself. -// -// Granting all permissions allows us to run the RHQ server as if no security -// manager was in place (which is assumed by default by JBoss AS) but be able -// to use it when we need it for our own purposes. - -grant { - permission java.security.AllPermission; -}; diff --git a/modules/enterprise/server/itests/pom.xml b/modules/enterprise/server/itests/pom.xml index 6898621..653ef99 100644 --- a/modules/enterprise/server/itests/pom.xml +++ b/modules/enterprise/server/itests/pom.xml @@ -58,6 +58,7 @@ <groupId>org.rhq</groupId> <artifactId>rhq-container-lib</artifactId> <version>${project.version}</version> + <scope>test</scope> </dependency>
<dependency> @@ -84,6 +85,32 @@ </dependency>
<dependency> + <groupId>org.rhq</groupId> + <artifactId>rhq-script-bindings</artifactId> + <version>${project.version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.rhq</groupId> + <artifactId>rhq-enterprise-server</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>org.rhq</groupId> + <artifactId>rhq-server-client-api</artifactId> + <version>${project.version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.rhq</groupId> + <artifactId>rhq-enterprise-server</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> <groupId>org.rhq.helpers</groupId> <artifactId>perftest-support</artifactId> <version>${project.version}</version> @@ -232,6 +259,7 @@ <artifactId>resteasy-jaxrs</artifactId> <version>${resteasy.version}</version> </dependency> + </dependencies>
<build> @@ -270,6 +298,23 @@ </execution> </executions> </plugin> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <!-- Everything but the web service tests, this is the standard test execution --> + <configuration> + <excludedGroups>${rhq.testng.excludedGroups}</excludedGroups> + <groups>${rhq.testng.includedGroups}</groups> + <systemPropertyVariables> + <embeddedDeployment>true</embeddedDeployment> + <deploymentDirectory>target/test-classes</deploymentDirectory> + </systemPropertyVariables> + <argLine>-Djava.security.manager -Djava.security.policy==${basedir}/target/test-classes/security.policy</argLine> + <additionalClasspathElements> + <!-- The below is required for tests to run against Oracle. --> + <additionalClasspathElement>${settings.localRepository}/com/oracle/ojdbc5/${ojdbc5.version}/ojdbc5-${ojdbc5.version}.jar</additionalClasspathElement> + </additionalClasspathElements> + </configuration> + </plugin> </plugins> </build> </project> diff --git a/modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java b/modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java new file mode 100644 index 0000000..ae848a8 --- /dev/null +++ b/modules/enterprise/server/itests/src/test/java/org/rhq/enterprise/client/security/test/JndiAccessTest.java @@ -0,0 +1,198 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2011 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.enterprise.client.security.test; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.SerializablePermission; +import java.security.PermissionCollection; +import java.util.Collections; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import org.rhq.bindings.SandboxedScriptEngine; +import org.rhq.bindings.ScriptEngineFactory; +import org.rhq.bindings.StandardBindings; +import org.rhq.bindings.StandardScriptPermissions; +import org.rhq.bindings.util.PackageFinder; +import org.rhq.core.domain.auth.Subject; +import org.rhq.enterprise.client.LocalClient; +import org.rhq.enterprise.server.test.AbstractEJB3Test; +import org.rhq.enterprise.server.util.LookupUtil; +import org.rhq.jndi.AllowRhqServerInternalsAccessPermission; + +/** + * + * + * @author Lukas Krejci + */ +@Test +public class JndiAccessTest extends AbstractEJB3Test { + + public void testEjbsAccessibleThroughPrivilegedCode() { + LookupUtil.getSubjectManager().getOverlord(); + } + + public void testEjbsAccessibleThroughLocalClient() throws ScriptException, IOException { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + engine.eval("SubjectManager.getSubjectByName('rhqadmin');"); + } + + public void testLocalEjbsInaccessibleThroughJndiLookup() throws ScriptException, IOException { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "subjectManager = context.lookup('SubjectManagerBean/local');\n" + + "subjectManager.getOverlord();"); + + Assert.fail("The script shouldn't have been able to call local SLSB method."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + public void testRemoteEjbsInaccessibleThroughJndiLookup() throws ScriptException, IOException { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "subjectManager = context.lookup('SubjectManagerBean/remote');\n" + + "subjectManager.getSubjectByName('rhqadmin');"); + + Assert.fail("The script shouldn't have been able to call remote SLSB method directly."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + public void testScriptCantUseSessionManagerMethods() throws Exception { + + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + final ScriptEngine engine = getEngine(overlord); + + class G { + private String sessionManager = "" + + "org.rhq.enterprise.server.auth.SessionManager.getInstance()."; + + public void testInvoke(String methodCall) throws ScriptException { + String code = sessionManager + methodCall; + + try { + engine.eval(code); + Assert.fail("The script shouldn't have been able to call a method on a SessionManager: " + methodCall); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + }; + G manager = new G(); + + manager.testInvoke("getlastAccess(0);"); + manager.testInvoke("getOverlord()"); + manager.testInvoke("getSubject(2);"); + manager.testInvoke("invalidate(0);"); + manager.testInvoke("invalidate("");"); + manager.testInvoke("put(new org.rhq.core.domain.auth.Subject());"); + manager.testInvoke("put(new org.rhq.core.domain.auth.Subject(), 0);"); + } + + public void testScriptCantObtainRawJDBCConnectionsWithoutCredentials() throws Exception { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "datasource = context.lookup('java:/RHQDS');\n" + + "con = datasource.getConnection();"); + + Assert.fail("The script shouldn't have been able to obtain the datasource from the JNDI."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + public void testScriptCantUseEntityManager() throws Exception { + Subject overlord = LookupUtil.getSubjectManager().getOverlord(); + + ScriptEngine engine = getEngine(overlord); + + try { + engine.eval("" + + "context = new javax.naming.InitialContext();\n" + + "entityManagerFactory = context.lookup('java:/RHQEntityManagerFactory');\n" + + "entityManager = entityManagerFactory.createEntityManager();\n" + + "entityManager.find(java.lang.Class.forName('org.rhq.core.domain.resource.Resource'), java.lang.Integer.valueOf('10001'));"); + + Assert.fail("The script shouldn't have been able to use the EntityManager."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + + //try harder with manually specifying the initial context factory + try { + engine.eval("" + + "env = new java.util.Hashtable();" + + "env.put('java.naming.factory.initial', 'org.jnp.interfaces.LocalOnlyContextFactory');" + + "env.put('java.naming.factory.url.pkgs', 'org.jboss.naming:org.jnp.interfaces');" + + "context = new javax.naming.InitialContext(env);\n" + + "entityManagerFactory = context.lookup('java:/RHQEntityManagerFactory');\n" + + "entityManager = entityManagerFactory.createEntityManager();\n" + + "entityManager.find(java.lang.Class.forName('org.rhq.core.domain.resource.Resource'), java.lang.Integer.valueOf('10001'));"); + + Assert.fail("The script shouldn't have been able to use the EntityManager even using custom initial context factory."); + } catch (ScriptException e) { + checkIsDesiredSecurityException(e); + } + } + + private ScriptEngine getEngine(Subject subject) throws ScriptException, IOException { + StandardBindings bindings = new StandardBindings(new PrintWriter(System.out), new LocalClient(subject)); + ScriptEngine engine = ScriptEngineFactory.getScriptEngine("JavaScript", new PackageFinder(Collections.<File>emptyList()), bindings); + + PermissionCollection perms = new StandardScriptPermissions(); + perms.add(new SerializablePermission("enableSubclassImplementation")); + + return new SandboxedScriptEngine(engine, perms); + } + + private static void checkIsDesiredSecurityException(ScriptException e) { + String message = e.getMessage(); + String permissionTrace = AllowRhqServerInternalsAccessPermission.class.getName(); + + Assert.assertTrue(message.contains(permissionTrace), "The script exception doesn't seem to be caused by the AllowRhqServerInternalsAccessPermission security exception. " + message); + } +} diff --git a/modules/enterprise/server/itests/src/test/resources/embedded-jboss-beans.xml b/modules/enterprise/server/itests/src/test/resources/embedded-jboss-beans.xml index 05edefa..85e2fb3 100644 --- a/modules/enterprise/server/itests/src/test/resources/embedded-jboss-beans.xml +++ b/modules/enterprise/server/itests/src/test/resources/embedded-jboss-beans.xml @@ -4,6 +4,14 @@ xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0">
+ <!-- This installs a custom initial context factory builder into the JVM + that will ensure that all the default InitialContexts are going to check + for the permission to access the RHQ internals. --> + <bean class="org.rhq.jndi.mbean.AccessCheckingInitialContextFactoryBuilderInstaller" + name="AccessCheckingInitialContextFactoryBuilderInstaller"> + </bean> + + <bean name="Naming" class="org.jnp.server.SingletonNamingServer"/>
<bean name="InitialContextProperties" class="java.util.Hashtable"> @@ -20,7 +28,7 @@ </entry> </map> </parameter> - </constructor> + </constructor> </bean>
<bean name="java:comp/Initializer" class="org.jboss.ejb3.embedded.JavaCompInitializer"> diff --git a/modules/enterprise/server/itests/src/test/resources/security.policy b/modules/enterprise/server/itests/src/test/resources/security.policy new file mode 100644 index 0000000..8860b47 --- /dev/null +++ b/modules/enterprise/server/itests/src/test/resources/security.policy @@ -0,0 +1,10 @@ +// We need the SecurityManager installed to enable sandboxing of CLI scripts +// but we don't define any other security measures on the RHQ server itself. +// +// Granting all permissions allows us to run the RHQ server as if no security +// manager was in place (which is assumed by default by JBoss AS) but be able +// to use it when we need it for our own purposes. + +grant { + permission java.security.AllPermission; +};
commit 583cdadd3ac3f2aabec176c57423895292fe6348 Author: Lukas Krejci lkrejci@redhat.com Date: Tue Jan 10 10:40:27 2012 +0100
Make sure the decorated contexts behave the same as the originals. hashCode and equals of the decorators are now taken from the original, the exceptions from the method calls are rethrown from the DispatchingInvocationHandler.
diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java index 19b30ea..e0f1d84 100644 --- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/DecoratingInitialContextFactory.java @@ -96,5 +96,15 @@ public class DecoratingInitialContextFactory implements InitialContextFactory { if (cls.getSuperclass() != null) { getAllImplementedInterfaces(cls.getSuperclass(), output); } - } + } + + @Override + public int hashCode() { + return factory.hashCode(); + } + + @Override + public boolean equals(Object o) { + return factory.equals(o); + } } diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java index 6bfec7e..8970b0a 100644 --- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/AccessCheckingContextDecorator.java @@ -251,4 +251,14 @@ public class AccessCheckingContextDecorator implements Context, ContextDecorator return str.substring(0, colon_posn); return null; } + + @Override + public int hashCode() { + return getOriginal() == null ? super.hashCode() : getOriginal().hashCode(); + } + + @Override + public boolean equals(Object o) { + return getOriginal() == null ? super.equals(o) : getOriginal().equals(o); + } } diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java index 20077de..3f3a95d 100644 --- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/context/URLPreferringContextDecorator.java @@ -209,4 +209,14 @@ public class URLPreferringContextDecorator implements Context, ContextDecorator, public String getNameInNamespace() throws NamingException { return getOriginal().getNameInNamespace(); } + + @Override + public int hashCode() { + return original == null ? super.hashCode() : original.hashCode(); + } + + @Override + public boolean equals(Object o) { + return original == null ? super.equals(o) : original.equals(o); + } } diff --git a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java index 356a6e9..1bfa7ac 100644 --- a/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java +++ b/modules/enterprise/server/container-lib/src/main/java/org/rhq/jndi/util/DecoratingInvocationHandler.java @@ -20,6 +20,7 @@ package org.rhq.jndi.util;
import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List;
@@ -41,6 +42,14 @@ public class DecoratingInvocationHandler<Type, Decorator extends Type> implement target = picker.decorate(target, methodClass); }
- return method.invoke(target, args); + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + if (e.getCause() != null) { + throw e.getCause(); + } else { + throw e; + } + } } } \ No newline at end of file
rhq-commits@lists.fedorahosted.org