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 0a430a3b25..0c558f8010 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
@@ -70,8 +70,16 @@ public class Create extends InputAbstract {
public static final String ETC_LOGGING_PROPERTIES = "etc/logging.properties";
public static final String ETC_BOOTSTRAP_XML = "etc/bootstrap.xml";
public static final String ETC_BROKER_XML = "etc/broker.xml";
+
+ // The JAAS PropertiesLogin module uses role=user(s) syntax, but the basic security uses user=role(s) syntax so we need 2 different files here
public static final String ETC_ARTEMIS_ROLES_PROPERTIES = "etc/artemis-roles.properties";
+ public static final String ETC_ARTEMIS_ROLES_BASIC_PROPERTIES = "etc/artemis-roles-basic.properties";
+ public static final String ETC_ARTEMIS_ROLES_JAAS_PROPERTIES = "etc/artemis-roles-jaas.properties";
+
public static final String ETC_ARTEMIS_USERS_PROPERTIES = "etc/artemis-users.properties";
+ public static final String ETC_JAAS_BROKER_SECURITY_SETTINGS_TXT = "etc/jaas-broker-security-settings.txt";
+ public static final String ETC_BASIC_BROKER_SECURITY_SETTINGS_TXT = "etc/basic-broker-security-settings.txt";
+ public static final String ETC_LOGIN_CONFIG = "etc/login.config";
public static final String ETC_REPLICATED_SETTINGS_TXT = "etc/replicated-settings.txt";
public static final String ETC_SHARED_STORE_SETTINGS_TXT = "etc/shared-store-settings.txt";
public static final String ETC_CLUSTER_SECURITY_SETTINGS_TXT = "etc/cluster-security-settings.txt";
@@ -161,10 +169,24 @@ public class Create extends InputAbstract {
@Option(name = "--aio", description = "Force aio journal on the configuration regardless of the library being available or not.")
boolean forceLibaio;
+ @Option(name = "--broker-security", description = "Use basic, file-based security or JAAS login module for broker security (Default: basic)")
+ String brokerSecurity;
+
boolean IS_WINDOWS;
boolean IS_CYGWIN;
+ public String getBrokerSecurity() {
+ if (brokerSecurity == null) {
+ brokerSecurity = "basic";
+ }
+ return brokerSecurity;
+ }
+
+ public void setBrokerSecurity(String security) {
+ this.brokerSecurity = security;
+ }
+
public int getMaxHops() {
return maxHops;
}
@@ -535,6 +557,29 @@ public class Create extends InputAbstract {
filters.put("${java-opts}", javaOptions);
+ if (isAllowAnonymous()) {
+ filters.put("${bootstrap.guest}", "default-user=\"" + getUser() + "\"");
+ }
+ else {
+ filters.put("${bootstrap.guest}", "");
+ }
+
+ if (brokerSecurity != null && brokerSecurity.equalsIgnoreCase("jaas")) {
+ filters.put("${broker-security-settings}", applyFilters(readTextFile(ETC_JAAS_BROKER_SECURITY_SETTINGS_TXT), filters));
+ filters.put("${login-config}", "-Djava.security.auth.login.config=" + path(directory, false) + "/etc/login.config");
+ write(ETC_LOGIN_CONFIG, filters, false);
+ write(ETC_ARTEMIS_ROLES_JAAS_PROPERTIES, filters, false);
+ File file = new File(directory, ETC_ARTEMIS_ROLES_JAAS_PROPERTIES);
+ file.renameTo(new File(directory, ETC_ARTEMIS_ROLES_PROPERTIES));
+ }
+ else {
+ filters.put("${broker-security-settings}", applyFilters(readTextFile(ETC_BASIC_BROKER_SECURITY_SETTINGS_TXT), filters));
+ filters.put("${login-config}", "");
+ write(ETC_ARTEMIS_ROLES_BASIC_PROPERTIES, filters, false);
+ File file = new File(directory, ETC_ARTEMIS_ROLES_BASIC_PROPERTIES);
+ file.renameTo(new File(directory, ETC_ARTEMIS_ROLES_PROPERTIES));
+ }
+
if (IS_WINDOWS) {
write(BIN_ARTEMIS_CMD, null, false);
write(BIN_ARTEMIS_SERVICE_EXE);
@@ -553,13 +598,6 @@ public class Create extends InputAbstract {
write(ETC_LOGGING_PROPERTIES, null, false);
- if (isAllowAnonymous()) {
- filters.put("${bootstrap.guest}", "default-user=\"" + getUser() + "\"");
- }
- else {
- filters.put("${bootstrap.guest}", "");
- }
-
if (noWeb) {
filters.put("${bootstrap-web-settings}", "");
}
@@ -571,7 +609,6 @@ public class Create extends InputAbstract {
write(ETC_BOOTSTRAP_XML, filters, false);
write(ETC_BROKER_XML, filters, false);
- write(ETC_ARTEMIS_ROLES_PROPERTIES, filters, false);
write(ETC_ARTEMIS_USERS_PROPERTIES, filters, false);
context.out.println("");
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/factory/JaasSecurityHandler.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/factory/JaasSecurityHandler.java
new file mode 100644
index 0000000000..2cd1785e11
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/factory/JaasSecurityHandler.java
@@ -0,0 +1,32 @@
+/*
+ * 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.factory;
+
+import org.apache.activemq.artemis.dto.JaasSecurityDTO;
+import org.apache.activemq.artemis.dto.SecurityDTO;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
+
+public class JaasSecurityHandler implements SecurityHandler {
+ @Override
+ public ActiveMQSecurityManager createSecurityManager(SecurityDTO security) throws Exception {
+ JaasSecurityDTO jaasSecurity = (JaasSecurityDTO) security;
+ ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
+ securityManager.setConfigurationName(jaasSecurity.loginModule);
+ return securityManager;
+ }
+}
diff --git a/artemis-cli/src/main/resources/META-INF/services/org/apache/activemq/artemis/broker/security/jaas-security b/artemis-cli/src/main/resources/META-INF/services/org/apache/activemq/artemis/broker/security/jaas-security
new file mode 100644
index 0000000000..013a63c20a
--- /dev/null
+++ b/artemis-cli/src/main/resources/META-INF/services/org/apache/activemq/artemis/broker/security/jaas-security
@@ -0,0 +1,17 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+class=org.apache.activemq.artemis.factory.JaasSecurityHandler
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-basic.properties
similarity index 100%
rename from artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
rename to artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles-basic.properties
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles-jaas.properties b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles-jaas.properties
new file mode 100644
index 0000000000..c9443dd9db
--- /dev/null
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles-jaas.properties
@@ -0,0 +1,17 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+${role}=${user}
\ No newline at end of file
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile
index 2a51e2adba..76ca12f449 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile
@@ -23,7 +23,7 @@ ARTEMIS_HOME='${artemis.home}'
# Java Opts
-JAVA_ARGS="-XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xms512M -Xmx1024M -Xbootclasspath/a:$ARTEMIS_HOME/lib/${logmanager} -Djava.util.logging.manager=org.jboss.logmanager.LogManager ${java-opts}"
+JAVA_ARGS="-XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xms512M -Xmx1024M -Xbootclasspath/a:$ARTEMIS_HOME/lib/${logmanager} -Djava.util.logging.manager=org.jboss.logmanager.LogManager ${login-config} ${java-opts}"
# Debug args: Uncomment to enable debug
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile.cmd b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile.cmd
index c52d70f2f0..835c7b70fc 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile.cmd
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile.cmd
@@ -21,7 +21,7 @@ rem Cluster Properties: Used to pass arguments to ActiveMQ Artemis which can be
rem set ARTEMIS_CLUSTER_PROPS=-Dactivemq.remoting.default.port=61617 -Dactivemq.remoting.amqp.port=5673 -Dactivemq.remoting.stomp.port=61614 -Dactivemq.remoting.hornetq.port=5446
rem Java Opts
-set JAVA_ARGS=-XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xms512M -Xmx1024M ${java-opts}
+set JAVA_ARGS=-XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xms512M -Xmx1024M ${login-config} ${java-opts}
rem Debug args: Uncomment to enable debug
rem set DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-broker-security-settings.txt b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-broker-security-settings.txt
new file mode 100644
index 0000000000..dd0a5f121b
--- /dev/null
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-broker-security-settings.txt
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml
index be51734db5..fe3f864191 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml
@@ -18,10 +18,7 @@
-
+${broker-security-settings}
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-broker-security-settings.txt b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-broker-security-settings.txt
new file mode 100644
index 0000000000..6521bf4b5f
--- /dev/null
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-broker-security-settings.txt
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/login.config b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/login.config
new file mode 100644
index 0000000000..fe8ca36878
--- /dev/null
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/login.config
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+PropertiesLogin {
+ org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule required
+ debug=true
+ org.apache.activemq.jaas.properties.user="artemis-users.properties"
+ org.apache.activemq.jaas.properties.role="artemis-roles.properties";
+};
\ No newline at end of file
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java
index 21579dc9b0..e1d045d05e 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java
@@ -40,7 +40,8 @@ public class StreamClassPathTest {
openStream(Create.ETC_LOGGING_PROPERTIES);
openStream(Create.ETC_BOOTSTRAP_XML);
openStream(Create.ETC_BROKER_XML);
- openStream(Create.ETC_ARTEMIS_ROLES_PROPERTIES);
+ openStream(Create.ETC_ARTEMIS_ROLES_BASIC_PROPERTIES);
+ openStream(Create.ETC_ARTEMIS_ROLES_JAAS_PROPERTIES);
openStream(Create.ETC_ARTEMIS_USERS_PROPERTIES);
openStream(Create.ETC_REPLICATED_SETTINGS_TXT);
openStream(Create.ETC_REPLICATED_SETTINGS_TXT);
@@ -50,6 +51,8 @@ public class StreamClassPathTest {
openStream(Create.ETC_CONNECTOR_SETTINGS_TXT);
openStream(Create.ETC_BOOTSTRAP_WEB_SETTINGS_TXT);
openStream(Create.ETC_JOURNAL_BUFFER_SETTINGS);
+ openStream(Create.ETC_JAAS_BROKER_SECURITY_SETTINGS_TXT);
+ openStream(Create.ETC_BASIC_BROKER_SECURITY_SETTINGS_TXT);
}
private void openStream(String source) throws Exception {
diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JaasSecurityDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JaasSecurityDTO.java
new file mode 100644
index 0000000000..99163cf55c
--- /dev/null
+++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JaasSecurityDTO.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dto;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "jaas-security")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class JaasSecurityDTO extends SecurityDTO {
+
+ @XmlAttribute(name = "login-module", required = true)
+ public String loginModule;
+}
diff --git a/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index b/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index
index 5803f734c4..b0bacd7ba7 100644
--- a/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index
+++ b/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index
@@ -2,19 +2,20 @@
## 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 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
+## See the License for the specific language governing permissions and
## limitations under the License.
## ---------------------------------------------------------------------------
BrokerDTO
SecurityDTO
BasicSecurityDTO
+JaasSecurityDTO
diff --git a/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCreatePlugin.java b/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCreatePlugin.java
index 39b6d8eeda..ba6cb8e045 100644
--- a/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCreatePlugin.java
+++ b/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCreatePlugin.java
@@ -113,6 +113,9 @@ public class ArtemisCreatePlugin extends ArtemisAbstractPlugin {
@Parameter(defaultValue = "ON_DEMAND")
private String messageLoadBalancing;
+ @Parameter(defaultValue = "basic")
+ private String brokerSecurity;
+
/**
* For extra stuff not covered by the properties
*/
@@ -200,7 +203,7 @@ public class ArtemisCreatePlugin extends ArtemisAbstractPlugin {
ArrayList listCommands = new ArrayList<>();
- add(listCommands, "create", "--allow-anonymous", "--silent", "--force", "--no-web", "--user", user, "--password", password, "--role", role, "--port-offset", "" + portOffset, "--data", dataFolder);
+ add(listCommands, "create", "--allow-anonymous", "--silent", "--force", "--no-web", "--user", user, "--password", password, "--role", role, "--port-offset", "" + portOffset, "--data", dataFolder, "--broker-security", brokerSecurity);
if (allowAnonymous) {
add(listCommands, "--allow-anonymous");
diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml
index 652ef81686..36cef29408 100644
--- a/artemis-server/pom.xml
+++ b/artemis-server/pom.xml
@@ -82,6 +82,24 @@
junittest
+
+ org.apache.directory.server
+ apacheds-server-integ
+ ${directory-version}
+ test
+
+
+ org.apache.directory.server
+ apacheds-core-integ
+ ${directory-version}
+ test
+
+
+ bouncycastle
+ bcprov-jdk15
+
+
+
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
new file mode 100644
index 0000000000..6a13f22e6d
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
@@ -0,0 +1,114 @@
+/*
+ * 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.spi.core.security;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.security.CheckType;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.spi.core.security.jaas.JaasCredentialCallbackHandler;
+import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
+
+/**
+ * This implementation delegates to the JAAS security interfaces.
+ *
+ * The {@link Subject} returned by the login context is expecting to have a set of {@link RolePrincipal} for each
+ * role of the user.
+ */
+public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager {
+
+ private final boolean trace = ActiveMQServerLogger.LOGGER.isTraceEnabled();
+
+ private String configurationName;
+
+ public boolean validateUser(final String user, final String password) {
+ try {
+ getAuthenticatedSubject(user, password);
+ return true;
+ }
+ catch (LoginException e) {
+ ActiveMQServerLogger.LOGGER.debug("Couldn't validate user: " + user, e);
+ return false;
+ }
+ }
+
+ public boolean validateUserAndRole(final String user,
+ final String password,
+ final Set roles,
+ final CheckType checkType) {
+ Subject localSubject;
+ try {
+ localSubject = getAuthenticatedSubject(user, password);
+ }
+ catch (LoginException e) {
+ ActiveMQServerLogger.LOGGER.debug("Couldn't validate user: " + user, e);
+ return false;
+ }
+
+ boolean authorized = false;
+
+ if (localSubject != null) {
+ Set rolesWithPermission = getPrincipalsInRole(checkType, roles);
+
+ // Check the caller's roles
+ Set rolesForSubject = localSubject.getPrincipals(RolePrincipal.class);
+ if (rolesForSubject.size() > 0 && rolesWithPermission.size() > 0) {
+ Iterator rolesForSubjectIter = rolesForSubject.iterator();
+ while (!authorized && rolesForSubjectIter.hasNext()) {
+ Iterator rolesWithPermissionIter = rolesWithPermission.iterator();
+ while (!authorized && rolesWithPermissionIter.hasNext()) {
+ Principal role = rolesWithPermissionIter.next();
+ authorized = rolesForSubjectIter.next().equals(role);
+ }
+ }
+ }
+
+ if (trace) {
+ ActiveMQServerLogger.LOGGER.trace("user " + user + (authorized ? " is " : " is NOT ") + "authorized");
+ }
+ }
+
+ return authorized;
+ }
+
+ private Subject getAuthenticatedSubject(final String user, final String password) throws LoginException {
+ LoginContext lc = new LoginContext(configurationName, new JaasCredentialCallbackHandler(user, password));
+ lc.login();
+ return lc.getSubject();
+ }
+
+ private Set getPrincipalsInRole(final CheckType checkType, final Set roles) {
+ Set principals = new HashSet<>();
+ for (Role role : roles) {
+ if (checkType.hasRole(role)) {
+ principals.add(new RolePrincipal(role.getName()));
+ }
+ }
+ return principals;
+ }
+
+ public void setConfigurationName(final String configurationName) {
+ this.configurationName = configurationName;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java
index 2962153c52..1e3cb108a3 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java
@@ -46,4 +46,4 @@ public interface ActiveMQSecurityManager2 extends ActiveMQSecurityManager {
* @return true if the user is valid and they have the correct roles for the given destination address
*/
boolean validateUserAndRole(String user, String password, Set roles, CheckType checkType, String address);
-}
+}
\ No newline at end of file
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/JAASSecurityManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/JAASSecurityManager.java
deleted file mode 100644
index 48699b64a1..0000000000
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/JAASSecurityManager.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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.spi.core.security;
-
-import java.security.Principal;
-import java.security.acl.Group;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.Configuration;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.activemq.artemis.core.security.CheckType;
-import org.apache.activemq.artemis.core.security.Role;
-import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
-
-/**
- * This implementation delegates to the JAAS security interfaces.
- *
- * The {@link Subject} returned by the login context is expecting to have a {@link Group} with the Roles name
- * containing a set of {@link Principal} for each role of the user.
- */
-public class JAASSecurityManager implements ActiveMQSecurityManager {
- // Static --------------------------------------------------------
-
- // Attributes ----------------------------------------------------
-
- private final boolean trace = ActiveMQServerLogger.LOGGER.isTraceEnabled();
-
- private String configurationName;
-
- private CallbackHandler callbackHandler;
-
- private Configuration config;
-
- // ActiveMQSecurityManager implementation -----------------------------
-
- public boolean validateUser(final String user, final String password) {
- try {
- getAuthenticatedSubject(user, password);
- return true;
- }
- catch (LoginException e1) {
- return false;
- }
- }
-
- public boolean validateUserAndRole(final String user,
- final String password,
- final Set roles,
- final CheckType checkType) {
- Subject localSubject = null;
- try {
- localSubject = getAuthenticatedSubject(user, password);
- }
- catch (LoginException e1) {
- return false;
- }
-
- boolean authenticated = true;
-
- if (localSubject != null) {
- Set rolePrincipals = getRolePrincipals(checkType, roles);
-
- // authenticated = realmMapping.doesUserHaveRole(principal, rolePrincipals);
-
- boolean hasRole = false;
- // check that the caller is authenticated to the current thread
-
- // Check the caller's roles
- Group subjectRoles = getSubjectRoles(localSubject);
- if (subjectRoles != null) {
- Iterator iter = rolePrincipals.iterator();
- while (!hasRole && iter.hasNext()) {
- Principal role = iter.next();
- hasRole = subjectRoles.isMember(role);
- }
- }
-
- authenticated = hasRole;
-
- if (trace) {
- ActiveMQServerLogger.LOGGER.trace("user " + user + (authenticated ? " is " : " is NOT ") + "authorized");
- }
- }
- return authenticated;
- }
-
- private Subject getAuthenticatedSubject(final String user, final String password) throws LoginException {
- SimplePrincipal principal = user == null ? null : new SimplePrincipal(user);
-
- char[] passwordChars = null;
-
- if (password != null) {
- passwordChars = password.toCharArray();
- }
-
- Subject subject = new Subject();
-
- if (user != null) {
- subject.getPrincipals().add(principal);
- }
- subject.getPrivateCredentials().add(passwordChars);
-
- LoginContext lc = new LoginContext(configurationName, subject, callbackHandler, config);
- lc.login();
- return lc.getSubject();
- }
-
- private Group getSubjectRoles(final Subject subject) {
- Set subjectGroups = subject.getPrincipals(Group.class);
- Iterator iter = subjectGroups.iterator();
- Group roles = null;
- while (iter.hasNext()) {
- Group grp = iter.next();
- String name = grp.getName();
- if (name.equals("Roles")) {
- roles = grp;
- }
- }
- return roles;
- }
-
- private Set getRolePrincipals(final CheckType checkType, final Set roles) {
- Set principals = new HashSet();
- for (Role role : roles) {
- if (checkType.hasRole(role)) {
- principals.add(new SimplePrincipal(role.getName()));
- }
- }
- return principals;
- }
-
- // Public --------------------------------------------------------
-
- public void setConfigurationName(final String configurationName) {
- this.configurationName = configurationName;
- }
-
- public void setCallbackHandler(final CallbackHandler handler) {
- callbackHandler = handler;
- }
-
- public void setConfiguration(final Configuration config) {
- this.config = config;
- }
-
- // Private -------------------------------------------------------
-
- // Inner classes -------------------------------------------------
-
- public static class SimplePrincipal implements Principal, java.io.Serializable {
-
- private static final long serialVersionUID = 1L;
-
- private final String name;
-
- public SimplePrincipal(final String name) {
- this.name = name;
- }
-
- /**
- * Compare this SimplePrincipal's name against another Principal
- *
- * @return true if name equals another.getName();
- */
- @Override
- public boolean equals(final Object another) {
- if (!(another instanceof Principal)) {
- return false;
- }
- String anotherName = ((Principal) another).getName();
- boolean equals = false;
- if (name == null) {
- equals = anotherName == null;
- }
- else {
- equals = name.equals(anotherName);
- }
- return equals;
- }
-
- @Override
- public int hashCode() {
- return name == null ? 0 : name.hashCode();
- }
-
- @Override
- public String toString() {
- return name;
- }
-
- public String getName() {
- return name;
- }
- }
-
-}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateCallback.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateCallback.java
new file mode 100644
index 0000000000..630dd32b4b
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateCallback.java
@@ -0,0 +1,48 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import javax.security.auth.callback.Callback;
+import java.security.cert.X509Certificate;
+
+/**
+ * A Callback for SSL certificates.
+ *
+ * Will return a certificate chain to its client.
+ */
+public class CertificateCallback implements Callback {
+
+ X509Certificate[] certificates;
+
+ /**
+ * Setter for certificate chain.
+ *
+ * @param certs The certificates to be returned.
+ */
+ public void setCertificates(X509Certificate[] certs) {
+ certificates = certs;
+ }
+
+ /**
+ * Getter for certificate chain.
+ *
+ * @return The certificates being carried.
+ */
+ public X509Certificate[] getCertificates() {
+ return certificates;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java
new file mode 100644
index 0000000000..db8808b720
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java
@@ -0,0 +1,183 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+/**
+ * A LoginModule that allows for authentication based on SSL certificates.
+ * Allows for subclasses to define methods used to verify user certificates and
+ * find user groups. Uses CertificateCallbacks to retrieve certificates.
+ */
+public abstract class CertificateLoginModule implements LoginModule {
+
+ private CallbackHandler callbackHandler;
+ private Subject subject;
+
+ private X509Certificate[] certificates;
+ private String username;
+ private Set groups;
+ private Set principals = new HashSet();
+ private boolean debug;
+
+ /**
+ * Overriding to allow for proper initialization. Standard JAAS.
+ */
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+
+ debug = "true".equalsIgnoreCase((String) options.get("debug"));
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Initialized debug");
+ }
+ }
+
+ /**
+ * Overriding to allow for certificate-based login. Standard JAAS.
+ */
+ @Override
+ public boolean login() throws LoginException {
+ Callback[] callbacks = new Callback[1];
+
+ callbacks[0] = new CertificateCallback();
+ try {
+ callbackHandler.handle(callbacks);
+ }
+ catch (IOException ioe) {
+ throw new LoginException(ioe.getMessage());
+ }
+ catch (UnsupportedCallbackException uce) {
+ throw new LoginException(uce.getMessage() + " Unable to obtain client certificates.");
+ }
+ certificates = ((CertificateCallback) callbacks[0]).getCertificates();
+
+ username = getUserNameForCertificates(certificates);
+ if (username == null) {
+ throw new FailedLoginException("No user for client certificate: " + getDistinguishedName(certificates));
+ }
+
+ groups = getUserGroups(username);
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Certificate for user: " + username);
+ }
+ return true;
+ }
+
+ /**
+ * Overriding to complete login process. Standard JAAS.
+ */
+ @Override
+ public boolean commit() throws LoginException {
+ principals.add(new UserPrincipal(username));
+
+ for (String group : groups) {
+ principals.add(new RolePrincipal(group));
+ }
+
+ subject.getPrincipals().addAll(principals);
+
+ clear();
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("commit");
+ }
+ return true;
+ }
+
+ /**
+ * Standard JAAS override.
+ */
+ @Override
+ public boolean abort() throws LoginException {
+ clear();
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("abort");
+ }
+ return true;
+ }
+
+ /**
+ * Standard JAAS override.
+ */
+ @Override
+ public boolean logout() {
+ subject.getPrincipals().removeAll(principals);
+ principals.clear();
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("logout");
+ }
+ return true;
+ }
+
+ /**
+ * Helper method.
+ */
+ private void clear() {
+ groups.clear();
+ certificates = null;
+ }
+
+ /**
+ * Should return a unique name corresponding to the certificates given. The
+ * name returned will be used to look up access levels as well as group
+ * associations.
+ *
+ * @param certs The distinguished name.
+ * @return The unique name if the certificate is recognized, null otherwise.
+ */
+ protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException;
+
+ /**
+ * Should return a set of the groups this user belongs to. The groups
+ * returned will be added to the user's credentials.
+ *
+ * @param username The username of the client. This is the same name that
+ * getUserNameForDn returned for the user's DN.
+ * @return A Set of the names of the groups this user belongs to.
+ */
+ protected abstract Set getUserGroups(final String username) throws LoginException;
+
+ protected String getDistinguishedName(final X509Certificate[] certs) {
+ if (certs != null && certs.length > 0 && certs[0] != null) {
+ return certs[0].getSubjectDN().getName();
+ }
+ else {
+ return null;
+ }
+ }
+
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/GuestLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/GuestLoginModule.java
new file mode 100644
index 0000000000..dbea86bad5
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/GuestLoginModule.java
@@ -0,0 +1,132 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+/**
+ * Always login the user with a default 'guest' identity.
+ *
+ * Useful for unauthenticated communication channels being used in the
+ * same broker as authenticated ones.
+ *
+ */
+public class GuestLoginModule implements LoginModule {
+
+ private static final String GUEST_USER = "org.apache.activemq.jaas.guest.user";
+ private static final String GUEST_ROLE = "org.apache.activemq.jaas.guest.role";
+
+ private String userName = "guest";
+ private String roleName = "guests";
+ private Subject subject;
+ private boolean debug;
+ private boolean credentialsInvalidate;
+ private Set principals = new HashSet();
+ private CallbackHandler callbackHandler;
+ private boolean loginSucceeded;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ debug = "true".equalsIgnoreCase((String) options.get("debug"));
+ credentialsInvalidate = "true".equalsIgnoreCase((String) options.get("credentialsInvalidate"));
+ if (options.get(GUEST_USER) != null) {
+ userName = (String) options.get(GUEST_USER);
+ }
+ if (options.get(GUEST_ROLE) != null) {
+ roleName = (String) options.get(GUEST_ROLE);
+ }
+ principals.add(new UserPrincipal(userName));
+ principals.add(new RolePrincipal(roleName));
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Initialized debug=" + debug + " guestUser=" + userName + " guestGroup=" + roleName);
+ }
+
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ loginSucceeded = true;
+ if (credentialsInvalidate) {
+ PasswordCallback passwordCallback = new PasswordCallback("Password: ", false);
+ try {
+ callbackHandler.handle(new Callback[]{passwordCallback});
+ if (passwordCallback.getPassword() != null) {
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Guest login failing (credentialsInvalidate=true) on presence of a password");
+ }
+ loginSucceeded = false;
+ passwordCallback.clearPassword();
+ }
+ }
+ catch (IOException ioe) {
+ }
+ catch (UnsupportedCallbackException uce) {
+ }
+ }
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Guest login " + loginSucceeded);
+ }
+ return loginSucceeded;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ if (loginSucceeded) {
+ subject.getPrincipals().addAll(principals);
+ }
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("commit");
+ }
+ return loginSucceeded;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("abort");
+ }
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ subject.getPrincipals().removeAll(principals);
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("logout");
+ }
+ return true;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCertificateCallbackHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCertificateCallbackHandler.java
new file mode 100644
index 0000000000..b53f946fbc
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCertificateCallbackHandler.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.spi.core.security.jaas;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+
+/**
+ * A Standard JAAS callback handler for SSL certificate requests. Will only
+ * handle callbacks of type CertificateCallback.
+ */
+public class JaasCertificateCallbackHandler implements CallbackHandler {
+
+ final X509Certificate[] certificates;
+
+ /**
+ * Basic constructor.
+ *
+ * @param certs The certificate returned when calling back.
+ */
+ public JaasCertificateCallbackHandler(X509Certificate[] certs) {
+ certificates = certs;
+ }
+
+ /**
+ * Overriding handle method to handle certificates.
+ *
+ * @param callbacks The callbacks requested.
+ * @throws IOException
+ * @throws UnsupportedCallbackException Thrown if an unkown Callback type is
+ * encountered.
+ */
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback callback = callbacks[i];
+ if (callback instanceof CertificateCallback) {
+ CertificateCallback certCallback = (CertificateCallback) callback;
+
+ certCallback.setCertificates(certificates);
+
+ }
+ else {
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCredentialCallbackHandler.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCredentialCallbackHandler.java
new file mode 100644
index 0000000000..34ae701467
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCredentialCallbackHandler.java
@@ -0,0 +1,63 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.IOException;
+
+/**
+ * A JAAS username password CallbackHandler.
+ */
+public class JaasCredentialCallbackHandler implements CallbackHandler {
+
+ private final String username;
+ private final String password;
+
+ public JaasCredentialCallbackHandler(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback callback = callbacks[i];
+ if (callback instanceof PasswordCallback) {
+ PasswordCallback passwordCallback = (PasswordCallback) callback;
+ if (password == null) {
+ passwordCallback.setPassword(null);
+ }
+ else {
+ passwordCallback.setPassword(password.toCharArray());
+ }
+ }
+ else if (callback instanceof NameCallback) {
+ NameCallback nameCallback = (NameCallback) callback;
+ if (username == null) {
+ nameCallback.setName(null);
+ }
+ else {
+ nameCallback.setName(username);
+ }
+ }
+ }
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
new file mode 100644
index 0000000000..6830828e58
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
@@ -0,0 +1,505 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import javax.naming.AuthenticationException;
+import javax.naming.CommunicationException;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+public class LDAPLoginModule implements LoginModule {
+
+ private static final String INITIAL_CONTEXT_FACTORY = "initialContextFactory";
+ private static final String CONNECTION_URL = "connectionURL";
+ private static final String CONNECTION_USERNAME = "connectionUsername";
+ private static final String CONNECTION_PASSWORD = "connectionPassword";
+ private static final String CONNECTION_PROTOCOL = "connectionProtocol";
+ private static final String AUTHENTICATION = "authentication";
+ private static final String USER_BASE = "userBase";
+ private static final String USER_SEARCH_MATCHING = "userSearchMatching";
+ private static final String USER_SEARCH_SUBTREE = "userSearchSubtree";
+ private static final String ROLE_BASE = "roleBase";
+ private static final String ROLE_NAME = "roleName";
+ private static final String ROLE_SEARCH_MATCHING = "roleSearchMatching";
+ private static final String ROLE_SEARCH_SUBTREE = "roleSearchSubtree";
+ private static final String USER_ROLE_NAME = "userRoleName";
+ private static final String EXPAND_ROLES = "expandRoles";
+ private static final String EXPAND_ROLES_MATCHING = "expandRolesMatching";
+
+ protected DirContext context;
+
+ private Subject subject;
+ private CallbackHandler handler;
+ private LDAPLoginProperty[] config;
+ private String username;
+ private Set groups = new HashSet();
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ this.subject = subject;
+ this.handler = callbackHandler;
+
+ config = new LDAPLoginProperty[]{new LDAPLoginProperty(INITIAL_CONTEXT_FACTORY, (String) options.get(INITIAL_CONTEXT_FACTORY)), new LDAPLoginProperty(CONNECTION_URL, (String) options.get(CONNECTION_URL)), new LDAPLoginProperty(CONNECTION_USERNAME, (String) options.get(CONNECTION_USERNAME)), new LDAPLoginProperty(CONNECTION_PASSWORD, (String) options.get(CONNECTION_PASSWORD)), new LDAPLoginProperty(CONNECTION_PROTOCOL, (String) options.get(CONNECTION_PROTOCOL)), new LDAPLoginProperty(AUTHENTICATION, (String) options.get(AUTHENTICATION)), new LDAPLoginProperty(USER_BASE, (String) options.get(USER_BASE)), new LDAPLoginProperty(USER_SEARCH_MATCHING, (String) options.get(USER_SEARCH_MATCHING)), new LDAPLoginProperty(USER_SEARCH_SUBTREE, (String) options.get(USER_SEARCH_SUBTREE)), new LDAPLoginProperty(ROLE_BASE, (String) options.get(ROLE_BASE)), new LDAPLoginProperty(ROLE_NAME, (String) options.get(ROLE_NAME)), new LDAPLoginProperty(ROLE_SEARCH_MATCHING, (String) options.get(ROLE_SEARCH_MATCHING)), new LDAPLoginProperty(ROLE_SEARCH_SUBTREE, (String) options.get(ROLE_SEARCH_SUBTREE)), new LDAPLoginProperty(USER_ROLE_NAME, (String) options.get(USER_ROLE_NAME)), new LDAPLoginProperty(EXPAND_ROLES, (String) options.get(EXPAND_ROLES)), new LDAPLoginProperty(EXPAND_ROLES_MATCHING, (String) options.get(EXPAND_ROLES_MATCHING))};
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+
+ Callback[] callbacks = new Callback[2];
+
+ callbacks[0] = new NameCallback("User name");
+ callbacks[1] = new PasswordCallback("Password", false);
+ try {
+ handler.handle(callbacks);
+ }
+ catch (IOException ioe) {
+ throw (LoginException) new LoginException().initCause(ioe);
+ }
+ catch (UnsupportedCallbackException uce) {
+ throw (LoginException) new LoginException().initCause(uce);
+ }
+
+ String password;
+
+ username = ((NameCallback) callbacks[0]).getName();
+ if (username == null)
+ return false;
+
+ if (((PasswordCallback) callbacks[1]).getPassword() != null)
+ password = new String(((PasswordCallback) callbacks[1]).getPassword());
+ else
+ password = "";
+
+ // authenticate will throw LoginException
+ // in case of failed authentication
+ authenticate(username, password);
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ username = null;
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ Set principals = subject.getPrincipals();
+ principals.add(new UserPrincipal(username));
+ for (RolePrincipal gp : groups) {
+ principals.add(gp);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ username = null;
+ return true;
+ }
+
+ protected void close(DirContext context) {
+ try {
+ context.close();
+ }
+ catch (Exception e) {
+ ActiveMQServerLogger.LOGGER.error(e.toString());
+ }
+ }
+
+ protected boolean authenticate(String username, String password) throws LoginException {
+
+ MessageFormat userSearchMatchingFormat;
+ boolean userSearchSubtreeBool;
+
+ DirContext context = null;
+
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("Create the LDAP initial context.");
+ }
+ try {
+ context = open();
+ }
+ catch (NamingException ne) {
+ FailedLoginException ex = new FailedLoginException("Error opening LDAP connection");
+ ex.initCause(ne);
+ throw ex;
+ }
+
+ if (!isLoginPropertySet(USER_SEARCH_MATCHING))
+ return false;
+
+ userSearchMatchingFormat = new MessageFormat(getLDAPPropertyValue(USER_SEARCH_MATCHING));
+ userSearchSubtreeBool = Boolean.valueOf(getLDAPPropertyValue(USER_SEARCH_SUBTREE)).booleanValue();
+
+ try {
+
+ String filter = userSearchMatchingFormat.format(new String[]{doRFC2254Encoding(username)});
+ SearchControls constraints = new SearchControls();
+ if (userSearchSubtreeBool) {
+ constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ }
+ else {
+ constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+ }
+
+ // setup attributes
+ List list = new ArrayList();
+ if (isLoginPropertySet(USER_ROLE_NAME)) {
+ list.add(getLDAPPropertyValue(USER_ROLE_NAME));
+ }
+ String[] attribs = new String[list.size()];
+ list.toArray(attribs);
+ constraints.setReturningAttributes(attribs);
+
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("Get the user DN.");
+ ActiveMQServerLogger.LOGGER.debug("Looking for the user in LDAP with ");
+ ActiveMQServerLogger.LOGGER.debug(" base DN: " + getLDAPPropertyValue(USER_BASE));
+ ActiveMQServerLogger.LOGGER.debug(" filter: " + filter);
+ }
+
+ NamingEnumeration results = context.search(getLDAPPropertyValue(USER_BASE), filter, constraints);
+
+ if (results == null || !results.hasMore()) {
+ ActiveMQServerLogger.LOGGER.warn("User " + username + " not found in LDAP.");
+ throw new FailedLoginException("User " + username + " not found in LDAP.");
+ }
+
+ SearchResult result = results.next();
+
+ if (results.hasMore()) {
+ // ignore for now
+ }
+
+ String dn;
+ if (result.isRelative()) {
+ ActiveMQServerLogger.LOGGER.debug("LDAP returned a relative name: " + result.getName());
+
+ NameParser parser = context.getNameParser("");
+ Name contextName = parser.parse(context.getNameInNamespace());
+ Name baseName = parser.parse(getLDAPPropertyValue(USER_BASE));
+ Name entryName = parser.parse(result.getName());
+ Name name = contextName.addAll(baseName);
+ name = name.addAll(entryName);
+ dn = name.toString();
+ }
+ else {
+ ActiveMQServerLogger.LOGGER.debug("LDAP returned an absolute name: " + result.getName());
+
+ try {
+ URI uri = new URI(result.getName());
+ String path = uri.getPath();
+
+ if (path.startsWith("/")) {
+ dn = path.substring(1);
+ }
+ else {
+ dn = path;
+ }
+ }
+ catch (URISyntaxException e) {
+ if (context != null) {
+ close(context);
+ }
+ FailedLoginException ex = new FailedLoginException("Error parsing absolute name as URI.");
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("Using DN [" + dn + "] for binding.");
+ }
+
+ Attributes attrs = result.getAttributes();
+ if (attrs == null) {
+ throw new FailedLoginException("User found, but LDAP entry malformed: " + username);
+ }
+ List roles = null;
+ if (isLoginPropertySet(USER_ROLE_NAME)) {
+ roles = addAttributeValues(getLDAPPropertyValue(USER_ROLE_NAME), attrs, roles);
+ }
+
+ // check the credentials by binding to server
+ if (bindUser(context, dn, password)) {
+ // if authenticated add more roles
+ roles = getRoles(context, dn, username, roles);
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("Roles " + roles + " for user " + username);
+ }
+ for (int i = 0; i < roles.size(); i++) {
+ groups.add(new RolePrincipal(roles.get(i)));
+ }
+ }
+ else {
+ throw new FailedLoginException("Password does not match for user: " + username);
+ }
+ }
+ catch (CommunicationException e) {
+ FailedLoginException ex = new FailedLoginException("Error contacting LDAP");
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (NamingException e) {
+ if (context != null) {
+ close(context);
+ }
+ FailedLoginException ex = new FailedLoginException("Error contacting LDAP");
+ ex.initCause(e);
+ throw ex;
+ }
+
+ return true;
+ }
+
+ protected List getRoles(DirContext context,
+ String dn,
+ String username,
+ List currentRoles) throws NamingException {
+ List list = currentRoles;
+ MessageFormat roleSearchMatchingFormat;
+ boolean roleSearchSubtreeBool;
+ boolean expandRolesBool;
+ roleSearchMatchingFormat = new MessageFormat(getLDAPPropertyValue(ROLE_SEARCH_MATCHING));
+ roleSearchSubtreeBool = Boolean.valueOf(getLDAPPropertyValue(ROLE_SEARCH_SUBTREE)).booleanValue();
+ expandRolesBool = Boolean.valueOf(getLDAPPropertyValue(EXPAND_ROLES)).booleanValue();
+
+ if (list == null) {
+ list = new ArrayList();
+ }
+ if (!isLoginPropertySet(ROLE_NAME)) {
+ return list;
+ }
+ String filter = roleSearchMatchingFormat.format(new String[]{doRFC2254Encoding(dn), doRFC2254Encoding(username)});
+
+ SearchControls constraints = new SearchControls();
+ if (roleSearchSubtreeBool) {
+ constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ }
+ else {
+ constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+ }
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("Get user roles.");
+ ActiveMQServerLogger.LOGGER.debug("Looking for the user roles in LDAP with ");
+ ActiveMQServerLogger.LOGGER.debug(" base DN: " + getLDAPPropertyValue(ROLE_BASE));
+ ActiveMQServerLogger.LOGGER.debug(" filter: " + filter);
+ }
+ HashSet haveSeenNames = new HashSet();
+ Queue pendingNameExpansion = new LinkedList();
+ NamingEnumeration results = context.search(getLDAPPropertyValue(ROLE_BASE), filter, constraints);
+ while (results.hasMore()) {
+ SearchResult result = results.next();
+ Attributes attrs = result.getAttributes();
+ if (expandRolesBool) {
+ haveSeenNames.add(result.getNameInNamespace());
+ pendingNameExpansion.add(result.getNameInNamespace());
+ }
+ if (attrs == null) {
+ continue;
+ }
+ list = addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, list);
+ }
+ if (expandRolesBool) {
+ MessageFormat expandRolesMatchingFormat = new MessageFormat(getLDAPPropertyValue(EXPAND_ROLES_MATCHING));
+ while (!pendingNameExpansion.isEmpty()) {
+ String name = pendingNameExpansion.remove();
+ filter = expandRolesMatchingFormat.format(new String[]{name});
+ results = context.search(getLDAPPropertyValue(ROLE_BASE), filter, constraints);
+ while (results.hasMore()) {
+ SearchResult result = results.next();
+ name = result.getNameInNamespace();
+ if (!haveSeenNames.contains(name)) {
+ Attributes attrs = result.getAttributes();
+ list = addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, list);
+ haveSeenNames.add(name);
+ pendingNameExpansion.add(name);
+ }
+ }
+ }
+ }
+ return list;
+ }
+
+ protected String doRFC2254Encoding(String inputString) {
+ StringBuffer buf = new StringBuffer(inputString.length());
+ for (int i = 0; i < inputString.length(); i++) {
+ char c = inputString.charAt(i);
+ switch (c) {
+ case '\\':
+ buf.append("\\5c");
+ break;
+ case '*':
+ buf.append("\\2a");
+ break;
+ case '(':
+ buf.append("\\28");
+ break;
+ case ')':
+ buf.append("\\29");
+ break;
+ case '\0':
+ buf.append("\\00");
+ break;
+ default:
+ buf.append(c);
+ break;
+ }
+ }
+ return buf.toString();
+ }
+
+ protected boolean bindUser(DirContext context, String dn, String password) throws NamingException {
+ boolean isValid = false;
+
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("Binding the user.");
+ }
+ context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
+ context.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
+ try {
+ context.getAttributes("", null);
+ isValid = true;
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("User " + dn + " successfully bound.");
+ }
+ }
+ catch (AuthenticationException e) {
+ isValid = false;
+ if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
+ ActiveMQServerLogger.LOGGER.debug("Authentication failed for dn=" + dn);
+ }
+ }
+
+ if (isLoginPropertySet(CONNECTION_USERNAME)) {
+ context.addToEnvironment(Context.SECURITY_PRINCIPAL, getLDAPPropertyValue(CONNECTION_USERNAME));
+ }
+ else {
+ context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
+ }
+ if (isLoginPropertySet(CONNECTION_PASSWORD)) {
+ context.addToEnvironment(Context.SECURITY_CREDENTIALS, getLDAPPropertyValue(CONNECTION_PASSWORD));
+ }
+ else {
+ context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
+ }
+
+ return isValid;
+ }
+
+ private List addAttributeValues(String attrId,
+ Attributes attrs,
+ List values) throws NamingException {
+
+ if (attrId == null || attrs == null) {
+ return values;
+ }
+ if (values == null) {
+ values = new ArrayList();
+ }
+ Attribute attr = attrs.get(attrId);
+ if (attr == null) {
+ return values;
+ }
+ NamingEnumeration> e = attr.getAll();
+ while (e.hasMore()) {
+ String value = (String) e.next();
+ values.add(value);
+ }
+ return values;
+ }
+
+ protected DirContext open() throws NamingException {
+ try {
+ Hashtable env = new Hashtable();
+ env.put(Context.INITIAL_CONTEXT_FACTORY, getLDAPPropertyValue(INITIAL_CONTEXT_FACTORY));
+ if (isLoginPropertySet(CONNECTION_USERNAME)) {
+ env.put(Context.SECURITY_PRINCIPAL, getLDAPPropertyValue(CONNECTION_USERNAME));
+ }
+ else {
+ throw new NamingException("Empty username is not allowed");
+ }
+
+ if (isLoginPropertySet(CONNECTION_PASSWORD)) {
+ env.put(Context.SECURITY_CREDENTIALS, getLDAPPropertyValue(CONNECTION_PASSWORD));
+ }
+ else {
+ throw new NamingException("Empty password is not allowed");
+ }
+ env.put(Context.SECURITY_PROTOCOL, getLDAPPropertyValue(CONNECTION_PROTOCOL));
+ env.put(Context.PROVIDER_URL, getLDAPPropertyValue(CONNECTION_URL));
+ env.put(Context.SECURITY_AUTHENTICATION, getLDAPPropertyValue(AUTHENTICATION));
+ context = new InitialDirContext(env);
+
+ }
+ catch (NamingException e) {
+ ActiveMQServerLogger.LOGGER.error(e.toString());
+ throw e;
+ }
+ return context;
+ }
+
+ private String getLDAPPropertyValue(String propertyName) {
+ for (int i = 0; i < config.length; i++)
+ if (config[i].getPropertyName() == propertyName)
+ return config[i].getPropertyValue();
+ return null;
+ }
+
+ private boolean isLoginPropertySet(String propertyName) {
+ for (int i = 0; i < config.length; i++) {
+ if (config[i].getPropertyName() == propertyName && (config[i].getPropertyValue() != null && !"".equals(config[i].getPropertyValue())))
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginProperty.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginProperty.java
new file mode 100644
index 0000000000..139d6c62cd
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginProperty.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.spi.core.security.jaas;
+
+public class LDAPLoginProperty {
+
+ private String name;
+ private String value;
+
+ public LDAPLoginProperty(String name) {
+ this.name = name;
+ }
+
+ public LDAPLoginProperty(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getPropertyName() {
+ return this.name;
+ }
+
+ public String getPropertyValue() {
+ return this.value;
+ }
+
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java
new file mode 100644
index 0000000000..97e2355e5e
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java
@@ -0,0 +1,75 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+class PrincipalProperties {
+
+ private final Properties principals;
+ private final long reloadTime;
+
+ PrincipalProperties(final String type, final File source, final ActiveMQServerLogger log) {
+ Properties props = new Properties();
+ long reloadTime = 0;
+ try {
+ load(source, props);
+ reloadTime = System.currentTimeMillis();
+ }
+ catch (IOException ioe) {
+ ioe.printStackTrace();
+ log.warn("Unable to load " + type + " properties file " + source);
+ }
+ this.reloadTime = reloadTime;
+ this.principals = props;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ Set> entries() {
+ return (Set) principals.entrySet();
+ }
+
+ String getProperty(String name) {
+ return principals.getProperty(name);
+ }
+
+ long getReloadTime() {
+ return reloadTime;
+ }
+
+ private void load(final File source, Properties props) throws FileNotFoundException, IOException {
+ FileInputStream in = new FileInputStream(source);
+ try {
+ props.load(in);
+ }
+ finally {
+ in.close();
+ }
+ }
+
+ Properties getPrincipals() {
+ return principals;
+ }
+}
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
new file mode 100644
index 0000000000..6b96ed0b2f
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
@@ -0,0 +1,215 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.io.File;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+public class PropertiesLoginModule implements LoginModule {
+
+ private static final String USER_FILE = "org.apache.activemq.jaas.properties.user";
+ private static final String GROUP_FILE = "org.apache.activemq.jaas.properties.role";
+
+ private Subject subject;
+ private CallbackHandler callbackHandler;
+
+ private boolean debug;
+ private boolean reload = false;
+ private static volatile PrincipalProperties users;
+ private static volatile PrincipalProperties roles;
+ private String user;
+ private final Set principals = new HashSet();
+ private File baseDir;
+ private boolean loginSucceeded;
+ // private boolean decrypt = true;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ loginSucceeded = false;
+
+ debug = "true".equalsIgnoreCase((String) options.get("debug"));
+ if (options.get("reload") != null) {
+ reload = "true".equalsIgnoreCase((String) options.get("reload"));
+ }
+
+ if (options.get("baseDir") != null) {
+ baseDir = new File((String) options.get("baseDir"));
+ }
+
+ setBaseDir();
+ String usersFile = options.get(USER_FILE) + "";
+ File uf = baseDir != null ? new File(baseDir, usersFile) : new File(usersFile);
+
+ if (reload || users == null || uf.lastModified() > users.getReloadTime()) {
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Reloading users from " + uf.getAbsolutePath());
+ }
+ users = new PrincipalProperties("user", uf, ActiveMQServerLogger.LOGGER);
+ // if( decrypt ) {
+ // try {
+ // EncryptionSupport.decrypt(users.getPrincipals());
+ // } catch(NoClassDefFoundError e) {
+ // // this Happens whe jasypt is not on the classpath..
+ // decrypt = false;
+ // ActiveMQServerLogger.LOGGER.info("jasypt is not on the classpath: password decryption disabled.");
+ // }
+ // }
+ }
+
+ String groupsFile = options.get(GROUP_FILE) + "";
+ File gf = baseDir != null ? new File(baseDir, groupsFile) : new File(groupsFile);
+ if (reload || roles == null || gf.lastModified() > roles.getReloadTime()) {
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Reloading roles from " + gf.getAbsolutePath());
+ }
+ roles = new PrincipalProperties("role", gf, ActiveMQServerLogger.LOGGER);
+ }
+ }
+
+ private void setBaseDir() {
+ if (baseDir == null) {
+ if (System.getProperty("java.security.auth.login.config") != null) {
+ baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Using basedir=" + baseDir.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ Callback[] callbacks = new Callback[2];
+
+ callbacks[0] = new NameCallback("Username: ");
+ callbacks[1] = new PasswordCallback("Password: ", false);
+ try {
+ callbackHandler.handle(callbacks);
+ }
+ catch (IOException ioe) {
+ throw new LoginException(ioe.getMessage());
+ }
+ catch (UnsupportedCallbackException uce) {
+ throw new LoginException(uce.getMessage() + " not available to obtain information from user");
+ }
+ user = ((NameCallback) callbacks[0]).getName();
+ char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
+ if (tmpPassword == null) {
+ tmpPassword = new char[0];
+ }
+ if (user == null) {
+ throw new FailedLoginException("user name is null");
+ }
+ String password = users.getProperty(user);
+
+ if (password == null) {
+ throw new FailedLoginException("User does exist");
+ }
+ if (!password.equals(new String(tmpPassword))) {
+ throw new FailedLoginException("Password does not match");
+ }
+ loginSucceeded = true;
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("login " + user);
+ }
+ return loginSucceeded;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ boolean result = loginSucceeded;
+ if (result) {
+ principals.add(new UserPrincipal(user));
+
+ for (Map.Entry entry : roles.entries()) {
+ String name = entry.getKey();
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("Inspecting role '" + name + "' with user(s): " + entry.getValue());
+ }
+ String[] userList = entry.getValue().split(",");
+ for (int i = 0; i < userList.length; i++) {
+ if (user.equals(userList[i])) {
+ principals.add(new RolePrincipal(name));
+ break;
+ }
+ }
+ }
+
+ subject.getPrincipals().addAll(principals);
+ }
+
+ // will whack loginSucceeded
+ clear();
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("commit, result: " + result);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ clear();
+
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("abort");
+ }
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ subject.getPrincipals().removeAll(principals);
+ principals.clear();
+ clear();
+ if (debug) {
+ ActiveMQServerLogger.LOGGER.debug("logout");
+ }
+ return true;
+ }
+
+ private void clear() {
+ user = null;
+ loginSucceeded = false;
+ }
+
+ /**
+ * For test-usage only.
+ */
+ public static void resetUsersAndGroupsCache() {
+ users = null;
+ roles = null;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/RolePrincipal.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/RolePrincipal.java
new file mode 100644
index 0000000000..3e5afd1eb5
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/RolePrincipal.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.spi.core.security.jaas;
+
+import java.security.Principal;
+
+public class RolePrincipal implements Principal {
+
+ private final String name;
+ private transient int hash;
+
+ public RolePrincipal(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final RolePrincipal that = (RolePrincipal) o;
+
+ if (!name.equals(that.name)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ if (hash == 0) {
+ hash = name.hashCode();
+ }
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java
new file mode 100644
index 0000000000..77f5a43fc5
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java
@@ -0,0 +1,147 @@
+/*
+ * 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.spi.core.security.jaas;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import java.io.File;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * A LoginModule allowing for SSL certificate based authentication based on
+ * Distinguished Names (DN) stored in text files. The DNs are parsed using a
+ * Properties class where each line is =. This class also
+ * uses a group definition file where each line is =,,etc.
+ * The user and group files' locations must be specified in the
+ * org.apache.activemq.jaas.textfiledn.user and
+ * org.apache.activemq.jaas.textfiledn.user properties respectively. NOTE: This
+ * class will re-read user and group files for every authentication (i.e it does
+ * live updates of allowed groups and users).
+ */
+public class TextFileCertificateLoginModule extends CertificateLoginModule {
+
+ private static final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user";
+ private static final String GROUP_FILE = "org.apache.activemq.jaas.textfiledn.group";
+
+ private File baseDir;
+ private String usersFilePathname;
+ private String groupsFilePathname;
+
+ /**
+ * Performs initialization of file paths. A standard JAAS override.
+ */
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+ super.initialize(subject, callbackHandler, sharedState, options);
+ if (System.getProperty("java.security.auth.login.config") != null) {
+ baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
+ }
+ else {
+ baseDir = new File(".");
+ }
+
+ usersFilePathname = (String) options.get(USER_FILE) + "";
+ groupsFilePathname = (String) options.get(GROUP_FILE) + "";
+ }
+
+ /**
+ * Overriding to allow DN authorization based on DNs specified in text
+ * files.
+ *
+ * @param certs The certificate the incoming connection provided.
+ * @return The user's authenticated name or null if unable to authenticate
+ * the user.
+ * @throws LoginException Thrown if unable to find user file or connection
+ * certificate.
+ */
+ @Override
+ protected String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException {
+ if (certs == null) {
+ throw new LoginException("Client certificates not found. Cannot authenticate.");
+ }
+
+ File usersFile = new File(baseDir, usersFilePathname);
+
+ Properties users = new Properties();
+
+ try {
+ java.io.FileInputStream in = new java.io.FileInputStream(usersFile);
+ users.load(in);
+ in.close();
+ }
+ catch (IOException ioe) {
+ throw new LoginException("Unable to load user properties file " + usersFile);
+ }
+
+ String dn = getDistinguishedName(certs);
+
+ Enumeration