From c52152448a8fc3f69ce1c2422cc57bc15a5b1204 Mon Sep 17 00:00:00 2001 From: Domenico Francesco Bruscino Date: Fri, 6 Feb 2026 09:38:42 +0100 Subject: [PATCH] ARTEMIS-5894 Fix RBAC address mismatch between canInvoke and invoke The canInvoke method received operation names with parameter signatures (e.g., "deleteAddress(java.lang.String)"), while invoke received them without signatures (e.g., "deleteAddress"). This caused the RBAC address built by canInvoke to differ from the one built by invoke, leading to permission check mismatches that prevented the console from properly hiding unauthorized menu items. This fix normalizes operation names by stripping parameter signatures before building RBAC addresses in both canInvoke and invoke. Also changes null operation checks to require VIEW instead of EDIT permission, allowing users to see MBeans they have view access to. --- .../management/ArtemisMBeanServerGuard.java | 7 + .../ArtemisRbacInvocationHandler.java | 19 ++- .../ArtemisMBeanServerGuardTest.java | 45 +++++++ .../ArtemisRbacMBeanServerBuilderTest.java | 91 ++++++++++++- tests/smoke-tests/pom.xml | 23 ---- .../artemis-roles.properties | 25 ++++ .../artemis-users.properties | 25 ++++ .../console-broker-security/broker.properties | 120 ++++++++++++++++++ .../console-broker-security/management.xml | 21 +++ .../servers/console/artemis-roles.properties | 3 +- .../servers/console/artemis-users.properties | 1 + .../resources/servers/console/management.xml | 1 + .../tests/smoke/console/AddressesTest.java | 94 ++++++++++++++ .../tests/smoke/console/ArtemisTest.java | 4 +- .../tests/smoke/console/ConsoleTest.java | 64 ++++++++-- .../tests/smoke/console/LoginTest.java | 4 +- .../tests/smoke/console/PageConstants.java | 6 +- .../tests/smoke/console/QueuesTest.java | 4 +- .../artemis/tests/smoke/console/RootTest.java | 4 +- .../artemis/tests/smoke/console/TabsTest.java | 4 +- .../smoke/console/jmx/ArtemisJMXTest.java | 4 +- .../tests/smoke/console/jmx/JMXTreeTest.java | 4 +- .../smoke/console/pages/AddressesPage.java | 56 ++++++++ .../smoke/console/pages/ArtemisPage.java | 26 ++++ 24 files changed, 601 insertions(+), 54 deletions(-) create mode 100644 tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-roles.properties create mode 100644 tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-users.properties create mode 100644 tests/smoke-tests/src/main/resources/servers/console-broker-security/broker.properties create mode 100644 tests/smoke-tests/src/main/resources/servers/console-broker-security/management.xml create mode 100644 tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/AddressesTest.java create mode 100644 tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/AddressesPage.java diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuard.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuard.java index 8729bebc06d..791347da886 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuard.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuard.java @@ -142,6 +142,13 @@ public boolean canInvoke(String object, String operationName) { if (operationName == null || canBypassRBAC(objectName)) { return true; } + + // Strip the parameter list from operationName. + int paramListIndex = operationName.indexOf('('); + if (paramListIndex > 0) { + operationName = operationName.substring(0, paramListIndex); + } + List requiredRoles = getRequiredRoles(objectName, operationName); for (String role : requiredRoles) { if (currentUserHasRole(role)) { 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 ffac95aee65..4715f83f30e 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 @@ -123,8 +123,23 @@ public boolean canInvoke(String name, String operationName) { try { final ObjectName objectName = ObjectName.getInstance(name); if (!isUncheckedDomain(objectName)) { - final SimpleString rbacAddress = addressFrom(objectName, operationName); - securityStoreCheck(rbacAddress, permissionFrom(operationName)); + if (operationName != null) { + // Strip the parameter list from operationName. + int paramListIndex = operationName.indexOf('('); + if (paramListIndex > 0) { + operationName = operationName.substring(0, paramListIndex); + } + + final SimpleString rbacAddress = addressFrom(objectName, operationName); + securityStoreCheck(rbacAddress, permissionFrom(operationName)); + } else { + // When operationName is null, canInvoke checks if the user has any access to the MBean. + // We use VIEW permission rather than EDIT because hawtio-react's hasInvokeRights method + // checks both canInvoke(mbean, null) AND canInvoke(mbean, method). This allows users with + // VIEW permission to see MBeans and then determine which specific operations they can invoke. + final SimpleString rbacAddress = addressFrom(objectName, null); + securityStoreCheck(rbacAddress, CheckType.VIEW); + } } okInvoke = true; } catch (Throwable expectedOnCheckFailOrInvalidObjectName) { diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuardTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuardTest.java index cfedb2d3ed0..dba3dea0378 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuardTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisMBeanServerGuardTest.java @@ -100,4 +100,49 @@ public void testCanInvokeMethodDoeNotHasRole() throws Throwable { }); assertFalse((Boolean) result); } + + @Test + public void testCanInvokeStripsParameterList() throws Throwable { + ArtemisMBeanServerGuard guard = new ArtemisMBeanServerGuard(); + JMXAccessControlList controlList = new JMXAccessControlList(); + guard.setJMXAccessControlList(controlList); + ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create("testdomain", "myBroker"); + ObjectName activeMQServerObjectName = objectNameBuilder.getActiveMQServerObjectName(); + + // Configure permission for operation without parameter list + controlList.addToRoleAccess("testdomain", "broker=myBroker", "deleteAddress", "admin"); + + Subject subject = new Subject(); + subject.getPrincipals().add(new RolePrincipal("admin")); + + // Test with operation name including parameter list + Object result = SecurityManagerShim.callAs(subject, (Callable) () -> { + try { + return guard.canInvoke(activeMQServerObjectName.getCanonicalName(), "deleteAddress(java.lang.String)"); + } catch (Exception e1) { + return e1; + } + }); + assertTrue((Boolean) result, "Should be able to invoke deleteAddress(java.lang.String) - parameter list should be stripped"); + + // Test with empty parameter list + result = SecurityManagerShim.callAs(subject, (Callable) () -> { + try { + return guard.canInvoke(activeMQServerObjectName.getCanonicalName(), "deleteAddress()"); + } catch (Exception e1) { + return e1; + } + }); + assertTrue((Boolean) result, "Should be able to invoke deleteAddress() - parameter list should be stripped"); + + // Test without parameter list (should also work) + result = SecurityManagerShim.callAs(subject, (Callable) () -> { + try { + return guard.canInvoke(activeMQServerObjectName.getCanonicalName(), "deleteAddress"); + } catch (Exception e1) { + return e1; + } + }); + assertTrue((Boolean) result, "Should be able to invoke deleteAddress without parameters"); + } } diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacMBeanServerBuilderTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacMBeanServerBuilderTest.java index ec5eee479a2..b591d9e9cc8 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacMBeanServerBuilderTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/management/ArtemisRbacMBeanServerBuilderTest.java @@ -18,7 +18,6 @@ package org.apache.activemq.artemis.core.server.management; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -666,7 +665,7 @@ public void testCanInvoke() throws Exception { } }); assertNotNull(result); - assertFalse((Boolean) result, "in the absence of an operation to check, update required"); + assertTrue((Boolean) result, "in the absence of an operation to check, view is sufficient"); result = SecurityManagerShim.callAs(viewSubject, (Callable) () -> { try { @@ -707,4 +706,92 @@ public void testCanInvoke() throws Exception { assertEquals("getVmName()", cd.get("Method")); assertEquals(true, cd.get("CanInvoke")); } + + @Test + public void testCanInvokeStripsParameterList() throws Exception { + + MBeanServer proxy = underTest.newMBeanServer("d", mbeanServer, mBeanServerDelegate); + + final ActiveMQServer server = createServer(false); + server.setMBeanServer(proxy); + server.getConfiguration().setJMXManagementEnabled(true).setSecurityEnabled(true); + + Set editRoles = new HashSet<>(); + editRoles.add(new Role("editors", false, false, false, false, false, false, false, false, false, false, false, true)); + + // Grant edit permission to deleteAddress operation WITHOUT parameter list + // This simulates the typical permission setup where operations are configured without parameter signatures + server.getConfiguration().putSecurityRoles("mops.broker.deleteAddress", editRoles); + + server.start(); + + final HawtioSecurityControl securityControl = JMX.newMBeanProxy( + proxy, ObjectNameBuilder.DEFAULT.getSecurityObjectName(), HawtioSecurityControl.class, false); + + ObjectName serverObjectName = ObjectNameBuilder.DEFAULT.getActiveMQServerObjectName(); + final ActiveMQServerControl serverControl = JMX.newMBeanProxy( + proxy, serverObjectName, ActiveMQServerControl.class, false); + assertNotNull(serverControl); + + Subject editSubject = new Subject(); + editSubject.getPrincipals().add(new UserPrincipal("e")); + editSubject.getPrincipals().add(new RolePrincipal("editors")); + + // Test with operation name including parameter list as sent by console + Object result = SecurityManagerShim.callAs(editSubject, (Callable) () -> { + try { + return securityControl.canInvoke(serverObjectName.toString(), "deleteAddress(java.lang.String)"); + } catch (Exception e1) { + return e1.getCause(); + } + }); + assertNotNull(result); + assertTrue((Boolean) result, "Should be able to invoke deleteAddress(java.lang.String) - parameter list should be stripped to match permission"); + + // Test with empty parameter list + result = SecurityManagerShim.callAs(editSubject, (Callable) () -> { + try { + return securityControl.canInvoke(serverObjectName.toString(), "deleteAddress()"); + } catch (Exception e1) { + return e1.getCause(); + } + }); + assertNotNull(result); + assertTrue((Boolean) result, "Should be able to invoke deleteAddress() - parameter list should be stripped"); + + // Test without parameter list (should also work) + result = SecurityManagerShim.callAs(editSubject, (Callable) () -> { + try { + return securityControl.canInvoke(serverObjectName.toString(), "deleteAddress"); + } catch (Exception e1) { + return e1.getCause(); + } + }); + assertNotNull(result); + assertTrue((Boolean) result, "Should be able to invoke deleteAddress without parameters"); + + // Test bulk query with parameter lists + Map> bulkQuery = new HashMap<>(); + bulkQuery.put(serverObjectName.toString(), List.of("deleteAddress(java.lang.String)", "deleteAddress()")); + + result = SecurityManagerShim.callAs(editSubject, (Callable) () -> { + try { + return securityControl.canInvoke(bulkQuery); + } catch (Exception e1) { + return e1.getCause(); + } + }); + assertNotNull(result); + assertEquals(2, ((TabularData)result).size()); + + CompositeData cd = ((TabularData)result).get(new Object[]{serverObjectName.toString(), "deleteAddress(java.lang.String)"}); + assertEquals(serverObjectName.toString(), cd.get("ObjectName")); + assertEquals("deleteAddress(java.lang.String)", cd.get("Method")); + assertEquals(true, cd.get("CanInvoke")); + + cd = ((TabularData)result).get(new Object[]{serverObjectName.toString(), "deleteAddress()"}); + assertEquals(serverObjectName.toString(), cd.get("ObjectName")); + assertEquals("deleteAddress()", cd.get("Method")); + assertEquals(true, cd.get("CanInvoke")); + } } diff --git a/tests/smoke-tests/pom.xml b/tests/smoke-tests/pom.xml index 5cf3cbae52d..d3a4b8c1883 100644 --- a/tests/smoke-tests/pom.xml +++ b/tests/smoke-tests/pom.xml @@ -302,29 +302,6 @@ org.apache.artemis artemis-maven-plugin - - test-compile - create-create-console - - create - - - amq,connections,sessions,consumers,producers,addresses,queues - admin - admin - false - false - ${basedir}/target/console - ${basedir}/target/classes/servers/console - - --http-host - ${sts-http-host} - --http-port - 8161 - - - - test-compile upgrade-linux diff --git a/tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-roles.properties b/tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-roles.properties new file mode 100644 index 00000000000..1de7fa72db1 --- /dev/null +++ b/tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-roles.properties @@ -0,0 +1,25 @@ +# +# 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. +# + +amq=admin +connections=connections +sessions=sessions +consumers=consumers +producers=producers +addresses=addresses,deleteAddresses +deleteAddresses=deleteAddresses +queues=queues diff --git a/tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-users.properties b/tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-users.properties new file mode 100644 index 00000000000..67b38439569 --- /dev/null +++ b/tests/smoke-tests/src/main/resources/servers/console-broker-security/artemis-users.properties @@ -0,0 +1,25 @@ +# +# 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. +# + +admin=admin +connections=connections +sessions=sessions +consumers=consumers +producers=producers +addresses=addresses +deleteAddresses=deleteAddresses +queues=queues diff --git a/tests/smoke-tests/src/main/resources/servers/console-broker-security/broker.properties b/tests/smoke-tests/src/main/resources/servers/console-broker-security/broker.properties new file mode 100644 index 00000000000..593526fb10e --- /dev/null +++ b/tests/smoke-tests/src/main/resources/servers/console-broker-security/broker.properties @@ -0,0 +1,120 @@ +# +# 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. +# + + +# The hawtio roles need the VIEW permission for mops.broker because apache-artemis-console uses jolokia search +# that invokes "javax.management.MBeanServer.queryNames with the argument "org.apache.activemq.artemis:broker=*" +# to find the ActiveMQServerControl MBean instance +securityRoles."mops.mbeanserver.queryNames".amq.view=true +securityRoles."mops.mbeanserver.queryNames".connections.view=true +securityRoles."mops.mbeanserver.queryNames".sessions.view=true +securityRoles."mops.mbeanserver.queryNames".consumers.view=true +securityRoles."mops.mbeanserver.queryNames".producers.view=true +securityRoles."mops.mbeanserver.queryNames".addresses.view=true +securityRoles."mops.mbeanserver.queryNames".queues.view=true + +securityRoles."mops.mbeanserver.queryMBeans".amq.view=true +securityRoles."mops.mbeanserver.queryMBeans".connections.view=true +securityRoles."mops.mbeanserver.queryMBeans".sessions.view=true +securityRoles."mops.mbeanserver.queryMBeans".consumers.view=true +securityRoles."mops.mbeanserver.queryMBeans".producers.view=true +securityRoles."mops.mbeanserver.queryMBeans".addresses.view=true +securityRoles."mops.mbeanserver.queryMBeans".queues.view=true + +securityRoles."mops.broker".amq.view=true +securityRoles."mops.broker".connections.view=true +securityRoles."mops.broker".sessions.view=true +securityRoles."mops.broker".consumers.view=true +securityRoles."mops.broker".producers.view=true +securityRoles."mops.broker".addresses.view=true +securityRoles."mops.broker".queues.view=true + + +# Global view permissions +securityRoles."mops.#".amq.view=true + + +# Broker and components permissions +securityRoles."mops.broker.#".amq.view=true +securityRoles."mops.broker.#".amq.edit=true + +securityRoles."mops.address.#".amq.view=true +securityRoles."mops.address.#".amq.edit=true +securityRoles."mops.address.#".addresses.view=true + +securityRoles."mops.queue.#".amq.view=true +securityRoles."mops.queue.#".amq.edit=true +securityRoles."mops.queue.#".queues.view=true + +securityRoles."mops.brokerconnection.#".amq.view=true +securityRoles."mops.brokerconnection.#".amq.edit=true + +securityRoles."mops.remotebrokerconnection.#".amq.view=true +securityRoles."mops.remotebrokerconnection.#".amq.edit=true + +securityRoles."mops.bridge.#".amq.view=true +securityRoles."mops.bridge.#".amq.edit=true + +securityRoles."mops.acceptor.#".amq.view=true +securityRoles."mops.acceptor.#".amq.edit=true + +securityRoles."mops.divert.#".amq.view=true +securityRoles."mops.divert.#".amq.edit=true + +securityRoles."mops.clusterconnection.#".amq.view=true +securityRoles."mops.clusterconnection.#".amq.edit=true + +securityRoles."mops.broadcastgroup.#".amq.view=true +securityRoles."mops.broadcastgroup.#".amq.edit=true + +securityRoles."mops.connectionrouter.#".amq.view=true +securityRoles."mops.connectionrouter.#".amq.edit=true + + +# Connections view permissions +securityRoles."mops.broker.listConnections".amq.view=true +securityRoles."mops.broker.listConnections".connections.view=true + + +# Sessions view permissions +securityRoles."mops.broker.listSessions".amq.view=true +securityRoles."mops.broker.listSessions".sessions.view=true + + +# Consumers view permissions +securityRoles."mops.broker.listConsumers".amq.view=true +securityRoles."mops.broker.listConsumers".consumers.view=true + + +# Producers view permissions +securityRoles."mops.broker.listProducers".amq.view=true +securityRoles."mops.broker.listProducers".producers.view=true + + +# Queues view permissions +securityRoles."mops.broker.listQueues".amq.view=true +securityRoles."mops.broker.listQueues".queues.view=true + + +# Addresses view permissions +securityRoles."mops.broker.listAddresses".amq.view=true +securityRoles."mops.broker.listAddresses".addresses.view=true + + +# DeleteAddress view permissions +securityRoles."mops.broker.deleteAddress".amq.edit=true +securityRoles."mops.broker.deleteAddress".deleteAddresses.edit=true diff --git a/tests/smoke-tests/src/main/resources/servers/console-broker-security/management.xml b/tests/smoke-tests/src/main/resources/servers/console-broker-security/management.xml new file mode 100644 index 00000000000..0e22f5a1095 --- /dev/null +++ b/tests/smoke-tests/src/main/resources/servers/console-broker-security/management.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/tests/smoke-tests/src/main/resources/servers/console/artemis-roles.properties b/tests/smoke-tests/src/main/resources/servers/console/artemis-roles.properties index a63148d7e88..55d0128b08f 100644 --- a/tests/smoke-tests/src/main/resources/servers/console/artemis-roles.properties +++ b/tests/smoke-tests/src/main/resources/servers/console/artemis-roles.properties @@ -20,5 +20,6 @@ connections=connections sessions=sessions consumers=consumers producers=producers -addresses=addresses +addresses=addresses,deleteAddresses +deleteAddresses=deleteAddresses queues=queues \ No newline at end of file diff --git a/tests/smoke-tests/src/main/resources/servers/console/artemis-users.properties b/tests/smoke-tests/src/main/resources/servers/console/artemis-users.properties index 704eb5b455b..67b38439569 100644 --- a/tests/smoke-tests/src/main/resources/servers/console/artemis-users.properties +++ b/tests/smoke-tests/src/main/resources/servers/console/artemis-users.properties @@ -21,4 +21,5 @@ sessions=sessions consumers=consumers producers=producers addresses=addresses +deleteAddresses=deleteAddresses queues=queues diff --git a/tests/smoke-tests/src/main/resources/servers/console/management.xml b/tests/smoke-tests/src/main/resources/servers/console/management.xml index 653ba0d3f5f..6aae9cb5be5 100644 --- a/tests/smoke-tests/src/main/resources/servers/console/management.xml +++ b/tests/smoke-tests/src/main/resources/servers/console/management.xml @@ -44,6 +44,7 @@ + diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/AddressesTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/AddressesTest.java new file mode 100644 index 00000000000..d8fb684b840 --- /dev/null +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/AddressesTest.java @@ -0,0 +1,94 @@ +/* + * 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.tests.smoke.console; + +import static org.apache.activemq.artemis.tests.smoke.console.PageConstants.DATA_ROW_CONTEXT_MENU; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.activemq.artemis.tests.extensions.parameterized.ParameterizedTestExtension; +import org.apache.activemq.artemis.tests.smoke.console.pages.AddressesPage; +import org.apache.activemq.artemis.tests.smoke.console.pages.LoginPage; +import org.apache.activemq.artemis.utils.Wait; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +@ExtendWith(ParameterizedTestExtension.class) +public class AddressesTest extends ArtemisTest { + + public AddressesTest(String browser, String serverName) { + super(browser, serverName); + } + + @TestTemplate + public void testDefaultAddresses() throws Exception { + testDefaultAddresses(SERVER_ADMIN_USERNAME, SERVER_ADMIN_PASSWORD, false, true, true, true); + } + + @TestTemplate + public void testDefaultAddressesWithViewUser() throws Exception { + testDefaultAddresses("addresses", "addresses", true, false, false, false); + } + + @TestTemplate + public void testDefaultAddressesWithDeleteUser() throws Exception { + testDefaultAddresses("deleteAddresses", "deleteAddresses", true, true, false, false); + } + + private void testDefaultAddresses(String username, String password, boolean isAlertExpected, boolean canDeleteAddress, boolean canSendMessage, boolean canCreateQueue) throws Exception { + loadLandingPage(); + LoginPage loginPage = new LoginPage(driver); + org.apache.activemq.artemis.tests.smoke.console.pages.StatusPage statusPage = loginPage.loginValidUser( + username, password, DEFAULT_TIMEOUT); + + assertEquals(isAlertExpected, statusPage.countAlerts() > 0); + statusPage.closeAlerts(); + + AddressesPage addressesPage = statusPage.getAddressesPage(DEFAULT_TIMEOUT); + + Wait.assertEquals(1, () -> addressesPage.countAddress("DLQ")); + assertEquals(0, addressesPage.getMessagesCount("DLQ")); + + testAddressContextMenu(addressesPage, "DLQ", canDeleteAddress, canSendMessage, canCreateQueue); + + Wait.assertEquals(1, () -> addressesPage.countAddress("ExpiryQueue")); + assertEquals(0, addressesPage.getMessagesCount("ExpiryQueue")); + + testAddressContextMenu(addressesPage, "ExpiryQueue", canDeleteAddress, canSendMessage, canCreateQueue); + } + + private void testAddressContextMenu(AddressesPage addressesPage, String addressName, boolean canDeleteAddress, boolean canSendMessage, boolean canCreateQueue) { + addressesPage.toggleContextMenu(addressName); + WebElement addressContextMenu = driver.findElement(DATA_ROW_CONTEXT_MENU); + + assertEquals(1, addressContextMenu.findElements( + By.xpath("//span[contains(text(),'Show in Artemis JMX')]")).size()); + assertEquals(1, addressContextMenu.findElements( + By.xpath("//span[contains(text(),'Attributes')]")).size()); + assertEquals(1, addressContextMenu.findElements( + By.xpath("//span[contains(text(),'Operations')]")).size()); + assertEquals(canDeleteAddress ? 1 : 0, addressContextMenu.findElements( + By.xpath("//span[contains(text(),'Delete Address')]")).size()); + assertEquals(canSendMessage ? 1 : 0, addressContextMenu.findElements( + By.xpath("//span[contains(text(),'Send Message')]")).size()); + assertEquals(canCreateQueue ? 1 : 0, addressContextMenu.findElements( + By.xpath("//span[contains(text(),'Create Queue')]")).size()); + + addressesPage.toggleContextMenu(addressName); + } +} diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ArtemisTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ArtemisTest.java index a9ebf201aba..a99d3f39d3a 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ArtemisTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ArtemisTest.java @@ -17,8 +17,8 @@ package org.apache.activemq.artemis.tests.smoke.console; public abstract class ArtemisTest extends ConsoleTest { - public ArtemisTest(String browser) { - super(browser); + public ArtemisTest(String browser, String serverName) { + super(browser, serverName); } diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ConsoleTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ConsoleTest.java index 582b17e86a7..6012c101920 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ConsoleTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/ConsoleTest.java @@ -25,16 +25,19 @@ import java.time.Duration; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.activemq.artemis.cli.commands.Create; +import org.apache.activemq.artemis.cli.commands.helper.HelperCreate; import org.apache.activemq.artemis.tests.extensions.parameterized.ParameterizedTestExtension; import org.apache.activemq.artemis.tests.extensions.parameterized.Parameters; import org.apache.activemq.artemis.tests.smoke.common.SmokeTestBase; import org.apache.activemq.artemis.util.ServerUtil; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.openqa.selenium.MutableCapabilities; @@ -51,15 +54,14 @@ import org.testcontainers.containers.BrowserWebDriverContainer; import org.testcontainers.shaded.org.apache.commons.io.FileUtils; -/** - * The server for ConsoleTest is created on the pom as there are some properties that are passed by argument on the CI - */ @ExtendWith(ParameterizedTestExtension.class) public abstract class ConsoleTest extends SmokeTestBase { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - protected static final String SERVER_NAME = "console"; + protected static final String SERVER_NAME_CONSOLE = "console"; + protected static final String SERVER_NAME_CONSOLE_BROKER_SECURITY = "console-broker-security"; + protected static final String SERVER_ADMIN_USERNAME = "admin"; protected static final String SERVER_ADMIN_PASSWORD = "admin"; @@ -68,29 +70,68 @@ public abstract class ConsoleTest extends SmokeTestBase { protected static final int DEFAULT_TIMEOUT = 10000; + protected String serverName; protected String browser; protected WebDriver driver; protected String webServerUrl; private BrowserWebDriverContainer browserWebDriverContainer; - @Parameters(name = "browser={0}") + @BeforeAll + public static void createServers() throws Exception { + File consoleServerLocation = getFileServerLocation(SERVER_NAME_CONSOLE); + deleteDirectory(consoleServerLocation); + + { + String httpHost = System.getProperty("sts-http-host", "localhost"); + HelperCreate cliCreateServer = helperCreate(); + cliCreateServer.setRole("amq,connections,sessions,consumers,producers,addresses,queues,deleteAddresses") + .setUser(SERVER_ADMIN_USERNAME).setPassword(SERVER_ADMIN_PASSWORD) + .setAllowAnonymous(false).setNoWeb(false).setArtemisInstance(consoleServerLocation) + .setConfiguration("./src/main/resources/servers/" + SERVER_NAME_CONSOLE) + .setArgs("--http-host", httpHost, "--http-port", "8161"); + cliCreateServer.createServer(); + } + + File consoleBrokerSecurityServerLocation = getFileServerLocation(SERVER_NAME_CONSOLE_BROKER_SECURITY); + deleteDirectory(consoleBrokerSecurityServerLocation); + + { + String httpHost = System.getProperty("sts-http-host", "localhost"); + HelperCreate cliCreateServer = helperCreate(); + cliCreateServer.setRole("amq,connections,sessions,consumers,producers,addresses,queues,deleteAddresses") + .setUser(SERVER_ADMIN_USERNAME).setPassword(SERVER_ADMIN_PASSWORD) + .setAllowAnonymous(false).setNoWeb(false).setArtemisInstance(consoleBrokerSecurityServerLocation) + .setConfiguration("./src/main/resources/servers/" + SERVER_NAME_CONSOLE_BROKER_SECURITY) + .setArgs("--http-host", httpHost, "--http-port", "8161", "--java-options", + "-Djava.rmi.server.hostname=localhost -Djavax.management.builder.initial=org.apache.activemq.artemis.core.server.management.ArtemisRbacMBeanServerBuilder"); + cliCreateServer.createServer(); + } + } + + @Parameters(name = "browser={0}, server={1}") public static Collection getParameters() { String webdriverBrowsers = System.getProperty("webdriver.browsers"); if (webdriverBrowsers == null) { webdriverBrowsers = BROWSER_CHROME + "," + BROWSER_FIREFOX; } - return Arrays.stream(webdriverBrowsers.split(",")). - map(browser -> new Object[]{browser}).collect(Collectors.toList()); + String[] browsers = webdriverBrowsers.split(","); + String[] servers = new String[]{SERVER_NAME_CONSOLE, SERVER_NAME_CONSOLE_BROKER_SECURITY}; + + return Arrays.stream(browsers) + .flatMap(browser -> Arrays.stream(servers) + .map(server -> new Object[]{browser, server})) + .collect(Collectors.toList()); } - public ConsoleTest(String browser) { + public ConsoleTest(String browser, String serverName) { this.browser = browser; + this.serverName = serverName; this.webServerUrl = String.format("%s://%s:%d", "http", System.getProperty("sts-http-host", "localhost"), 8161); } @BeforeEach public void before() throws Exception { - File jolokiaAccessFile = Paths.get(getServerLocation(SERVER_NAME), "etc", Create.ETC_JOLOKIA_ACCESS_XML).toFile(); + File jolokiaAccessFile = Paths.get(getServerLocation(serverName), "etc", Create.ETC_JOLOKIA_ACCESS_XML).toFile(); String jolokiaAccessContent = FileUtils.readFileToString(jolokiaAccessFile, "UTF-8"); if (!jolokiaAccessContent.contains("testcontainers")) { jolokiaAccessContent = jolokiaAccessContent.replaceAll("", @@ -98,9 +139,9 @@ public void before() throws Exception { FileUtils.writeStringToFile(jolokiaAccessFile, jolokiaAccessContent, "UTF-8"); } - cleanupData(SERVER_NAME); + cleanupData(serverName); disableCheckThread(); - startServer(SERVER_NAME, 0, 0); + startServer(serverName, 0, 0); ServerUtil.waitForServerToStart(0, SERVER_ADMIN_USERNAME, SERVER_ADMIN_PASSWORD, 30000); @@ -136,6 +177,7 @@ public void before() throws Exception { if (BROWSER_CHROME.equals(browser)) { webdriverName = "chrome"; browserOptions = new ChromeOptions(); + ((ChromeOptions)browserOptions).setExperimentalOption("prefs", Collections.singletonMap("profile.password_manager_leak_detection", false)); webDriverConstructor = browserOpts -> new ChromeDriver((ChromeOptions)browserOpts); webdriverArgumentsSetter = (browserOpts, arguments) -> ((ChromeOptions) browserOpts).addArguments(arguments); } else if (BROWSER_FIREFOX.equals(browser)) { diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/LoginTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/LoginTest.java index ab3dde286f6..cf793f51069 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/LoginTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/LoginTest.java @@ -30,8 +30,8 @@ public class LoginTest extends ConsoleTest { - public LoginTest(String browser) { - super(browser); + public LoginTest(String browser, String serverName) { + super(browser, serverName); } @TestTemplate diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/PageConstants.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/PageConstants.java index ce8abf49738..eea3b5efd8f 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/PageConstants.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/PageConstants.java @@ -100,6 +100,8 @@ public class PageConstants { public static final By DATA_TABLE = By.id("data-table"); + public static final By DATA_ROW_CONTEXT_MENU = By.cssSelector("div[class='pf-v5-c-menu']"); + public static final By MESSAGE_TABLE = By.id("message-table"); public static final By TABLE_ROW_LOCATOR = By.cssSelector("tr[data-ouia-component-type='PF5/TableRow']"); @@ -111,7 +113,7 @@ public class PageConstants { public static final By QUEUES_TAB_SELECTED = By.cssSelector("button[aria-label='queues'][aria-selected='true']"); - public static final By ADDRESSES_TAB = By.xpath("//button/span[contains(text(),'Addresses')]"); + public static final By ADDRESSES_TAB = By.xpath("//button[@role='tab']/span[contains(text(),'Addresses')]"); public static final By SEND_MESSAGE_BUTTON = By.xpath("//span[contains(text(),'Send Message')]"); @@ -138,4 +140,6 @@ public class PageConstants { public static final By BROKER_NODE_LOCATOR = By.id(BROKER_NODE); public static final By BROKER_BUTTON_LOCATOR = By.xpath("//button[contains(text(), 'Broker 0.0.0.0')]"); + + public static final By ALERT_LOCATOR = By.xpath("//ul/li/div[contains(@class, 'pf-v5-c-alert')]"); } diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/QueuesTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/QueuesTest.java index 05efef48b7b..5bc2a25ccfc 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/QueuesTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/QueuesTest.java @@ -43,8 +43,8 @@ @ExtendWith(ParameterizedTestExtension.class) public class QueuesTest extends ArtemisTest { - public QueuesTest(String browser) { - super(browser); + public QueuesTest(String browser, String serverName) { + super(browser, serverName); } @TestTemplate diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/RootTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/RootTest.java index 17d184184a6..41d53c3730a 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/RootTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/RootTest.java @@ -26,8 +26,8 @@ @ExtendWith(ParameterizedTestExtension.class) public class RootTest extends ConsoleTest { - public RootTest(String browser) { - super(browser); + public RootTest(String browser, String serverName) { + super(browser, serverName); } @TestTemplate diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/TabsTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/TabsTest.java index 643692b6c13..14f52c5370e 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/TabsTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/TabsTest.java @@ -29,8 +29,8 @@ @ExtendWith(ParameterizedTestExtension.class) public class TabsTest extends ArtemisTest { - public TabsTest(String browser) { - super(browser); + public TabsTest(String browser, String serverName) { + super(browser, serverName); } @TestTemplate diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/ArtemisJMXTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/ArtemisJMXTest.java index e9c22735338..42fc705a729 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/ArtemisJMXTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/ArtemisJMXTest.java @@ -19,8 +19,8 @@ import org.apache.activemq.artemis.tests.smoke.console.ConsoleTest; public abstract class ArtemisJMXTest extends ConsoleTest { - public ArtemisJMXTest(String browser) { - super(browser); + public ArtemisJMXTest(String browser, String serverName) { + super(browser, serverName); } diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/JMXTreeTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/JMXTreeTest.java index 9f38e80b87b..48424022de7 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/JMXTreeTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/jmx/JMXTreeTest.java @@ -33,8 +33,8 @@ public class JMXTreeTest extends ArtemisJMXTest { - public JMXTreeTest(String browser) { - super(browser); + public JMXTreeTest(String browser, String serverName) { + super(browser, serverName); } @TestTemplate diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/AddressesPage.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/AddressesPage.java new file mode 100644 index 00000000000..f1691ed96d8 --- /dev/null +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/AddressesPage.java @@ -0,0 +1,56 @@ +/* + * 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.tests.smoke.console.pages; + +import static org.apache.activemq.artemis.tests.smoke.console.PageConstants.BUTTON_LOCATOR; + +public class AddressesPage extends ArtemisPage { + private static final String MESSAGE_COUNT_COLUMN_NAME = "Message Count"; + + public AddressesPage(org.openqa.selenium.WebDriver driver) { + super(driver); + } + + public int countAddress(String name) { + return driver.findElements(getAddressLocator(name)).size(); + } + + public int getMessagesCount(String name) { + org.openqa.selenium.WebElement addressRowWebElement = driver.findElement(getAddressLocator(name)); + + String messageCountText = addressRowWebElement. + findElement(org.openqa.selenium.By.xpath("./..")). + findElements(org.openqa.selenium.By.tagName("td")) + .get(getIndexOfColumn(MESSAGE_COUNT_COLUMN_NAME)).getText(); + + return Integer.parseInt(messageCountText); + } + + public void toggleContextMenu(String name) { + org.openqa.selenium.WebElement addressRowWebElement = driver.findElement(getAddressLocator(name)); + + java.util.List tdElements = addressRowWebElement. + findElement(org.openqa.selenium.By.xpath("./..")). + findElements(org.openqa.selenium.By.tagName("td")); + + tdElements.get(tdElements.size() - 1).findElement(BUTTON_LOCATOR).click(); + } + + private org.openqa.selenium.By getAddressLocator(String name) { + return org.openqa.selenium.By.xpath("//tr/td[contains(text(), '" + name + "')]"); + } +} diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/ArtemisPage.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/ArtemisPage.java index d3b7a82cefe..8877fc13468 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/ArtemisPage.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/console/pages/ArtemisPage.java @@ -28,6 +28,7 @@ import org.openqa.selenium.interactions.Actions; import static org.apache.activemq.artemis.tests.smoke.console.PageConstants.ADDRESSES_TAB; +import static org.apache.activemq.artemis.tests.smoke.console.PageConstants.ALERT_LOCATOR; import static org.apache.activemq.artemis.tests.smoke.console.PageConstants.BUTTON_LOCATOR; import static org.apache.activemq.artemis.tests.smoke.console.PageConstants.DATA_TABLE; import static org.apache.activemq.artemis.tests.smoke.console.PageConstants.LOGOUT_DROPDOWN_LOCATOR; @@ -75,6 +76,16 @@ public String getUser() { return null; } + public AddressesPage getAddressesPage(int timeout) { + WebElement queuesMenuItem = driver.findElement(ADDRESSES_TAB); + + Actions actions = new Actions(driver); + + actions.moveToElement(queuesMenuItem).click().perform(); + + return new AddressesPage(driver); + } + public SendMessagePage getAddressSendMessagePage(String address, int timeout) { refresh(timeout); WebElement element = driver.findElement(ADDRESSES_TAB); @@ -202,4 +213,19 @@ public Object postJolokiaExecRequest(String mbean, String operation, String argu return response; } + + public int countAlerts() { + return driver.findElements(ALERT_LOCATOR).size(); + } + + public void closeAlerts() { + List alerts = driver.findElements(ALERT_LOCATOR); + + for (WebElement alert : alerts) { + List alertButtons = alert.findElements(By.tagName("button")); + for (WebElement alertButton : alertButtons) { + alertButton.click(); + } + } + } }