From 4a1fc61fcc8c5a9404f15d3bae845fe33219e48a Mon Sep 17 00:00:00 2001 From: Justin Bertram Date: Tue, 29 Jan 2019 18:35:16 -0600 Subject: [PATCH] ARTEMIS-2243 user/role ops for PropertiesLoginModule via mgmnt --- artemis-cli/pom.xml | 7 +- .../artemis/cli/commands/user/AddUser.java | 3 +- .../artemis/cli/commands/user/ListUser.java | 25 +- .../artemis/cli/commands/user/RemoveUser.java | 3 +- .../artemis/cli/commands/user/ResetUser.java | 3 +- .../artemis/cli/commands/user/UserAction.java | 32 -- .../apache/activemq/cli/test/ArtemisTest.java | 276 +++++++++++++++++- .../management/ActiveMQServerControl.java | 54 ++++ .../src/main/resources/bin/artemis | 2 +- .../src/main/resources/bin/artemis.cmd | 2 +- .../src/main/resources/features.xml | 3 + artemis-server/pom.xml | 4 + .../impl/ActiveMQServerControlImpl.java | 50 ++++ .../core/server/ActiveMQMessageBundle.java | 20 ++ .../PropertiesLoginModuleConfigurator.java | 143 +++++---- docs/user-manual/en/security.md | 11 + pom.xml | 2 +- .../management/ActiveMQServerControlTest.java | 40 +++ .../ActiveMQServerControlUsingCoreTest.java | 25 ++ 19 files changed, 600 insertions(+), 105 deletions(-) rename artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/FileBasedSecStoreConfig.java => artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java (57%) diff --git a/artemis-cli/pom.xml b/artemis-cli/pom.xml index d0007bc5bb..3d535453fb 100644 --- a/artemis-cli/pom.xml +++ b/artemis-cli/pom.xml @@ -118,13 +118,12 @@ org.apache.commons - commons-configuration2 - ${commons.config.version} + commons-lang3 org.apache.commons - commons-lang3 - ${commons.lang.version} + commons-configuration2 + test com.sun.winsw diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java index 37bd676841..a105bcd5b0 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java @@ -20,6 +20,7 @@ import io.airlift.airline.Command; import io.airlift.airline.Option; import org.apache.activemq.artemis.cli.commands.ActionContext; import org.apache.activemq.artemis.cli.commands.util.HashUtil; +import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator; import org.apache.commons.lang3.StringUtils; /** @@ -53,7 +54,7 @@ public class AddUser extends PasswordAction { * @throws IllegalArgumentException if user exists */ private void add(String hash, String... role) throws Exception { - FileBasedSecStoreConfig config = getConfiguration(); + PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc()); config.addNewUser(username, hash, role); config.save(); context.out.println("User added successfully."); diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java index c0fb9790d7..b18deba5ff 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java @@ -16,10 +16,12 @@ */ package org.apache.activemq.artemis.cli.commands.user; -import java.util.List; +import java.util.Map; +import java.util.Set; import io.airlift.airline.Command; import org.apache.activemq.artemis.cli.commands.ActionContext; +import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator; /** * list existing users, example: @@ -42,11 +44,24 @@ public class ListUser extends UserAction { * if username is not specified */ private void list() throws Exception { - FileBasedSecStoreConfig config = getConfiguration(); - List result = config.listUser(username); - for (String str : result) { - context.out.println(str); + PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc()); + Map> result = config.listUser(username); + StringBuilder logMessage = new StringBuilder("--- \"user\"(roles) ---\n"); + int userCount = 0; + for (Map.Entry> entry : result.entrySet()) { + logMessage.append("\"").append(entry.getKey()).append("\"("); + int roleCount = 0; + for (String role : entry.getValue()) { + logMessage.append(role); + if (++roleCount < entry.getValue().size()) { + logMessage.append(","); + } + } + logMessage.append(")\n"); + userCount++; } + logMessage.append("\n Total: ").append(userCount); + context.out.println(logMessage); } } diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java index a9dce8df9e..c49e36b11d 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java @@ -18,6 +18,7 @@ package org.apache.activemq.artemis.cli.commands.user; import io.airlift.airline.Command; import org.apache.activemq.artemis.cli.commands.ActionContext; +import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator; /** * Remove a user, example: @@ -35,7 +36,7 @@ public class RemoveUser extends UserAction { } private void remove() throws Exception { - FileBasedSecStoreConfig config = getConfiguration(); + PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc()); config.removeUser(username); config.save(); context.out.println("User removed."); diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java index 2e3e7250be..162aea6269 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java @@ -20,6 +20,7 @@ import io.airlift.airline.Command; import io.airlift.airline.Option; import org.apache.activemq.artemis.cli.commands.ActionContext; import org.apache.activemq.artemis.cli.commands.util.HashUtil; +import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator; import org.apache.commons.lang3.StringUtils; /** @@ -57,7 +58,7 @@ public class ResetUser extends PasswordAction { context.err.println("Nothing to update."); return; } - FileBasedSecStoreConfig config = getConfiguration(); + PropertiesLoginModuleConfigurator config = new PropertiesLoginModuleConfigurator(entry, getBrokerEtc()); config.updateUser(username, password, roles); config.save(); context.out.println("User updated"); diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java index e4a2e45e3e..d6f8ba930d 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java @@ -16,16 +16,8 @@ */ package org.apache.activemq.artemis.cli.commands.user; -import javax.security.auth.login.AppConfigurationEntry; -import javax.security.auth.login.Configuration; -import java.io.File; - import io.airlift.airline.Option; import org.apache.activemq.artemis.cli.commands.InputAbstract; -import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule; - -import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.ROLE_FILE_PROP_NAME; -import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.USER_FILE_PROP_NAME; public abstract class UserAction extends InputAbstract { @@ -50,30 +42,6 @@ public abstract class UserAction extends InputAbstract { } } - FileBasedSecStoreConfig getConfiguration() throws Exception { - - Configuration securityConfig = Configuration.getConfiguration(); - AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry(entry); - - for (AppConfigurationEntry entry : entries) { - if (entry.getLoginModuleName().equals(PropertiesLoginModule.class.getName())) { - String userFileName = (String) entry.getOptions().get(USER_FILE_PROP_NAME); - String roleFileName = (String) entry.getOptions().get(ROLE_FILE_PROP_NAME); - - File etcDir = new File(getBrokerEtc()); - File userFile = new File(etcDir, userFileName); - File roleFile = new File(etcDir, roleFileName); - - if (!userFile.exists() || !roleFile.exists()) { - throw new IllegalArgumentException("Couldn't find user file or role file!"); - } - - return new FileBasedSecStoreConfig(userFile, roleFile); - } - } - throw new IllegalArgumentException("Failed to load security file"); - } - public void setUsername(String username) { this.username = username; } diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java index 23493b91b7..cb8980967e 100644 --- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java +++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java @@ -23,6 +23,8 @@ import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; +import javax.json.JsonArray; +import javax.json.JsonObject; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -34,20 +36,24 @@ import java.io.InputStreamReader; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; +import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException; +import org.apache.activemq.artemis.api.core.JsonUtil; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl; import org.apache.activemq.artemis.cli.Artemis; import org.apache.activemq.artemis.cli.CLIException; import org.apache.activemq.artemis.cli.commands.ActionContext; import org.apache.activemq.artemis.cli.commands.Create; import org.apache.activemq.artemis.cli.commands.Mask; -import org.apache.activemq.artemis.cli.commands.queue.StatQueue; import org.apache.activemq.artemis.cli.commands.Run; +import org.apache.activemq.artemis.cli.commands.queue.StatQueue; import org.apache.activemq.artemis.cli.commands.user.AddUser; import org.apache.activemq.artemis.cli.commands.user.ListUser; import org.apache.activemq.artemis.cli.commands.user.RemoveUser; @@ -389,6 +395,173 @@ public class ArtemisTest extends CliTestBase { assertTrue(result.contains("Total: 0")); } + @Test + public void testUserCommandViaManagement() throws Exception { + Run.setEmbedded(true); + File instance1 = new File(temporaryFolder.getRoot(), "instance_user"); + System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config"); + Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor"); + System.setProperty("artemis.instance", instance1.getAbsolutePath()); + Object result = Artemis.internalExecute("run"); + ActiveMQServer activeMQServer = ((Pair)result).getB(); + ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl(); + + File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties"); + File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties"); + + //default only one user admin with role amq + String jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq"); + checkRole("admin", roleFile, "amq"); + + //add a simple user + activeMQServerControl.addUser("guest", "guest123", "admin", true, "activemq"); + + //verify add + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin"); + checkRole("guest", roleFile, "admin"); + assertTrue(checkPassword("guest", "guest123", userFile)); + + //add a user with 2 roles + activeMQServerControl.addUser("scott", "tiger", "admin,operator", true, "activemq"); + + //verify add + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator"); + checkRole("scott", roleFile, "admin", "operator"); + assertTrue(checkPassword("scott", "tiger", userFile)); + + try { + activeMQServerControl.addUser("scott", "password", "visitor", true, "activemq"); + fail("should throw an exception if adding a existing user"); + } catch (IllegalArgumentException expected) { + } + + //check existing users are intact + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq"); + contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator"); + + //check listing with just one user + jsonResult = activeMQServerControl.listUser("admin", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq"); + contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin", false); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin", false); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator", false); + + //check listing with another single user + jsonResult = activeMQServerControl.listUser("guest", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq", false); + contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin", false); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator", false); + + //remove a user + activeMQServerControl.removeUser("guest", "activemq"); + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq"); + contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin", false); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator"); + + //remove another + activeMQServerControl.removeUser("scott", "activemq"); + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq"); + contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin", false); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "admin", false); + contains(JsonUtil.readJsonArray(jsonResult), "scott", "operator", false); + + //remove non-exist + try { + activeMQServerControl.removeUser("alien", "activemq"); + fail("should throw exception when removing a non-existing user"); + } catch (IllegalArgumentException expected) { + } + + //check + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq"); + + //now remove last + activeMQServerControl.removeUser("admin", "activemq"); + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq", false); + + stopServer(); + } + + @Test + public void testBadSecurityEntryNameViaManagement() throws Exception { + Run.setEmbedded(true); + File instance1 = new File(temporaryFolder.getRoot(), "instance_user"); + System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config"); + Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor"); + System.setProperty("artemis.instance", instance1.getAbsolutePath()); + Object result = Artemis.internalExecute("run"); + ActiveMQServer activeMQServer = ((Pair)result).getB(); + ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl(); + + try { + activeMQServerControl.listUser("", "activemqx"); + fail(); + } catch (ActiveMQIllegalStateException expected) { + } + + stopServer(); + } + + @Test + public void testMissingUserFileViaManagement() throws Exception { + Run.setEmbedded(true); + File instance1 = new File(temporaryFolder.getRoot(), "instance_user"); + System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config"); + Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor"); + System.setProperty("artemis.instance", instance1.getAbsolutePath()); + Object result = Artemis.internalExecute("run"); + ActiveMQServer activeMQServer = ((Pair)result).getB(); + ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl(); + + File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties"); + userFile.delete(); + // File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties"); + + try { + activeMQServerControl.listUser("", "activemq"); + fail(); + } catch (ActiveMQIllegalStateException expected) { + } + + stopServer(); + } + + @Test + public void testMissingRoleFileViaManagement() throws Exception { + Run.setEmbedded(true); + File instance1 = new File(temporaryFolder.getRoot(), "instance_user"); + System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config"); + Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor"); + System.setProperty("artemis.instance", instance1.getAbsolutePath()); + Object result = Artemis.internalExecute("run"); + ActiveMQServer activeMQServer = ((Pair)result).getB(); + ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl(); + + File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties"); + roleFile.delete(); + + try { + activeMQServerControl.listUser("", "activemq"); + fail(); + } catch (ActiveMQIllegalStateException expected) { + } + + stopServer(); + } + @Test public void testUserCommandReset() throws Exception { Run.setEmbedded(true); @@ -454,9 +627,9 @@ public class ArtemisTest extends CliTestBase { assertTrue(result.contains("Total: 4")); assertTrue(result.contains("\"guest\"(admin)")); - assertTrue(result.contains("\"user1\"(admin,manager)")); - assertTrue(result.contains("\"user2\"(admin,manager,master)")); - assertTrue(result.contains("\"user3\"(master,system)")); + assertTrue(Pattern.compile("\"user1\"\\((admin|manager),(admin|manager)\\)").matcher(result).find()); + assertTrue(Pattern.compile("\"user2\"\\((admin|manager|master),(admin|manager|master),(admin|manager|master)\\)").matcher(result).find()); + assertTrue(Pattern.compile("\"user3\"\\((master|system),(master|system)\\)").matcher(result).find()); checkRole("user1", roleFile, "admin", "manager"); @@ -488,6 +661,72 @@ public class ArtemisTest extends CliTestBase { assertTrue(checkPassword("user3", "newpassword3", userFile)); } + @Test + public void testUserCommandResetViaManagement() throws Exception { + Run.setEmbedded(true); + File instance1 = new File(temporaryFolder.getRoot(), "instance_user"); + System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config"); + Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-autotune", "--no-web", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-stomp-acceptor", "--no-hornetq-acceptor"); + System.setProperty("artemis.instance", instance1.getAbsolutePath()); + Object result = Artemis.internalExecute("run"); + ActiveMQServer activeMQServer = ((Pair)result).getB(); + ActiveMQServerControl activeMQServerControl = activeMQServer.getActiveMQServerControl(); + + File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties"); + File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties"); + + //default only one user admin with role amq + String jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq"); + checkRole("admin", roleFile, "amq"); + + //remove a user + activeMQServerControl.removeUser("admin", "activemq"); + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "admin", "amq", false); + + //add some users + activeMQServerControl.addUser("guest", "guest123", "admin", true, "activemq"); + activeMQServerControl.addUser("user1", "password1", "admin,manager", true, "activemq"); + assertTrue(checkPassword("user1", "password1", userFile)); + activeMQServerControl.addUser("user2", "password2", "admin,manager,master", true, "activemq"); + activeMQServerControl.addUser("user3", "password3", "system,master", true, "activemq"); + + + //verify use list cmd + jsonResult = activeMQServerControl.listUser("", "activemq"); + contains(JsonUtil.readJsonArray(jsonResult), "guest", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "user1", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "user1", "manager"); + contains(JsonUtil.readJsonArray(jsonResult), "user2", "admin"); + contains(JsonUtil.readJsonArray(jsonResult), "user2", "manager"); + contains(JsonUtil.readJsonArray(jsonResult), "user2", "master"); + contains(JsonUtil.readJsonArray(jsonResult), "user3", "master"); + contains(JsonUtil.readJsonArray(jsonResult), "user3", "system"); + + checkRole("user1", roleFile, "admin", "manager"); + + //reset password + activeMQServerControl.resetUser("user1", "newpassword1", null, "activemq"); + + checkRole("user1", roleFile, "admin", "manager"); + assertFalse(checkPassword("user1", "password1", userFile)); + assertTrue(checkPassword("user1", "newpassword1", userFile)); + + //reset role + activeMQServerControl.resetUser("user2", null, "manager,master,operator", "activemq"); + + checkRole("user2", roleFile, "manager", "master", "operator"); + assertTrue(checkPassword("user2", "password2", userFile)); + + //reset both + activeMQServerControl.resetUser("user3", "newpassword3", "admin,system", "activemq"); + + checkRole("user3", roleFile, "admin", "system"); + assertTrue(checkPassword("user3", "newpassword3", userFile)); + stopServer(); + } + @Test public void testMaskCommand() throws Exception { @@ -1074,4 +1313,33 @@ public class ArtemisTest extends CliTestBase { return processor.compare(password.toCharArray(), storedPassword); } + private void contains(JsonArray users, String username, String role) { + contains(users, username, role, true); + } + + private void contains(JsonArray users, String username, String role, boolean contains) { + boolean userFound = false; + boolean roleFound = false; + for (int i = 0; i < users.size(); i++) { + JsonObject user = users.getJsonObject(i); + if (user.getString("username").equals(username)) { + userFound = true; + JsonArray roles = user.getJsonArray("roles"); + for (int j = 0; j < roles.size(); j++) { + if (roles.getString(j).equals(role)) { + roleFound = true; + break; + } + } + } + } + if (contains) { + assertTrue("user " + username + " not found", userFound); + assertTrue("role " + role + " not found", roleFound); + } else { + assertFalse("user " + username + " found", userFound); + assertFalse("role " + role + " found", roleFound); + } + } + } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java index 06819b2e76..04c7e16ba2 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java @@ -1341,5 +1341,59 @@ public interface ActiveMQServerControl { */ @Operation(desc = "Names of the cluster-connections deployed on this server", impact = MBeanOperationInfo.INFO) String[] getClusterConnectionNames(); + + /** + * Add a user (only applicable when using the JAAS PropertiesLoginModule) + * + * @param username + * @param password + * @param roles + * @param entryName + * @throws Exception + */ + @Operation(desc = "add a user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION) + void addUser(@Parameter(name = "username", desc = "Name of the user") String username, + @Parameter(name = "password", desc = "User's password") String password, + @Parameter(name = "roles (comma separated)", desc = "User's role") String roles, + @Parameter(name = "plaintext", desc = "whether or not to store the password in plaintext or hash it") boolean plaintext, + @Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception; + + /** + * List the information about a user or all users if no username is supplied (only applicable when using the JAAS PropertiesLoginModule). + * + * @param username + * @param entryName + * @return JSON array of user & role information + * @throws Exception + */ + @Operation(desc = "list info about a user or all users if no username is supplied (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION) + String listUser(@Parameter(name = "username", desc = "Name of the user; leave null to list all known users") String username, + @Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception; + + /** + * Remove a user (only applicable when using the JAAS PropertiesLoginModule). + * + * @param username + * @param entryName + * @throws Exception + */ + @Operation(desc = "remove a user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION) + void removeUser(@Parameter(name = "username", desc = "Name of the user") String username, + @Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception; + + /** + * Set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule). + * + * @param username + * @param password + * @param roles + * @param entryName + * @throws Exception + */ + @Operation(desc = "set new properties on an existing user (only applicable when using the JAAS PropertiesLoginModule)", impact = MBeanOperationInfo.ACTION) + void resetUser(@Parameter(name = "username", desc = "Name of the user") String username, + @Parameter(name = "password", desc = "User's password") String password, + @Parameter(name = "roles (comma separated)", desc = "User's role") String roles, + @Parameter(name = "entryName", desc = "Name of entry in login.config ('activemq' by default)") String entryName) throws Exception; } diff --git a/artemis-distribution/src/main/resources/bin/artemis b/artemis-distribution/src/main/resources/bin/artemis index 4b5ebe5ad2..6fb73852cd 100755 --- a/artemis-distribution/src/main/resources/bin/artemis +++ b/artemis-distribution/src/main/resources/bin/artemis @@ -46,7 +46,7 @@ fi # Set Defaults Properties JAVA_ARGS="-XX:+UseParallelGC -Xms512M -Xmx1024M" -CLASSPATH="$ARTEMIS_HOME/lib/artemis-boot.jar" +CLASSPATH="$ARTEMIS_HOME/lib/*" # OS specific support. cygwin=false; diff --git a/artemis-distribution/src/main/resources/bin/artemis.cmd b/artemis-distribution/src/main/resources/bin/artemis.cmd index 66f4cdbbb1..c43d1b8bc5 100755 --- a/artemis-distribution/src/main/resources/bin/artemis.cmd +++ b/artemis-distribution/src/main/resources/bin/artemis.cmd @@ -50,7 +50,7 @@ set JAVA_ARGS=-XX:+UseParallelGC -Xms512M -Xmx1024M rem "Create full JVM Args" set JVM_ARGS=%JAVA_ARGS% if not "%ARTEMIS_CLUSTER_PROPS%"=="" set JVM_ARGS=%JVM_ARGS% %ARTEMIS_CLUSTER_PROPS% -set JVM_ARGS=%JVM_ARGS% -classpath %ARTEMIS_HOME%\lib\artemis-boot.jar +set JVM_ARGS=%JVM_ARGS% -classpath %ARTEMIS_HOME%\lib\* set JVM_ARGS=%JVM_ARGS% -Dartemis.home=%ARTEMIS_HOME% if not "%DEBUG_ARGS%"=="" set JVM_ARGS=%JVM_ARGS% %DEBUG_ARGS% diff --git a/artemis-features/src/main/resources/features.xml b/artemis-features/src/main/resources/features.xml index 38764e9069..d3bcae4eef 100644 --- a/artemis-features/src/main/resources/features.xml +++ b/artemis-features/src/main/resources/features.xml @@ -62,6 +62,9 @@ mvn:org.apache.activemq/artemis-features/${pom.version}/xml/artemis mvn:org.apache.geronimo.specs/geronimo-jms_2.0_spec/${geronimo.jms.2.spec.version} + mvn:org.apache.commons/commons-configuration2/${commons.config.version} + mvn:org.apache.commons/commons-text/1.6 + mvn:org.apache.commons/commons-lang3/${commons.lang.version} mvn:org.apache.activemq/artemis-native/${pom.version} mvn:org.apache.activemq/artemis-server-osgi/${pom.version} diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml index f24731bf8c..d7b8f709e5 100644 --- a/artemis-server/pom.xml +++ b/artemis-server/pom.xml @@ -127,6 +127,10 @@ commons-beanutils commons-beanutils + + org.apache.commons + commons-configuration2 + commons-io commons-io diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java index 302da89a73..c89dacb0dd 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java @@ -30,6 +30,7 @@ import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.transaction.xa.Xid; +import java.net.URL; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; @@ -118,8 +119,10 @@ import org.apache.activemq.artemis.core.transaction.TransactionDetailFactory; import org.apache.activemq.artemis.core.transaction.impl.CoreTransactionDetail; import org.apache.activemq.artemis.core.transaction.impl.XidImpl; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; +import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator; import org.apache.activemq.artemis.utils.JsonLoader; import org.apache.activemq.artemis.utils.ListUtil; +import org.apache.activemq.artemis.utils.PasswordMaskingUtil; import org.apache.activemq.artemis.utils.SecurityFormatter; import org.apache.activemq.artemis.utils.collections.TypedProperties; import org.jboss.logging.Logger; @@ -2956,5 +2959,52 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active this.broadcaster.sendNotification(new Notification(type.toString(), this, notifSeq.incrementAndGet(), notification.toString())); } + @Override + public void addUser(String username, String password, String roles, boolean plaintext, String entryName) throws Exception { + PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName); + config.addNewUser(username, plaintext ? password : PasswordMaskingUtil.getHashProcessor().hash(password), roles.split(",")); + config.save(); + } + + @Override + public String listUser(String username, String entryName) throws Exception { + PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName); + Map> info = config.listUser(username); + JsonArrayBuilder users = JsonLoader.createArrayBuilder(); + for (Entry> entry : info.entrySet()) { + JsonObjectBuilder user = JsonLoader.createObjectBuilder(); + user.add("username", entry.getKey()); + JsonArrayBuilder roles = JsonLoader.createArrayBuilder(); + for (String role : entry.getValue()) { + roles.add(role); + } + user.add("roles", roles); + users.add(user); + } + return users.build().toString(); + } + + @Override + public void removeUser(String username, String entryName) throws Exception { + PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName); + config.removeUser(username); + config.save(); + } + + @Override + public void resetUser(String username, String password, String roles, String entryName) throws Exception { + PropertiesLoginModuleConfigurator config = getPropertiesLoginModuleConfigurator(entryName); + config.updateUser(username, password, roles == null ? null : roles.split(",")); + config.save(); + } + + private PropertiesLoginModuleConfigurator getPropertiesLoginModuleConfigurator(String entryName) throws Exception { + URL configurationUrl = server.getConfiguration().getConfigurationUrl(); + if (configurationUrl == null) { + throw ActiveMQMessageBundle.BUNDLE.failedToLocateConfigURL(); + } + String path = configurationUrl.getPath(); + return new PropertiesLoginModuleConfigurator(entryName, path.substring(0, path.lastIndexOf("/"))); + } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java index d649d8da02..5cb8c60edf 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQMessageBundle.java @@ -449,4 +449,24 @@ public interface ActiveMQMessageBundle { @Message(id = 119217, value = "Can't write to closed file: {0}", format = Message.Format.MESSAGE_FORMAT) ActiveMQIOErrorException cannotWriteToClosedFile(SequentialFile file); + @Message(id = 229218, value = "Failed to locate broker configuration URL") + ActiveMQIllegalStateException failedToLocateConfigURL(); + + @Message(id = 229219, value = "Failed to load security configuration") + ActiveMQIllegalStateException failedToLoadSecurityConfig(); + + @Message(id = 229220, value = "Failed to load user file: {0}", format = Message.Format.MESSAGE_FORMAT) + ActiveMQIllegalStateException failedToLoadUserFile(String path); + + @Message(id = 229221, value = "Failed to load role file: {0}", format = Message.Format.MESSAGE_FORMAT) + ActiveMQIllegalStateException failedToLoadRoleFile(String path); + + @Message(id = 229222, value = "Failed to find login module entry {0} from JAAS configuration", format = Message.Format.MESSAGE_FORMAT) + ActiveMQIllegalStateException failedToFindLoginModuleEntry(String entry); + + @Message(id = 229223, value = "User {0} already exists", format = Message.Format.MESSAGE_FORMAT) + IllegalArgumentException userAlreadyExists(String user); + + @Message(id = 229224, value = "User {0} does not exist", format = Message.Format.MESSAGE_FORMAT) + IllegalArgumentException userDoesNotExist(String user); } diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/FileBasedSecStoreConfig.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java similarity index 57% rename from artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/FileBasedSecStoreConfig.java rename to artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java index 1f8e29771e..d1e7873085 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/FileBasedSecStoreConfig.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModuleConfigurator.java @@ -14,20 +14,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.activemq.artemis.cli.commands.user; +package org.apache.activemq.artemis.spi.core.security.jaas; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.activemq.artemis.api.core.Pair; +import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; import org.apache.activemq.artemis.utils.StringUtil; import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; import org.apache.commons.configuration2.builder.fluent.Configurations; -class FileBasedSecStoreConfig { +import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.ROLE_FILE_PROP_NAME; +import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.USER_FILE_PROP_NAME; + +public class PropertiesLoginModuleConfigurator { private static final String LICENSE_HEADER = "## ---------------------------------------------------------------------------\n" + @@ -51,80 +61,110 @@ class FileBasedSecStoreConfig { private PropertiesConfiguration userConfig; private PropertiesConfiguration roleConfig; - FileBasedSecStoreConfig(File userFile, File roleFile) throws Exception { - Configurations configs = new Configurations(); - userBuilder = configs.propertiesBuilder(userFile); - roleBuilder = configs.propertiesBuilder(roleFile); - userConfig = userBuilder.getConfiguration(); - roleConfig = roleBuilder.getConfiguration(); + public PropertiesLoginModuleConfigurator(String entryName, String brokerEtc) throws Exception { + if (entryName == null || entryName.length() == 0) { + entryName = "activemq"; + } - String roleHeader = roleConfig.getLayout().getHeaderComment(); - String userHeader = userConfig.getLayout().getHeaderComment(); + Configuration securityConfig = Configuration.getConfiguration(); + AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry(entryName); - if (userHeader == null) { - if (userConfig.isEmpty()) { - //clean and reset header - userConfig.clear(); - userConfig.setHeader(LICENSE_HEADER); + if (entries == null || entries.length == 0) { + throw ActiveMQMessageBundle.BUNDLE.failedToLoadSecurityConfig(); + } + + int entriesInspected = 0; + for (AppConfigurationEntry entry : entries) { + entriesInspected++; + if (entry.getLoginModuleName().equals(PropertiesLoginModule.class.getName())) { + String userFileName = (String) entry.getOptions().get(USER_FILE_PROP_NAME); + String roleFileName = (String) entry.getOptions().get(ROLE_FILE_PROP_NAME); + + File etcDir = new File(brokerEtc); + File userFile = new File(etcDir, userFileName); + File roleFile = new File(etcDir, roleFileName); + + if (!userFile.exists()) { + throw ActiveMQMessageBundle.BUNDLE.failedToLoadUserFile(brokerEtc + userFileName); + } + + if (!roleFile.exists()) { + throw ActiveMQMessageBundle.BUNDLE.failedToLoadRoleFile(brokerEtc + roleFileName); + } + + Configurations configs = new Configurations(); + userBuilder = configs.propertiesBuilder(userFile); + roleBuilder = configs.propertiesBuilder(roleFile); + userConfig = userBuilder.getConfiguration(); + roleConfig = roleBuilder.getConfiguration(); + + String roleHeader = roleConfig.getLayout().getHeaderComment(); + String userHeader = userConfig.getLayout().getHeaderComment(); + + if (userHeader == null) { + if (userConfig.isEmpty()) { + //clean and reset header + userConfig.clear(); + userConfig.setHeader(LICENSE_HEADER); + } + } + + if (roleHeader == null) { + if (roleConfig.isEmpty()) { + //clean and reset header + roleConfig.clear(); + roleConfig.setHeader(LICENSE_HEADER); + } + } + return; } } - if (roleHeader == null) { - if (roleConfig.isEmpty()) { - //clean and reset header - roleConfig.clear(); - roleConfig.setHeader(LICENSE_HEADER); - } + if (entriesInspected == entries.length) { + throw ActiveMQMessageBundle.BUNDLE.failedToFindLoginModuleEntry(entryName); } } - void addNewUser(String username, String hash, String... roles) throws Exception { + public void addNewUser(String username, String hash, String... roles) throws Exception { if (userConfig.getString(username) != null) { - throw new IllegalArgumentException("User already exist: " + username); + throw ActiveMQMessageBundle.BUNDLE.userAlreadyExists(username); } userConfig.addProperty(username, hash); addRoles(username, roles); } - void save() throws Exception { + public void save() throws Exception { userBuilder.save(); roleBuilder.save(); } - void removeUser(String username) throws Exception { + public void removeUser(String username) { if (userConfig.getProperty(username) == null) { - throw new IllegalArgumentException("user " + username + " doesn't exist."); + throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(username); } userConfig.clearProperty(username); removeRoles(username); } - List listUser(String username) { - List result = new ArrayList<>(); - result.add("--- \"user\"(roles) ---\n"); + public Map> listUser(String username) { + Map> result = new HashMap<>(); - int totalUsers = 0; - if (username != null) { - String roles = findRoles(username); - result.add("\"" + username + "\"(" + roles + ")"); - totalUsers++; + if (username != null && username.length() > 0) { + result.put(username, findRoles(username)); } else { Iterator iter = userConfig.getKeys(); while (iter.hasNext()) { String keyUser = iter.next(); - String roles = findRoles(keyUser); - result.add("\"" + keyUser + "\"(" + roles + ")"); - totalUsers++; + result.put(keyUser, findRoles(keyUser)); } } - result.add("\n Total: " + totalUsers); return result; } - void updateUser(String username, String password, String[] roles) { + public void updateUser(String username, String password, String[] roles) { String oldPassword = (String) userConfig.getProperty(username); if (oldPassword == null) { - throw new IllegalArgumentException("user " + username + " doesn't exist."); + throw ActiveMQMessageBundle.BUNDLE.userDoesNotExist(username); } if (password != null) { @@ -138,33 +178,28 @@ class FileBasedSecStoreConfig { } } - private String findRoles(String uname) { + private Set findRoles(String username) { Iterator iter = roleConfig.getKeys(); - StringBuilder builder = new StringBuilder(); - boolean first = true; + Set roles = new HashSet(); while (iter.hasNext()) { String role = iter.next(); - List names = roleConfig.getList(String.class, role); - for (String value : names) { - //each value may be a comma separated list - String[] items = value.split(","); + for (String roleList : roleConfig.getList(String.class, role)) { + //each roleList may be a comma separated list + String[] items = roleList.split(","); for (String item : items) { - if (item.equals(uname)) { - if (!first) { - builder.append(","); - } - builder.append(role); - first = false; + if (item.equals(username)) { + roles.add(role); } } } } - return builder.toString(); + return roles; } private void addRoles(String username, String[] roles) { for (String role : roles) { + role = role.trim(); List users = roleConfig.getList(String.class, role); if (users == null) { users = new ArrayList<>(); diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md index 0ac37c4562..ef0f363e28 100644 --- a/docs/user-manual/en/security.md +++ b/docs/user-manual/en/security.md @@ -546,6 +546,17 @@ users=system,user guests=guest ``` +As mentioned above, the Artemis command-line interface supports a command to +`add` a user. Commands to `list` (one or all) users, `remove` a user, and `reset` +a user's password and/or role(s) are also supported via the command-line +interface as well as the normal management interfaces (e.g. JMX, web console, +etc.). + +> **Warning** +> +> Management and CLI operations to manipulate user & role data are only available +> when using the `PropertiesLoginModule`. + #### LDAPLoginModule The LDAP login module enables you to perform authentication and authorization diff --git a/pom.xml b/pom.xml index 3fe3697ab4..3b82a86ded 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 4.0.6 4.9.1 - 2.1 + 2.4 3.0 5.14.5 10.11.1.1 diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java index c74e6d55a3..847b01ec75 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java @@ -2683,6 +2683,46 @@ public class ActiveMQServerControlTest extends ManagementTestBase { Assert.assertTrue(((org.apache.activemq.artemis.jms.client.ActiveMQMessageConsumer)JMSclient).isClosed()); } + @Test + public void testAddUser() throws Exception { + ActiveMQServerControl serverControl = createManagementControl(); + try { + serverControl.addUser("x", "x", "x", true, "x"); + fail(); + } catch (Exception expected) { + } + } + + @Test + public void testRemoveUser() throws Exception { + ActiveMQServerControl serverControl = createManagementControl(); + try { + serverControl.removeUser("x", "x"); + fail(); + } catch (Exception expected) { + } + } + + @Test + public void testListUser() throws Exception { + ActiveMQServerControl serverControl = createManagementControl(); + try { + serverControl.listUser("x", "x"); + fail(); + } catch (Exception expected) { + } + } + + @Test + public void testResetUser() throws Exception { + ActiveMQServerControl serverControl = createManagementControl(); + try { + serverControl.resetUser("x","x","x", "x"); + fail(); + } catch (Exception expected) { + } + } + protected void scaleDown(ScaleDownHandler handler) throws Exception { SimpleString address = new SimpleString("testQueue"); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java index 716b5d0685..f0315e8d03 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java @@ -335,6 +335,31 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes return null; } + @Override + public void addUser(String username, + String password, + String role, + boolean plaintext, + String entryName) throws Exception { + proxy.invokeOperation("addUser", username, password, role, plaintext, entryName); + + } + + @Override + public String listUser(String username, String entryName) throws Exception { + return (String) proxy.invokeOperation("listUser", username, entryName, String.class); + } + + @Override + public void removeUser(String username, String entryName) throws Exception { + proxy.invokeOperation("removeUser", username, entryName); + } + + @Override + public void resetUser(String username, String password, String roles, String entryName) throws Exception { + proxy.invokeOperation("resetUser", username, password, roles, entryName); + } + @Override public String getUptime() { return null;