summaryrefslogtreecommitdiff
path: root/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java')
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java285
1 files changed, 285 insertions, 0 deletions
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java
new file mode 100644
index 0000000000..f845ff1214
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.naming.NamingException;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.url.URLSyntaxException;
+
+/**
+ * Abstract test case for ACLs.
+ *
+ * This base class contains convenience methods to mange ACL files and implements a mechanism that allows each
+ * test method to run its own setup code before the broker starts.
+ *
+ * TODO move the pre broker-startup setup method invocation code to {@link QpidBrokerTestCase}
+ *
+ * @see SimpleACLTest
+ * @see ExternalACLTest
+ * @see ExternalACLFileTest
+ * @see ExternalACLJMXTest
+ * @see ExternalAdminACLTest
+ * @see ExhaustiveACLTest
+ */
+public abstract class AbstractACLTestCase extends QpidBrokerTestCase implements ConnectionListener
+{
+ /** Used to synchronise {@link #tearDown()} when exceptions are thrown */
+ protected CountDownLatch _exceptionReceived;
+
+ /** Override this to return the name of the configuration XML file. */
+ public String getConfig()
+ {
+ return "config-systests-acl.xml";
+ }
+
+ /** Override this to setup external ACL files for virtual hosts. */
+ public List<String> getHostList()
+ {
+ return Collections.emptyList();
+ }
+
+ /**
+ * This setup method checks {@link #getConfig()} and {@link #getHostList()} to initialise the broker with specific
+ * ACL configurations and then runs an optional per-test setup method, which is simply a method with the same name
+ * as the test, but starting with {@code setUp} rather than {@code test}.
+ *
+ * @see #setUpACLFile(String)
+ * @see org.apache.qpid.test.utils.QpidBrokerTestCase#setUp()
+ */
+ @Override
+ public void setUp() throws Exception
+ {
+ if (QpidHome == null)
+ {
+ fail("QPID_HOME not set");
+ }
+
+ // Initialise ACLs.
+ _configFile = new File(QpidHome, "etc" + File.separator + getConfig());
+
+ // Initialise ACL files
+ for (String virtualHost : getHostList())
+ {
+ setUpACLFile(virtualHost);
+ }
+
+ // run test specific setup
+ String testSetup = StringUtils.replace(getName(), "test", "setUp");
+ try
+ {
+ Method setup = getClass().getDeclaredMethod(testSetup);
+ setup.invoke(this);
+ }
+ catch (NoSuchMethodException e)
+ {
+ // Ignore
+ }
+ catch (InvocationTargetException e)
+ {
+ throw (Exception) e.getTargetException();
+ }
+
+ super.setUp();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ try
+ {
+ super.tearDown();
+ }
+ catch (JMSException e)
+ {
+ //we're throwing this away as it can happen in this test as the state manager remembers exceptions
+ //that we provoked with authentication failures, where the test passes - we can ignore on con close
+ }
+ }
+
+ /**
+ * Configures specific ACL files for a virtual host.
+ *
+ * This method checks for ACL files that exist on the filesystem. If dynamically generatyed ACL files are required in a test,
+ * then it is easier to use the {@code setUp} prefix on a method to generate the ACL file. In order, this method looks
+ * for three files:
+ * <ol>
+ * <li><em>virtualhost</em>-<em>class</em>-<em>test</em>.txt
+ * <li><em>virtualhost</em>-<em>class</em>.txt
+ * <li><em>virtualhost</em>-default.txt
+ * </ol>
+ * The <em>class</em> and <em>test</em> parts are the test class and method names respectively, with the word {@code test}
+ * removed and the rest of the text converted to lowercase. For example, the test class and method named
+ * {@code org.apache.qpid.test.AccessExampleTest#testExampleMethod} on the {@code testhost} virtualhost would use
+ * one of the following files:
+ * <ol>
+ * <li>testhost-accessexample-examplemethod.txt
+ * <li>testhost-accessexample.txt
+ * <li>testhost-default.txt
+ * </ol>
+ * These files should be copied to the <em>${QPID_HOME}/etc</em> directory when the test is run.
+ *
+ * @see #writeACLFile(String, String...)
+ */
+ public void setUpACLFile(String virtualHost) throws IOException, ConfigurationException
+ {
+ String path = QpidHome + File.separator + "etc";
+ String className = StringUtils.substringBeforeLast(getClass().getSimpleName().toLowerCase(), "test");
+ String testName = StringUtils.substringAfter(getName(), "test").toLowerCase();
+
+ File aclFile = new File(path, virtualHost + "-" + className + "-" + testName + ".txt");
+ if (!aclFile.exists())
+ {
+ aclFile = new File(path, virtualHost + "-" + className + ".txt");
+ if (!aclFile.exists())
+ {
+ aclFile = new File(path, virtualHost + "-" + "default.txt");
+ }
+ }
+
+ // Set the ACL file configuration property
+ if (virtualHost.equals("global"))
+ {
+ setConfigurationProperty("security.aclv2", aclFile.getAbsolutePath());
+ }
+ else
+ {
+ setConfigurationProperty("virtualhosts.virtualhost." + virtualHost + ".security.aclv2", aclFile.getAbsolutePath());
+ }
+ }
+
+ public void writeACLFile(String vhost, String...rules) throws ConfigurationException, IOException
+ {
+ File aclFile = File.createTempFile(getClass().getSimpleName(), getName());
+ aclFile.deleteOnExit();
+
+ if ("global".equals(vhost))
+ {
+ setConfigurationProperty("security.aclv2", aclFile.getAbsolutePath());
+ }
+ else
+ {
+ setConfigurationProperty("virtualhosts.virtualhost." + vhost + ".security.aclv2", aclFile.getAbsolutePath());
+ }
+
+ PrintWriter out = new PrintWriter(new FileWriter(aclFile));
+ out.println(String.format("# %s", _testName));
+ for (String line : rules)
+ {
+ out.println(line);
+ }
+ out.close();
+ }
+
+ /**
+ * Creates a connection to the broker, and sets a connection listener to prevent failover and an exception listener
+ * with a {@link CountDownLatch} to synchronise in the {@link #check403Exception(Throwable)} method and allow the
+ * {@link #tearDown()} method to complete properly.
+ */
+ public Connection getConnection(String vhost, String username, String password) throws NamingException, JMSException, URLSyntaxException
+ {
+ AMQConnection connection = (AMQConnection) getConnection(createConnectionURL(vhost, username, password));
+
+ //Prevent Failover
+ connection.setConnectionListener(this);
+
+ //QPID-2081: use a latch to sync on exception causing connection close, to work
+ //around the connection close race during tearDown() causing sporadic failures
+ _exceptionReceived = new CountDownLatch(1);
+
+ connection.setExceptionListener(new ExceptionListener()
+ {
+ public void onException(JMSException e)
+ {
+ _exceptionReceived.countDown();
+ }
+ });
+
+ return (Connection) connection;
+ }
+
+ // Connection Listener Interface - Used here to block failover
+
+ public void bytesSent(long count)
+ {
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ //Prevent failover.
+ return false;
+ }
+
+ public boolean preResubscribe()
+ {
+ return false;
+ }
+
+ public void failoverComplete()
+ {
+ }
+
+ /**
+ * Convenience method to build an {@link AMQConnectionURL} with the right parameters.
+ */
+ public AMQConnectionURL createConnectionURL(String vhost, String username, String password) throws URLSyntaxException
+ {
+ String url = "amqp://" + username + ":" + password + "@clientid/" + vhost + "?brokerlist='" + getBroker() + "?retries='0''";
+ return new AMQConnectionURL(url);
+ }
+
+ /**
+ * Convenience method to validate a JMS exception with a linked {@link AMQConstant#ACCESS_REFUSED} 403 error code exception.
+ */
+ public void check403Exception(Throwable t) throws Exception
+ {
+ assertNotNull("There was no linked exception", t);
+ assertTrue("Wrong linked exception type", t instanceof AMQException);
+ assertEquals("Incorrect error code received", 403, ((AMQException) t).getErrorCode().getCode());
+
+ //use the latch to ensure the control thread waits long enough for the exception thread
+ //to have done enough to mark the connection closed before teardown commences
+ assertTrue("Timed out waiting for conneciton to report close", _exceptionReceived.await(2, TimeUnit.SECONDS));
+ }
+}