summaryrefslogtreecommitdiff
path: root/qpid/java/systests/src/main/java/org/apache/qpid/server/security
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/systests/src/main/java/org/apache/qpid/server/security')
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java285
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java195
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java184
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java244
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java37
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java186
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java644
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java298
8 files changed, 2073 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));
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java
new file mode 100644
index 0000000000..1b2c98d30a
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * ACL version 2/3 file testing to verify that ACL entries control queue creation with specific properties.
+ *
+ * Tests have their own ACL files that setup specific permissions, and then try to create queues with every possible combination
+ * of properties to show that rule matching works correctly. For example, a rule that specified {@code autodelete="true"} for
+ * queues with {@link name="temp.true.*"} as well should not affect queues that have names that do not match, or queues that
+ * are not autodelete, or both. Also checks that ACL entries only affect the specified users and virtual hosts.
+ */
+public class ExhaustiveACLTest extends AbstractACLTestCase
+{
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test", "test2");
+ }
+
+ /**
+ * Creates a queue.
+ *
+ * Connects to the broker as a particular user and create the named queue on a virtual host, with the provided
+ * parameters. Uses a new {@link Connection} and {@link Session} and closes them afterwards.
+ */
+ private void createQueue(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception
+ {
+ Connection conn = getConnection(vhost, user, "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString(name), autoDelete, durable, false);
+ sess.commit();
+ conn.close();
+ }
+
+ /**
+ * Calls {@link #createQueue(String, String, String, boolean, boolean)} with the provided parameters and checks that
+ * no exceptions were thrown.
+ */
+ private void createQueueSuccess(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception
+ {
+ try
+ {
+ createQueue(vhost, user, name, autoDelete, durable);
+ }
+ catch (AMQException e)
+ {
+ fail(String.format("Create queue should have worked for \"%s\" for user %s@%s, autoDelete=%s, durable=%s",
+ name, user, vhost, Boolean.toString(autoDelete), Boolean.toString(durable)));
+ }
+ }
+
+ /**
+ * Calls {@link #createQueue(String, String, String, boolean, boolean)} with the provided parameters and checks that
+ * the exception thrown was an {@link AMQConstant#ACCESS_REFUSED} or 403 error code.
+ */
+ private void createQueueFailure(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception
+ {
+ try
+ {
+ createQueue(vhost, user, name, autoDelete, durable);
+ fail(String.format("Create queue should have failed for \"%s\" for user %s@%s, autoDelete=%s, durable=%s",
+ name, user, vhost, Boolean.toString(autoDelete), Boolean.toString(durable)));
+ }
+ catch (AMQException e)
+ {
+ assertEquals("Should be an ACCESS_REFUSED error", 403, e.getErrorCode().getCode());
+ }
+ }
+
+ public void setUpAuthoriseCreateQueueAutodelete() throws Exception
+ {
+ writeACLFile("test",
+ "acl allow client access virtualhost",
+ "acl allow server access virtualhost",
+ "acl allow client create queue name=\"temp.true.*\" autodelete=true",
+ "acl allow client create queue name=\"temp.false.*\" autodelete=false",
+ "acl deny client create queue",
+ "acl allow client delete queue",
+ "acl deny all create queue"
+ );
+ }
+
+ /**
+ * Test creation of temporary queues, with the autodelete property set to true.
+ */
+ public void testAuthoriseCreateQueueAutodelete() throws Exception
+ {
+ createQueueSuccess("test", "client", "temp.true.00", true, false);
+ createQueueSuccess("test", "client", "temp.true.01", true, false);
+ createQueueSuccess("test", "client", "temp.true.02", true, true);
+ createQueueSuccess("test", "client", "temp.false.03", false, false);
+ createQueueSuccess("test", "client", "temp.false.04", false, false);
+ createQueueSuccess("test", "client", "temp.false.05", false, true);
+ createQueueFailure("test", "client", "temp.true.06", false, false);
+ createQueueFailure("test", "client", "temp.false.07", true, false);
+ createQueueFailure("test", "server", "temp.true.08", true, false);
+ createQueueFailure("test", "client", "temp.other.09", false, false);
+ createQueueSuccess("test2", "guest", "temp.true.01", false, false);
+ createQueueSuccess("test2", "guest", "temp.false.02", true, false);
+ createQueueSuccess("test2", "guest", "temp.true.03", true, false);
+ createQueueSuccess("test2", "guest", "temp.false.04", false, false);
+ createQueueSuccess("test2", "guest", "temp.other.05", false, false);
+ }
+
+ public void setUpAuthoriseCreateQueue() throws Exception
+ {
+ writeACLFile("test",
+ "acl allow client access virtualhost",
+ "acl allow server access virtualhost",
+ "acl allow client create queue name=\"create.*\""
+ );
+ }
+
+ /**
+ * Tests creation of named queues.
+ *
+ * If a named queue is specified
+ */
+ public void testAuthoriseCreateQueue() throws Exception
+ {
+ createQueueSuccess("test", "client", "create.00", true, true);
+ createQueueSuccess("test", "client", "create.01", true, false);
+ createQueueSuccess("test", "client", "create.02", false, true);
+ createQueueSuccess("test", "client", "create.03", true, false);
+ createQueueFailure("test", "server", "create.04", true, true);
+ createQueueFailure("test", "server", "create.05", true, false);
+ createQueueFailure("test", "server", "create.06", false, true);
+ createQueueFailure("test", "server", "create.07", true, false);
+ createQueueSuccess("test2", "guest", "create.00", true, true);
+ createQueueSuccess("test2", "guest", "create.01", true, false);
+ createQueueSuccess("test2", "guest", "create.02", false, true);
+ createQueueSuccess("test2", "guest", "create.03", true, false);
+ }
+
+ public void setUpAuthoriseCreateQueueBoth() throws Exception
+ {
+ writeACLFile("test",
+ "acl allow all access virtualhost",
+ "acl allow client create queue name=\"create.*\"",
+ "acl allow all create queue temporary=true"
+ );
+ }
+
+ /**
+ * Tests creation of named queues.
+ *
+ * If a named queue is specified
+ */
+ public void testAuthoriseCreateQueueBoth() throws Exception
+ {
+ createQueueSuccess("test", "client", "create.00", true, false);
+ createQueueSuccess("test", "client", "create.01", false, false);
+ createQueueFailure("test", "server", "create.02", false, false);
+ createQueueFailure("test", "guest", "create.03", false, false);
+ createQueueSuccess("test", "client", "tmp.00", true, false);
+ createQueueSuccess("test", "server", "tmp.01", true, false);
+ createQueueSuccess("test", "guest", "tmp.02", true, false);
+ createQueueSuccess("test2", "guest", "create.02", false, false);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java
new file mode 100644
index 0000000000..1d08015669
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+
+/**
+ * Tests that ACL version 2/3 files following the specification work correctly.
+ *
+ * ACL lines that are identical in meaning apart from differences allowed by the specification, such as whitespace or case
+ * of tokens are set up for numbered queues and the queues are then created to show that the meaning is correctly parsed by
+ * the plugin.
+ *
+ * TODO move this to the access-control plugin unit tests instead
+ */
+public class ExternalACLFileTest extends AbstractACLTestCase
+{
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test");
+ }
+
+ private void createQueuePrefixList(String prefix, int count)
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+
+ //Create n queues
+ for (int n = 0; n < count; n++)
+ {
+ AMQShortString queueName = new AMQShortString(String.format("%s.%03d", prefix, n));
+ ((AMQSession<?, ?>) sess).createQueue(queueName, false, false, false);
+ }
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ private void createQueueNameList(String...queueNames)
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+
+ //Create all queues
+ for (String queueName : queueNames)
+ {
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString(queueName), false, false, false);
+ }
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void setUpCreateQueueMixedCase() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name=mixed.000",
+ "ACL ALLOW client CREATE QUEUE NAME=mixed.001",
+ "Acl Allow client Create Queue Name=mixed.002",
+ "aCL aLLOW client cREATE qUEUE nAME=mixed.003",
+ "aCl AlLoW client cReAtE qUeUe NaMe=mixed.004"
+ );
+ }
+
+ public void testCreateQueueMixedCase()
+ {
+ createQueuePrefixList("mixed", 5);
+ }
+
+ public void setUpCreateQueueContinuation() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name=continuation.000",
+ "acl allow client create queue \\",
+ " name=continuation.001",
+ "acl allow client \\",
+ " create queue \\",
+ " name=continuation.002",
+ "acl allow \\",
+ " client \\",
+ " create queue \\",
+ " name=continuation.003",
+ "acl \\",
+ " allow \\",
+ " client \\",
+ " create queue \\",
+ " name=continuation.004"
+ );
+ }
+
+ public void testCreateQueueContinuation()
+ {
+ createQueuePrefixList("continuation", 5);
+ }
+
+ public void setUpCreateQueueWhitespace() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name=whitespace.000",
+ "acl\tallow\tclient\tcreate\tqueue\tname=whitespace.001",
+ "acl allow client create queue name = whitespace.002",
+ "acl\tallow\tclient\tcreate\tqueue\tname\t=\twhitespace.003",
+ "acl allow\t\tclient\t \tcreate\t\t queue\t \t name \t =\t \twhitespace.004"
+ );
+ }
+
+ public void testCreateQueueWhitespace()
+ {
+ createQueuePrefixList("whitespace", 5);
+ }
+
+ public void setUpCreateQueueQuoting() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name='quoting.ABC.000'",
+ "acl allow client create queue name='quoting.*.000'",
+ "acl allow client create queue name='quoting.#.000'",
+ "acl allow client create queue name='quoting. .000'",
+ "acl allow client create queue name='quoting.!@$%.000'"
+ );
+ }
+
+ public void testCreateQueueQuoting()
+ {
+ createQueueNameList(
+ "quoting.ABC.000",
+ "quoting.*.000",
+ "quoting.#.000",
+ "quoting. .000",
+ "quoting.!@$%.000"
+ );
+ }
+}
+
+
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java
new file mode 100644
index 0000000000..b823690002
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+
+import org.apache.qpid.AMQConnectionClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQSecurityException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.JMXTestUtils;
+
+/**
+ * Tests that ACL entries that apply to AMQP objects also apply when those objects are accessed via JMX.
+ */
+public class ExternalACLJMXTest extends AbstractACLTestCase
+{
+ private JMXTestUtils _jmx;
+
+ private static final String QUEUE_NAME = "kipper";
+ private static final String EXCHANGE_NAME = "amq.kipper";
+
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test");
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _jmx = new JMXTestUtils(this, "admin", "admin");
+ _jmx.setUp();
+ super.setUp();
+ _jmx.open();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _jmx.close();
+ super.tearDown();
+ }
+
+ // test-externalacljmx.txt
+ // create queue owner=client # success
+ public void testCreateClientQueueSuccess() throws Exception
+ {
+ //Queue Parameters
+ String queueOwner = "client";
+
+ _jmx.createQueue("test", QUEUE_NAME, queueOwner, true);
+ }
+
+ // test-externalacljmx.txt
+ // create queue owner=client # failure
+ public void testCreateServerQueueFailure() throws Exception
+ {
+ //Queue Parameters
+ String queueOwner = "server";
+
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, queueOwner, true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ // no create queue acl in file # failure
+ public void testCreateQueueFailure() throws Exception
+ {
+ //Queue Parameters
+ String queueOwner = "guest";
+
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, queueOwner, true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ // test-externalacljmx.txt
+ // allow create exchange name=amq.kipper.success
+ public void testCreateExchangeSuccess() throws Exception
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".success", "direct", true);
+ }
+
+ // test-externalacljmx.txt
+ // deny create exchange name=amq.kipper.failure
+ public void testCreateExchangeFailure() throws Exception
+ {
+ try
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".failure", "direct", true);
+
+ fail("Exchange create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: exchange-name 'amq.kipper.failure' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ // test-externalacljmx.txt
+ // allow create exchange name=amq.kipper.success
+ // allow delete exchange name=amq.kipper.success
+ public void testDeleteExchangeSuccess() throws Exception
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".success", "direct", true);
+ _jmx.unregisterExchange("test", EXCHANGE_NAME + ".success");
+ }
+
+ // test-externalacljmx-deleteexchangefailure.txt
+ // allow create exchange name=amq.kipper.delete
+ // deny delete exchange name=amq.kipper.delete
+ public void testDeleteExchangeFailure() throws Exception
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".delete", "direct", true);
+ try
+ {
+ _jmx.unregisterExchange("test", EXCHANGE_NAME + ".delete");
+
+ fail("Exchange delete should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ /**
+ * admin user has JMX right but not AMQP
+ */
+ public void setUpCreateQueueJMXRights() throws Exception
+ {
+ writeACLFile("test",
+ "ACL ALLOW admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"",
+ "ACL DENY admin CREATE QUEUE");
+ }
+
+ public void testCreateQueueJMXRights() throws Exception
+ {
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, "admin", true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ /**
+ * admin user has AMQP right but not JMX
+ */
+ public void setUpCreateQueueAMQPRights() throws Exception
+ {
+ writeACLFile("test",
+ "ACL DENY admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"",
+ "ACL ALLOW admin CREATE QUEUE");
+ }
+
+ public void testCreateQueueAMQPRights() throws Exception
+ {
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, "admin", true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertEquals("Cause message incorrect", "Permission denied: Execute createNewQueue", e.getMessage());
+ }
+ }
+
+ /**
+ * admin has both JMX and AMQP rights
+ */
+ public void setUpCreateQueueJMXAMQPRights() throws Exception
+ {
+ writeACLFile("test",
+ "ACL ALLOW admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"",
+ "ACL ALLOW admin CREATE QUEUE");
+ }
+
+ public void testCreateQueueJMXAMQPRights() throws Exception
+ {
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, "admin", true);
+ }
+ catch (Exception e)
+ {
+ fail("Queue create should succeed: " + e.getCause().getMessage());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java
new file mode 100644
index 0000000000..4603cc1862
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+
+public class ExternalACLTest extends SimpleACLTest
+{
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test", "test2");
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java
new file mode 100644
index 0000000000..290cbfdc14
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.qpid.server.logging.management.LoggingManagementMBean;
+import org.apache.qpid.test.utils.JMXTestUtils;
+
+/**
+ * Tests that ACLs can be applied to mangement operations that do not correspond to a specific AMQP object.
+ *
+ * Theses tests use the logging component, exposed as the {@link LoggingManagementMBean}, to get and set properties.
+ */
+public class ExternalAdminACLTest extends AbstractACLTestCase
+{
+ private static final String CATEGORY_PRIORITY = "LogManMBeanTest.category.priority";
+ private static final String CATEGORY_LEVEL = "LogManMBeanTest.category.level";
+ private static final String LOGGER_LEVEL = "LogManMBeanTest.logger.level";
+
+ private static final String NEWLINE = System.getProperty("line.separator");
+
+ private JMXTestUtils _jmx;
+ private File _testConfigFile;
+
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("global");
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _testConfigFile = createTempTestLog4JConfig();
+
+ _jmx = new JMXTestUtils(this, "admin", "admin");
+ _jmx.setUp();
+ super.setUp();
+ _jmx.open();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _jmx.close();
+ super.tearDown();
+ }
+
+ private File createTempTestLog4JConfig()
+ {
+ File tmpFile = null;
+ try
+ {
+ tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp");
+ tmpFile.deleteOnExit();
+
+ FileWriter fstream = new FileWriter(tmpFile);
+ BufferedWriter writer = new BufferedWriter(fstream);
+
+ writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE);
+ writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE);
+
+ writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " +
+ "threshold=\"null\">"+NEWLINE);
+
+ writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE);
+ writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE);
+ writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE);
+ writer.write(" </layout>"+NEWLINE);
+ writer.write(" </appender>"+NEWLINE);
+
+ //Example of a 'category' with a 'priority'
+ writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_PRIORITY +"\">"+NEWLINE);
+ writer.write(" <priority value=\"info\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </category>"+NEWLINE);
+
+ //Example of a 'category' with a 'level'
+ writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_LEVEL +"\">"+NEWLINE);
+ writer.write(" <level value=\"warn\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </category>"+NEWLINE);
+
+ //Example of a 'logger' with a 'level'
+ writer.write(" <logger additivity=\"true\" name=\"" + LOGGER_LEVEL + "\">"+NEWLINE);
+ writer.write(" <level value=\"error\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </logger>"+NEWLINE);
+
+ //'root' logger
+ writer.write(" <root>"+NEWLINE);
+ writer.write(" <priority value=\"info\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </root>"+NEWLINE);
+
+ writer.write("</log4j:configuration>"+NEWLINE);
+
+ writer.flush();
+ writer.close();
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create temporary test log4j configuration");
+ }
+
+ return tmpFile;
+ }
+
+ public void testGetAllLoggerLevels() throws Exception
+ {
+ String[] levels = _jmx.getAvailableLoggerLevels();
+ for (int i = 0; i < levels.length; i++)
+ {
+ System.out.println(levels[i]);
+ }
+ assertEquals("Got incorrect number of log levels", 9, levels.length);
+ }
+
+ public void testGetAllLoggerLevelsDenied() throws Exception
+ {
+ try
+ {
+ _jmx.getAvailableLoggerLevels();
+ fail("Got list of log levels");
+ }
+ catch (Exception e)
+ {
+ // Exception throws
+ e.printStackTrace();
+ assertEquals("Permission denied: Access getAvailableLoggerLevels", e.getMessage());
+ }
+ }
+
+ public void testChangeLoggerLevel() throws Exception
+ {
+ String oldLevel = _jmx.getRuntimeRootLoggerLevel();
+ System.out.println("old level = " + oldLevel);
+ _jmx.setRuntimeRootLoggerLevel("DEBUG");
+ String newLevel = _jmx.getRuntimeRootLoggerLevel();
+ System.out.println("new level = " + newLevel);
+ assertEquals("Logging level was not changed", "DEBUG", newLevel);
+ }
+
+ public void testChangeLoggerLevelDenied() throws Exception
+ {
+ try
+ {
+ _jmx.setRuntimeRootLoggerLevel("DEBUG");
+ fail("Logging level was changed");
+ }
+ catch (Exception e)
+ {
+ assertEquals("Permission denied: Update setRuntimeRootLoggerLevel", e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java
new file mode 100644
index 0000000000..a50817e659
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java
@@ -0,0 +1,644 @@
+/*
+ * 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.IOException;
+
+import javax.jms.Connection;
+import javax.jms.DeliveryMode;
+import javax.jms.IllegalStateException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicSubscriber;
+import javax.naming.NamingException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.url.URLSyntaxException;
+
+/**
+ * Basic access control list tests.
+ *
+ * These tests require an access control security plugin to be configured in the broker, and carry out various broker
+ * operations that will succeed or fail depending on the user and virtual host. See the {@code config-systests-acl-setup.xml}
+ * configuration file for the SimpleXML version of the ACLs used by the Java broker only, or the various {@code .txt}
+ * files in the system tests directory for the external version 3 ACL files used by both the Java and C++ brokers.
+ * <p>
+ * This class can be extended and the {@link #getConfig()} method overridden to run the same tests with a different type
+ * of access control mechanism. Extension classes should differ only in the configuration file used, but extra tests can be
+ * added that are specific to a particular configuration.
+ * <p>
+ * The tests perform basic AMQP operations like creating queues or excahnges and publishing and consuming messages, using
+ * JMS to contact the broker.
+ *
+ * @see ExternalACLTest
+ */
+public class SimpleACLTest extends AbstractACLTestCase
+{
+ public void testAccessAuthorizedSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+
+ //Do something to show connection is active.
+ sess.rollback();
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Connection was not created due to:" + e);
+ }
+ }
+
+ public void testAccessVhostAuthorisedGuestSuccess() throws IOException, Exception
+ {
+ //The 'guest' user has no access to the 'test' vhost, as tested below in testAccessNoRights(), and so
+ //is unable to perform actions such as connecting (and by extension, creating a queue, and consuming
+ //from a queue etc). In order to test the vhost-wide 'access' ACL right, the 'guest' user has been given
+ //this right in the 'test2' vhost.
+
+ try
+ {
+ //get a connection to the 'test2' vhost using the guest user and perform various actions.
+ Connection conn = getConnection("test2", "guest", "guest");
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+
+ //create Queues and consumers for each
+ Queue namedQueue = sess.createQueue("vhostAccessCreatedQueue" + getTestQueueName());
+ Queue tempQueue = sess.createTemporaryQueue();
+ MessageConsumer consumer = sess.createConsumer(namedQueue);
+ MessageConsumer tempConsumer = sess.createConsumer(tempQueue);
+
+ //send a message to each queue (also causing an exchange declare)
+ MessageProducer sender = ((AMQSession<?, ?>) sess).createProducer(null);
+ ((org.apache.qpid.jms.MessageProducer) sender).send(namedQueue, sess.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+ ((org.apache.qpid.jms.MessageProducer) sender).send(tempQueue, sess.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ //consume the messages from the queues
+ consumer.receive(2000);
+ tempConsumer.receive(2000);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testAccessNoRightsFailure() throws Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "guest", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+ sess.rollback();
+
+ fail("Connection was created.");
+ }
+ catch (JMSException e)
+ {
+ // JMSException -> linkedException -> cause = AMQException (403 or 320)
+ Exception linkedException = e.getLinkedException();
+ assertNotNull("There was no linked exception", linkedException);
+ Throwable cause = linkedException.getCause();
+ assertNotNull("Cause was null", cause);
+ assertTrue("Wrong linked exception type", cause instanceof AMQException);
+ AMQConstant errorCode = isBroker010() ? AMQConstant.CONTEXT_IN_USE : AMQConstant.ACCESS_REFUSED;
+ assertEquals("Incorrect error code received", errorCode, ((AMQException) cause).getErrorCode());
+ }
+ }
+
+ public void testClientDeleteQueueSuccess() throws Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+
+ // create kipper
+ Topic kipper = sess.createTopic("kipper");
+ TopicSubscriber subscriber = sess.createDurableSubscriber(kipper, "kipper");
+
+ subscriber.close();
+ sess.unsubscribe("kipper");
+
+ //Do something to show connection is active.
+ sess.rollback();
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testServerDeleteQueueFailure() throws Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+
+ // create kipper
+ Topic kipper = sess.createTopic("kipper");
+ TopicSubscriber subscriber = sess.createDurableSubscriber(kipper, "kipper");
+
+ subscriber.close();
+ sess.unsubscribe("kipper");
+
+ //Do something to show connection is active.
+ sess.rollback();
+ conn.close();
+ }
+ catch (JMSException e)
+ {
+ // JMSException -> linedException = AMQException.403
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testClientConsumeFromTempQueueSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createTemporaryQueue());
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testClientConsumeFromNamedQueueFailure() throws NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createQueue("IllegalQueue"));
+
+ fail("Test failed as consumer was created.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testClientCreateTemporaryQueueSuccess() throws JMSException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create Temporary Queue - can't use the createTempQueue as QueueName is null.
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("doesnt_matter_as_autodelete_means_tmp"),
+ true, false, false);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testClientCreateNamedQueueFailure() throws NamingException, JMSException, AMQException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create a Named Queue
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("IllegalQueue"), false, false, false);
+
+ fail("Test failed as Queue creation succeded.");
+ //conn will be automatically closed
+ }
+ catch (AMQException e)
+ {
+ check403Exception(e);
+ }
+ }
+
+ public void testClientPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+
+ conn.start();
+
+ MessageProducer sender = sess.createProducer(sess.createQueue("example.RequestQueue"));
+
+ sender.send(sess.createTextMessage("test"));
+
+ //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker.
+ sess.commit();
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test publish failed:" + e);
+ }
+ }
+
+ public void testClientPublishValidQueueSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ MessageProducer sender = ((AMQSession<?, ?>) sess).createProducer(null);
+
+ Queue queue = sess.createQueue("example.RequestQueue");
+
+ // Send a message that we will wait to be sent, this should give the broker time to process the msg
+ // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not
+ // queue existence.
+ ((org.apache.qpid.jms.MessageProducer) sender).send(queue, sess.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test publish failed:" + e);
+ }
+ }
+
+ public void testClientPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ MessageProducer sender = ((AMQSession<?, ?>) session).createProducer(null);
+
+ Queue queue = session.createQueue("Invalid");
+
+ // Send a message that we will wait to be sent, this should give the broker time to close the connection
+ // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not
+ // queue existence.
+ ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ // Test the connection with a valid consumer
+ // This may fail as the session may be closed before the queue or the consumer created.
+ Queue temp = session.createTemporaryQueue();
+
+ session.createConsumer(temp).close();
+
+ //Connection should now be closed and will throw the exception caused by the above send
+ conn.close();
+
+ fail("Close is not expected to succeed.");
+ }
+ catch (IllegalStateException e)
+ {
+ _logger.info("QPID-2345: Session became closed and we got that error rather than the authentication error.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerConsumeFromNamedQueueValid() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createQueue("example.RequestQueue"));
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testServerConsumeFromNamedQueueInvalid() throws AMQException, URLSyntaxException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createQueue("Invalid"));
+
+ fail("Test failed as consumer was created.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerConsumeFromTemporaryQueue() throws AMQException, URLSyntaxException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createTemporaryQueue());
+
+ fail("Test failed as consumer was created.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerCreateNamedQueueValid() throws JMSException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create Temporary Queue
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("example.RequestQueue"), false, false, false);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testServerCreateNamedQueueInvalid() throws JMSException, URLSyntaxException, AMQException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create a Named Queue
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("IllegalQueue"), false, false, false);
+
+ fail("Test failed as creation succeded.");
+ }
+ catch (Exception e)
+ {
+ check403Exception(e);
+ }
+ }
+
+ public void testServerCreateTemporaryQueueInvalid() throws NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ session.createTemporaryQueue();
+
+ fail("Test failed as creation succeded.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerCreateAutoDeleteQueueInvalid() throws NamingException, JMSException, AMQException, Exception
+ {
+ try
+ {
+ Connection connection = getConnection("test", "server", "guest");
+
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ connection.start();
+
+ ((AMQSession<?, ?>) session).createQueue(new AMQShortString("again_ensure_auto_delete_queue_for_temporary"),
+ true, false, false);
+
+ fail("Test failed as creation succeded.");
+ }
+ catch (Exception e)
+ {
+ check403Exception(e);
+ }
+ }
+
+ /**
+ * This test uses both the cilent and sender to validate that the Server is able to publish to a temporary queue.
+ * The reason the client must be involved is that the Server is unable to create its own Temporary Queues.
+ *
+ * @throws AMQException
+ * @throws URLSyntaxException
+ * @throws JMSException
+ */
+ public void testServerPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception
+ {
+ //Set up the Server
+ Connection serverConnection = getConnection("test", "server", "guest");
+
+ Session serverSession = serverConnection.createSession(true, Session.SESSION_TRANSACTED);
+
+ Queue requestQueue = serverSession.createQueue("example.RequestQueue");
+
+ MessageConsumer server = serverSession.createConsumer(requestQueue);
+
+ serverConnection.start();
+
+ //Set up the consumer
+ Connection clientConnection = getConnection("test", "client", "guest");
+
+ //Send a test mesage
+ Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue responseQueue = clientSession.createTemporaryQueue();
+
+ MessageConsumer clientResponse = clientSession.createConsumer(responseQueue);
+
+ clientConnection.start();
+
+ Message request = clientSession.createTextMessage("Request");
+
+ assertNotNull("Response Queue is null", responseQueue);
+
+ request.setJMSReplyTo(responseQueue);
+
+ clientSession.createProducer(requestQueue).send(request);
+
+ try
+ {
+ Message msg = null;
+
+ msg = server.receive(2000);
+
+ while (msg != null && !((TextMessage) msg).getText().equals("Request"))
+ {
+ msg = server.receive(2000);
+ }
+
+ assertNotNull("Message not received", msg);
+
+ assertNotNull("Reply-To is Null", msg.getJMSReplyTo());
+
+ MessageProducer sender = serverSession.createProducer(msg.getJMSReplyTo());
+
+ sender.send(serverSession.createTextMessage("Response"));
+
+ //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker.
+ serverSession.commit();
+
+ //Ensure Response is received.
+ Message clientResponseMsg = clientResponse.receive(2000);
+ assertNotNull("Client did not receive response message,", clientResponseMsg);
+ assertEquals("Incorrect message received", "Response", ((TextMessage) clientResponseMsg).getText());
+
+ }
+ catch (Exception e)
+ {
+ fail("Test publish failed:" + e);
+ }
+ finally
+ {
+ try
+ {
+ serverConnection.close();
+ }
+ finally
+ {
+ clientConnection.close();
+ }
+ }
+ }
+
+ public void testServerPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ ((AMQConnection) conn).setConnectionListener(this);
+
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ MessageProducer sender = ((AMQSession<?, ?>) session).createProducer(null);
+
+ Queue queue = session.createQueue("Invalid");
+
+ // Send a message that we will wait to be sent, this should give the broker time to close the connection
+ // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not
+ // queue existence.
+ ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ // Test the connection with a valid consumer
+ // This may not work as the session may be closed before the queue or consumer creation can occur.
+ // The correct JMSexception with linked error will only occur when the close method is recevied whilst in
+ // the failover safe block
+ session.createConsumer(session.createQueue("example.RequestQueue")).close();
+
+ //Connection should now be closed and will throw the exception caused by the above send
+ conn.close();
+
+ fail("Close is not expected to succeed.");
+ }
+ catch (IllegalStateException e)
+ {
+ _logger.info("QPID-2345: Session became closed and we got that error rather than the authentication error.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java
new file mode 100644
index 0000000000..f40e95885d
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java
@@ -0,0 +1,298 @@
+/*
+ * 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.firewall;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+public class FirewallConfigTest extends QpidBrokerTestCase
+{
+ private File _tmpConfig, _tmpVirtualhosts;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ // do setup
+ final String QPID_HOME = System.getProperty("QPID_HOME");
+
+ if (QPID_HOME == null)
+ {
+ fail("QPID_HOME not set");
+ }
+
+ // Setup initial config file.
+ _configFile = new File(QPID_HOME, "etc/config-systests-firewall.xml");
+
+ // Setup temporary config file
+ _tmpConfig = File.createTempFile("config-systests-firewall", ".xml");
+ setSystemProperty("QPID_FIREWALL_CONFIG_SETTINGS", _tmpConfig.getAbsolutePath());
+ _tmpConfig.deleteOnExit();
+
+ // Setup temporary virtualhosts file
+ _tmpVirtualhosts = File.createTempFile("virtualhosts-systests-firewall", ".xml");
+ setSystemProperty("QPID_FIREWALL_VIRTUALHOSTS_SETTINGS", _tmpVirtualhosts.getAbsolutePath());
+ _tmpVirtualhosts.deleteOnExit();
+ }
+
+ private void writeFirewallFile(boolean allow, boolean inVhost) throws IOException
+ {
+ FileWriter out = new FileWriter(inVhost ? _tmpVirtualhosts : _tmpConfig);
+ String ipAddr = "127.0.0.1"; // FIXME: get this from InetAddress.getLocalHost().getAddress() ?
+ if (inVhost)
+ {
+ out.write("<virtualhosts><virtualhost><test>");
+ }
+ else
+ {
+ out.write("<broker>");
+ }
+ out.write("<security><firewall>");
+ out.write("<rule access=\""+((allow) ? "allow" : "deny")+"\" network=\""+ipAddr +"\"/>");
+ out.write("</firewall></security>");
+ if (inVhost)
+ {
+ out.write("</test></virtualhost></virtualhosts>");
+ }
+ else
+ {
+ out.write("</broker>");
+ }
+ out.close();
+ }
+
+ public void testVhostAllowBrokerDeny() throws Exception
+ {
+ if (_broker.equals(VM))
+ {
+ //No point running this test with an InVM broker as the
+ //firewall plugin only functions for TCP connections.
+ return;
+ }
+
+ _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-2.xml");
+
+ super.setUp();
+
+ Connection conn = null;
+ try
+ {
+ //Try to get a connection to the 'test2' vhost
+ //This is expected to succeed as it is allowed at the vhost level
+ conn = getConnection(new AMQConnectionURL("amqp://guest:guest@clientid/test2?brokerlist='" + getBroker() + "'"));
+ }
+ catch (JMSException e)
+ {
+ e.getLinkedException().printStackTrace();
+ fail("The connection was expected to succeed: " + e.getMessage());
+ }
+
+ conn = null;
+ try
+ {
+ //Try to get a connection to the 'test' vhost
+ //This is expected to fail as it is denied at the broker level
+ conn = getConnection();
+ fail("We expected the connection to fail");
+ }
+ catch (JMSException e)
+ {
+ //ignore
+ }
+ }
+
+ public void testVhostDenyBrokerAllow() throws Exception
+ {
+ if (_broker.equals(VM))
+ {
+ //No point running this test with an InVM broker as the
+ //firewall plugin only functions for TCP connections.
+ return;
+ }
+
+ _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-3.xml");
+
+ super.setUp();
+
+ Connection conn = null;
+ try
+ {
+ //Try to get a connection to the 'test2' vhost
+ //This is expected to fail as it is denied at the vhost level
+ conn = getConnection(new AMQConnectionURL("amqp://guest:guest@clientid/test2?brokerlist='" + getBroker() + "'"));
+ fail("The connection was expected to fail");
+ }
+ catch (JMSException e)
+ {
+ //ignore
+ }
+
+ conn = null;
+ try
+ {
+ //Try to get a connection to the 'test' vhost
+ //This is expected to succeed as it is allowed at the broker level
+ conn = getConnection();
+ }
+ catch (JMSException e)
+ {
+ e.getLinkedException().printStackTrace();
+ fail("The connection was expected to succeed: " + e.getMessage());
+ }
+ }
+
+ public void testDenyOnRestart() throws Exception
+ {
+ testDeny(false, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ restartBroker();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
+ public void testDenyOnRestartInVhost() throws Exception
+ {
+ testDeny(true, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ restartBroker();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
+ public void testAllowOnReloadInVhost() throws Exception
+ {
+ testFirewall(false, true, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ reloadBrokerSecurityConfig();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
+ public void testDenyOnReload() throws Exception
+ {
+ testDeny(false, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ reloadBrokerSecurityConfig();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ }
+ );
+ }
+
+ public void testDenyOnReloadInVhost() throws Exception
+ {
+ testDeny(true, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ reloadBrokerSecurityConfig();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ }
+ );
+
+ }
+
+ private void testDeny(boolean inVhost, Runnable restartOrReload) throws Exception
+ {
+ testFirewall(true, inVhost, restartOrReload);
+ }
+
+ /*
+ * Check we can get a connection
+ */
+ private boolean checkConnection() throws Exception
+ {
+ Exception exception = null;
+ Connection conn = null;
+ try
+ {
+ conn = getConnection();
+ }
+ catch (JMSException e)
+ {
+ exception = e;
+ }
+
+ return conn != null;
+ }
+
+ private void testFirewall(boolean initial, boolean inVhost, Runnable restartOrReload) throws Exception
+ {
+ if (_broker.equals(VM))
+ {
+ // No point running this test in a vm broker
+ return;
+ }
+
+ writeFirewallFile(initial, inVhost);
+ setConfigurationProperty("management.enabled", String.valueOf(true));
+ super.setUp();
+
+ assertEquals("Initial connection check failed", initial, checkConnection());
+
+ // Reload changed firewall file after restart or reload
+ writeFirewallFile(!initial, inVhost);
+ restartOrReload.run();
+
+ assertEquals("Second connection check failed", !initial, checkConnection());
+ }
+}