This is an automated email from the ASF dual-hosted git repository.
jbertram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/artemis.git
The following commit(s) were added to refs/heads/main by this push:
new 20bb13c765 ARTEMIS-5958 refactor security store to expose
hasPermission bool, to get a fast path for rbac chechs that restrict access.
the regular check is still in play for invocations
20bb13c765 is described below
commit 20bb13c7657041a3b9367c49a2c44faa52fa49a8
Author: Gary Tully <[email protected]>
AuthorDate: Thu Mar 19 21:00:12 2026 +0000
ARTEMIS-5958 refactor security store to expose hasPermission bool, to get a
fast path for rbac chechs that restrict access. the regular check is still in
play for invocations
Assisted-by: Claude
---
.../artemis/core/security/SecurityStore.java | 2 +
.../core/security/impl/SecurityStoreImpl.java | 152 ++--
.../management/ArtemisRbacInvocationHandler.java | 22 +-
.../core/security/impl/SecurityStoreImplTest.java | 905 +++++++++++++++++++++
4 files changed, 1007 insertions(+), 74 deletions(-)
diff --git
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java
index 4307bbeeb5..3cd78c687a 100644
---
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java
+++
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/SecurityStore.java
@@ -30,6 +30,8 @@ public interface SecurityStore {
void check(SimpleString address, SimpleString queue, CheckType checkType,
SecurityAuth session) throws Exception;
+ boolean hasPermission(SimpleString address, SimpleString queue, CheckType
checkType, SecurityAuth session) throws Exception;
+
boolean isSecurityEnabled();
void setSecurityEnabled(boolean securityEnabled);
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 c07d7bafef..0ba198aaa5 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
@@ -297,22 +297,42 @@ public class SecurityStoreImpl implements SecurityStore,
HierarchicalRepositoryC
}
@Override
- public void check(final SimpleString address,
- final SimpleString queue,
- final CheckType checkType,
- final SecurityAuth session) throws Exception {
- if (securityEnabled) {
+ public boolean hasPermission(final SimpleString address,
+ final SimpleString queue,
+ final CheckType checkType,
+ final SecurityAuth session) throws Exception {
+ if (!securityEnabled) {
+ return true;
+ }
+
+ // bypass permission checks for management cluster user
+ String user = session.getUsername();
+ if (managementClusterUser.equals(user) &&
session.getPassword().equals(managementClusterPassword)) {
+ return true;
+ }
+
+ // Special case: detect authentication failure for
ActiveMQSecurityManager5
+ // This must throw an authentication exception, not an authorization
exception
+ Subject subjectForManager5 = null;
+ if (securityManager instanceof ActiveMQSecurityManager5 manager5) {
+ subjectForManager5 = getSubjectForAuthorization(session, manager5);
+ /*
+ * A user may authenticate successfully at first, but then later when
their Subject is evicted from the
+ * local cache re-authentication may fail. This could happen, for
example, if the user was removed from LDAP
+ * or the user's token expired.
+ *
+ * If the subject is null then authorization will *always* fail.
+ */
+ if (subjectForManager5 == null) {
+ authenticationFailed(session.getUsername(),
session.getRemotingConnection());
+ }
+ }
+ try {
SimpleString bareAddress =
CompositeAddress.extractAddressName(address);
SimpleString bareQueue = CompositeAddress.extractQueueName(queue);
logger.trace("checking access permissions to {}", bareAddress);
- // bypass permission checks for management cluster user
- String user = session.getUsername();
- if (managementClusterUser.equals(user) &&
session.getPassword().equals(managementClusterPassword)) {
- AUTHORIZATION_SUCCESS_COUNT_UPDATER.incrementAndGet(this);
- return;
- }
Set<Role> roles = securityRepository.getMatch(bareAddress.toString());
@@ -325,27 +345,14 @@ public class SecurityStoreImpl implements SecurityStore,
HierarchicalRepositoryC
}
}
- if (checkAuthorizationCache(fqqn != null ? fqqn : bareAddress, user,
checkType)) {
- AUTHORIZATION_SUCCESS_COUNT_UPDATER.incrementAndGet(this);
- return;
+ if (checkAuthorizationCache(fqqn != null ? fqqn : bareAddress, user,
checkType)) {
+ return true;
}
final Boolean validated;
if (securityManager instanceof ActiveMQSecurityManager5 manager5) {
- Subject subject = getSubjectForAuthorization(session, manager5);
-
- /*
- * A user may authenticate successfully at first, but then later
when their Subject is evicted from the
- * local cache re-authentication may fail. This could happen, for
example, if the user was removed from LDAP
- * or the user's token expired.
- *
- * If the subject is null then authorization will *always* fail.
- */
- if (subject == null) {
- authenticationFailed(user, session.getRemotingConnection());
- }
-
- validated = manager5.authorize(subject, roles, checkType, fqqn !=
null ? fqqn.toString() : bareAddress.toString());
+ // Reuse subject from earlier authentication check
+ validated = manager5.authorize(subjectForManager5, roles,
checkType, fqqn != null ? fqqn.toString() : bareAddress.toString());
} else if (securityManager instanceof ActiveMQSecurityManager4
manager4) {
validated = manager4.validateUserAndRole(user,
session.getPassword(), roles, checkType, bareAddress.toString(),
session.getRemotingConnection(), session.getSecurityDomain()) != null;
} else if (securityManager instanceof ActiveMQSecurityManager3
manager3) {
@@ -356,52 +363,67 @@ public class SecurityStoreImpl implements SecurityStore,
HierarchicalRepositoryC
validated = securityManager.validateUserAndRole(user,
session.getPassword(), roles, checkType);
}
- if (!validated) {
- if (notificationService != null) {
- TypedProperties props = new TypedProperties();
-
- props.putSimpleStringProperty(ManagementHelper.HDR_ADDRESS,
bareAddress);
- props.putSimpleStringProperty(ManagementHelper.HDR_CHECK_TYPE,
SimpleString.of(checkType.toString()));
- props.putSimpleStringProperty(ManagementHelper.HDR_USER,
SimpleString.of(getCaller(user, session.getRemotingConnection().getSubject())));
-
- Notification notification = new Notification(null,
CoreNotificationType.SECURITY_PERMISSION_VIOLATION, props);
-
- notificationService.sendNotification(notification);
- }
-
- Exception ex;
- if (bareQueue == null) {
- ex =
ActiveMQMessageBundle.BUNDLE.userNoPermissions(getCaller(user,
session.getRemotingConnection().getSubject()), checkType, bareAddress);
+ if (validated && user != null) {
+ // if we get here we're granted, add to the cache
+ ConcurrentHashSet<SimpleString> set;
+ String key = createAuthorizationCacheKey(user, checkType);
+ ConcurrentHashSet<SimpleString> act =
getAuthorizationCacheEntry(key);
+ if (act != null) {
+ set = act;
} else {
- ex =
ActiveMQMessageBundle.BUNDLE.userNoPermissionsQueue(getCaller(user,
session.getRemotingConnection().getSubject()), checkType, bareQueue,
bareAddress);
+ set = new ConcurrentHashSet<>();
+ putAuthorizationCacheEntry(set, key);
}
-
AuditLogger.securityFailure(session.getRemotingConnection().getSubject(),
session.getRemotingConnection().getRemoteAddress(), ex.getMessage(), ex);
- AUTHORIZATION_FAILURE_COUNT_UPDATER.incrementAndGet(this);
- throw ex;
+ set.add(Objects.requireNonNullElse(fqqn, bareAddress));
}
- // if we get here we're granted, add to the cache
+ return validated;
+ } catch (Exception e) {
+ logger.debug("Permission check failed", e);
+ return false;
+ }
+ }
+
+ @Override
+ public void check(final SimpleString address,
+ final SimpleString queue,
+ final CheckType checkType,
+ final SecurityAuth session) throws Exception {
+ if (!securityEnabled) {
+ return;
+ }
+ if (hasPermission(address, queue, checkType, session)) {
AUTHORIZATION_SUCCESS_COUNT_UPDATER.incrementAndGet(this);
+ return;
+ }
- if (user == null) {
- // should get all user/pass into a subject and only cache subjects
- // till then when subject is in play, the user may be null and
- // we cannot cache as we don't have a unique key
- return;
- }
+ // Permission denied - handle side effects
+ SimpleString bareAddress = CompositeAddress.extractAddressName(address);
+ SimpleString bareQueue = CompositeAddress.extractQueueName(queue);
+ String user = session.getUsername();
- ConcurrentHashSet<SimpleString> set;
- String key = createAuthorizationCacheKey(user, checkType);
- ConcurrentHashSet<SimpleString> act = getAuthorizationCacheEntry(key);
- if (act != null) {
- set = act;
- } else {
- set = new ConcurrentHashSet<>();
- putAuthorizationCacheEntry(set, key);
- }
- set.add(Objects.requireNonNullElse(fqqn, bareAddress));
+ if (notificationService != null) {
+ TypedProperties props = new TypedProperties();
+
+ props.putSimpleStringProperty(ManagementHelper.HDR_ADDRESS,
bareAddress);
+ props.putSimpleStringProperty(ManagementHelper.HDR_CHECK_TYPE,
SimpleString.of(checkType.toString()));
+ props.putSimpleStringProperty(ManagementHelper.HDR_USER,
SimpleString.of(getCaller(user, session.getRemotingConnection().getSubject())));
+
+ Notification notification = new Notification(null,
CoreNotificationType.SECURITY_PERMISSION_VIOLATION, props);
+
+ notificationService.sendNotification(notification);
+ }
+
+ Exception ex;
+ if (bareQueue == null) {
+ ex = ActiveMQMessageBundle.BUNDLE.userNoPermissions(getCaller(user,
session.getRemotingConnection().getSubject()), checkType, bareAddress);
+ } else {
+ ex =
ActiveMQMessageBundle.BUNDLE.userNoPermissionsQueue(getCaller(user,
session.getRemotingConnection().getSubject()), checkType, bareQueue,
bareAddress);
}
+
AuditLogger.securityFailure(session.getRemotingConnection().getSubject(),
session.getRemotingConnection().getRemoteAddress(), ex.getMessage(), ex);
+ AUTHORIZATION_FAILURE_COUNT_UPDATER.incrementAndGet(this);
+ throw ex;
}
@Override
diff --git
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacInvocationHandler.java
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacInvocationHandler.java
index 9453d90e5b..40b225ebd4 100644
---
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacInvocationHandler.java
+++
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacInvocationHandler.java
@@ -329,17 +329,12 @@ public class ArtemisRbacInvocationHandler implements
GuardInvocationHandler {
}
private boolean viewPermissionCheckFails(Object candidate) {
- boolean failed = false;
ObjectName objectName = candidate instanceof ObjectInstance oi ?
oi.getObjectName() : (ObjectName) candidate;
- if (!isUncheckedDomain(objectName)) {
- try {
- final SimpleString rbacAddress = addressFrom(objectName);
- securityStoreCheck(rbacAddress, CheckType.VIEW);
- } catch (Exception checkFailed) {
- failed = true;
- }
+ if (isUncheckedDomain(objectName)) {
+ return false;
}
- return failed;
+ final SimpleString rbacAddress = addressFrom(objectName);
+ return !hasPermission(rbacAddress, CheckType.VIEW);
}
private void securityStoreCheck(SimpleString rbacAddress, CheckType
checkType) throws Exception {
@@ -347,6 +342,15 @@ public class ArtemisRbacInvocationHandler implements
GuardInvocationHandler {
activeMQServer.getSecurityStore().check(rbacAddress, checkType,
delegateToAccessController);
}
+ private boolean hasPermission(SimpleString rbacAddress, CheckType
checkType) {
+ // use accessor as security store can be updated on config reload
+ try {
+ return activeMQServer.getSecurityStore().hasPermission(rbacAddress,
null, checkType, delegateToAccessController);
+ } catch (Exception notAuthenticated) {
+ return false;
+ }
+ }
+
// sufficiently empty to delegate to use of AccessController
// ideally AccessController should be the source of truth
private final SecurityAuth delegateToAccessController = new SecurityAuth() {
diff --git
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java
index 558c4764e8..9aea05f044 100644
---
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java
+++
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImplTest.java
@@ -23,10 +23,14 @@ import java.util.concurrent.Callable;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.api.core.management.CoreNotificationType;
+import org.apache.activemq.artemis.api.core.management.ManagementHelper;
import
org.apache.activemq.artemis.core.management.impl.ManagementRemotingConnection;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.security.SecurityAuth;
+import org.apache.activemq.artemis.core.server.management.Notification;
+import org.apache.activemq.artemis.core.server.management.NotificationService;
import
org.apache.activemq.artemis.core.settings.impl.HierarchicalObjectRepository;
import org.apache.activemq.artemis.logs.AssertionLoggerHandler;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
@@ -36,6 +40,7 @@ import
org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.apache.activemq.artemis.utils.sm.SecurityManagerShim;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
@@ -228,4 +233,904 @@ public class SecurityStoreImplTest {
assertFalse(handler.findText("AMQ224163"));
}
}
+
+ @Test
+ public void testHasPermissionSecurityDisabled() throws Exception {
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, false, "", null, null,
0, 0);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return "user";
+ }
+
+ @Override
+ public String getPassword() {
+ return "pass";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return null;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ assertTrue(securityStore.hasPermission(SimpleString.of("test.address"),
null, CheckType.SEND, session));
+ }
+
+ @Test
+ public void testHasPermissionClusterUser() throws Exception {
+ final String clusterUser = "clusterUser";
+ final String clusterPassword = "clusterPassword";
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, true, clusterUser,
clusterPassword, null, 0, 0);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return clusterUser;
+ }
+
+ @Override
+ public String getPassword() {
+ return clusterPassword;
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return null;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ assertTrue(securityStore.hasPermission(SimpleString.of("test.address"),
null, CheckType.SEND, session));
+ }
+
+ @Test
+ public void testHasPermissionAuthenticationFailure() throws Exception {
+ ActiveMQSecurityManager5 nullSubjectManager = new
ActiveMQSecurityManager5() {
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ return null; // Simulate authentication failure
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return true;
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), nullSubjectManager, 999, true, "", null,
null, 0, 0);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return "user";
+ }
+
+ @Override
+ public String getPassword() {
+ return "pass";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return Mockito.mock(RemotingConnection.class);
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ try {
+ securityStore.hasPermission(SimpleString.of("test.address"), null,
CheckType.SEND, session);
+ fail("Should throw authentication exception");
+ } catch (ActiveMQSecurityException e) {
+ // Expected
+ assertEquals(1, securityStore.getAuthenticationFailureCount());
+ }
+ }
+
+ @Test
+ public void testHasPermissionAuthorized() throws Exception {
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "authorizedUser";
+ securityStore.authenticate(user, "password",
Mockito.mock(RemotingConnection.class));
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return Mockito.mock(RemotingConnection.class);
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ assertTrue(securityStore.hasPermission(SimpleString.of("test.address"),
null, CheckType.SEND, session));
+ }
+
+ @Test
+ public void testHasPermissionNotAuthorized() throws Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false; // Deny authorization
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "unauthorizedUser";
+ securityStore.authenticate(user, "password",
Mockito.mock(RemotingConnection.class));
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return Mockito.mock(RemotingConnection.class);
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ assertFalse(securityStore.hasPermission(SimpleString.of("test.address"),
null, CheckType.SEND, session));
+ }
+
+ @Test
+ public void testHasPermissionUsesCache() throws Exception {
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "cachedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ SimpleString address = SimpleString.of("test.address");
+
+ // First call - should authorize and cache
+ assertTrue(securityStore.hasPermission(address, null, CheckType.SEND,
session));
+ assertEquals(1, securityStore.getAuthorizationCacheSize());
+
+ // Second call - should use cache
+ assertTrue(securityStore.hasPermission(address, null, CheckType.SEND,
session));
+ assertEquals(1, securityStore.getAuthorizationCacheSize());
+ }
+
+ @Test
+ public void testHasPermissionNoSideEffects() throws Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false; // Deny
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "deniedUser";
+ securityStore.authenticate(user, "password",
Mockito.mock(RemotingConnection.class));
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return Mockito.mock(RemotingConnection.class);
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ // hasPermission should return false without incrementing failure counter
+ long initialFailureCount = securityStore.getAuthorizationFailureCount();
+ assertFalse(securityStore.hasPermission(SimpleString.of("test.address"),
null, CheckType.SEND, session));
+ assertEquals(initialFailureCount,
securityStore.getAuthorizationFailureCount(), "hasPermission should not
increment failure counter");
+ }
+
+ @Test
+ public void testCheckSecurityDisabled() throws Exception {
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, false, "", null, null,
0, 0);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return "user";
+ }
+
+ @Override
+ public String getPassword() {
+ return "pass";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return null;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ // Should not throw exception when security is disabled
+ securityStore.check(SimpleString.of("test.address"), null,
CheckType.SEND, session);
+ assertEquals(0, securityStore.getAuthorizationSuccessCount());
+ assertEquals(0, securityStore.getAuthorizationFailureCount());
+ }
+
+ @Test
+ public void testCheckAuthorizedIncrementsSuccessCounter() throws Exception {
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "authorizedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ long initialSuccessCount = securityStore.getAuthorizationSuccessCount();
+ securityStore.check(SimpleString.of("test.address"), null,
CheckType.SEND, session);
+ assertEquals(initialSuccessCount + 1,
securityStore.getAuthorizationSuccessCount(), "check should increment success
counter");
+ assertEquals(0, securityStore.getAuthorizationFailureCount());
+ }
+
+ @Test
+ public void testCheckDeniedThrowsException() throws Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false; // Deny authorization
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "deniedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ SimpleString address = SimpleString.of("test.address");
+ long initialFailureCount = securityStore.getAuthorizationFailureCount();
+
+ try {
+ securityStore.check(address, null, CheckType.SEND, session);
+ fail("Should throw ActiveMQSecurityException");
+ } catch (ActiveMQSecurityException e) {
+ // Expected
+ assertTrue(e.getMessage().contains(user), "Exception should contain
username");
+ assertTrue(e.getMessage().contains(address.toString()), "Exception
should contain address");
+ assertTrue(e.getMessage().contains("SEND"), "Exception should contain
check type");
+ assertEquals(initialFailureCount + 1,
securityStore.getAuthorizationFailureCount(), "check should increment failure
counter");
+ }
+ }
+
+ @Test
+ public void testCheckDeniedWithQueueThrowsCorrectException() throws
Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "deniedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ SimpleString address = SimpleString.of("test.address");
+ SimpleString queue = SimpleString.of("test.queue");
+
+ try {
+ securityStore.check(address, queue, CheckType.CONSUME, session);
+ fail("Should throw ActiveMQSecurityException");
+ } catch (ActiveMQSecurityException e) {
+ // Expected
+ assertTrue(e.getMessage().contains(user), "Exception should contain
username");
+ assertTrue(e.getMessage().contains(queue.toString()), "Exception
should contain queue name");
+ assertTrue(e.getMessage().contains(address.toString()), "Exception
should contain address");
+ assertTrue(e.getMessage().contains("CONSUME"), "Exception should
contain check type");
+ }
+ }
+
+ @Test
+ public void testCheckDelegatesClusterUserBypass() throws Exception {
+ final String clusterUser = "clusterUser";
+ final String clusterPassword = "clusterPassword";
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, true, clusterUser,
clusterPassword, null, 0, 0);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return clusterUser;
+ }
+
+ @Override
+ public String getPassword() {
+ return clusterPassword;
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return null;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ long initialSuccessCount = securityStore.getAuthorizationSuccessCount();
+ securityStore.check(SimpleString.of("test.address"), null,
CheckType.SEND, session);
+ assertEquals(initialSuccessCount + 1,
securityStore.getAuthorizationSuccessCount(), "Cluster user should increment
success counter");
+ }
+
+ @Test
+ public void testCheckCachesSuccessfulAuthorization() throws Exception {
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "cachedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ SimpleString address = SimpleString.of("test.address");
+
+ // First check should cache
+ securityStore.check(address, null, CheckType.SEND, session);
+ assertEquals(1, securityStore.getAuthorizationCacheSize());
+
+ // Second check should use cache
+ securityStore.check(address, null, CheckType.SEND, session);
+ assertEquals(1, securityStore.getAuthorizationCacheSize());
+ }
+
+ @Test
+ public void testCheckSendsNotificationOnFailure() throws Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false; // Deny
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ NotificationService notificationService =
Mockito.mock(NotificationService.class);
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null,
notificationService, 10, 10);
+
+ final String user = "deniedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ Mockito.when(connection.getSubject()).thenReturn(new Subject());
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ SimpleString address = SimpleString.of("test.address");
+
+ try {
+ securityStore.check(address, null, CheckType.SEND, session);
+ fail("Should throw ActiveMQSecurityException");
+ } catch (ActiveMQSecurityException e) {
+ // Expected
+ }
+
+ // Verify notification was sent
+ ArgumentCaptor<Notification> notificationCaptor =
ArgumentCaptor.forClass(Notification.class);
+ Mockito.verify(notificationService,
Mockito.times(1)).sendNotification(notificationCaptor.capture());
+
+ Notification notification = notificationCaptor.getValue();
+ assertEquals(CoreNotificationType.SECURITY_PERMISSION_VIOLATION,
notification.getType());
+ assertEquals(address,
notification.getProperties().getSimpleStringProperty(ManagementHelper.HDR_ADDRESS));
+ assertEquals(SimpleString.of(CheckType.SEND.toString()),
notification.getProperties().getSimpleStringProperty(ManagementHelper.HDR_CHECK_TYPE));
+ assertEquals(SimpleString.of(user),
notification.getProperties().getSimpleStringProperty(ManagementHelper.HDR_USER));
+ }
+
+ @Test
+ public void testCheckSendsNotificationWithQueueInfo() throws Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ NotificationService notificationService =
Mockito.mock(NotificationService.class);
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null,
notificationService, 10, 10);
+
+ final String user = "deniedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ Mockito.when(connection.getSubject()).thenReturn(new Subject());
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ SimpleString address = SimpleString.of("test.address");
+
+ try {
+ securityStore.check(address, null, CheckType.CONSUME, session);
+ fail("Should throw ActiveMQSecurityException");
+ } catch (ActiveMQSecurityException e) {
+ // Expected
+ }
+
+ // Verify notification was sent with correct check type
+ ArgumentCaptor<Notification> notificationCaptor =
ArgumentCaptor.forClass(Notification.class);
+ Mockito.verify(notificationService,
Mockito.times(1)).sendNotification(notificationCaptor.capture());
+
+ Notification notification = notificationCaptor.getValue();
+ assertEquals(CoreNotificationType.SECURITY_PERMISSION_VIOLATION,
notification.getType());
+ assertEquals(SimpleString.of(CheckType.CONSUME.toString()),
notification.getProperties().getSimpleStringProperty(ManagementHelper.HDR_CHECK_TYPE));
+ }
+
+ @Test
+ public void testCheckNoNotificationWhenServiceIsNull() throws Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ // notificationService is null
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null, null,
10, 10);
+
+ final String user = "deniedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ Mockito.when(connection.getSubject()).thenReturn(new Subject());
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ try {
+ securityStore.check(SimpleString.of("test.address"), null,
CheckType.SEND, session);
+ fail("Should throw ActiveMQSecurityException");
+ } catch (ActiveMQSecurityException e) {
+ // Expected - should still throw exception even though no
notification sent
+ assertTrue(e.getMessage().contains(user));
+ }
+ }
+
+ @Test
+ public void testHasPermissionDoesNotSendNotification() throws Exception {
+ ActiveMQSecurityManager5 denyingManager = new ActiveMQSecurityManager5()
{
+ @Override
+ public Subject authenticate(String user, String password,
RemotingConnection remotingConnection, String securityDomain) {
+ Subject subject = new Subject();
+ subject.getPrincipals().add(new UserPrincipal(user));
+ return subject;
+ }
+
+ @Override
+ public boolean authorize(Subject subject, Set<Role> roles, CheckType
checkType, String address) {
+ return false; // Deny
+ }
+
+ @Override
+ public boolean validateUser(String user, String password) {
+ return false;
+ }
+
+ @Override
+ public boolean validateUserAndRole(String user, String password,
Set<Role> roles, CheckType checkType) {
+ return false;
+ }
+ };
+
+ NotificationService notificationService =
Mockito.mock(NotificationService.class);
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), denyingManager, 999, true, "", null,
notificationService, 10, 10);
+
+ final String user = "deniedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ // Call hasPermission - should return false
+ assertFalse(securityStore.hasPermission(SimpleString.of("test.address"),
null, CheckType.SEND, session));
+
+ // Verify NO notification was sent (hasPermission has no side effects)
+ Mockito.verify(notificationService,
Mockito.never()).sendNotification(ArgumentMatchers.any());
+ }
+
+ @Test
+ public void testCheckNoNotificationOnSuccess() throws Exception {
+ NotificationService notificationService =
Mockito.mock(NotificationService.class);
+ SecurityStoreImpl securityStore = new SecurityStoreImpl(new
HierarchicalObjectRepository<>(), securityManager, 999, true, "", null,
notificationService, 10, 10);
+
+ final String user = "authorizedUser";
+ RemotingConnection connection = Mockito.mock(RemotingConnection.class);
+ securityStore.authenticate(user, "password", connection);
+
+ SecurityAuth session = new SecurityAuth() {
+ @Override
+ public String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String getPassword() {
+ return "password";
+ }
+
+ @Override
+ public RemotingConnection getRemotingConnection() {
+ return connection;
+ }
+
+ @Override
+ public String getSecurityDomain() {
+ return null;
+ }
+ };
+
+ // Successful check
+ securityStore.check(SimpleString.of("test.address"), null,
CheckType.SEND, session);
+
+ // Verify NO notification was sent on success
+ Mockito.verify(notificationService,
Mockito.never()).sendNotification(ArgumentMatchers.any());
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]