added a BrokerPlugin mechanism to allow users to customize the Broker interceptor stack

git-svn-id: https://svn.apache.org/repos/asf/incubator/activemq/trunk@377991 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
James Strachan 2006-02-15 11:48:17 +00:00
parent 7b945f9d92
commit 4686ec5f0b
9 changed files with 484 additions and 180 deletions

View File

@ -0,0 +1,32 @@
/**
*
* Copyright 2005-2006 The Apache Software Foundation
*
* Licensed 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.activemq.broker;
/**
* Represents a plugin into a Broker
*
* @version $Revision$
*/
public interface BrokerPlugin {
/**
* Installs the plugin into the interceptor chain of the broker, returning the new
* intercepted broker to use.
*/
public Broker installPlugin(Broker broker);
}

View File

@ -112,6 +112,7 @@ public class BrokerService implements Service {
private URI vmConnectorURI;
private PolicyMap destinationPolicy;
private AtomicBoolean started = new AtomicBoolean(false);
private BrokerPlugin[] plugins;
/**
* Adds a new transport connector for the given bind address
@ -677,6 +678,17 @@ public class BrokerService implements Service {
this.destinationPolicy = policyMap;
}
public BrokerPlugin[] getPlugins() {
return plugins;
}
/**
* Sets a number of broker plugins to install such as for security authentication or authorization
*/
public void setPlugins(BrokerPlugin[] plugins) {
this.plugins = plugins;
}
// Implementation methods
// -------------------------------------------------------------------------
/**
@ -876,6 +888,12 @@ public class BrokerService implements Service {
if (isPopulateJMSXUserID()) {
broker = new UserIDBroker(broker);
}
if (plugins != null) {
for (int i = 0; i < plugins.length; i++) {
BrokerPlugin plugin = plugins[i];
broker = plugin.installPlugin(broker);
}
}
return broker;
}

View File

@ -0,0 +1,87 @@
/**
*
* Copyright 2005-2006 The Apache Software Foundation
*
* Licensed 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.activemq.security;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;
import java.net.URL;
/**
* Adds a JAAS based authentication security plugin
*
* @org.apache.xbean.XBean description="Provides a JAAS based authentication plugin"
*
* @version $Revision$
*/
public class JassAuthenticationPlugin implements BrokerPlugin {
private String configuration = "activemq-domain";
private boolean discoverLoginConfig = true;
public Broker installPlugin(Broker broker) {
initialiseJaas();
return new JaasAuthenticationBroker(broker, configuration);
}
// Properties
// -------------------------------------------------------------------------
public String getConfiguration() {
return configuration;
}
/**
* Sets the JAAS configuration domain name used
*/
public void setConfiguration(String jaasConfiguration) {
this.configuration = jaasConfiguration;
}
public boolean isDiscoverLoginConfig() {
return discoverLoginConfig;
}
/**
* Enables or disables the auto-discovery of the login.config file for JAAS to initialize itself.
* This flag is enabled by default such that if the <b>java.security.auth.login.config</b> system property
* is not defined then it is set to the location of the <b>login.config</b> file on the classpath.
*/
public void setDiscoverLoginConfig(boolean discoverLoginConfig) {
this.discoverLoginConfig = discoverLoginConfig;
}
// Implementation methods
// -------------------------------------------------------------------------
protected void initialiseJaas() {
if (discoverLoginConfig) {
String path = System.getProperty("java.security.auth.login.config");
if (path == null) {
//URL resource = Thread.currentThread().getContextClassLoader().getResource("login.config");
URL resource = null;
if (resource == null) {
resource = getClass().getClassLoader().getResource("login.config");
}
if (resource != null) {
path = resource.getFile();
System.setProperty("java.security.auth.login.config", path);
}
}
}
}
}

View File

@ -0,0 +1,62 @@
/**
*
* Copyright 2005-2006 The Apache Software Foundation
*
* Licensed 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.activemq.security;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;
import java.util.Map;
/**
* A simple authentication plugin
*
* @org.apache.xbean.XBean element="simpleAuthenticationPlugin" description="Provides a simple authentication
* plugin configured with a map of user-passwords and a map of user-groups"
*
* @version $Revision$
*/
public class SimpleAuthenticationPlugin implements BrokerPlugin {
private Map userPasswords;
private Map userGroups;
public Broker installPlugin(Broker broker) {
return new SimpleAuthenticationBroker(broker, userPasswords, userGroups);
}
public Map getUserGroups() {
return userGroups;
}
/**
* Sets the groups a user is in. The key is the user name and the value is a Set of groups
*/
public void setUserGroups(Map userGroups) {
this.userGroups = userGroups;
}
public Map getUserPasswords() {
return userPasswords;
}
/**
* Sets the map indexed by user name with the value the password
*/
public void setUserPasswords(Map userPasswords) {
this.userPasswords = userPasswords;
}
}

View File

@ -0,0 +1,65 @@
/**
*
* Copyright 2005-2006 The Apache Software Foundation
*
* Licensed 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.activemq.security;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;
import org.apache.activemq.filter.DestinationMap;
/**
* A simple authorization plugin
*
* @org.apache.xbean.XBean element="simpleAuthorizationPlugin" description="Provides a simple authorization
* plugin where each ACL is a destination map of destinations to role names"
*
* @version $Revision$
*/
public class SimpleAuthorizationPlugin implements BrokerPlugin {
private DestinationMap writeACLs;
private DestinationMap readACLs;
private DestinationMap adminACLs;
public Broker installPlugin(Broker broker) {
return new SimpleAuthorizationBroker(broker, writeACLs, readACLs, adminACLs);
}
public DestinationMap getAdminACLs() {
return adminACLs;
}
public void setAdminACLs(DestinationMap adminACLs) {
this.adminACLs = adminACLs;
}
public DestinationMap getReadACLs() {
return readACLs;
}
public void setReadACLs(DestinationMap readACLs) {
this.readACLs = readACLs;
}
public DestinationMap getWriteACLs() {
return writeACLs;
}
public void setWriteACLs(DestinationMap writeACLs) {
this.writeACLs = writeACLs;
}
}

View File

@ -0,0 +1,168 @@
/**
*
* Copyright 2005-2006 The Apache Software Foundation
*
* Licensed 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.activemq.security;
import org.apache.activemq.JmsTestSupport;
import org.apache.activemq.command.ActiveMQDestination;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
/**
*
* @version $Revision$
*/
public class SecurityTestSupport extends JmsTestSupport {
public ActiveMQDestination destination;
public void testUserReceiveFails() throws JMSException {
doReceive(true);
}
public void testInvalidAuthentication() throws JMSException {
try {
// No user id
Connection c = factory.createConnection();
connections.add(c);
c.start();
fail("Expected exception.");
}
catch (JMSException e) {
}
try {
// Bad password
Connection c = factory.createConnection("user", "krap");
connections.add(c);
c.start();
fail("Expected exception.");
}
catch (JMSException e) {
}
try {
// Bad userid
Connection c = factory.createConnection("userkrap", null);
connections.add(c);
c.start();
fail("Expected exception.");
}
catch (JMSException e) {
}
}
public void testUserReceiveSucceeds() throws JMSException {
doReceive(false);
}
public void testGuestReceiveSucceeds() throws JMSException {
doReceive(false);
}
public void testGuestReceiveFails() throws JMSException {
doReceive(true);
}
public void testUserSendSucceeds() throws JMSException {
doSend(false);
}
public void testUserSendFails() throws JMSException {
doSend(true);
}
public void testGuestSendFails() throws JMSException {
doSend(true);
}
public void testGuestSendSucceeds() throws JMSException {
doSend(false);
}
/**
* @throws JMSException
*/
public void doSend(boolean fail) throws JMSException {
Connection adminConnection = factory.createConnection("system", "manager");
connections.add(adminConnection);
adminConnection.start();
Session adminSession = adminConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = adminSession.createConsumer(destination);
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
try {
sendMessages(session, destination, 1);
}
catch (JMSException e) {
// If test is expected to fail, the cause must only be a
// SecurityException
// otherwise rethrow the exception
if (!fail || !(e.getCause() instanceof SecurityException)) {
throw e;
}
}
Message m = consumer.receive(1000);
if (fail)
assertNull(m);
else {
assertNotNull(m);
assertEquals("0", ((TextMessage) m).getText());
assertNull(consumer.receiveNoWait());
}
}
/**
* @throws JMSException
*/
public void doReceive(boolean fail) throws JMSException {
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = null;
try {
consumer = session.createConsumer(destination);
if (fail)
fail("Expected failure due to security constraint.");
}
catch (JMSException e) {
if (fail && e.getCause() instanceof SecurityException)
return;
throw e;
}
Connection adminConnection = factory.createConnection("system", "manager");
connections.add(adminConnection);
Session adminSession = adminConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
sendMessages(adminSession, destination, 1);
Message m = consumer.receive(1000);
assertNotNull(m);
assertEquals("0", ((TextMessage) m).getText());
assertNull(consumer.receiveNoWait());
}
}

View File

@ -16,55 +16,39 @@
*/
package org.apache.activemq.security;
import java.io.IOException;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.filter.DestinationMap;
import org.apache.activemq.jaas.GroupPrincipal;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
import junit.framework.Test;
import org.apache.activemq.JmsTestSupport;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.filter.DestinationMap;
import org.apache.activemq.jaas.GroupPrincipal;
import org.apache.activemq.security.JaasAuthenticationBroker;
import org.apache.activemq.security.SimpleAuthenticationBroker;
import org.apache.activemq.security.SimpleAuthorizationBroker;
/**
* Tests that the broker allows/fails access to destinations based on the
* security policy installed on the broker.
*
* @version $Revision$
*/
public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
public class SimpleSecurityBrokerSystemTest extends SecurityTestSupport {
static final GroupPrincipal guests = new GroupPrincipal("guests");
static final GroupPrincipal users = new GroupPrincipal("users");
static final GroupPrincipal admins = new GroupPrincipal("admins");
public ActiveMQDestination destination;
public SecurityFactory authorizationFactory;
public SecurityFactory authenticationFactory;
public BrokerPlugin authorizationPlugin;
public BrokerPlugin authenticationPlugin;
interface SecurityFactory {
public Broker create(Broker broker);
}
class SimpleAuthorizationFactory implements SecurityFactory {
public Broker create(Broker broker) {
class SimpleAuthorizationFactory implements BrokerPlugin {
public Broker installPlugin(Broker broker) {
DestinationMap readAccess = new DestinationMap();
readAccess.put(new ActiveMQQueue(">"), admins);
@ -104,8 +88,8 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
}
}
class SimpleAuthenticationFactory implements SecurityFactory {
public Broker create(Broker broker) {
class SimpleAuthenticationFactory implements BrokerPlugin {
public Broker installPlugin(Broker broker) {
HashMap u = new HashMap();
u.put("system", "manager");
@ -136,15 +120,6 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
System.out.println("Path to login config: "+path);
}
class JaasAuthenticationFactory implements SecurityFactory {
public Broker create(Broker broker) {
return new JaasAuthenticationBroker(broker, "activemq-test-domain");
}
public String toString() {
return "JassAuthenticationBroker";
}
}
public static Test suite() {
return suite(SimpleSecurityBrokerSystemTest.class);
}
@ -154,24 +129,18 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
}
public void initCombos() {
addCombinationValues("authorizationFactory", new Object[] {
addCombinationValues("authorizationPlugin", new Object[] {
new SimpleAuthorizationFactory(),
});
addCombinationValues("authenticationFactory", new Object[] {
addCombinationValues("authenticationPlugin", new Object[] {
new SimpleAuthenticationFactory(),
new JaasAuthenticationFactory(),
new JassAuthenticationPlugin(),
});
}
protected BrokerService createBroker() throws Exception {
BrokerService broker = new BrokerService() {
protected Broker addInterceptors(Broker broker) throws IOException {
broker = super.addInterceptors(broker);
broker = authorizationFactory.create(broker);
broker = authenticationFactory.create(broker);
return broker;
}
};
BrokerService broker = new BrokerService();
broker.setPlugins(new BrokerPlugin[] {authorizationPlugin, authenticationPlugin});
broker.setPersistent(false);
return broker;
}
@ -186,44 +155,10 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("GUEST.BAR"),
});
}
public void testUserReceiveFails() throws JMSException {
doReceive(true);
}
public void initCombosForTestInvalidAuthentication() {
addCombinationValues("userName", new Object[] {"user"});
addCombinationValues("password", new Object[] {"password"});
}
public void testInvalidAuthentication() throws JMSException {
try {
// No user id
Connection c= factory.createConnection();
connections.add(c);
c.start();
fail("Expected exception.");
} catch (JMSException e) {
}
try {
// Bad password
Connection c = factory.createConnection("user", "krap");
connections.add(c);
c.start();
fail("Expected exception.");
} catch (JMSException e) {
}
try {
// Bad userid
Connection c = factory.createConnection("userkrap", null);
connections.add(c);
c.start();
fail("Expected exception.");
} catch (JMSException e) {
}
}
public void initCombosForTestUserReceiveSucceeds() {
addCombinationValues("userName", new Object[] {"user"});
addCombinationValues("password", new Object[] {"password"});
@ -232,11 +167,6 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("USERS.FOO"),
});
}
public void testUserReceiveSucceeds() throws JMSException {
doReceive(false);
}
public void initCombosForTestGuestReceiveSucceeds() {
addCombinationValues("userName", new Object[] {"guest"});
addCombinationValues("password", new Object[] {"password"});
@ -245,10 +175,6 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("GUEST.BAR"),
});
}
public void testGuestReceiveSucceeds() throws JMSException {
doReceive(false);
}
public void initCombosForTestGuestReceiveFails() {
addCombinationValues("userName", new Object[] {"guest"});
addCombinationValues("password", new Object[] {"password"});
@ -259,11 +185,6 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("USERS.FOO"),
});
}
public void testGuestReceiveFails() throws JMSException {
doReceive(true);
}
public void initCombosForTestUserSendSucceeds() {
addCombinationValues("userName", new Object[] {"user"});
addCombinationValues("password", new Object[] {"password"});
@ -274,10 +195,6 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("GUEST.BAR"),
});
}
public void testUserSendSucceeds() throws JMSException {
doSend(false);
}
public void initCombosForTestUserSendFails() {
addCombinationValues("userName", new Object[] {"user"});
addCombinationValues("password", new Object[] {"password"});
@ -286,11 +203,6 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("TEST"),
});
}
public void testUserSendFails() throws JMSException {
doSend(true);
}
public void initCombosForTestGuestSendFails() {
addCombinationValues("userName", new Object[] {"guest"});
addCombinationValues("password", new Object[] {"password"});
@ -301,10 +213,6 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("USERS.FOO"),
});
}
public void testGuestSendFails() throws JMSException {
doSend(true);
}
public void initCombosForTestGuestSendSucceeds() {
addCombinationValues("userName", new Object[] {"guest"});
addCombinationValues("password", new Object[] {"password"});
@ -313,71 +221,4 @@ public class SimpleSecurityBrokerSystemTest extends JmsTestSupport {
new ActiveMQTopic("GUEST.BAR"),
});
}
public void testGuestSendSucceeds() throws JMSException {
doSend(false);
}
/**
* @throws JMSException
*/
public void doSend(boolean fail) throws JMSException {
Connection adminConnection = factory.createConnection("system", "manager");
connections.add(adminConnection);
adminConnection.start();
Session adminSession = adminConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = adminSession.createConsumer(destination);
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
try {
sendMessages(session, destination, 1);
} catch (JMSException e) {
// If test is expected to fail, the cause must only be a SecurityException
// otherwise rethrow the exception
if (!fail || !(e.getCause() instanceof SecurityException)) {
throw e;
}
}
Message m = consumer.receive(1000);
if(fail)
assertNull(m);
else {
assertNotNull(m);
assertEquals("0", ((TextMessage)m).getText());
assertNull(consumer.receiveNoWait());
}
}
/**
* @throws JMSException
*/
public void doReceive(boolean fail) throws JMSException {
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer=null;
try {
consumer = session.createConsumer(destination);
if( fail )
fail("Expected failure due to security constraint.");
} catch (JMSException e) {
if (fail && e.getCause() instanceof SecurityException)
return;
throw e;
}
Connection adminConnection = factory.createConnection("system", "manager");
connections.add(adminConnection);
Session adminSession = adminConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
sendMessages(adminSession, destination, 1);
Message m = consumer.receive(1000);
assertNotNull(m);
assertEquals("0", ((TextMessage)m).getText());
assertNull(consumer.receiveNoWait());
}
}

View File

@ -1,4 +1,4 @@
activemq-test-domain {
activemq-domain {
org.apache.activemq.jaas.PropertiesLoginModule required
debug=true
org.apache.activemq.jaas.properties.user="org/apache/activemq/security/users.properties"

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2005-2006 The Apache Software Foundation
Licensed 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.
-->
<!-- this file can only be parsed using the xbean-spring library -->
<!-- START SNIPPET: xbean -->
<beans xmlns="http://activemq.org/config/1.0">
<broker useJmx="false" persistent="false">
<plugins>
<!-- use JAAS to authenticate using the login.config file on the classpath to configure JAAS -->
<jassAuthenticationPlugin configuration="activemq-domain"/>
</plugins>
</broker>
</beans>
<!-- END SNIPPET: xbean -->