ARTEMIS-74 import JAAS auth from 5.x This change allows the use of JAAS login modules for basic authentication and authorization.
Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/6ed9c5ae Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/6ed9c5ae Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/6ed9c5ae Branch: refs/heads/master Commit: 6ed9c5ae91dc7a08cdb3825fb17a5da24037fa36 Parents: e971f11 Author: jbertram <[email protected]> Authored: Thu Oct 1 16:58:26 2015 -0500 Committer: jbertram <[email protected]> Committed: Fri Oct 9 11:42:22 2015 -0500 ---------------------------------------------------------------------- .../activemq/artemis/cli/commands/Create.java | 53 +- .../artemis/factory/JaasSecurityHandler.java | 32 ++ .../artemis/broker/security/jaas-security | 17 + .../commands/etc/artemis-roles-basic.properties | 17 + .../commands/etc/artemis-roles-jaas.properties | 17 + .../cli/commands/etc/artemis-roles.properties | 17 - .../artemis/cli/commands/etc/artemis.profile | 2 +- .../cli/commands/etc/artemis.profile.cmd | 2 +- .../etc/basic-broker-security-settings.txt | 5 + .../artemis/cli/commands/etc/bootstrap.xml | 5 +- .../etc/jaas-broker-security-settings.txt | 2 + .../artemis/cli/commands/etc/login.config | 22 + .../activemq/cli/test/StreamClassPathTest.java | 5 +- .../activemq/artemis/dto/JaasSecurityDTO.java | 30 ++ .../org/apache/activemq/artemis/dto/jaxb.index | 7 +- .../artemis/maven/ArtemisCreatePlugin.java | 5 +- artemis-server/pom.xml | 18 + .../security/ActiveMQJAASSecurityManager.java | 114 +++++ .../core/security/ActiveMQSecurityManager2.java | 2 +- .../spi/core/security/JAASSecurityManager.java | 216 -------- .../core/security/jaas/CertificateCallback.java | 48 ++ .../security/jaas/CertificateLoginModule.java | 183 +++++++ .../core/security/jaas/GuestLoginModule.java | 132 +++++ .../jaas/JaasCertificateCallbackHandler.java | 65 +++ .../jaas/JaasCredentialCallbackHandler.java | 63 +++ .../spi/core/security/jaas/LDAPLoginModule.java | 505 +++++++++++++++++++ .../core/security/jaas/LDAPLoginProperty.java | 41 ++ .../core/security/jaas/PrincipalProperties.java | 75 +++ .../security/jaas/PropertiesLoginModule.java | 215 ++++++++ .../spi/core/security/jaas/RolePrincipal.java | 68 +++ .../jaas/TextFileCertificateLoginModule.java | 147 ++++++ .../spi/core/security/jaas/UserPrincipal.java | 68 +++ .../jaas/CertificateLoginModuleTest.java | 154 ++++++ .../security/jaas/GuestLoginModuleTest.java | 91 ++++ .../core/security/jaas/LDAPLoginModuleTest.java | 149 ++++++ .../jaas/LDAPModuleRoleExpansionTest.java | 136 +++++ .../PropertiesLoginModuleRaceConditionTest.java | 195 +++++++ .../jaas/PropertiesLoginModuleTest.java | 130 +++++ .../core/security/jaas/RolePrincipalTest.java | 61 +++ .../jaas/StubCertificateLoginModule.java | 47 ++ .../core/security/jaas/UserPrincipalTest.java | 61 +++ .../artemis/tests/util/ActiveMQTestBase.java | 24 +- artemis-server/src/test/resources/login.config | 118 +++++ .../src/test/resources/roles.properties | 20 + artemis-server/src/test/resources/test.ldif | 39 ++ .../src/test/resources/users.properties | 19 + docs/user-manual/en/security.md | 288 ++++++++++- examples/features/standard/pom.xml | 2 + .../features/standard/security-jaas/pom.xml | 111 ++++ .../features/standard/security-jaas/readme.html | 324 ++++++++++++ .../jms/example/JaasSecurityExample.java | 282 +++++++++++ .../activemq/server0/artemis-roles.properties | 20 + .../activemq/server0/artemis-users.properties | 20 + .../main/resources/activemq/server0/broker.xml | 81 +++ .../src/main/resources/jndi.properties | 22 + pom.xml | 2 + tests/integration-tests/pom.xml | 18 + .../integration/security/LDAPSecurityTest.java | 347 +++++++++++++ .../integration/security/SecurityTest.java | 314 +++++++++++- .../src/test/resources/login.config | 118 +++++ .../src/test/resources/roles.properties | 20 + .../src/test/resources/test.ldif | 39 ++ .../src/test/resources/users.properties | 19 + 63 files changed, 5186 insertions(+), 283 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/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 0a430a3..0c558f8 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(""); http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/java/org/apache/activemq/artemis/factory/JaasSecurityHandler.java ---------------------------------------------------------------------- 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 0000000..2cd1785 --- /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; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/META-INF/services/org/apache/activemq/artemis/broker/security/jaas-security ---------------------------------------------------------------------- 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 0000000..013a63c --- /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 http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/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-basic.properties b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles-basic.properties new file mode 100644 index 0000000..04c3c4c --- /dev/null +++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles-basic.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. +## --------------------------------------------------------------------------- +${user}=${role} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles-jaas.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 0000000..c9443dd --- /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 http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/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 deleted file mode 100644 index 04c3c4c..0000000 --- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties +++ /dev/null @@ -1,17 +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. -## --------------------------------------------------------------------------- -${user}=${role} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile ---------------------------------------------------------------------- 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 2a51e2a..76ca12f 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 http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis.profile.cmd ---------------------------------------------------------------------- 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 c52d70f..835c7b7 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 http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-broker-security-settings.txt ---------------------------------------------------------------------- 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 0000000..dd0a5f1 --- /dev/null +++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/basic-broker-security-settings.txt @@ -0,0 +1,5 @@ + + <basic-security + users="file:${artemis.instance}/etc/artemis-users.properties" + roles="file:${artemis.instance}/etc/artemis-roles.properties" + ${bootstrap.guest}/> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/bootstrap.xml ---------------------------------------------------------------------- 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 be51734..fe3f864 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 xmlns="http://activemq.org/schema"> - <basic-security - users="file:${artemis.instance}/etc/artemis-users.properties" - roles="file:${artemis.instance}/etc/artemis-roles.properties" - ${bootstrap.guest}/> +${broker-security-settings} <server configuration="file:${artemis.instance}/etc/broker.xml"/> http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-broker-security-settings.txt ---------------------------------------------------------------------- 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 0000000..6521bf4 --- /dev/null +++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/jaas-broker-security-settings.txt @@ -0,0 +1,2 @@ + + <jaas-security login-module="PropertiesLogin"/> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/login.config ---------------------------------------------------------------------- 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 0000000..fe8ca36 --- /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 http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-cli/src/test/java/org/apache/activemq/cli/test/StreamClassPathTest.java ---------------------------------------------------------------------- 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 21579dc..e1d045d 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 { http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/JaasSecurityDTO.java ---------------------------------------------------------------------- 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 0000000..99163cf --- /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; +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-dto/src/main/resources/org/apache/activemq/artemis/dto/jaxb.index ---------------------------------------------------------------------- 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 5803f73..b0bacd7 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 http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-maven-plugin/src/main/java/org/apache/activemq/artemis/maven/ArtemisCreatePlugin.java ---------------------------------------------------------------------- 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 39b6d8e..ba6cb8e 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<String> 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"); http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/pom.xml ---------------------------------------------------------------------- diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml index 652ef81..36cef29 100644 --- a/artemis-server/pom.xml +++ b/artemis-server/pom.xml @@ -82,6 +82,24 @@ <artifactId>junit</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.directory.server</groupId> + <artifactId>apacheds-server-integ</artifactId> + <version>${directory-version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.directory.server</groupId> + <artifactId>apacheds-core-integ</artifactId> + <version>${directory-version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>bouncycastle</groupId> + <artifactId>bcprov-jdk15</artifactId> + </exclusion> + </exclusions> + </dependency> </dependencies> <profiles> http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java ---------------------------------------------------------------------- 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 0000000..6a13f22 --- /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<Role> 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<RolePrincipal> rolesWithPermission = getPrincipalsInRole(checkType, roles); + + // Check the caller's roles + Set<RolePrincipal> rolesForSubject = localSubject.getPrincipals(RolePrincipal.class); + if (rolesForSubject.size() > 0 && rolesWithPermission.size() > 0) { + Iterator<RolePrincipal> rolesForSubjectIter = rolesForSubject.iterator(); + while (!authorized && rolesForSubjectIter.hasNext()) { + Iterator<RolePrincipal> 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<RolePrincipal> getPrincipalsInRole(final CheckType checkType, final Set<Role> roles) { + Set<RolePrincipal> 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; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager2.java ---------------------------------------------------------------------- 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 2962153..1e3cb10 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<Role> roles, CheckType checkType, String address); -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/JAASSecurityManager.java ---------------------------------------------------------------------- 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 48699b6..0000000 --- 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 <code>Roles</code> 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<Role> roles, - final CheckType checkType) { - Subject localSubject = null; - try { - localSubject = getAuthenticatedSubject(user, password); - } - catch (LoginException e1) { - return false; - } - - boolean authenticated = true; - - if (localSubject != null) { - Set<Principal> 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<Principal> 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<Group> subjectGroups = subject.getPrincipals(Group.class); - Iterator<Group> 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<Principal> getRolePrincipals(final CheckType checkType, final Set<Role> roles) { - Set<Principal> principals = new HashSet<Principal>(); - 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; - } - } - -} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateCallback.java ---------------------------------------------------------------------- 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 0000000..630dd32 --- /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; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java ---------------------------------------------------------------------- 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 0000000..db8808b --- /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<String> groups; + private Set<Principal> principals = new HashSet<Principal>(); + 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<String> 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; + } + } + +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/GuestLoginModule.java ---------------------------------------------------------------------- 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 0000000..dbea86b --- /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<Principal> principals = new HashSet<Principal>(); + 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; + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCertificateCallbackHandler.java ---------------------------------------------------------------------- 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 0000000..b53f946 --- /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); + } + } + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/JaasCredentialCallbackHandler.java ---------------------------------------------------------------------- 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 0000000..34ae701 --- /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); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/6ed9c5ae/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java ---------------------------------------------------------------------- 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 0000000..6830828 --- /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<RolePrincipal> groups = new HashSet<RolePrincipal>(); + + @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_S EARCH_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<Principal> 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<String> list = new ArrayList<String>(); + 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<SearchResult> 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<String> 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<String> getRoles(DirContext context, + String dn, + String username, + List<String> currentRoles) throws NamingException { + List<String> 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<String>(); + } + 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<String> haveSeenNames = new HashSet<String>(); + Queue<String> pendingNameExpansion = new LinkedList<String>(); + NamingEnumeration<SearchResult> 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<String> addAttributeValues(String attrId, + Attributes attrs, + List<String> values) throws NamingException { + + if (attrId == null || attrs == null) { + return values; + } + if (values == null) { + values = new ArrayList<String>(); + } + 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<String, String> env = new Hashtable<String, String>(); + 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; + } + +}
