diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java index 209b42415f..a12ff4f55f 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java @@ -34,6 +34,7 @@ import org.apache.activemq.artemis.core.server.management.NotificationService; import org.apache.activemq.artemis.core.settings.HierarchicalRepository; import org.apache.activemq.artemis.core.settings.HierarchicalRepositoryChangeListener; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2; import org.apache.activemq.artemis.utils.ConcurrentHashSet; import org.apache.activemq.artemis.utils.TypedProperties; @@ -159,7 +160,16 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC return; } - if (!securityManager.validateUserAndRole(user, session.getPassword(), roles, checkType)) { + final boolean validated; + if (securityManager instanceof ActiveMQSecurityManager2) { + final ActiveMQSecurityManager2 securityManager2 = (ActiveMQSecurityManager2) securityManager; + validated = securityManager2.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress); + } + else { + validated = securityManager.validateUserAndRole(user, session.getPassword(), roles, checkType); + } + + if (!validated) { if (notificationService != null) { TypedProperties props = new TypedProperties(); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java new file mode 100644 index 0000000000..2962153c52 --- /dev/null +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java @@ -0,0 +1,49 @@ +/* + * 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.activemq.artemis.spi.core.security; + +import java.util.Set; + +import org.apache.activemq.artemis.core.security.CheckType; +import org.apache.activemq.artemis.core.security.Role; + +/** + * Used to validate whether a user is authorized to connect to the + * server and perform certain functions on certain destinations. + * + * This is an evolution of {@link ActiveMQSecurityManager} that adds + * the ability to perform authorization taking the destination address + * into account. + */ +public interface ActiveMQSecurityManager2 extends ActiveMQSecurityManager { + + /** + * Determine whether the given user is valid and whether they have + * the correct role for the given destination address. + * + * This method is called instead of + * {@link ActiveMQSecurityManager.validateUserAndRole}. + * + * @param user the user + * @param password the user's password + * @param roles the user's roles + * @param checkType which permission to validate + * @param address the address for which to perform authorization + * @return true if the user is valid and they have the correct roles for the given destination address + */ + boolean validateUserAndRole(String user, String password, Set roles, CheckType checkType, String address); +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java index 15b5423580..1eb0ed877a 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java @@ -33,10 +33,14 @@ import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.tests.util.CreateMessage; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.Role; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.Queue; +import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl; import org.apache.activemq.artemis.core.settings.HierarchicalRepository; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; +import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManagerImpl; import org.junit.Assert; import org.junit.Before; @@ -1049,6 +1053,213 @@ public class SecurityTest extends ActiveMQTestBase { } + @Test + public void testCustomSecurityManager() throws Exception { + final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); + final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager() { + public boolean validateUser(final String username, final String password) { + return (username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate"); + } + public boolean validateUserAndRole( + final String username, + final String password, + final Set requiredRoles, + final CheckType checkType) { + + if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate")) { + + if (username.equals("all")) { + return true; + } + else if (username.equals("foo")) { + return checkType == CheckType.CONSUME || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; + } + else if (username.equals("bar")) { + return checkType == CheckType.SEND || checkType == CheckType.CREATE_NON_DURABLE_QUEUE; + } + else { + return false; + } + } + else { + return false; + } + } + }; + final ActiveMQServer server = addServer(new ActiveMQServerImpl(configuration, customSecurityManager)); + server.start(); + + final ServerLocator locator = createInVMNonHALocator(); + locator.setBlockOnNonDurableSend(true).setBlockOnDurableSend(true); + final ClientSessionFactory factory = createSessionFactory(locator); + ClientSession adminSession = factory.createSession("all", "frobnicate", false, true, true, false, -1); + + final String queueName = "test.queue"; + adminSession.createQueue(queueName, queueName, false); + + // Wrong user name + try { + factory.createSession("baz", "frobnicate", false, true, true, false, -1); + Assert.fail("should throw exception"); + } + catch (ActiveMQSecurityException se) { + //ok + } + catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Wrong password + try { + factory.createSession("foo", "xxx", false, true, true, false, -1); + Assert.fail("should throw exception"); + } + catch (ActiveMQSecurityException se) { + //ok + } + catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Correct user and password, allowed to send but not receive + { + final ClientSession session = factory.createSession("foo", "frobnicate", false, true, true, false, -1); + checkUserReceiveNoSend(queueName, session, adminSession); + } + + // Correct user and password, allowed to receive but not send + { + final ClientSession session = factory.createSession("bar", "frobnicate", false, true, true, false, -1); + checkUserSendNoReceive(queueName, session); + } + + } + + @Test + public void testCustomSecurityManager2() throws Exception { + final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); + final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager2() { + public boolean validateUser(final String username, final String password) { + return (username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate"); + } + public boolean validateUserAndRole( + final String username, + final String password, + final Set requiredRoles, + final CheckType checkType) { + + fail("Unexpected call to overridden method"); + return false; + } + + public boolean validateUserAndRole( + final String username, + final String password, + final Set requiredRoles, + final CheckType checkType, + final String address) { + + if ((username.equals("foo") || username.equals("bar") || username.equals("all")) && + password.equals("frobnicate")) { + + if (username.equals("all")) { + return true; + } + else if (username.equals("foo")) { + return address.equals("test.queue") && checkType == CheckType.CONSUME; + } + else if (username.equals("bar")) { + return address.equals("test.queue") && checkType == CheckType.SEND; + } + else { + return false; + } + } + else { + return false; + } + } + }; + final ActiveMQServer server = addServer(new ActiveMQServerImpl(configuration, customSecurityManager)); + server.start(); + + final ServerLocator locator = createInVMNonHALocator(); + locator.setBlockOnNonDurableSend(true).setBlockOnDurableSend(true); + final ClientSessionFactory factory = createSessionFactory(locator); + ClientSession adminSession = factory.createSession("all", "frobnicate", false, true, true, false, -1); + + final String queueName = "test.queue"; + adminSession.createQueue(queueName, queueName, false); + + final String otherQueueName = "other.queue"; + adminSession.createQueue(otherQueueName, otherQueueName, false); + + // Wrong user name + try { + factory.createSession("baz", "frobnicate", false, true, true, false, -1); + Assert.fail("should throw exception"); + } + catch (ActiveMQSecurityException se) { + //ok + } + catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Wrong password + try { + factory.createSession("foo", "xxx", false, true, true, false, -1); + Assert.fail("should throw exception"); + } + catch (ActiveMQSecurityException se) { + //ok + } + catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Correct user and password, wrong queue for sending + try { + final ClientSession session = factory.createSession("foo", "frobnicate", false, true, true, false, -1); + checkUserReceiveNoSend(otherQueueName, session, adminSession); + Assert.fail("should throw exception"); + } + catch (ActiveMQSecurityException se) { + //ok + } + catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Correct user and password, wrong queue for receiving + try { + final ClientSession session = factory.createSession("foo", "frobnicate", false, true, true, false, -1); + checkUserReceiveNoSend(otherQueueName, session, adminSession); + Assert.fail("should throw exception"); + } + catch (ActiveMQSecurityException se) { + //ok + } + catch (ActiveMQException e) { + fail("Invalid Exception type:" + e.getType()); + } + + // Correct user and password, allowed to send but not receive + { + final ClientSession session = factory.createSession("foo", "frobnicate", false, true, true, false, -1); + checkUserReceiveNoSend(queueName, session, adminSession); + } + + // Correct user and password, allowed to receive but not send + { + final ClientSession session = factory.createSession("bar", "frobnicate", false, true, true, false, -1); + checkUserSendNoReceive(queueName, session); + } + } + // Check the user connection has both send and receive permissions on the queue private void checkUserSendAndReceive(final String genericQueueName, final ClientSession connection) throws Exception {