You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by cl...@apache.org on 2016/11/02 19:53:14 UTC

[1/5] activemq-artemis git commit: ARTEMIS-786 Store user's password in hash form by default - user passwords for PropertiesLoginModule stored using PBKDF2 algothrim by default - implements cli user command to help create and manage user/roles

Repository: activemq-artemis
Updated Branches:
  refs/heads/master 8b57516e1 -> 9f7fc8836


http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SecureHashProcessor.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SecureHashProcessor.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SecureHashProcessor.java
new file mode 100644
index 0000000..81db051
--- /dev/null
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SecureHashProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.utils;
+
+/**
+ * Hash function
+ */
+public class SecureHashProcessor implements HashProcessor {
+
+   private static final String BEGIN_HASH = "ENC(";
+   private static final String END_HASH = ")";
+
+   private DefaultSensitiveStringCodec codec;
+
+   public SecureHashProcessor(DefaultSensitiveStringCodec codec) {
+      this.codec = codec;
+   }
+
+   @Override
+   public String hash(String plainText) throws Exception {
+      return BEGIN_HASH + codec.encode(plainText) + END_HASH;
+   }
+
+   @Override
+   //storedValue must take form of ENC(...)
+   public boolean compare(char[] inputValue, String storedValue) {
+      String storedHash = storedValue.substring(4, storedValue.length() - 2);
+      return codec.verify(inputValue, storedHash);
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SensitiveDataCodec.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SensitiveDataCodec.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SensitiveDataCodec.java
index b1bfd2b..d585976 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SensitiveDataCodec.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/SensitiveDataCodec.java
@@ -29,5 +29,7 @@ public interface SensitiveDataCodec<T> {
 
    T decode(Object mask) throws Exception;
 
-   void init(Map<String, String> params);
+   T encode(Object secret) throws Exception;
+
+   void init(Map<String, String> params) throws Exception;
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodecTest.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodecTest.java b/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodecTest.java
new file mode 100644
index 0000000..393558d
--- /dev/null
+++ b/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodecTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.utils;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class DefaultSensitiveStringCodecTest {
+
+   @Test
+   public void testDefaultAlgorithm() throws Exception {
+      SensitiveDataCodec<String> codec = PasswordMaskingUtil.getDefaultCodec();
+      assertTrue(codec instanceof DefaultSensitiveStringCodec);
+   }
+
+   @Test
+   public void testOnewayAlgorithm() throws Exception {
+      DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
+      Map<String, String> params = new HashMap<>();
+      params.put(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.ONE_WAY);
+      codec.init(params);
+
+      String plainText = "some_password";
+      String maskedText = codec.encode(plainText);
+      System.out.println("encoded value: " + maskedText);
+
+      //one way can't decode
+      try {
+         codec.decode(maskedText);
+         fail("one way algorithm can't decode");
+      } catch (IllegalArgumentException expected) {
+      }
+
+      assertTrue(codec.verify(plainText.toCharArray(), maskedText));
+
+      String otherPassword = "some_other_password";
+      assertFalse(codec.verify(otherPassword.toCharArray(), maskedText));
+   }
+
+   @Test
+   public void testTwowayAlgorithm() throws Exception {
+      DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
+      Map<String, String> params = new HashMap<>();
+      params.put(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.TWO_WAY);
+      codec.init(params);
+
+      String plainText = "some_password";
+      String maskedText = codec.encode(plainText);
+      System.out.println("encoded value: " + maskedText);
+
+      String decoded = codec.decode(maskedText);
+      System.out.println("encoded value: " + maskedText);
+
+      assertEquals("decoded result not match: " + decoded, decoded, plainText);
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/HashProcessorTest.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/HashProcessorTest.java b/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/HashProcessorTest.java
new file mode 100644
index 0000000..83199b5
--- /dev/null
+++ b/artemis-commons/src/test/java/org/apache/activemq/artemis/utils/HashProcessorTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.utils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class HashProcessorTest {
+
+   private static final String USER1_PASSWORD = "password";
+   private static final String USER1_HASHED_PASSWORD = "ENC(1024:973A466A489ABFDED3D4B3D181DC77F410F2FC6E87432809A46B72B294147D76:C999ECA8A85387E1FFB14E4FE5CECD17948BA80BA04318A9BE4C3E34B7FE2925F43AB6BC9DFE0D9855DA67439AEEB9850351BC4D5D3AEC6A6903C42B8EB4ED1E)";
+
+   private static final String USER2_PASSWORD = "manager";
+   private static final String USER2_HASHED_PASSWORD = "ENC(1024:48018CDB1B5925DA2CC51DBD6F7E8C5FF156C22C03C6C69720C56F8BE76A1D48:0A0F68C2C01F46D347C6C51D641291A4608EDA50A873ED122909D9134B7A757C14176F0C033F0BD3CE35B3C373D5B652650CDE5FFBBB0F286D4495CEFEEDB166)";
+
+   private static final String USER3_PASSWORD = "artemis000";
+
+   @Parameterized.Parameters(name = "{index}: testing password {0}")
+   public static Collection<Object[]> data() {
+      return Arrays.asList(new Object[][] {
+         {USER1_PASSWORD, USER1_HASHED_PASSWORD, true},
+         {USER2_PASSWORD, USER2_HASHED_PASSWORD, true},
+         {USER3_PASSWORD, USER3_PASSWORD, true},
+         {USER1_PASSWORD, USER2_PASSWORD, false},
+         {USER3_PASSWORD, USER2_HASHED_PASSWORD, false}
+      });
+   }
+
+   private String password;
+   private String storedPassword;
+   private boolean match;
+
+   public HashProcessorTest(String password, String storedPassword, boolean match) {
+      this.password = password;
+      this.storedPassword = storedPassword;
+      this.match = match;
+   }
+
+   @Test
+   public void testPasswordVerification() throws Exception {
+      HashProcessor processor = PasswordMaskingUtil.getHashProcessor(storedPassword);
+      boolean result = processor.compare(password.toCharArray(), storedPassword);
+      assertEquals(match, result);
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-distribution/src/main/assembly/dep.xml
----------------------------------------------------------------------
diff --git a/artemis-distribution/src/main/assembly/dep.xml b/artemis-distribution/src/main/assembly/dep.xml
index d21f1e0..aa3635e 100644
--- a/artemis-distribution/src/main/assembly/dep.xml
+++ b/artemis-distribution/src/main/assembly/dep.xml
@@ -89,6 +89,8 @@
             <include>commons-beanutils:commons-beanutils</include>
             <include>commons-logging:commons-logging</include>
             <include>commons-collections:commons-collections</include>
+            <include>org.apache.commons:commons-configuration2</include>
+            <include>org.apache.commons:commons-lang3</include>
             <include>org.fusesource.hawtbuf:hawtbuf</include>
             <include>org.jgroups:jgroups</include>
             <include>org.apache.geronimo.specs:geronimo-json_1.0_spec</include>

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
index d120a98..957bb8a 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
@@ -32,14 +32,16 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
+import org.apache.activemq.artemis.utils.HashProcessor;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
 import org.jboss.logging.Logger;
 
 public class PropertiesLoginModule extends PropertiesLoader implements LoginModule {
 
    private static final Logger logger = Logger.getLogger(PropertiesLoginModule.class);
 
-   private static final String USER_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.user";
-   private static final String ROLE_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.role";
+   public static final String USER_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.user";
+   public static final String ROLE_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.role";
 
    private Subject subject;
    private CallbackHandler callbackHandler;
@@ -49,6 +51,7 @@ public class PropertiesLoginModule extends PropertiesLoader implements LoginModu
    private String user;
    private final Set<Principal> principals = new HashSet<>();
    private boolean loginSucceeded;
+   private HashProcessor hashProcessor;
 
    @Override
    public void initialize(Subject subject,
@@ -90,10 +93,21 @@ public class PropertiesLoginModule extends PropertiesLoader implements LoginModu
       if (password == null) {
          throw new FailedLoginException("User does exist");
       }
-      if (!password.equals(new String(tmpPassword))) {
-         throw new FailedLoginException("Password does not match");
+
+      //password is hashed
+      try {
+         hashProcessor = PasswordMaskingUtil.getHashProcessor(password);
+
+         if (!hashProcessor.compare(tmpPassword, password)) {
+            throw new FailedLoginException("Password does not match");
+         }
+         loginSucceeded = true;
+      } catch (Exception e) {
+         if (debug) {
+            logger.debug("Exception getting a hash processor", e);
+         }
+         throw new FailedLoginException("Failed to get hash processor");
       }
-      loginSucceeded = true;
 
       if (debug) {
          logger.debug("login " + user);

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/docs/user-manual/en/configuration-index.md
----------------------------------------------------------------------
diff --git a/docs/user-manual/en/configuration-index.md b/docs/user-manual/en/configuration-index.md
index 65ef931..240d6db 100644
--- a/docs/user-manual/en/configuration-index.md
+++ b/docs/user-manual/en/configuration-index.md
@@ -396,29 +396,14 @@ will have to be in masked form.
 ### Masking passwords in artemis-users.properties
 
 Apache ActiveMQ Artemis's built-in security manager uses plain properties files
-where the user passwords are specified in plaintext forms by default. To
-mask those parameters the following two properties need to be set
-in the 'bootstrap.xml' file.
+where the user passwords are specified in hash forms by default. 
 
-`mask-password` -- If set to "true" all the passwords are masked.
-Default is false.
-
-`password-codec` -- Class name and its parameters for the Decoder used
-to decode the masked password. Ignored if `mask-password` is false. The
-format of this property is a full qualified class name optionally
-followed by key/value pairs. It is the same format as that for JMS
-Bridges. Example:
+Please use Artemis CLI command to add a password. For example
 
-```xml
-<mask-password>true</mask-password>
-<password-codec>org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;key=hello world</password-codec>
+```sh
+    ./artemis user add --username guest --password guest --role admin
 ```
 
-When so configured, the Apache ActiveMQ Artemis security manager will initialize a
-DefaultSensitiveStringCodec with the parameters "key"-\>"hello world",
-then use it to decode all the masked passwords in this configuration
-file.
-
 ### Choosing a decoder for password masking
 
 As described in the previous sections, all password masking requires a


[5/5] activemq-artemis git commit: This closes #835

Posted by cl...@apache.org.
This closes #835


Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/9f7fc883
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/9f7fc883
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/9f7fc883

Branch: refs/heads/master
Commit: 9f7fc883634eada05bfeb3b3b0e28b27025273d3
Parents: 8b57516 aa0965c
Author: Clebert Suconic <cl...@apache.org>
Authored: Wed Nov 2 15:53:00 2016 -0400
Committer: Clebert Suconic <cl...@apache.org>
Committed: Wed Nov 2 15:53:00 2016 -0400

----------------------------------------------------------------------
 artemis-cli/pom.xml                             |  12 +
 .../apache/activemq/artemis/cli/Artemis.java    |  10 +-
 .../activemq/artemis/cli/commands/Create.java   |   5 +-
 .../activemq/artemis/cli/commands/Mask.java     | 101 +++++
 .../commands/destination/CreateDestination.java |   6 +-
 .../commands/destination/DeleteDestination.java |   6 +-
 .../commands/destination/DestinationAction.java |  29 +-
 .../artemis/cli/commands/messages/Browse.java   |   2 +-
 .../commands/messages/ConnectionAbstract.java   |  68 ++++
 .../artemis/cli/commands/messages/Consumer.java |   2 +-
 .../cli/commands/messages/DestAbstract.java     |  12 +-
 .../artemis/cli/commands/messages/Producer.java |   2 +-
 .../artemis/cli/commands/user/AddUser.java      |  62 +++
 .../artemis/cli/commands/user/HelpUser.java     |  55 +++
 .../artemis/cli/commands/user/ListUser.java     |  53 +++
 .../cli/commands/user/PasswordAction.java       |  37 ++
 .../artemis/cli/commands/user/RemoveUser.java   |  45 +++
 .../artemis/cli/commands/user/ResetUser.java    |  66 ++++
 .../artemis/cli/commands/user/UserAction.java   |  85 ++++
 .../artemis/cli/commands/util/HashUtil.java     |  41 ++
 .../artemis/util/FileBasedSecStoreConfig.java   | 222 +++++++++++
 .../cli/commands/etc/artemis-roles.properties   |   3 +-
 .../cli/commands/etc/artemis-users.properties   |   3 +-
 .../apache/activemq/cli/test/ArtemisTest.java   | 391 +++++++++++++++++--
 .../activemq/cli/test/TestActionContext.java    |  50 +++
 .../apache/activemq/artemis/utils/ByteUtil.java |   8 +
 .../utils/DefaultSensitiveStringCodec.java      | 212 ++++++++--
 .../activemq/artemis/utils/HashProcessor.java   |  41 ++
 .../activemq/artemis/utils/NoHashProcessor.java |  35 ++
 .../artemis/utils/PasswordMaskingUtil.java      |  70 +++-
 .../artemis/utils/SecureHashProcessor.java      |  44 +++
 .../artemis/utils/SensitiveDataCodec.java       |   4 +-
 .../utils/DefaultSensitiveStringCodecTest.java  |  77 ++++
 .../artemis/utils/HashProcessorTest.java        |  66 ++++
 artemis-distribution/src/main/assembly/dep.xml  |   2 +
 .../security/jaas/PropertiesLoginModule.java    |  24 +-
 docs/user-manual/en/configuration-index.md      |  23 +-
 37 files changed, 1823 insertions(+), 151 deletions(-)
----------------------------------------------------------------------



[4/5] activemq-artemis git commit: NO-JIRA small improvement on CLI, retry with user input

Posted by cl...@apache.org.
NO-JIRA small improvement on CLI, retry with user input


Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/aa0965c0
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/aa0965c0
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/aa0965c0

Branch: refs/heads/master
Commit: aa0965c0ca7bab4434645b7ed3672316f943e900
Parents: 119476d
Author: Clebert Suconic <cl...@apache.org>
Authored: Wed Nov 2 15:33:06 2016 -0400
Committer: Clebert Suconic <cl...@apache.org>
Committed: Wed Nov 2 15:52:25 2016 -0400

----------------------------------------------------------------------
 .../commands/destination/CreateDestination.java |  6 +-
 .../commands/destination/DeleteDestination.java |  6 +-
 .../commands/destination/DestinationAction.java | 29 ++-------
 .../artemis/cli/commands/messages/Browse.java   |  2 +-
 .../commands/messages/ConnectionAbstract.java   | 68 ++++++++++++++++++++
 .../artemis/cli/commands/messages/Consumer.java |  2 +-
 .../cli/commands/messages/DestAbstract.java     | 12 +---
 .../artemis/cli/commands/messages/Producer.java |  2 +-
 8 files changed, 85 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/CreateDestination.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/CreateDestination.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/CreateDestination.java
index a0ae4bf..4cbaaa6 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/CreateDestination.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/CreateDestination.java
@@ -58,7 +58,7 @@ public class CreateDestination extends DestinationAction {
    }
 
    private void createJmsTopic(final ActionContext context) throws Exception {
-      performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
+      performJmsManagement(new ManagementCallback<Message>() {
          @Override
          public void setUpInvocation(Message message) throws Exception {
             JMSManagementHelper.putOperationInvocation(message, "jms.server", "createTopic", getName(), bindings);
@@ -90,7 +90,7 @@ public class CreateDestination extends DestinationAction {
    }
 
    private void createCoreQueue(final ActionContext context) throws Exception {
-      performCoreManagement(brokerURL, user, password, new ManagementCallback<ClientMessage>() {
+      performCoreManagement(new ManagementCallback<ClientMessage>() {
          @Override
          public void setUpInvocation(ClientMessage message) throws Exception {
             String address = getAddress();
@@ -112,7 +112,7 @@ public class CreateDestination extends DestinationAction {
 
    private void createJmsQueue(final ActionContext context) throws Exception {
 
-      performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
+      performJmsManagement(new ManagementCallback<Message>() {
 
          @Override
          public void setUpInvocation(Message message) throws Exception {

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DeleteDestination.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DeleteDestination.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DeleteDestination.java
index eeb0506..93dbf5e 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DeleteDestination.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DeleteDestination.java
@@ -49,7 +49,7 @@ public class DeleteDestination extends DestinationAction {
    }
 
    private void deleteJmsTopic(final ActionContext context) throws Exception {
-      performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
+      performJmsManagement(new ManagementCallback<Message>() {
          @Override
          public void setUpInvocation(Message message) throws Exception {
             JMSManagementHelper.putOperationInvocation(message, "jms.server", "destroyTopic", getName(), removeConsumers);
@@ -74,7 +74,7 @@ public class DeleteDestination extends DestinationAction {
    }
 
    private void deleteJmsQueue(final ActionContext context) throws Exception {
-      performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
+      performJmsManagement(new ManagementCallback<Message>() {
          @Override
          public void setUpInvocation(Message message) throws Exception {
             JMSManagementHelper.putOperationInvocation(message, "jms.server", "destroyQueue", getName(), removeConsumers);
@@ -99,7 +99,7 @@ public class DeleteDestination extends DestinationAction {
    }
 
    private void deleteCoreQueue(final ActionContext context) throws Exception {
-      performCoreManagement(brokerURL, user, password, new ManagementCallback<ClientMessage>() {
+      performCoreManagement(new ManagementCallback<ClientMessage>() {
          @Override
          public void setUpInvocation(ClientMessage message) throws Exception {
             ManagementHelper.putOperationInvocation(message, "core.server", "destroyQueue", getName());

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DestinationAction.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DestinationAction.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DestinationAction.java
index c128fc5..e161dd3 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DestinationAction.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/destination/DestinationAction.java
@@ -31,13 +31,12 @@ import org.apache.activemq.artemis.api.core.client.ServerLocator;
 import org.apache.activemq.artemis.api.core.management.ManagementHelper;
 import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient;
 import org.apache.activemq.artemis.api.jms.management.JMSManagementHelper;
-import org.apache.activemq.artemis.cli.commands.InputAbstract;
-import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
+import org.apache.activemq.artemis.cli.commands.messages.ConnectionAbstract;
 import org.apache.activemq.artemis.jms.client.ActiveMQConnection;
 import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
 import org.apache.activemq.artemis.jms.client.ActiveMQSession;
 
-public abstract class DestinationAction extends InputAbstract {
+public abstract class DestinationAction extends ConnectionAbstract {
 
    public static final String JMS_QUEUE = "jms-queue";
    public static final String JMS_TOPIC = "topic";
@@ -46,24 +45,12 @@ public abstract class DestinationAction extends InputAbstract {
    @Option(name = "--type", description = "type of destination to be created (one of jms-queue, topic and core-queue, default jms-queue")
    String destType = JMS_QUEUE;
 
-   @Option(name = "--url", description = "URL towards the broker. (default: tcp://localhost:61616)")
-   String brokerURL = "tcp://localhost:61616";
-
-   @Option(name = "--user", description = "User used to connect")
-   String user;
-
-   @Option(name = "--password", description = "Password used to connect")
-   String password;
-
    @Option(name = "--name", description = "destination name")
    String name;
 
-   public static void performJmsManagement(String brokerURL,
-                                           String user,
-                                           String password,
-                                           ManagementCallback<Message> cb) throws Exception {
+   public void performJmsManagement(ManagementCallback<Message> cb) throws Exception {
 
-      try (ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
+      try (ActiveMQConnectionFactory factory = createConnectionFactory();
            ActiveMQConnection connection = (ActiveMQConnection) factory.createConnection();
            ActiveMQSession session = (ActiveMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
 
@@ -88,12 +75,10 @@ public abstract class DestinationAction extends InputAbstract {
       }
    }
 
-   public static void performCoreManagement(String brokerURL,
-                                            String user,
-                                            String password,
-                                            ManagementCallback<ClientMessage> cb) throws Exception {
+   public void performCoreManagement(ManagementCallback<ClientMessage> cb) throws Exception {
 
-      try (ServerLocator locator = ServerLocatorImpl.newLocator(brokerURL);
+      try (ActiveMQConnectionFactory factory = createConnectionFactory();
+         ServerLocator locator = factory.getServerLocator();
            ClientSessionFactory sessionFactory = locator.createSessionFactory();
            ClientSession session = sessionFactory.createSession(user, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)) {
          session.start();

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Browse.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Browse.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Browse.java
index 3149708..936fba9 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Browse.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Browse.java
@@ -40,7 +40,7 @@ public class Browse extends DestAbstract {
 
       System.out.println("Consumer:: filter = " + filter);
 
-      ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
+      ActiveMQConnectionFactory factory = createConnectionFactory();
 
       Destination dest = ActiveMQDestination.createDestination(this.destination, ActiveMQDestination.QUEUE_TYPE);
       try (Connection connection = factory.createConnection()) {

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/ConnectionAbstract.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/ConnectionAbstract.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/ConnectionAbstract.java
new file mode 100644
index 0000000..03bc075
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/ConnectionAbstract.java
@@ -0,0 +1,68 @@
+/**
+ * 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.cli.commands.messages;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.JMSSecurityException;
+
+import io.airlift.airline.Option;
+import org.apache.activemq.artemis.cli.commands.InputAbstract;
+import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
+
+public class ConnectionAbstract extends InputAbstract {
+   @Option(name = "--url", description = "URL towards the broker. (default: tcp://localhost:61616)")
+   protected String brokerURL = "tcp://localhost:61616";
+
+   @Option(name = "--user", description = "User used to connect")
+   protected String user;
+
+   @Option(name = "--password", description = "Password used to connect")
+   protected String password;
+
+
+   protected ActiveMQConnectionFactory createConnectionFactory() throws Exception {
+      ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerURL, user, password);
+      try {
+         Connection connection = cf.createConnection();
+         connection.close();
+         return cf;
+      } catch (JMSSecurityException e) {
+         // if a security exception will get the user and password through an input
+         context.err.println("Connection failed::" + e.getMessage());
+         userPassword();
+         return new ActiveMQConnectionFactory(brokerURL, user, password);
+      } catch (JMSException e) {
+         // if a connection exception will ask for the URL, user and password
+         context.err.println("Connection failed::" + e.getMessage());
+         brokerURL = input("--url",  "Type in the broker URL for a retry (e.g. tcp://localhost:61616)", brokerURL);
+         userPassword();
+         return new ActiveMQConnectionFactory(brokerURL, user, password);
+      }
+   }
+
+   private void userPassword() {
+      if (user == null) {
+         user = input("--user", "Type the username for a retry", null);
+      }
+      if (password == null) {
+         password = inputPassword("--password", "Type the password for a retry", null);
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Consumer.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Consumer.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Consumer.java
index 71bf40c..ef5aefd 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Consumer.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Consumer.java
@@ -49,7 +49,7 @@ public class Consumer extends DestAbstract {
 
       System.out.println("Consumer:: filter = " + filter);
 
-      ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
+      ActiveMQConnectionFactory factory = createConnectionFactory();
 
       Destination dest = ActiveMQDestination.createDestination(this.destination, ActiveMQDestination.QUEUE_TYPE);
       try (Connection connection = factory.createConnection()) {

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/DestAbstract.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/DestAbstract.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/DestAbstract.java
index 1e3ac11..95b3c99 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/DestAbstract.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/DestAbstract.java
@@ -18,12 +18,8 @@
 package org.apache.activemq.artemis.cli.commands.messages;
 
 import io.airlift.airline.Option;
-import org.apache.activemq.artemis.cli.commands.ActionAbstract;
 
-public class DestAbstract extends ActionAbstract {
-
-   @Option(name = "--url", description = "URL towards the broker. (default: tcp://localhost:61616)")
-   String brokerURL = "tcp://localhost:61616";
+public class DestAbstract extends ConnectionAbstract {
 
    @Option(name = "--destination", description = "Destination to be used. it could be prefixed with queue:// or topic:: (Default: queue://TEST")
    String destination = "queue://TEST";
@@ -31,12 +27,6 @@ public class DestAbstract extends ActionAbstract {
    @Option(name = "--message-count", description = "Number of messages to act on (Default: 1000)")
    int messageCount = 1000;
 
-   @Option(name = "--user", description = "User used to connect")
-   String user;
-
-   @Option(name = "--password", description = "Password used to connect")
-   String password;
-
    @Option(name = "--sleep", description = "Time wait between each message")
    int sleep = 0;
 

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/aa0965c0/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Producer.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Producer.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Producer.java
index 1f6a4b1..8f76d31 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Producer.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/messages/Producer.java
@@ -50,7 +50,7 @@ public class Producer extends DestAbstract {
    public Object execute(ActionContext context) throws Exception {
       super.execute(context);
 
-      ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
+      ActiveMQConnectionFactory factory = createConnectionFactory();
 
       Destination dest = ActiveMQDestination.createDestination(this.destination, ActiveMQDestination.QUEUE_TYPE);
       try (Connection connection = factory.createConnection()) {


[2/5] activemq-artemis git commit: ARTEMIS-786 Store user's password in hash form by default - user passwords for PropertiesLoginModule stored using PBKDF2 algothrim by default - implements cli user command to help create and manage user/roles

Posted by cl...@apache.org.
ARTEMIS-786 Store user's password in hash form by default
  - user passwords for PropertiesLoginModule stored using PBKDF2 algothrim
    by default
  - implements cli user command to help create and manage user/roles
  - adds a mask cli command to mask passwords


Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/cd7b8389
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/cd7b8389
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/cd7b8389

Branch: refs/heads/master
Commit: cd7b838952dcc654527cb2d2cdd00b3d0f269bd1
Parents: 8b57516
Author: Howard Gao <ho...@gmail.com>
Authored: Thu Oct 27 11:06:24 2016 +0800
Committer: Clebert Suconic <cl...@apache.org>
Committed: Wed Nov 2 14:59:00 2016 -0400

----------------------------------------------------------------------
 artemis-cli/pom.xml                             |  12 +
 .../apache/activemq/artemis/cli/Artemis.java    |  10 +-
 .../activemq/artemis/cli/commands/Create.java   |   5 +-
 .../activemq/artemis/cli/commands/Mask.java     | 101 +++++
 .../artemis/cli/commands/user/AddUser.java      |  62 +++
 .../artemis/cli/commands/user/HelpUser.java     |  55 +++
 .../artemis/cli/commands/user/ListUser.java     |  38 ++
 .../artemis/cli/commands/user/RemoveUser.java   |  36 ++
 .../artemis/cli/commands/user/ResetUser.java    |  65 +++
 .../artemis/cli/commands/user/UserAction.java   | 107 +++++
 .../artemis/cli/commands/util/HashUtil.java     |  41 ++
 .../artemis/util/FileBasedSecStoreConfig.java   | 222 +++++++++++
 .../cli/commands/etc/artemis-roles.properties   |   3 +-
 .../cli/commands/etc/artemis-users.properties   |   3 +-
 .../apache/activemq/cli/test/ArtemisTest.java   | 391 +++++++++++++++++--
 .../activemq/cli/test/TestActionContext.java    |  50 +++
 .../apache/activemq/artemis/utils/ByteUtil.java |   8 +
 .../utils/DefaultSensitiveStringCodec.java      | 212 ++++++++--
 .../activemq/artemis/utils/HashProcessor.java   |  41 ++
 .../activemq/artemis/utils/NoHashProcessor.java |  35 ++
 .../artemis/utils/PasswordMaskingUtil.java      |  70 +++-
 .../artemis/utils/SecureHashProcessor.java      |  44 +++
 .../artemis/utils/SensitiveDataCodec.java       |   4 +-
 .../utils/DefaultSensitiveStringCodecTest.java  |  77 ++++
 .../artemis/utils/HashProcessorTest.java        |  66 ++++
 artemis-distribution/src/main/assembly/dep.xml  |   2 +
 .../security/jaas/PropertiesLoginModule.java    |  24 +-
 docs/user-manual/en/configuration-index.md      |  23 +-
 28 files changed, 1698 insertions(+), 109 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/pom.xml
----------------------------------------------------------------------
diff --git a/artemis-cli/pom.xml b/artemis-cli/pom.xml
index e8c8358..0589955 100644
--- a/artemis-cli/pom.xml
+++ b/artemis-cli/pom.xml
@@ -30,6 +30,8 @@
    <properties>
       <activemq.basedir>${project.basedir}/..</activemq.basedir>
       <winsw.version>1.18</winsw.version>
+      <commons.config.version>2.1</commons.config.version>
+      <commons.lang.version>3.0</commons.lang.version>
    </properties>
 
    <dependencies>
@@ -68,6 +70,16 @@
          <artifactId>airline</artifactId>
       </dependency>
       <dependency>
+         <groupId>org.apache.commons</groupId>
+         <artifactId>commons-configuration2</artifactId>
+         <version>${commons.config.version}</version>
+      </dependency>
+      <dependency>
+         <groupId>org.apache.commons</groupId>
+         <artifactId>commons-lang3</artifactId>
+         <version>${commons.lang.version}</version>
+      </dependency>
+      <dependency>
         <groupId>com.sun.winsw</groupId>
         <artifactId>winsw</artifactId>
         <version>${winsw.version}</version>

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
index 16dcd03..17c4457 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
@@ -27,6 +27,7 @@ import org.apache.activemq.artemis.cli.commands.ActionContext;
 import org.apache.activemq.artemis.cli.commands.Create;
 import org.apache.activemq.artemis.cli.commands.HelpAction;
 import org.apache.activemq.artemis.cli.commands.Kill;
+import org.apache.activemq.artemis.cli.commands.Mask;
 import org.apache.activemq.artemis.cli.commands.Run;
 import org.apache.activemq.artemis.cli.commands.Stop;
 import org.apache.activemq.artemis.cli.commands.destination.CreateDestination;
@@ -42,6 +43,11 @@ import org.apache.activemq.artemis.cli.commands.tools.HelpData;
 import org.apache.activemq.artemis.cli.commands.tools.PrintData;
 import org.apache.activemq.artemis.cli.commands.tools.XmlDataExporter;
 import org.apache.activemq.artemis.cli.commands.tools.XmlDataImporter;
+import org.apache.activemq.artemis.cli.commands.user.AddUser;
+import org.apache.activemq.artemis.cli.commands.user.HelpUser;
+import org.apache.activemq.artemis.cli.commands.user.ListUser;
+import org.apache.activemq.artemis.cli.commands.user.RemoveUser;
+import org.apache.activemq.artemis.cli.commands.user.ResetUser;
 
 /**
  * Artemis is the main CLI entry point for managing/running a broker.
@@ -120,7 +126,7 @@ public class Artemis {
 
    private static Cli.CliBuilder<Action> builder(File artemisInstance) {
       String instance = artemisInstance != null ? artemisInstance.getAbsolutePath() : System.getProperty("artemis.instance");
-      Cli.CliBuilder<Action> builder = Cli.<Action>builder("artemis").withDescription("ActiveMQ Artemis Command Line").withCommand(HelpAction.class).withCommand(Producer.class).withCommand(Consumer.class).withCommand(Browse.class).withDefaultCommand(HelpAction.class);
+      Cli.CliBuilder<Action> builder = Cli.<Action>builder("artemis").withDescription("ActiveMQ Artemis Command Line").withCommand(HelpAction.class).withCommand(Producer.class).withCommand(Consumer.class).withCommand(Browse.class).withCommand(Mask.class).withDefaultCommand(HelpAction.class);
 
       builder.withGroup("destination").withDescription("Destination tools group (create|delete) (example ./artemis destination create)").
          withDefaultCommand(HelpDestination.class).withCommands(CreateDestination.class, DeleteDestination.class);
@@ -128,6 +134,8 @@ public class Artemis {
       if (instance != null) {
          builder.withGroup("data").withDescription("data tools group (print|exp|imp|exp|encode|decode|compact) (example ./artemis data print)").
             withDefaultCommand(HelpData.class).withCommands(PrintData.class, XmlDataExporter.class, XmlDataImporter.class, DecodeJournal.class, EncodeJournal.class, CompactJournal.class);
+         builder.withGroup("user").withDescription("default file-based user management (add|rm|list|reset) (example ./artemis user list)").
+                 withDefaultCommand(HelpUser.class).withCommands(ListUser.class, AddUser.class, RemoveUser.class, ResetUser.class);
          builder = builder.withCommands(Run.class, Stop.class, Kill.class);
       } else {
          builder.withGroup("data").withDescription("data tools group (print) (example ./artemis data print)").

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
index be788cd..5bd55e9 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
@@ -38,6 +38,7 @@ import io.airlift.airline.Arguments;
 import io.airlift.airline.Command;
 import io.airlift.airline.Option;
 import org.apache.activemq.artemis.cli.CLIException;
+import org.apache.activemq.artemis.cli.commands.util.HashUtil;
 import org.apache.activemq.artemis.cli.commands.util.SyncCalculation;
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
 import org.apache.activemq.artemis.jlibaio.LibaioContext;
@@ -415,9 +416,11 @@ public class Create extends InputAbstract {
    public String getPassword() {
 
       if (password == null) {
-         this.password = inputPassword("--password", "Please provide the default password:", "admin");
+         password = inputPassword("--password", "Please provide the default password:", "admin");
       }
 
+      password = HashUtil.tryHash(context, password);
+
       return password;
    }
 

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java
new file mode 100644
index 0000000..7b845a2
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java
@@ -0,0 +1,101 @@
+/*
+ * 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.cli.commands;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+@Command(name = "mask", description = "mask a password and print it out")
+public class Mask implements Action {
+
+   @Arguments(description = "The password to be masked", required = true)
+   String password;
+
+   @Option(name = "--hash", description = "whether to use hash (one-way), default false")
+   boolean hash = false;
+
+   @Option(name = "--key", description = "the key (Blowfish) to mask a password")
+   String key;
+
+   private DefaultSensitiveStringCodec codec;
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      Map<String, String> params = new HashMap<>();
+
+      if (hash) {
+         params.put(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.ONE_WAY);
+      }
+
+      if (key != null) {
+         if (hash) {
+            context.out.println("Option --key ignored in case of hashing");
+         } else {
+            params.put(DefaultSensitiveStringCodec.BLOWFISH_KEY, key);
+         }
+      }
+
+      codec = PasswordMaskingUtil.getDefaultCodec();
+      codec.init(params);
+
+      String masked = codec.encode(password);
+      context.out.println("result: " + masked);
+      return masked;
+   }
+
+   @Override
+   public boolean isVerbose() {
+      return false;
+   }
+
+   @Override
+   public void setHomeValues(File brokerHome, File brokerInstance) {
+   }
+
+   @Override
+   public String getBrokerInstance() {
+      return null;
+   }
+
+   @Override
+   public String getBrokerHome() {
+      return null;
+   }
+
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   public void setHash(boolean hash) {
+      this.hash = hash;
+   }
+
+   public void setKey(String key) {
+      this.key = key;
+   }
+
+   public DefaultSensitiveStringCodec getCodec() {
+      return codec;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..cbb8f60
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java
@@ -0,0 +1,62 @@
+/*
+ * 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.cli.commands.user;
+
+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.commons.lang3.StringUtils;
+
+/**
+ * Adding a new user, example:
+ * ./artemis user add --username guest --role admin --password ***
+ */
+@Command(name = "add", description = "Add a new user")
+public class AddUser extends UserAction {
+
+   @Option(name = "--password", description = "the password (Default: input)")
+   String password;
+
+   @Option(name = "--role", description = "user's role(s), comma separated", required = true)
+   String role;
+
+   @Option(name = "--plaintext", description = "using plaintext (Default false)")
+   boolean plaintext = false;
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+
+      if (password == null) {
+         password = inputPassword("--password", "Please provide the password:", null);
+      }
+
+      String hash = plaintext ? password : HashUtil.tryHash(context, password);
+      add(hash, StringUtils.split(role, ","));
+
+      return null;
+   }
+
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   public void setRole(String role) {
+      this.role = role;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java
new file mode 100644
index 0000000..7a898be
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java
@@ -0,0 +1,55 @@
+/*
+ * 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.cli.commands.user;
+
+import io.airlift.airline.Help;
+import org.apache.activemq.artemis.cli.commands.Action;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HelpUser extends Help implements Action {
+
+   @Override
+   public boolean isVerbose() {
+      return false;
+   }
+
+   @Override
+   public void setHomeValues(File brokerHome, File brokerInstance) {
+   }
+
+   @Override
+   public String getBrokerInstance() {
+      return null;
+   }
+
+   @Override
+   public String getBrokerHome() {
+      return null;
+   }
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      List<String> commands = new ArrayList<>(1);
+      commands.add("user");
+      help(global, commands);
+      return null;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..cb3ff39
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java
@@ -0,0 +1,38 @@
+/*
+ * 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.cli.commands.user;
+
+import io.airlift.airline.Command;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+/**
+ * list existing users, example:
+ * ./artemis user list --username guest
+ */
+@Command(name = "list", description = "List existing user(s)")
+public class ListUser extends UserAction {
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+
+      list();
+
+      return null;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..172a76d
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java
@@ -0,0 +1,36 @@
+/*
+ * 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.cli.commands.user;
+
+import io.airlift.airline.Command;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+/**
+ * Remove a user, example:
+ * ./artemis user rm --username guest
+ */
+@Command(name = "rm", description = "Remove an existing user")
+public class RemoveUser extends UserAction {
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+      remove();
+      return null;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..27da6c7
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java
@@ -0,0 +1,65 @@
+/*
+ * 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.cli.commands.user;
+
+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.commons.lang3.StringUtils;
+
+/**
+ * Reset a user's password or roles, example:
+ * ./artemis user reset --username guest --role admin --password ***
+ */
+@Command(name = "reset", description = "Reset user's password or roles")
+public class ResetUser extends UserAction {
+
+   @Option(name = "--password", description = "the password (Default: input)")
+   String password;
+
+   @Option(name = "--role", description = "user's role(s), comma separated")
+   String role;
+
+   @Option(name = "--plaintext", description = "using plaintext (Default false)")
+   boolean plaintext = false;
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+
+      if (password != null) {
+         password = plaintext ? password : HashUtil.tryHash(context, password);
+      }
+
+      String[] roles = null;
+      if (role != null) {
+         roles = StringUtils.split(role, ",");
+      }
+
+      reset(password, roles);
+      return null;
+   }
+
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   public void setRole(String role) {
+      this.role = role;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..918338e
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java
@@ -0,0 +1,107 @@
+/*
+ * 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.cli.commands.user;
+
+import io.airlift.airline.Option;
+import org.apache.activemq.artemis.cli.commands.InputAbstract;
+import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule;
+import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import java.io.File;
+import java.util.List;
+
+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 {
+
+   @Option(name = "--user", description = "The user name")
+   String username = null;
+
+   /**
+    * Adding a new user
+    * @param hash the password
+    * @param role the role
+    * @throws IllegalArgumentException if user exists
+    */
+   protected void add(String hash, String... role) throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.addNewUser(username, hash, role);
+      config.save();
+      context.out.println("User added successfully.");
+   }
+
+   /**
+    * list a single user or all users
+    * if username is not specified
+    */
+   protected void list() throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      List<String> result = config.listUser(username);
+      for (String str : result) {
+         context.out.println(str);
+      }
+   }
+
+   protected void remove() throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.removeUser(username);
+      config.save();
+      context.out.println("User removed.");
+   }
+
+   protected void reset(String password, String[] roles) throws Exception {
+      if (password == null && roles == null) {
+         context.err.println("Nothing to update.");
+         return;
+      }
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.updateUser(username, password, roles);
+      config.save();
+      context.out.println("User updated");
+   }
+
+   private FileBasedSecStoreConfig getConfiguration() throws Exception {
+
+      Configuration securityConfig = Configuration.getConfiguration();
+      AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry("activemq");
+
+      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(getBrokerInstance(), "etc");
+            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;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java
new file mode 100644
index 0000000..67b9e44
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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.cli.commands.util;
+
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+import org.apache.activemq.artemis.utils.HashProcessor;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
+
+public class HashUtil {
+
+   private static final HashProcessor HASH_PROCESSOR = PasswordMaskingUtil.getHashProcessor();
+
+   //calculate the hash for plaintext.
+   //any exception will cause plaintext returned unchanged.
+   public static String tryHash(ActionContext context, String plaintext) {
+
+      try {
+         String hash = HASH_PROCESSOR.hash(plaintext);
+         return hash;
+      } catch (Exception e) {
+         context.err.println("Warning: Failed to calculate hash value for password using " + HASH_PROCESSOR);
+         context.err.println("Reason: " + e.getMessage());
+         e.printStackTrace();
+      }
+      return plaintext;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java
new file mode 100644
index 0000000..c3f30b3
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java
@@ -0,0 +1,222 @@
+/*
+ * 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.util;
+
+import org.apache.activemq.artemis.api.core.Pair;
+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;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class FileBasedSecStoreConfig {
+
+   private static final String LICENSE_HEADER =
+           "## ---------------------------------------------------------------------------\n" +
+           "## Licensed to the Apache Software Foundation (ASF) under one or more\n" +
+           "## contributor license agreements.  See the NOTICE file distributed with\n" +
+           "## this work for additional information regarding copyright ownership.\n" +
+           "## The ASF licenses this file to You under the Apache License, Version 2.0\n" +
+           "## (the \"License\"); you may not use this file except in compliance with\n" +
+           "## the License.  You may obtain a copy of the License at\n" +
+           "##\n" +
+           "## http://www.apache.org/licenses/LICENSE-2.0\n" +
+           "##\n" +
+           "## Unless required by applicable law or agreed to in writing, software\n" +
+           "## distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
+           "## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
+           "## See the License for the specific language governing permissions and\n" +
+           "## limitations under the License.\n" +
+           "## ---------------------------------------------------------------------------\n";
+   private FileBasedConfigurationBuilder<PropertiesConfiguration> userBuilder;
+   private FileBasedConfigurationBuilder<PropertiesConfiguration> roleBuilder;
+   private PropertiesConfiguration userConfig;
+   private PropertiesConfiguration roleConfig;
+
+   public 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();
+
+      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);
+         }
+      }
+   }
+
+   public void addNewUser(String username, String hash, String... roles) throws Exception {
+      if (userConfig.getString(username) != null) {
+         throw new IllegalArgumentException("User already exist: " + username);
+      }
+      userConfig.addProperty(username, hash);
+      addRoles(username, roles);
+   }
+
+   public void save() throws Exception {
+      userBuilder.save();
+      roleBuilder.save();
+   }
+
+   public void removeUser(String username) throws Exception {
+      if (userConfig.getProperty(username) == null) {
+         throw new IllegalArgumentException("user " + username + " doesn't exist.");
+      }
+      userConfig.clearProperty(username);
+      removeRoles(username);
+   }
+
+   public List<String> listUser(String username) {
+      List<String> result = new ArrayList<>();
+      result.add("--- \"user\"(roles) ---\n");
+
+      int totalUsers = 0;
+      if (username != null) {
+         String roles = findRoles(username);
+         result.add("\"" + username + "\"(" + roles + ")");
+         totalUsers++;
+      } else {
+         Iterator<String> iter = userConfig.getKeys();
+         while (iter.hasNext()) {
+            String keyUser = iter.next();
+            String roles = findRoles(keyUser);
+            result.add("\"" + keyUser + "\"(" + roles + ")");
+            totalUsers++;
+         }
+      }
+      result.add("\n Total: " + totalUsers);
+      return result;
+   }
+
+   private String findRoles(String uname) {
+      Iterator<String> iter = roleConfig.getKeys();
+      StringBuilder builder = new StringBuilder();
+      boolean first = true;
+      while (iter.hasNext()) {
+         String role = iter.next();
+         List<String> names = roleConfig.getList(String.class, role);
+         for (String value : names) {
+            //each value may be a comma separated list
+            String[] items = value.split(",");
+            for (String item : items) {
+               if (item.equals(uname)) {
+                  if (!first) {
+                     builder.append(",");
+                  }
+                  builder.append(role);
+                  first = false;
+               }
+            }
+         }
+      }
+
+      return builder.toString();
+   }
+
+   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.");
+      }
+
+      if (password != null) {
+         userConfig.setProperty(username, password);
+      }
+
+      if (roles != null && roles.length > 0) {
+
+         removeRoles(username);
+         addRoles(username, roles);
+      }
+   }
+
+   private void addRoles(String username, String[] roles) {
+      for (String role : roles) {
+         List<String> users = roleConfig.getList(String.class, role);
+         if (users == null) {
+            users = new ArrayList<>();
+         }
+         users.add(username);
+         roleConfig.setProperty(role, StringUtil.joinStringList(users, ","));
+      }
+   }
+
+   private void removeRoles(String username) {
+
+      Iterator<String> iterKeys = roleConfig.getKeys();
+
+      List<Pair<String, List<String>>> updateMap = new ArrayList<>();
+      while (iterKeys.hasNext()) {
+         String theRole = iterKeys.next();
+
+         List<String> userList = roleConfig.getList(String.class, theRole);
+         List<String> newList = new ArrayList<>();
+
+         boolean roleChaned = false;
+         for (String value : userList) {
+            //each value may be comma separated.
+            List<String> update = new ArrayList<>();
+            String[] items = value.split(",");
+            boolean found = false;
+            for (String item : items) {
+               if (!item.equals(username)) {
+                  update.add(item);
+               } else {
+                  found = true;
+                  roleChaned = true;
+               }
+            }
+            if (found) {
+               if (update.size() > 0) {
+                  newList.add(StringUtil.joinStringList(update, ","));
+               }
+            }
+         }
+         if (roleChaned) {
+            updateMap.add(new Pair(theRole, newList));
+         }
+      }
+      //do update
+      Iterator<Pair<String, List<String>>> iterUpdate = updateMap.iterator();
+      while (iterUpdate.hasNext()) {
+         Pair<String, List<String>> entry = iterUpdate.next();
+         roleConfig.clearProperty(entry.getA());
+         if (entry.getB().size() > 0) {
+            roleConfig.addProperty(entry.getA(), entry.getB());
+         }
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
index c9443dd..74f4266 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
@@ -14,4 +14,5 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
-${role}=${user}
\ No newline at end of file
+
+${role} = ${user}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
index 81462f8..b437025 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
@@ -14,4 +14,5 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
-${user}=${password}
\ No newline at end of file
+
+${user} = ${password}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
----------------------------------------------------------------------
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 2359f1d..3d89aa8 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
@@ -26,6 +26,7 @@ import javax.xml.parsers.ParserConfigurationException;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.activemq.artemis.api.core.SimpleString;
@@ -35,16 +36,29 @@ import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
 import org.apache.activemq.artemis.api.core.client.ServerLocator;
 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.Run;
 import org.apache.activemq.artemis.cli.commands.tools.LockAbstract;
+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;
+import org.apache.activemq.artemis.cli.commands.user.ResetUser;
 import org.apache.activemq.artemis.cli.commands.util.SyncCalculation;
 import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
 import org.apache.activemq.artemis.jlibaio.LibaioContext;
 import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
 import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
 import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoader;
+import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
 import org.apache.activemq.artemis.utils.ThreadLeakCheckRule;
+import org.apache.activemq.artemis.utils.HashProcessor;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
+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;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -55,6 +69,11 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 /**
  * Test to validate that the CLI doesn't throw improper exceptions when invoked.
  */
@@ -134,7 +153,7 @@ public class ArtemisTest {
       System.out.println("TotalAvg = " + totalAvg);
       long nanoTime = SyncCalculation.toNanos(totalAvg, writes);
       System.out.println("nanoTime avg = " + nanoTime);
-      Assert.assertEquals(0, LibaioContext.getTotalMaxIO());
+      assertEquals(0, LibaioContext.getTotalMaxIO());
 
    }
 
@@ -146,47 +165,47 @@ public class ArtemisTest {
       File instance1 = new File(temporaryFolder.getRoot(), "instance1");
       Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-fsync");
       File bootstrapFile = new File(new File(instance1, "etc"), "bootstrap.xml");
-      Assert.assertTrue(bootstrapFile.exists());
+      assertTrue(bootstrapFile.exists());
       Document config = parseXml(bootstrapFile);
       Element webElem = (Element) config.getElementsByTagName("web").item(0);
 
       String bindAttr = webElem.getAttribute("bind");
       String bindStr = "http://localhost:" + Create.HTTP_PORT;
 
-      Assert.assertEquals(bindAttr, bindStr);
+      assertEquals(bindAttr, bindStr);
       //no any of those
-      Assert.assertFalse(webElem.hasAttribute("keyStorePath"));
-      Assert.assertFalse(webElem.hasAttribute("keyStorePassword"));
-      Assert.assertFalse(webElem.hasAttribute("clientAuth"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
+      assertFalse(webElem.hasAttribute("keyStorePath"));
+      assertFalse(webElem.hasAttribute("keyStorePassword"));
+      assertFalse(webElem.hasAttribute("clientAuth"));
+      assertFalse(webElem.hasAttribute("trustStorePath"));
+      assertFalse(webElem.hasAttribute("trustStorePassword"));
 
       //instance2: https
       File instance2 = new File(temporaryFolder.getRoot(), "instance2");
       Artemis.main("create", instance2.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore", "--ssl-key-password", "password1", "--no-fsync");
       bootstrapFile = new File(new File(instance2, "etc"), "bootstrap.xml");
-      Assert.assertTrue(bootstrapFile.exists());
+      assertTrue(bootstrapFile.exists());
       config = parseXml(bootstrapFile);
       webElem = (Element) config.getElementsByTagName("web").item(0);
 
       bindAttr = webElem.getAttribute("bind");
       bindStr = "https://localhost:" + Create.HTTP_PORT;
-      Assert.assertEquals(bindAttr, bindStr);
+      assertEquals(bindAttr, bindStr);
 
       String keyStr = webElem.getAttribute("keyStorePath");
-      Assert.assertEquals("etc/keystore", keyStr);
+      assertEquals("etc/keystore", keyStr);
       String keyPass = webElem.getAttribute("keyStorePassword");
-      Assert.assertEquals("password1", keyPass);
+      assertEquals("password1", keyPass);
 
-      Assert.assertFalse(webElem.hasAttribute("clientAuth"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
+      assertFalse(webElem.hasAttribute("clientAuth"));
+      assertFalse(webElem.hasAttribute("trustStorePath"));
+      assertFalse(webElem.hasAttribute("trustStorePassword"));
 
       //instance3: https with clientAuth
       File instance3 = new File(temporaryFolder.getRoot(), "instance3");
       Artemis.main("create", instance3.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore", "--ssl-key-password", "password1", "--use-client-auth", "--ssl-trust", "etc/truststore", "--ssl-trust-password", "password2", "--no-fsync");
       bootstrapFile = new File(new File(instance3, "etc"), "bootstrap.xml");
-      Assert.assertTrue(bootstrapFile.exists());
+      assertTrue(bootstrapFile.exists());
 
       byte[] contents = Files.readAllBytes(bootstrapFile.toPath());
       String cfgText = new String(contents);
@@ -197,19 +216,298 @@ public class ArtemisTest {
 
       bindAttr = webElem.getAttribute("bind");
       bindStr = "https://localhost:" + Create.HTTP_PORT;
-      Assert.assertEquals(bindAttr, bindStr);
+      assertEquals(bindAttr, bindStr);
 
       keyStr = webElem.getAttribute("keyStorePath");
-      Assert.assertEquals("etc/keystore", keyStr);
+      assertEquals("etc/keystore", keyStr);
       keyPass = webElem.getAttribute("keyStorePassword");
-      Assert.assertEquals("password1", keyPass);
+      assertEquals("password1", keyPass);
 
       String clientAuthAttr = webElem.getAttribute("clientAuth");
-      Assert.assertEquals("true", clientAuthAttr);
+      assertEquals("true", clientAuthAttr);
       String trustPathAttr = webElem.getAttribute("trustStorePath");
-      Assert.assertEquals("etc/truststore", trustPathAttr);
+      assertEquals("etc/truststore", trustPathAttr);
       String trustPass = webElem.getAttribute("trustStorePassword");
-      Assert.assertEquals("password2", trustPass);
+      assertEquals("password2", trustPass);
+   }
+
+   @Test
+   public void testUserCommand() 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");
+      System.setProperty("artemis.instance", instance1.getAbsolutePath());
+
+      File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
+      File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
+
+      ListUser listCmd = new ListUser();
+      TestActionContext context = new TestActionContext();
+      listCmd.execute(context);
+
+      String result = context.getStdout();
+      System.out.println("output1:\n" + result);
+
+      //default only one user admin with role amq
+      assertTrue(result.contains("\"admin\"(amq)"));
+      checkRole("admin", roleFile, "amq");
+
+      //add a simple user
+      AddUser addCmd = new AddUser();
+      addCmd.setUsername("guest");
+      addCmd.setPassword("guest123");
+      addCmd.setRole("admin");
+      addCmd.execute(new TestActionContext());
+
+      //verify use list cmd
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output2:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("\"guest\"(admin)"));
+
+      checkRole("guest", roleFile, "admin");
+      assertTrue(checkPassword("guest", "guest123", userFile));
+
+      //add a user with 2 roles
+      addCmd = new AddUser();
+      addCmd.setUsername("scott");
+      addCmd.setPassword("tiger");
+      addCmd.setRole("admin,operator");
+      addCmd.execute(ActionContext.system());
+
+      //verify
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output3:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("\"guest\"(admin)"));
+      assertTrue(result.contains("\"scott\"(admin,operator)"));
+
+      checkRole("scott", roleFile, "admin", "operator");
+      assertTrue(checkPassword("scott", "tiger", userFile));
+
+      //add an existing user
+      addCmd = new AddUser();
+      addCmd.setUsername("scott");
+      addCmd.setPassword("password");
+      addCmd.setRole("visitor");
+      try {
+         addCmd.execute(ActionContext.system());
+         fail("should throw an exception if adding a existing user");
+      } catch (IllegalArgumentException expected) {
+      }
+
+      //check existing users are intact
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output4:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("\"guest\"(admin)"));
+      assertTrue(result.contains("\"scott\"(admin,operator)"));
+
+      //remove a user
+      RemoveUser rmCmd = new RemoveUser();
+      rmCmd.setUsername("guest");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output5:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertFalse(result.contains("\"guest\"(admin)"));
+      assertTrue(result.contains("\"scott\"(admin,operator)") || result.contains("\"scott\"(operator,admin)"));
+      assertTrue(result.contains("Total: 2"));
+
+      //remove another
+      rmCmd = new RemoveUser();
+      rmCmd.setUsername("scott");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output6:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertFalse(result.contains("\"guest\"(admin)"));
+      assertFalse(result.contains("\"scott\"(admin,operator)") || result.contains("\"scott\"(operator,admin)"));
+      assertTrue(result.contains("Total: 1"));
+
+      //remove non-exist
+      rmCmd = new RemoveUser();
+      rmCmd.setUsername("alien");
+      try {
+         rmCmd.execute(ActionContext.system());
+         fail("should throw exception when removing a non-existing user");
+      } catch (IllegalArgumentException expected) {
+      }
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output7:\n" + result);
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("Total: 1"));
+
+      //now remove last
+      rmCmd = new RemoveUser();
+      rmCmd.setUsername("admin");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output8:\n" + result);
+
+      assertTrue(result.contains("Total: 0"));
+   }
+
+   @Test
+   public void testUserCommandReset() 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");
+      System.setProperty("artemis.instance", instance1.getAbsolutePath());
+
+      File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
+      File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
+
+      ListUser listCmd = new ListUser();
+      TestActionContext context = new TestActionContext();
+      listCmd.execute(context);
+
+      String result = context.getStdout();
+      System.out.println("output1:\n" + result);
+
+      //default only one user admin with role amq
+      assertTrue(result.contains("\"admin\"(amq)"));
+
+      //remove a user
+      RemoveUser rmCmd = new RemoveUser();
+      rmCmd.setUsername("admin");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output8:\n" + result);
+
+      assertTrue(result.contains("Total: 0"));
+
+      //add some users
+      AddUser addCmd = new AddUser();
+      addCmd.setUsername("guest");
+      addCmd.setPassword("guest123");
+      addCmd.setRole("admin");
+      addCmd.execute(new TestActionContext());
+
+      addCmd.setUsername("user1");
+      addCmd.setPassword("password1");
+      addCmd.setRole("admin,manager");
+      addCmd.execute(new TestActionContext());
+      assertTrue(checkPassword("user1", "password1", userFile));
+
+      addCmd.setUsername("user2");
+      addCmd.setPassword("password2");
+      addCmd.setRole("admin,manager,master");
+      addCmd.execute(new TestActionContext());
+
+      addCmd.setUsername("user3");
+      addCmd.setPassword("password3");
+      addCmd.setRole("system,master");
+      addCmd.execute(new TestActionContext());
+
+      //verify use list cmd
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output2:\n" + result);
+
+      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)"));
+
+      checkRole("user1", roleFile, "admin", "manager");
+
+      //reset password
+      context = new TestActionContext();
+      ResetUser resetCommand = new ResetUser();
+      resetCommand.setUsername("user1");
+      resetCommand.setPassword("newpassword1");
+      resetCommand.execute(context);
+
+      checkRole("user1", roleFile, "admin", "manager");
+      assertFalse(checkPassword("user1", "password1", userFile));
+      assertTrue(checkPassword("user1", "newpassword1", userFile));
+
+      //reset role
+      resetCommand.setUsername("user2");
+      resetCommand.setRole("manager,master,operator");
+      resetCommand.execute(new TestActionContext());
+
+      checkRole("user2", roleFile, "manager", "master", "operator");
+
+      //reset both
+      resetCommand.setUsername("user3");
+      resetCommand.setPassword("newpassword3");
+      resetCommand.setRole("admin,system");
+      resetCommand.execute(new ActionContext());
+
+      checkRole("user3", roleFile, "admin", "system");
+      assertTrue(checkPassword("user3", "newpassword3", userFile));
+   }
+
+   @Test
+   public void testMaskCommand() throws Exception {
+
+      String password1 = "password";
+      String encrypt1 = "3a34fd21b82bf2a822fa49a8d8fa115d";
+      String newKey = "artemisfun";
+      String encrypt2 = "-2b8e3b47950b9b481a6f3100968e42e9";
+
+
+      TestActionContext context = new TestActionContext();
+      Mask mask = new Mask();
+      mask.setPassword(password1);
+
+      String result = (String) mask.execute(context);
+      System.out.println(context.getStdout());
+      assertEquals(encrypt1, result);
+
+      context = new TestActionContext();
+      mask = new Mask();
+      mask.setPassword(password1);
+      mask.setHash(true);
+      result = (String) mask.execute(context);
+      System.out.println(context.getStdout());
+      DefaultSensitiveStringCodec codec = mask.getCodec();
+      codec.verify(password1.toCharArray(), result);
+
+      context = new TestActionContext();
+      mask = new Mask();
+      mask.setPassword(password1);
+      mask.setKey(newKey);
+      result = (String) mask.execute(context);
+      System.out.println(context.getStdout());
+      assertEquals(encrypt2, result);
    }
 
    @Test
@@ -251,11 +549,11 @@ public class ArtemisTest {
               ClientSession coreSession = factory.createSession("admin", "admin", false, true, true, false, 0)) {
             for (String str : queues.split(",")) {
                ClientSession.QueueQuery queryResult = coreSession.queueQuery(SimpleString.toSimpleString("jms.queue." + str));
-               Assert.assertTrue("Couldn't find queue " + str, queryResult.isExists());
+               assertTrue("Couldn't find queue " + str, queryResult.isExists());
             }
             for (String str : topics.split(",")) {
                ClientSession.QueueQuery queryResult = coreSession.queueQuery(SimpleString.toSimpleString("jms.topic." + str));
-               Assert.assertTrue("Couldn't find topic " + str, queryResult.isExists());
+               assertTrue("Couldn't find topic " + str, queryResult.isExists());
             }
          }
 
@@ -266,8 +564,8 @@ public class ArtemisTest {
          }
          Artemis.internalExecute("data", "print", "--f");
 
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("producer", "--message-count", "100", "--verbose", "--user", "admin", "--password", "admin"));
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("producer", "--message-count", "100", "--verbose", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
 
          ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
          Connection connection = cf.createConnection("admin", "admin");
@@ -288,20 +586,20 @@ public class ArtemisTest {
          connection.close();
          cf.close();
 
-         Assert.assertEquals(Integer.valueOf(1), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(1), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
 
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='orange'", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='orange'", "--user", "admin", "--password", "admin"));
 
-         Assert.assertEquals(Integer.valueOf(101), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(101), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--user", "admin", "--password", "admin"));
 
          // should only receive 10 messages on browse as I'm setting messageCount=10
-         Assert.assertEquals(Integer.valueOf(10), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--message-count", "10", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(10), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--message-count", "10", "--user", "admin", "--password", "admin"));
 
          // Nothing was consumed until here as it was only browsing, check it's receiving again
-         Assert.assertEquals(Integer.valueOf(1), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(1), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
 
          // Checking it was acked before
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
       } finally {
          stopServer();
       }
@@ -312,7 +610,7 @@ public class ArtemisTest {
          Artemis.main(args);
       } catch (Exception e) {
          e.printStackTrace();
-         Assert.fail("Exception caught " + e.getMessage());
+         fail("Exception caught " + e.getMessage());
       }
    }
 
@@ -322,8 +620,8 @@ public class ArtemisTest {
 
    private void stopServer() throws Exception {
       Artemis.internalExecute("stop");
-      Assert.assertTrue(Run.latchRunning.await(5, TimeUnit.SECONDS));
-      Assert.assertEquals(0, LibaioContext.getTotalMaxIO());
+      assertTrue(Run.latchRunning.await(5, TimeUnit.SECONDS));
+      assertEquals(0, LibaioContext.getTotalMaxIO());
    }
 
    private static Document parseXml(File xmlFile) throws ParserConfigurationException, IOException, SAXException {
@@ -332,4 +630,27 @@ public class ArtemisTest {
       return domBuilder.parse(xmlFile);
    }
 
+   private void checkRole(String user, File roleFile, String... roles) throws Exception {
+      Configurations configs = new Configurations();
+      FileBasedConfigurationBuilder<PropertiesConfiguration> roleBuilder = configs.propertiesBuilder(roleFile);
+      PropertiesConfiguration roleConfig = roleBuilder.getConfiguration();
+
+      for (String r : roles) {
+         String storedUsers = (String) roleConfig.getProperty(r);
+
+         System.out.println("users in role: " + r + " ; " + storedUsers);
+         List<String> userList = StringUtil.splitStringList(storedUsers, ",");
+         assertTrue(userList.contains(user));
+      }
+   }
+
+   private boolean checkPassword(String user, String password, File userFile) throws Exception {
+      Configurations configs = new Configurations();
+      FileBasedConfigurationBuilder<PropertiesConfiguration> userBuilder = configs.propertiesBuilder(userFile);
+      PropertiesConfiguration userConfig = userBuilder.getConfiguration();
+      String storedPassword = (String) userConfig.getProperty(user);
+      HashProcessor processor = PasswordMaskingUtil.getHashProcessor(storedPassword);
+      return processor.compare(password.toCharArray(), storedPassword);
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java
new file mode 100644
index 0000000..0a2da11
--- /dev/null
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cli.test;
+
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+public class TestActionContext extends ActionContext {
+
+   public ByteArrayOutputStream stdout;
+   public ByteArrayOutputStream stderr;
+   private int bufferSize;
+
+   public TestActionContext(int bufferSize) {
+      this.bufferSize = bufferSize;
+      this.stdout = new ByteArrayOutputStream(bufferSize);
+      this.stderr = new ByteArrayOutputStream(bufferSize);
+      this.in = System.in;
+      this.out = new PrintStream(stdout);
+      this.err = new PrintStream(stderr);
+   }
+
+   public TestActionContext() {
+      this(4096);
+   }
+
+   public String getStdout() {
+      return stdout.toString();
+   }
+
+   public String getStderr() {
+      return stderr.toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
index c9143f5..c22f37e 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
@@ -134,6 +134,14 @@ public class ByteUtil {
       return buffer.array();
    }
 
+   public static byte[] hexToBytes(String hexStr) {
+      byte[] bytes = new byte[hexStr.length() / 2];
+      for (int i = 0; i < bytes.length; i++) {
+         bytes[i] = (byte) Integer.parseInt(hexStr.substring(2 * i, 2 * i + 2), 16);
+      }
+      return bytes;
+   }
+
    public static String readLine(ActiveMQBuffer buffer) {
       StringBuilder sb = new StringBuilder("");
       char c = buffer.readChar();

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
index 02bec5f..227a60b 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
@@ -16,15 +16,19 @@
  */
 package org.apache.activemq.artemis.utils;
 
-import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 import java.math.BigInteger;
-import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Properties;
 
 /**
  * A DefaultSensitiveDataCodec
@@ -34,52 +38,39 @@ import java.util.Map;
  * file to use a masked password but doesn't give a
  * codec implementation.
  *
- * The decode() and encode() method is copied originally from
- * JBoss AS code base.
+ * It supports one-way hash (digest) and two-way (encrypt-decrpt) algorithms
+ * The two-way uses "Blowfish" algorithm
+ * The one-way uses "PBKDF2" hash algorithm
  */
 public class DefaultSensitiveStringCodec implements SensitiveDataCodec<String> {
 
-   private byte[] internalKey = "clusterpassword".getBytes();
+   public static final String ALGORITHM = "algorithm";
+   public static final String BLOWFISH_KEY = "key";
+   public static final String ONE_WAY = "one-way";
+   public static final String TWO_WAY = "two-way";
 
-   @Override
-   public String decode(Object secret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
-      SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
-
-      BigInteger n = new BigInteger((String) secret, 16);
-      byte[] encoding = n.toByteArray();
-
-      // JBAS-3457: fix leading zeros
-      if (encoding.length % 8 != 0) {
-         int length = encoding.length;
-         int newLength = ((length / 8) + 1) * 8;
-         int pad = newLength - length; // number of leading zeros
-         byte[] old = encoding;
-         encoding = new byte[newLength];
-         System.arraycopy(old, 0, encoding, pad, old.length);
-      }
+   private CodecAlgorithm algorithm = new BlowfishAlgorithm(Collections.EMPTY_MAP);
 
-      Cipher cipher = Cipher.getInstance("Blowfish");
-      cipher.init(Cipher.DECRYPT_MODE, key);
-      byte[] decode = cipher.doFinal(encoding);
-
-      return new String(decode);
+   @Override
+   public String decode(Object secret) throws Exception {
+      return algorithm.decode((String) secret);
    }
 
-   public Object encode(String secret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
-      SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
-
-      Cipher cipher = Cipher.getInstance("Blowfish");
-      cipher.init(Cipher.ENCRYPT_MODE, key);
-      byte[] encoding = cipher.doFinal(secret.getBytes());
-      BigInteger n = new BigInteger(encoding);
-      return n.toString(16);
+   @Override
+   public String encode(Object secret) throws Exception {
+      return algorithm.encode((String) secret);
    }
 
    @Override
-   public void init(Map<String, String> params) {
-      String key = params.get("key");
-      if (key != null) {
-         updateKey(key);
+   public void init(Map<String, String> params) throws Exception {
+      String algorithm = params.get(ALGORITHM);
+      if (algorithm == null || algorithm.equals(TWO_WAY)) {
+         //two way
+         this.algorithm = new BlowfishAlgorithm(params);
+      } else if (algorithm.equals(ONE_WAY)) {
+         this.algorithm = new PBKDF2Algorithm(params);
+      } else {
+         throw new IllegalArgumentException("Invalid algorithm: " + algorithm);
       }
    }
 
@@ -96,12 +87,149 @@ public class DefaultSensitiveStringCodec implements SensitiveDataCodec<String> {
          System.exit(-1);
       }
       DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
+      Map<String, String> params = new HashMap<>();
+      Properties properties = System.getProperties();
+      for (final String name: properties.stringPropertyNames()) {
+         params.put(name, properties.getProperty(name));
+      }
+      codec.init(params);
       Object encode = codec.encode(args[0]);
+
       System.out.println("Encoded password (without quotes): \"" + encode + "\"");
    }
 
-   private void updateKey(String key) {
-      this.internalKey = key.getBytes();
+   public boolean verify(char[] inputValue, String storedValue) {
+      return algorithm.verify(inputValue, storedValue);
+   }
+
+   private abstract class CodecAlgorithm {
+
+      protected Map<String, String> params;
+
+      CodecAlgorithm(Map<String, String> params) {
+         this.params = params;
+      }
+
+      public abstract String decode(String secret) throws Exception;
+      public abstract String encode(String secret) throws Exception;
+
+      public boolean verify(char[] inputValue, String storedValue) {
+         return false;
+      }
    }
 
+   private class BlowfishAlgorithm extends CodecAlgorithm {
+
+      private byte[] internalKey = "clusterpassword".getBytes();
+
+      BlowfishAlgorithm(Map<String, String> params) {
+         super(params);
+         String key = params.get(BLOWFISH_KEY);
+         if (key != null) {
+            updateKey(key);
+         }
+      }
+
+      private void updateKey(String key) {
+         this.internalKey = key.getBytes();
+      }
+
+      @Override
+      public String decode(String secret) throws Exception {
+         SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
+
+         BigInteger n = new BigInteger((String) secret, 16);
+         byte[] encoding = n.toByteArray();
+
+         if (encoding.length % 8 != 0) {
+            int length = encoding.length;
+            int newLength = ((length / 8) + 1) * 8;
+            int pad = newLength - length; // number of leading zeros
+            byte[] old = encoding;
+            encoding = new byte[newLength];
+            System.arraycopy(old, 0, encoding, pad, old.length);
+         }
+
+         Cipher cipher = Cipher.getInstance("Blowfish");
+         cipher.init(Cipher.DECRYPT_MODE, key);
+         byte[] decode = cipher.doFinal(encoding);
+
+         return new String(decode);
+      }
+
+      @Override
+      public String encode(String secret) throws Exception {
+         SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
+
+         Cipher cipher = Cipher.getInstance("Blowfish");
+         cipher.init(Cipher.ENCRYPT_MODE, key);
+         byte[] encoding = cipher.doFinal(secret.getBytes());
+         BigInteger n = new BigInteger(encoding);
+         return n.toString(16);
+      }
+   }
+
+   private class PBKDF2Algorithm extends CodecAlgorithm {
+      private static final String SEPERATOR = ":";
+      private String sceretKeyAlgorithm = "PBKDF2WithHmacSHA1";
+      private String randomScheme = "SHA1PRNG";
+      private int keyLength = 64 * 8;
+      private int saltLength = 32;
+      private int iterations = 1024;
+      private SecretKeyFactory skf;
+
+      PBKDF2Algorithm(Map<String, String> params) throws NoSuchAlgorithmException {
+         super(params);
+         skf = SecretKeyFactory.getInstance(sceretKeyAlgorithm);
+      }
+
+      @Override
+      public String decode(String secret) throws Exception {
+         throw new IllegalArgumentException("Algorithm doesn't support decoding");
+      }
+
+      public byte[] getSalt() throws NoSuchAlgorithmException {
+         byte[] salt = new byte[this.saltLength];
+
+         SecureRandom sr = SecureRandom.getInstance(this.randomScheme);
+         sr.nextBytes(salt);
+         return salt;
+      }
+
+      @Override
+      public String encode(String secret) throws Exception {
+         char[] chars = secret.toCharArray();
+         byte[] salt = getSalt();
+
+         StringBuilder builder = new StringBuilder();
+         builder.append(iterations).append(SEPERATOR).append(ByteUtil.bytesToHex(salt)).append(SEPERATOR);
+
+         PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, keyLength);
+
+         byte[] hash = skf.generateSecret(spec).getEncoded();
+         String hexValue = ByteUtil.bytesToHex(hash);
+         builder.append(hexValue);
+
+         return builder.toString();
+      }
+
+      @Override
+      public boolean verify(char[] plainChars, String storedValue) {
+         String[] parts = storedValue.split(SEPERATOR);
+         int originalIterations = Integer.parseInt(parts[0]);
+         byte[] salt = ByteUtil.hexToBytes(parts[1]);
+         byte[] originalHash = ByteUtil.hexToBytes(parts[2]);
+
+         PBEKeySpec spec = new PBEKeySpec(plainChars, salt, originalIterations, originalHash.length * 8);
+         byte[] newHash;
+
+         try {
+            newHash = skf.generateSecret(spec).getEncoded();
+         } catch (InvalidKeySpecException e) {
+            return false;
+         }
+
+         return Arrays.equals(newHash, originalHash);
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java
new file mode 100644
index 0000000..0b44132
--- /dev/null
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java
@@ -0,0 +1,41 @@
+/*
+ * 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.utils;
+
+
+/**
+ * Used to process Hash text for passwords
+ */
+public interface HashProcessor {
+
+   /**
+    * produce hash text from plain text
+    * @param plainText Plain text input
+    * @return the Hash value of the input plain text
+    * @throws Exception
+    */
+   String hash(String plainText) throws Exception;
+
+   /**
+    * compare the plain char array against the hash value
+    * @param inputValue value of the plain text
+    * @param storedHash the existing hash value
+    * @return true if the char array matches the hash value,
+    * otherwise false.
+    */
+   boolean compare(char[] inputValue, String storedHash);
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java
new file mode 100644
index 0000000..67e7f6a
--- /dev/null
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java
@@ -0,0 +1,35 @@
+/*
+ * 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.utils;
+
+import java.util.Arrays;
+
+/**
+ * A hash processor that just does plain text comparison
+ */
+public class NoHashProcessor implements HashProcessor {
+
+   @Override
+   public String hash(String plainText) throws Exception {
+      return plainText;
+   }
+
+   @Override
+   public boolean compare(char[] inputValue, String storedHash) {
+      return Arrays.equals(inputValue, storedHash.toCharArray());
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
index 2ef0daa..bee3861 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
@@ -27,6 +27,64 @@ import org.apache.activemq.artemis.logs.ActiveMQUtilBundle;
 
 public class PasswordMaskingUtil {
 
+   private static final String PLAINTEXT_PROCESSOR = "plaintext";
+   private static final String SECURE_PROCESSOR = "secure";
+
+   private static final Map<String, HashProcessor> processors = new HashMap<>();
+
+   //stored password takes 2 forms, ENC() or plain text
+   public static HashProcessor getHashProcessor(String storedPassword) throws Exception {
+
+      if (!isEncoded(storedPassword)) {
+         return getPlaintextProcessor();
+      }
+      return getSecureProcessor();
+   }
+
+   private static boolean isEncoded(String storedPassword) {
+      if (storedPassword == null) {
+         return true;
+      }
+
+      if (storedPassword.startsWith("ENC(") && storedPassword.endsWith(")")) {
+         return true;
+      }
+      return false;
+   }
+
+   public static HashProcessor getHashProcessor() {
+      HashProcessor processor = null;
+      try {
+         processor = getSecureProcessor();
+      } catch (Exception e) {
+         processor = getPlaintextProcessor();
+      }
+      return processor;
+   }
+
+   public static HashProcessor getPlaintextProcessor() {
+      synchronized (processors) {
+         HashProcessor plain = processors.get(PLAINTEXT_PROCESSOR);
+         if (plain == null) {
+            plain = new NoHashProcessor();
+            processors.put(PLAINTEXT_PROCESSOR, plain);
+         }
+         return plain;
+      }
+   }
+
+   public static HashProcessor getSecureProcessor() throws Exception {
+      synchronized (processors) {
+         HashProcessor processor = processors.get(SECURE_PROCESSOR);
+         if (processor == null) {
+            DefaultSensitiveStringCodec codec = (DefaultSensitiveStringCodec) getCodec("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;algorithm=one-way");
+            processor = new SecureHashProcessor(codec);
+            processors.put(SECURE_PROCESSOR, processor);
+         }
+         return processor;
+      }
+   }
+
    /*
     * Loading the codec class.
     *
@@ -37,7 +95,7 @@ public class PasswordMaskingUtil {
     * Where only <full qualified class name> is required. key/value pairs are optional
     */
    public static SensitiveDataCodec<String> getCodec(String codecDesc) throws ActiveMQException {
-      SensitiveDataCodec<String> codecInstance = null;
+      SensitiveDataCodec<String> codecInstance;
 
       // semi colons
       String[] parts = codecDesc.split(";");
@@ -70,13 +128,19 @@ public class PasswordMaskingUtil {
                throw ActiveMQUtilBundle.BUNDLE.invalidProperty(parts[i]);
             props.put(keyVal[0], keyVal[1]);
          }
-         codecInstance.init(props);
+         try {
+            codecInstance.init(props);
+         } catch (Exception e) {
+            throw new ActiveMQException("Fail to init codec", e, ActiveMQExceptionType.SECURITY_EXCEPTION);
+         }
       }
 
       return codecInstance;
    }
 
-   public static SensitiveDataCodec<String> getDefaultCodec() {
+   public static DefaultSensitiveStringCodec getDefaultCodec() {
       return new DefaultSensitiveStringCodec();
    }
+
+
 }


[3/5] activemq-artemis git commit: ARTEMIS-786 checking for inputs and some reorg of the class model on user actions

Posted by cl...@apache.org.
ARTEMIS-786 checking for inputs and some reorg of the class model on user actions


Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/119476dd
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/119476dd
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/119476dd

Branch: refs/heads/master
Commit: 119476ddcc77cfc7a2192f8ba87101dcbd7b44b1
Parents: cd7b838
Author: Clebert Suconic <cl...@apache.org>
Authored: Wed Nov 2 14:58:48 2016 -0400
Committer: Clebert Suconic <cl...@apache.org>
Committed: Wed Nov 2 15:17:31 2016 -0400

----------------------------------------------------------------------
 .../artemis/cli/commands/user/AddUser.java      | 32 ++++++------
 .../artemis/cli/commands/user/ListUser.java     | 15 ++++++
 .../cli/commands/user/PasswordAction.java       | 37 ++++++++++++++
 .../artemis/cli/commands/user/RemoveUser.java   |  9 ++++
 .../artemis/cli/commands/user/ResetUser.java    | 27 +++++-----
 .../artemis/cli/commands/user/UserAction.java   | 54 ++++++--------------
 6 files changed, 107 insertions(+), 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/119476dd/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java
----------------------------------------------------------------------
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 cbb8f60..caa32a7 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.util.FileBasedSecStoreConfig;
 import org.apache.commons.lang3.StringUtils;
 
 /**
@@ -27,13 +28,7 @@ import org.apache.commons.lang3.StringUtils;
  * ./artemis user add --username guest --role admin --password ***
  */
 @Command(name = "add", description = "Add a new user")
-public class AddUser extends UserAction {
-
-   @Option(name = "--password", description = "the password (Default: input)")
-   String password;
-
-   @Option(name = "--role", description = "user's role(s), comma separated", required = true)
-   String role;
+public class AddUser extends PasswordAction {
 
    @Option(name = "--plaintext", description = "using plaintext (Default false)")
    boolean plaintext = false;
@@ -42,9 +37,9 @@ public class AddUser extends UserAction {
    public Object execute(ActionContext context) throws Exception {
       super.execute(context);
 
-      if (password == null) {
-         password = inputPassword("--password", "Please provide the password:", null);
-      }
+      checkInputUser();
+      checkInputPassword();
+      checkInputRole();
 
       String hash = plaintext ? password : HashUtil.tryHash(context, password);
       add(hash, StringUtils.split(role, ","));
@@ -52,11 +47,16 @@ public class AddUser extends UserAction {
       return null;
    }
 
-   public void setPassword(String password) {
-      this.password = password;
-   }
-
-   public void setRole(String role) {
-      this.role = role;
+   /**
+    * Adding a new user
+    * @param hash the password
+    * @param role the role
+    * @throws IllegalArgumentException if user exists
+    */
+   protected void add(String hash, String... role) throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.addNewUser(username, hash, role);
+      config.save();
+      context.out.println("User added successfully.");
    }
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/119476dd/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java
----------------------------------------------------------------------
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 cb3ff39..136a417 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,8 +16,11 @@
  */
 package org.apache.activemq.artemis.cli.commands.user;
 
+import java.util.List;
+
 import io.airlift.airline.Command;
 import org.apache.activemq.artemis.cli.commands.ActionContext;
+import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
 
 /**
  * list existing users, example:
@@ -35,4 +38,16 @@ public class ListUser extends UserAction {
       return null;
    }
 
+   /**
+    * list a single user or all users
+    * if username is not specified
+    */
+   protected void list() throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      List<String> result = config.listUser(username);
+      for (String str : result) {
+         context.out.println(str);
+      }
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/119476dd/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/PasswordAction.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/PasswordAction.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/PasswordAction.java
new file mode 100644
index 0000000..2260488
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/PasswordAction.java
@@ -0,0 +1,37 @@
+/**
+ * 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.cli.commands.user;
+
+import io.airlift.airline.Option;
+
+public class PasswordAction extends UserAction {
+
+   @Option(name = "--password", description = "the password (Default: input)")
+   String password;
+
+   protected void checkInputPassword() {
+      if (password == null) {
+         password = inputPassword("--password", "Please provide the password:", null);
+      }
+   }
+
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/119476dd/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java
----------------------------------------------------------------------
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 172a76d..70167da 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.util.FileBasedSecStoreConfig;
 
 /**
  * Remove a user, example:
@@ -29,8 +30,16 @@ public class RemoveUser extends UserAction {
    @Override
    public Object execute(ActionContext context) throws Exception {
       super.execute(context);
+      checkInputUser();
       remove();
       return null;
    }
 
+   protected void remove() throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.removeUser(username);
+      config.save();
+      context.out.println("User removed.");
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/119476dd/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java
----------------------------------------------------------------------
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 27da6c7..c219ef5 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.util.FileBasedSecStoreConfig;
 import org.apache.commons.lang3.StringUtils;
 
 /**
@@ -27,13 +28,7 @@ import org.apache.commons.lang3.StringUtils;
  * ./artemis user reset --username guest --role admin --password ***
  */
 @Command(name = "reset", description = "Reset user's password or roles")
-public class ResetUser extends UserAction {
-
-   @Option(name = "--password", description = "the password (Default: input)")
-   String password;
-
-   @Option(name = "--role", description = "user's role(s), comma separated")
-   String role;
+public class ResetUser extends PasswordAction {
 
    @Option(name = "--plaintext", description = "using plaintext (Default false)")
    boolean plaintext = false;
@@ -42,6 +37,9 @@ public class ResetUser extends UserAction {
    public Object execute(ActionContext context) throws Exception {
       super.execute(context);
 
+      checkInputUser();
+      checkInputPassword();
+
       if (password != null) {
          password = plaintext ? password : HashUtil.tryHash(context, password);
       }
@@ -55,11 +53,14 @@ public class ResetUser extends UserAction {
       return null;
    }
 
-   public void setPassword(String password) {
-      this.password = password;
-   }
-
-   public void setRole(String role) {
-      this.role = role;
+   protected void reset(String password, String[] roles) throws Exception {
+      if (password == null && roles == null) {
+         context.err.println("Nothing to update.");
+         return;
+      }
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.updateUser(username, password, roles);
+      config.save();
+      context.out.println("User updated");
    }
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/119476dd/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java
----------------------------------------------------------------------
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 918338e..2f7c77f 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
@@ -24,63 +24,41 @@ import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
 import javax.security.auth.login.AppConfigurationEntry;
 import javax.security.auth.login.Configuration;
 import java.io.File;
-import java.util.List;
 
 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 {
 
-   @Option(name = "--user", description = "The user name")
+   @Option(name = "--role", description = "user's role(s), comma separated")
+   String role;
+
+   @Option(name = "--user", description = "The user name (Default: input)")
    String username = null;
 
-   /**
-    * Adding a new user
-    * @param hash the password
-    * @param role the role
-    * @throws IllegalArgumentException if user exists
-    */
-   protected void add(String hash, String... role) throws Exception {
-      FileBasedSecStoreConfig config = getConfiguration();
-      config.addNewUser(username, hash, role);
-      config.save();
-      context.out.println("User added successfully.");
-   }
+   @Option(name = "--entry", description = "The appConfigurationEntry (default: activemq)")
+   String entry = "activemq";
 
-   /**
-    * list a single user or all users
-    * if username is not specified
-    */
-   protected void list() throws Exception {
-      FileBasedSecStoreConfig config = getConfiguration();
-      List<String> result = config.listUser(username);
-      for (String str : result) {
-         context.out.println(str);
+   protected void checkInputUser() {
+      if (username == null) {
+         username = input("--user", "Please provider the userName:", null);
       }
    }
 
-   protected void remove() throws Exception {
-      FileBasedSecStoreConfig config = getConfiguration();
-      config.removeUser(username);
-      config.save();
-      context.out.println("User removed.");
+   public void setRole(String role) {
+      this.role = role;
    }
 
-   protected void reset(String password, String[] roles) throws Exception {
-      if (password == null && roles == null) {
-         context.err.println("Nothing to update.");
-         return;
+   public void checkInputRole() {
+      if (role == null) {
+         role = input("--role", "type a comma separated list of roles", null);
       }
-      FileBasedSecStoreConfig config = getConfiguration();
-      config.updateUser(username, password, roles);
-      config.save();
-      context.out.println("User updated");
    }
 
-   private FileBasedSecStoreConfig getConfiguration() throws Exception {
+   protected FileBasedSecStoreConfig getConfiguration() throws Exception {
 
       Configuration securityConfig = Configuration.getConfiguration();
-      AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry("activemq");
+      AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry(entry);
 
       for (AppConfigurationEntry entry : entries) {
          if (entry.getLoginModuleName().equals(PropertiesLoginModule.class.getName())) {