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(); + } + } + } }