This closes #188
This commit is contained in:
commit
1b49559c64
|
@ -70,8 +70,16 @@ public class Create extends InputAbstract {
|
||||||
public static final String ETC_LOGGING_PROPERTIES = "etc/logging.properties";
|
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_BOOTSTRAP_XML = "etc/bootstrap.xml";
|
||||||
public static final String ETC_BROKER_XML = "etc/broker.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_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_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_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_SHARED_STORE_SETTINGS_TXT = "etc/shared-store-settings.txt";
|
||||||
public static final String ETC_CLUSTER_SECURITY_SETTINGS_TXT = "etc/cluster-security-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.")
|
@Option(name = "--aio", description = "Force aio journal on the configuration regardless of the library being available or not.")
|
||||||
boolean forceLibaio;
|
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_WINDOWS;
|
||||||
|
|
||||||
boolean IS_CYGWIN;
|
boolean IS_CYGWIN;
|
||||||
|
|
||||||
|
public String getBrokerSecurity() {
|
||||||
|
if (brokerSecurity == null) {
|
||||||
|
brokerSecurity = "basic";
|
||||||
|
}
|
||||||
|
return brokerSecurity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerSecurity(String security) {
|
||||||
|
this.brokerSecurity = security;
|
||||||
|
}
|
||||||
|
|
||||||
public int getMaxHops() {
|
public int getMaxHops() {
|
||||||
return maxHops;
|
return maxHops;
|
||||||
}
|
}
|
||||||
|
@ -535,6 +557,29 @@ public class Create extends InputAbstract {
|
||||||
|
|
||||||
filters.put("${java-opts}", javaOptions);
|
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) {
|
if (IS_WINDOWS) {
|
||||||
write(BIN_ARTEMIS_CMD, null, false);
|
write(BIN_ARTEMIS_CMD, null, false);
|
||||||
write(BIN_ARTEMIS_SERVICE_EXE);
|
write(BIN_ARTEMIS_SERVICE_EXE);
|
||||||
|
@ -553,13 +598,6 @@ public class Create extends InputAbstract {
|
||||||
|
|
||||||
write(ETC_LOGGING_PROPERTIES, null, false);
|
write(ETC_LOGGING_PROPERTIES, null, false);
|
||||||
|
|
||||||
if (isAllowAnonymous()) {
|
|
||||||
filters.put("${bootstrap.guest}", "default-user=\"" + getUser() + "\"");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
filters.put("${bootstrap.guest}", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noWeb) {
|
if (noWeb) {
|
||||||
filters.put("${bootstrap-web-settings}", "");
|
filters.put("${bootstrap-web-settings}", "");
|
||||||
}
|
}
|
||||||
|
@ -571,7 +609,6 @@ public class Create extends InputAbstract {
|
||||||
|
|
||||||
write(ETC_BOOTSTRAP_XML, filters, false);
|
write(ETC_BOOTSTRAP_XML, filters, false);
|
||||||
write(ETC_BROKER_XML, filters, false);
|
write(ETC_BROKER_XML, filters, false);
|
||||||
write(ETC_ARTEMIS_ROLES_PROPERTIES, filters, false);
|
|
||||||
write(ETC_ARTEMIS_USERS_PROPERTIES, filters, false);
|
write(ETC_ARTEMIS_USERS_PROPERTIES, filters, false);
|
||||||
|
|
||||||
context.out.println("");
|
context.out.println("");
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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}
|
|
@ -23,7 +23,7 @@ ARTEMIS_HOME='${artemis.home}'
|
||||||
|
|
||||||
|
|
||||||
# Java Opts
|
# 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
|
# Debug args: Uncomment to enable debug
|
||||||
|
|
|
@ -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 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
|
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 Debug args: Uncomment to enable debug
|
||||||
rem set DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
|
rem set DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
<basic-security
|
||||||
|
users="file:${artemis.instance}/etc/artemis-users.properties"
|
||||||
|
roles="file:${artemis.instance}/etc/artemis-roles.properties"
|
||||||
|
${bootstrap.guest}/>
|
|
@ -18,10 +18,7 @@
|
||||||
|
|
||||||
<broker xmlns="http://activemq.org/schema">
|
<broker xmlns="http://activemq.org/schema">
|
||||||
|
|
||||||
<basic-security
|
${broker-security-settings}
|
||||||
users="file:${artemis.instance}/etc/artemis-users.properties"
|
|
||||||
roles="file:${artemis.instance}/etc/artemis-roles.properties"
|
|
||||||
${bootstrap.guest}/>
|
|
||||||
|
|
||||||
<server configuration="file:${artemis.instance}/etc/broker.xml"/>
|
<server configuration="file:${artemis.instance}/etc/broker.xml"/>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
<jaas-security login-module="PropertiesLogin"/>
|
|
@ -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";
|
||||||
|
};
|
|
@ -40,7 +40,8 @@ public class StreamClassPathTest {
|
||||||
openStream(Create.ETC_LOGGING_PROPERTIES);
|
openStream(Create.ETC_LOGGING_PROPERTIES);
|
||||||
openStream(Create.ETC_BOOTSTRAP_XML);
|
openStream(Create.ETC_BOOTSTRAP_XML);
|
||||||
openStream(Create.ETC_BROKER_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_ARTEMIS_USERS_PROPERTIES);
|
||||||
openStream(Create.ETC_REPLICATED_SETTINGS_TXT);
|
openStream(Create.ETC_REPLICATED_SETTINGS_TXT);
|
||||||
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_CONNECTOR_SETTINGS_TXT);
|
||||||
openStream(Create.ETC_BOOTSTRAP_WEB_SETTINGS_TXT);
|
openStream(Create.ETC_BOOTSTRAP_WEB_SETTINGS_TXT);
|
||||||
openStream(Create.ETC_JOURNAL_BUFFER_SETTINGS);
|
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 {
|
private void openStream(String source) throws Exception {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -2,19 +2,20 @@
|
||||||
## Licensed to the Apache Software Foundation (ASF) under one or more
|
## Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
## contributor license agreements. See the NOTICE file distributed with
|
## contributor license agreements. See the NOTICE file distributed with
|
||||||
## this work for additional information regarding copyright ownership.
|
## 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 not use this file except in compliance with
|
||||||
## the License. You may obtain a copy of the License at
|
## the License. You may obtain a copy of the License at
|
||||||
##
|
##
|
||||||
## http://www.apache.org/licenses/LICENSE-2.0
|
## http://www.apache.org/licenses/LICENSE-2.0
|
||||||
##
|
##
|
||||||
## Unless required by applicable law or agreed to in writing, software
|
## Unless required by applicable law or agreed to in writing, software
|
||||||
## distributed under the License is distributed on an "AS IS" BASIS,
|
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
## 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.
|
## limitations under the License.
|
||||||
## ---------------------------------------------------------------------------
|
## ---------------------------------------------------------------------------
|
||||||
BrokerDTO
|
BrokerDTO
|
||||||
SecurityDTO
|
SecurityDTO
|
||||||
BasicSecurityDTO
|
BasicSecurityDTO
|
||||||
|
JaasSecurityDTO
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,9 @@ public class ArtemisCreatePlugin extends ArtemisAbstractPlugin {
|
||||||
@Parameter(defaultValue = "ON_DEMAND")
|
@Parameter(defaultValue = "ON_DEMAND")
|
||||||
private String messageLoadBalancing;
|
private String messageLoadBalancing;
|
||||||
|
|
||||||
|
@Parameter(defaultValue = "basic")
|
||||||
|
private String brokerSecurity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For extra stuff not covered by the properties
|
* For extra stuff not covered by the properties
|
||||||
*/
|
*/
|
||||||
|
@ -200,7 +203,7 @@ public class ArtemisCreatePlugin extends ArtemisAbstractPlugin {
|
||||||
|
|
||||||
ArrayList<String> listCommands = new ArrayList<>();
|
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) {
|
if (allowAnonymous) {
|
||||||
add(listCommands, "--allow-anonymous");
|
add(listCommands, "--allow-anonymous");
|
||||||
|
|
|
@ -82,6 +82,24 @@
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
* @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);
|
boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType, String address);
|
||||||
}
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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_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<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Map.Entry<String, String>> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Principal> principals = new HashSet<Principal>();
|
||||||
|
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<String, String> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <user_name>=<user_DN>. This class also
|
||||||
|
* uses a group definition file where each line is <group_name>=<user_name_1>,<user_name_2>,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<Object> keys = users.keys();
|
||||||
|
for (Enumeration<Object> vals = users.elements(); vals.hasMoreElements(); ) {
|
||||||
|
if (((String) vals.nextElement()).equals(dn)) {
|
||||||
|
return (String) keys.nextElement();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys.nextElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overriding to allow for group discovery based on text files.
|
||||||
|
*
|
||||||
|
* @param username The name of the user being examined. This is the same
|
||||||
|
* name returned by getUserNameForCertificates.
|
||||||
|
* @return A Set of name Strings for groups this user belongs to.
|
||||||
|
* @throws LoginException Thrown if unable to find group definition file.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Set<String> getUserGroups(String username) throws LoginException {
|
||||||
|
File groupsFile = new File(baseDir, groupsFilePathname);
|
||||||
|
|
||||||
|
Properties groups = new Properties();
|
||||||
|
try {
|
||||||
|
java.io.FileInputStream in = new java.io.FileInputStream(groupsFile);
|
||||||
|
groups.load(in);
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new LoginException("Unable to load group properties file " + groupsFile);
|
||||||
|
}
|
||||||
|
Set<String> userGroups = new HashSet<String>();
|
||||||
|
for (Enumeration<Object> enumeration = groups.keys(); enumeration.hasMoreElements(); ) {
|
||||||
|
String groupName = (String) enumeration.nextElement();
|
||||||
|
String[] userList = (groups.getProperty(groupName) + "").split(",");
|
||||||
|
for (int i = 0; i < userList.length; i++) {
|
||||||
|
if (username.equals(userList[i])) {
|
||||||
|
userGroups.add(groupName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return userGroups;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 UserPrincipal implements Principal {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private transient int hash;
|
||||||
|
|
||||||
|
public UserPrincipal(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 UserPrincipal that = (UserPrincipal) 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
* 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.core.security.jaas;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCertificateCallbackHandler;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class CertificateLoginModuleTest extends Assert {
|
||||||
|
|
||||||
|
private static final String USER_NAME = "testUser";
|
||||||
|
private static final List<String> GROUP_NAMES = new Vector<String>();
|
||||||
|
|
||||||
|
private StubCertificateLoginModule loginModule;
|
||||||
|
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
public CertificateLoginModuleTest() {
|
||||||
|
GROUP_NAMES.add("testGroup1");
|
||||||
|
GROUP_NAMES.add("testGroup2");
|
||||||
|
GROUP_NAMES.add("testGroup3");
|
||||||
|
GROUP_NAMES.add("testGroup4");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
subject = new Subject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loginWithCredentials(String userName, Set<String> groupNames) throws LoginException {
|
||||||
|
loginModule = new StubCertificateLoginModule(userName, new HashSet<String>(groupNames));
|
||||||
|
JaasCertificateCallbackHandler callbackHandler = new JaasCertificateCallbackHandler(null);
|
||||||
|
|
||||||
|
loginModule.initialize(subject, callbackHandler, null, new HashMap());
|
||||||
|
|
||||||
|
loginModule.login();
|
||||||
|
loginModule.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPrincipalsMatch(Subject subject) {
|
||||||
|
boolean nameFound = false;
|
||||||
|
boolean[] groupsFound = new boolean[GROUP_NAMES.size()];
|
||||||
|
for (int i = 0; i < groupsFound.length; ++i) {
|
||||||
|
groupsFound[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext(); ) {
|
||||||
|
Principal currentPrincipal = (Principal) iter.next();
|
||||||
|
|
||||||
|
if (currentPrincipal instanceof UserPrincipal) {
|
||||||
|
if (currentPrincipal.getName().equals(USER_NAME)) {
|
||||||
|
if (!nameFound) {
|
||||||
|
nameFound = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fail("UserPrincipal found twice.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fail("Unknown UserPrincipal found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (currentPrincipal instanceof RolePrincipal) {
|
||||||
|
int principalIdx = GROUP_NAMES.indexOf(((RolePrincipal) currentPrincipal).getName());
|
||||||
|
|
||||||
|
if (principalIdx < 0) {
|
||||||
|
fail("Unknown GroupPrincipal found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupsFound[principalIdx]) {
|
||||||
|
groupsFound[principalIdx] = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fail("GroupPrincipal found twice.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fail("Unknown Principal type found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginSuccess() throws IOException {
|
||||||
|
try {
|
||||||
|
loginWithCredentials(USER_NAME, new HashSet<String>(GROUP_NAMES));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail("Unable to login: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPrincipalsMatch(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginFailure() throws IOException {
|
||||||
|
boolean loginFailed = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
loginWithCredentials(null, new HashSet<String>());
|
||||||
|
}
|
||||||
|
catch (LoginException e) {
|
||||||
|
loginFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loginFailed) {
|
||||||
|
fail("Logged in with unknown certificate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogOut() throws IOException {
|
||||||
|
try {
|
||||||
|
loginWithCredentials(USER_NAME, new HashSet<String>(GROUP_NAMES));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail("Unable to login: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
loginModule.logout();
|
||||||
|
|
||||||
|
assertEquals("logout should have cleared Subject principals.", 0, subject.getPrincipals().size());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* 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.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.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class GuestLoginModuleTest extends Assert {
|
||||||
|
|
||||||
|
static {
|
||||||
|
String path = System.getProperty("java.security.auth.login.config");
|
||||||
|
if (path == null) {
|
||||||
|
URL resource = GuestLoginModuleTest.class.getClassLoader().getResource("login.config");
|
||||||
|
if (resource != null) {
|
||||||
|
path = resource.getFile();
|
||||||
|
System.setProperty("java.security.auth.login.config", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogin() throws LoginException {
|
||||||
|
LoginContext context = new LoginContext("GuestLogin", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
assertEquals("Should have no Callbacks", 0, callbacks.length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.login();
|
||||||
|
|
||||||
|
Subject subject = context.getSubject();
|
||||||
|
|
||||||
|
assertEquals("Should have two principals", 2, subject.getPrincipals().size());
|
||||||
|
assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size());
|
||||||
|
assertTrue("User principal is 'foo'", subject.getPrincipals(UserPrincipal.class).contains(new UserPrincipal("foo")));
|
||||||
|
|
||||||
|
assertEquals("Should have one group principal", 1, subject.getPrincipals(RolePrincipal.class).size());
|
||||||
|
assertTrue("Role principal is 'bar'", subject.getPrincipals(RolePrincipal.class).contains(new RolePrincipal("bar")));
|
||||||
|
|
||||||
|
context.logout();
|
||||||
|
|
||||||
|
assertEquals("Should have zero principals", 0, subject.getPrincipals().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginWithDefaults() throws LoginException {
|
||||||
|
LoginContext context = new LoginContext("GuestLoginWithDefaults", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
assertEquals("Should have no Callbacks", 0, callbacks.length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.login();
|
||||||
|
|
||||||
|
Subject subject = context.getSubject();
|
||||||
|
|
||||||
|
assertEquals("Should have two principals", 2, subject.getPrincipals().size());
|
||||||
|
assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size());
|
||||||
|
assertTrue("User principal is 'guest'", subject.getPrincipals(UserPrincipal.class).contains(new UserPrincipal("guest")));
|
||||||
|
|
||||||
|
assertEquals("Should have one group principal", 1, subject.getPrincipals(RolePrincipal.class).size());
|
||||||
|
assertTrue("Role principal is 'guests'", subject.getPrincipals(RolePrincipal.class).contains(new RolePrincipal("guests")));
|
||||||
|
|
||||||
|
context.logout();
|
||||||
|
|
||||||
|
assertEquals("Should have zero principals", 0, subject.getPrincipals().size());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* 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.core.security.jaas;
|
||||||
|
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.NameClassPair;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
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.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
import org.apache.directory.server.annotations.CreateLdapServer;
|
||||||
|
import org.apache.directory.server.annotations.CreateTransport;
|
||||||
|
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
|
||||||
|
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
|
||||||
|
import org.apache.directory.server.core.integ.FrameworkRunner;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@RunWith(FrameworkRunner.class)
|
||||||
|
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)})
|
||||||
|
@ApplyLdifFiles("test.ldif")
|
||||||
|
public class LDAPLoginModuleTest extends AbstractLdapTestUnit {
|
||||||
|
|
||||||
|
private static final String PRINCIPAL = "uid=admin,ou=system";
|
||||||
|
private static final String CREDENTIALS = "secret";
|
||||||
|
|
||||||
|
private final String loginConfigSysPropName = "java.security.auth.login.config";
|
||||||
|
private String oldLoginConfig;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setLoginConfigSysProperty() {
|
||||||
|
oldLoginConfig = System.getProperty(loginConfigSysPropName, null);
|
||||||
|
System.setProperty(loginConfigSysPropName, "src/test/resources/login.config");
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void resetLoginConfigSysProperty() {
|
||||||
|
if (oldLoginConfig != null) {
|
||||||
|
System.setProperty(loginConfigSysPropName, oldLoginConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testRunning() throws Exception {
|
||||||
|
|
||||||
|
Hashtable env = new Hashtable();
|
||||||
|
env.put(Context.PROVIDER_URL, "ldap://localhost:1024");
|
||||||
|
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||||
|
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, PRINCIPAL);
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, CREDENTIALS);
|
||||||
|
DirContext ctx = new InitialDirContext(env);
|
||||||
|
|
||||||
|
HashSet set = new HashSet();
|
||||||
|
|
||||||
|
NamingEnumeration list = ctx.list("ou=system");
|
||||||
|
|
||||||
|
while (list.hasMore()) {
|
||||||
|
NameClassPair ncp = (NameClassPair) list.next();
|
||||||
|
set.add(ncp.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(set.contains("uid=admin"));
|
||||||
|
assertTrue(set.contains("ou=users"));
|
||||||
|
assertTrue(set.contains("ou=groups"));
|
||||||
|
assertTrue(set.contains("ou=configuration"));
|
||||||
|
assertTrue(set.contains("prefNodeName=sysPrefRoot"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogin() throws LoginException {
|
||||||
|
LoginContext context = new LoginContext("LDAPLogin", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (int i = 0; i < callbacks.length; i++) {
|
||||||
|
if (callbacks[i] instanceof NameCallback) {
|
||||||
|
((NameCallback) callbacks[i]).setName("first");
|
||||||
|
}
|
||||||
|
else if (callbacks[i] instanceof PasswordCallback) {
|
||||||
|
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedCallbackException(callbacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.login();
|
||||||
|
context.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnauthenticated() throws LoginException {
|
||||||
|
LoginContext context = new LoginContext("UnAuthenticatedLDAPLogin", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (int i = 0; i < callbacks.length; i++) {
|
||||||
|
if (callbacks[i] instanceof NameCallback) {
|
||||||
|
((NameCallback) callbacks[i]).setName("first");
|
||||||
|
}
|
||||||
|
else if (callbacks[i] instanceof PasswordCallback) {
|
||||||
|
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedCallbackException(callbacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
context.login();
|
||||||
|
}
|
||||||
|
catch (LoginException le) {
|
||||||
|
assertEquals(le.getCause().getMessage(), "Empty password is not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("Should have failed authenticating");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* 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.core.security.jaas;
|
||||||
|
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.NameClassPair;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
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.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
||||||
|
import org.apache.directory.server.annotations.CreateLdapServer;
|
||||||
|
import org.apache.directory.server.annotations.CreateTransport;
|
||||||
|
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
|
||||||
|
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
|
||||||
|
import org.apache.directory.server.core.integ.FrameworkRunner;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@RunWith(FrameworkRunner.class)
|
||||||
|
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)})
|
||||||
|
@ApplyLdifFiles("test.ldif")
|
||||||
|
public class LDAPModuleRoleExpansionTest extends AbstractLdapTestUnit {
|
||||||
|
|
||||||
|
private static final String PRINCIPAL = "uid=admin,ou=system";
|
||||||
|
private static final String CREDENTIALS = "secret";
|
||||||
|
private final String loginConfigSysPropName = "java.security.auth.login.config";
|
||||||
|
private String oldLoginConfig;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setLoginConfigSysProperty() {
|
||||||
|
oldLoginConfig = System.getProperty(loginConfigSysPropName, null);
|
||||||
|
System.setProperty(loginConfigSysPropName, "src/test/resources/login.config");
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void resetLoginConfigSysProperty() {
|
||||||
|
if (oldLoginConfig != null) {
|
||||||
|
System.setProperty(loginConfigSysPropName, oldLoginConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testRunning() throws Exception {
|
||||||
|
|
||||||
|
Hashtable env = new Hashtable();
|
||||||
|
env.put(Context.PROVIDER_URL, "ldap://localhost:1024");
|
||||||
|
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||||
|
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, PRINCIPAL);
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, CREDENTIALS);
|
||||||
|
DirContext ctx = new InitialDirContext(env);
|
||||||
|
|
||||||
|
HashSet set = new HashSet();
|
||||||
|
|
||||||
|
NamingEnumeration list = ctx.list("ou=system");
|
||||||
|
|
||||||
|
while (list.hasMore()) {
|
||||||
|
NameClassPair ncp = (NameClassPair) list.next();
|
||||||
|
set.add(ncp.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(set.contains("uid=admin"));
|
||||||
|
assertTrue(set.contains("ou=users"));
|
||||||
|
assertTrue(set.contains("ou=groups"));
|
||||||
|
assertTrue(set.contains("ou=configuration"));
|
||||||
|
assertTrue(set.contains("prefNodeName=sysPrefRoot"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRoleExpansion() throws LoginException {
|
||||||
|
LoginContext context = new LoginContext("ExpandedLDAPLogin", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (int i = 0; i < callbacks.length; i++) {
|
||||||
|
if (callbacks[i] instanceof NameCallback) {
|
||||||
|
((NameCallback) callbacks[i]).setName("first");
|
||||||
|
}
|
||||||
|
else if (callbacks[i] instanceof PasswordCallback) {
|
||||||
|
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedCallbackException(callbacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.login();
|
||||||
|
Subject subject = context.getSubject();
|
||||||
|
boolean isAdmin = false;
|
||||||
|
boolean isUser = false;
|
||||||
|
for (Principal principal : subject.getPrincipals()) {
|
||||||
|
if (principal instanceof RolePrincipal) {
|
||||||
|
RolePrincipal groupPrincipal = (RolePrincipal) principal;
|
||||||
|
if (groupPrincipal.getName().equalsIgnoreCase("admins"))
|
||||||
|
isAdmin = true;
|
||||||
|
if (groupPrincipal.getName().equalsIgnoreCase("users"))
|
||||||
|
isUser = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Should be in users by virtue of being in admins
|
||||||
|
assertTrue(isAdmin && isUser);
|
||||||
|
context.logout();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* 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.core.security.jaas;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCredentialCallbackHandler;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ErrorCollector;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.junit.rules.TestName;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class PropertiesLoginModuleRaceConditionTest {
|
||||||
|
|
||||||
|
private static final String GROUPS_FILE = "roles.properties";
|
||||||
|
private static final String USERS_FILE = "users.properties";
|
||||||
|
private static final String USERNAME = "first";
|
||||||
|
private static final String PASSWORD = "secret";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ErrorCollector e = new ErrorCollector();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TemporaryFolder temp = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestName name = new TestName();
|
||||||
|
|
||||||
|
private Map<String, String> options;
|
||||||
|
private BlockingQueue<Exception> errors;
|
||||||
|
private ExecutorService pool;
|
||||||
|
private CallbackHandler callback;
|
||||||
|
|
||||||
|
private static class LoginTester implements Runnable {
|
||||||
|
|
||||||
|
private final CountDownLatch finished;
|
||||||
|
private final BlockingQueue<Exception> errors;
|
||||||
|
private final Map<String, String> options;
|
||||||
|
private final CountDownLatch start;
|
||||||
|
private final CallbackHandler callback;
|
||||||
|
|
||||||
|
LoginTester(CountDownLatch start,
|
||||||
|
CountDownLatch finished,
|
||||||
|
BlockingQueue<Exception> errors,
|
||||||
|
Map<String, String> options,
|
||||||
|
CallbackHandler callbackHandler) {
|
||||||
|
this.finished = finished;
|
||||||
|
this.errors = errors;
|
||||||
|
this.options = options;
|
||||||
|
this.start = start;
|
||||||
|
this.callback = callbackHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
start.await();
|
||||||
|
|
||||||
|
Subject subject = new Subject();
|
||||||
|
PropertiesLoginModule module = new PropertiesLoginModule();
|
||||||
|
module.initialize(subject, callback, new HashMap<Object, Object>(), options);
|
||||||
|
module.login();
|
||||||
|
module.commit();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
errors.offer(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
finished.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws FileNotFoundException, IOException {
|
||||||
|
createUsers();
|
||||||
|
createGroups();
|
||||||
|
|
||||||
|
options = new HashMap<String, String>();
|
||||||
|
options.put("reload", "true"); // Used to simplify reproduction of the
|
||||||
|
// race condition
|
||||||
|
options.put("org.apache.activemq.jaas.properties.user", USERS_FILE);
|
||||||
|
options.put("org.apache.activemq.jaas.properties.role", GROUPS_FILE);
|
||||||
|
options.put("baseDir", temp.getRoot().getAbsolutePath());
|
||||||
|
|
||||||
|
errors = new ArrayBlockingQueue<Exception>(processorCount());
|
||||||
|
pool = Executors.newFixedThreadPool(processorCount());
|
||||||
|
callback = new JaasCredentialCallbackHandler(USERNAME, PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() throws InterruptedException {
|
||||||
|
pool.shutdown();
|
||||||
|
assertTrue(pool.awaitTermination(500, TimeUnit.SECONDS));
|
||||||
|
PropertiesLoginModule.resetUsersAndGroupsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void raceConditionInUsersAndGroupsLoading() throws InterruptedException, FileNotFoundException, IOException {
|
||||||
|
|
||||||
|
// Brute force approach to increase the likelihood of the race condition occurring
|
||||||
|
for (int i = 0; i < 25000; i++) {
|
||||||
|
final CountDownLatch start = new CountDownLatch(1);
|
||||||
|
final CountDownLatch finished = new CountDownLatch(processorCount());
|
||||||
|
prepareLoginThreads(start, finished);
|
||||||
|
|
||||||
|
// Releases every login thread simultaneously to increase our chances of
|
||||||
|
// encountering the race condition
|
||||||
|
start.countDown();
|
||||||
|
|
||||||
|
finished.await();
|
||||||
|
if (isRaceConditionDetected()) {
|
||||||
|
e.addError(new AssertionError("At least one race condition in PropertiesLoginModule " + "has been encountered. Please examine the " + "following stack traces for more details:"));
|
||||||
|
for (Exception exception : errors) {
|
||||||
|
e.addError(exception);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRaceConditionDetected() {
|
||||||
|
return errors.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareLoginThreads(final CountDownLatch start, final CountDownLatch finished) {
|
||||||
|
for (int processor = 1; processor <= processorCount() * 2; processor++) {
|
||||||
|
pool.submit(new LoginTester(start, finished, errors, options, callback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int processorCount() {
|
||||||
|
return Runtime.getRuntime().availableProcessors();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void store(Properties from, File to) throws FileNotFoundException, IOException {
|
||||||
|
FileOutputStream output = new FileOutputStream(to);
|
||||||
|
try {
|
||||||
|
from.store(output, "Generated by " + name.getMethodName());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
output.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createGroups() throws FileNotFoundException, IOException {
|
||||||
|
Properties groups = new Properties();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
groups.put("group" + i, "first,second,third");
|
||||||
|
}
|
||||||
|
store(groups, temp.newFile(GROUPS_FILE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUsers() throws FileNotFoundException, IOException {
|
||||||
|
Properties users = new Properties();
|
||||||
|
users.put(USERNAME, PASSWORD);
|
||||||
|
users.put("second", PASSWORD);
|
||||||
|
users.put("third", PASSWORD);
|
||||||
|
store(users, temp.newFile(USERS_FILE));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* 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.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.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PropertiesLoginModuleTest extends Assert {
|
||||||
|
|
||||||
|
static {
|
||||||
|
String path = System.getProperty("java.security.auth.login.config");
|
||||||
|
if (path == null) {
|
||||||
|
URL resource = PropertiesLoginModuleTest.class.getClassLoader().getResource("login.config");
|
||||||
|
if (resource != null) {
|
||||||
|
path = resource.getFile();
|
||||||
|
System.setProperty("java.security.auth.login.config", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogin() throws LoginException {
|
||||||
|
LoginContext context = new LoginContext("PropertiesLogin", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (int i = 0; i < callbacks.length; i++) {
|
||||||
|
if (callbacks[i] instanceof NameCallback) {
|
||||||
|
((NameCallback) callbacks[i]).setName("first");
|
||||||
|
}
|
||||||
|
else if (callbacks[i] instanceof PasswordCallback) {
|
||||||
|
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedCallbackException(callbacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.login();
|
||||||
|
|
||||||
|
Subject subject = context.getSubject();
|
||||||
|
|
||||||
|
assertEquals("Should have three principals", 3, subject.getPrincipals().size());
|
||||||
|
assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size());
|
||||||
|
assertEquals("Should have two group principals", 2, subject.getPrincipals(RolePrincipal.class).size());
|
||||||
|
|
||||||
|
context.logout();
|
||||||
|
|
||||||
|
assertEquals("Should have zero principals", 0, subject.getPrincipals().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBadUseridLogin() throws Exception {
|
||||||
|
LoginContext context = new LoginContext("PropertiesLogin", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (int i = 0; i < callbacks.length; i++) {
|
||||||
|
if (callbacks[i] instanceof NameCallback) {
|
||||||
|
((NameCallback) callbacks[i]).setName("BAD");
|
||||||
|
}
|
||||||
|
else if (callbacks[i] instanceof PasswordCallback) {
|
||||||
|
((PasswordCallback) callbacks[i]).setPassword("secret".toCharArray());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedCallbackException(callbacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
context.login();
|
||||||
|
fail("Should have thrown a FailedLoginException");
|
||||||
|
}
|
||||||
|
catch (FailedLoginException doNothing) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBadPWLogin() throws Exception {
|
||||||
|
LoginContext context = new LoginContext("PropertiesLogin", new CallbackHandler() {
|
||||||
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (int i = 0; i < callbacks.length; i++) {
|
||||||
|
if (callbacks[i] instanceof NameCallback) {
|
||||||
|
((NameCallback) callbacks[i]).setName("first");
|
||||||
|
}
|
||||||
|
else if (callbacks[i] instanceof PasswordCallback) {
|
||||||
|
((PasswordCallback) callbacks[i]).setPassword("BAD".toCharArray());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedCallbackException(callbacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
context.login();
|
||||||
|
fail("Should have thrown a FailedLoginException");
|
||||||
|
}
|
||||||
|
catch (FailedLoginException doNothing) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.core.security.jaas;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class RolePrincipalTest extends Assert {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testArguments() {
|
||||||
|
RolePrincipal principal = new RolePrincipal("FOO");
|
||||||
|
|
||||||
|
assertEquals("FOO", principal.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
new RolePrincipal(null);
|
||||||
|
fail("Should have thrown IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException ingore) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHash() {
|
||||||
|
RolePrincipal p1 = new RolePrincipal("FOO");
|
||||||
|
RolePrincipal p2 = new RolePrincipal("FOO");
|
||||||
|
|
||||||
|
assertEquals(p1.hashCode(), p1.hashCode());
|
||||||
|
assertEquals(p1.hashCode(), p2.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEquals() {
|
||||||
|
RolePrincipal p1 = new RolePrincipal("FOO");
|
||||||
|
RolePrincipal p2 = new RolePrincipal("FOO");
|
||||||
|
RolePrincipal p3 = new RolePrincipal("BAR");
|
||||||
|
|
||||||
|
assertTrue(p1.equals(p1));
|
||||||
|
assertTrue(p1.equals(p2));
|
||||||
|
assertFalse(p1.equals(null));
|
||||||
|
assertFalse(p1.equals("FOO"));
|
||||||
|
assertFalse(p1.equals(p3));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.core.security.jaas;
|
||||||
|
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateLoginModule;
|
||||||
|
|
||||||
|
public class StubCertificateLoginModule extends CertificateLoginModule {
|
||||||
|
|
||||||
|
final String userName;
|
||||||
|
final Set groupNames;
|
||||||
|
|
||||||
|
String lastUserName;
|
||||||
|
X509Certificate[] lastCertChain;
|
||||||
|
|
||||||
|
public StubCertificateLoginModule(String userName, Set groupNames) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.groupNames = groupNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getUserNameForCertificates(X509Certificate[] certs) throws LoginException {
|
||||||
|
lastCertChain = certs;
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set getUserGroups(String username) throws LoginException {
|
||||||
|
lastUserName = username;
|
||||||
|
return this.groupNames;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.core.security.jaas;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class UserPrincipalTest extends Assert {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testArguments() {
|
||||||
|
UserPrincipal principal = new UserPrincipal("FOO");
|
||||||
|
|
||||||
|
assertEquals("FOO", principal.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
new UserPrincipal(null);
|
||||||
|
fail("Should have thrown IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException ingore) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHash() {
|
||||||
|
UserPrincipal p1 = new UserPrincipal("FOO");
|
||||||
|
UserPrincipal p2 = new UserPrincipal("FOO");
|
||||||
|
|
||||||
|
assertEquals(p1.hashCode(), p1.hashCode());
|
||||||
|
assertEquals(p1.hashCode(), p2.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEquals() {
|
||||||
|
UserPrincipal p1 = new UserPrincipal("FOO");
|
||||||
|
UserPrincipal p2 = new UserPrincipal("FOO");
|
||||||
|
UserPrincipal p3 = new UserPrincipal("BAR");
|
||||||
|
|
||||||
|
assertTrue(p1.equals(p1));
|
||||||
|
assertTrue(p1.equals(p2));
|
||||||
|
assertFalse(p1.equals(null));
|
||||||
|
assertFalse(p1.equals("FOO"));
|
||||||
|
assertFalse(p1.equals(p3));
|
||||||
|
}
|
||||||
|
}
|
|
@ -793,20 +793,24 @@ public abstract class ActiveMQTestBase extends Assert {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void clearDataRecreateServerDirs() {
|
protected final void clearDataRecreateServerDirs() {
|
||||||
clearDataRecreateServerDirs(getTestDir());
|
clearDataRecreateServerDirs(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void clearDataRecreateServerDirs(final String testDir1) {
|
protected final void clearDataRecreateServerDirs(int index, boolean backup) {
|
||||||
|
clearDataRecreateServerDirs(getTestDir(), index, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clearDataRecreateServerDirs(final String testDir1, int index, boolean backup) {
|
||||||
// Need to delete the root
|
// Need to delete the root
|
||||||
|
|
||||||
File file = new File(testDir1);
|
File file = new File(testDir1);
|
||||||
deleteDirectory(file);
|
deleteDirectory(file);
|
||||||
file.mkdirs();
|
file.mkdirs();
|
||||||
|
|
||||||
recreateDirectory(getJournalDir(testDir1));
|
recreateDirectory(getJournalDir(testDir1, index, backup));
|
||||||
recreateDirectory(getBindingsDir(testDir1));
|
recreateDirectory(getBindingsDir(testDir1, index, backup));
|
||||||
recreateDirectory(getPageDir(testDir1));
|
recreateDirectory(getPageDir(testDir1, index, backup));
|
||||||
recreateDirectory(getLargeMessagesDir(testDir1));
|
recreateDirectory(getLargeMessagesDir(testDir1, index, backup));
|
||||||
recreateDirectory(getClientLargeMessagesDir(testDir1));
|
recreateDirectory(getClientLargeMessagesDir(testDir1));
|
||||||
recreateDirectory(getTemporaryDir(testDir1));
|
recreateDirectory(getTemporaryDir(testDir1));
|
||||||
}
|
}
|
||||||
|
@ -815,7 +819,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
||||||
* @return the journalDir
|
* @return the journalDir
|
||||||
*/
|
*/
|
||||||
public String getJournalDir() {
|
public String getJournalDir() {
|
||||||
return getJournalDir(getTestDir());
|
return getJournalDir(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String getJournalDir(final String testDir1) {
|
protected static String getJournalDir(final String testDir1) {
|
||||||
|
@ -834,7 +838,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
||||||
* @return the bindingsDir
|
* @return the bindingsDir
|
||||||
*/
|
*/
|
||||||
protected String getBindingsDir() {
|
protected String getBindingsDir() {
|
||||||
return getBindingsDir(getTestDir());
|
return getBindingsDir(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -859,7 +863,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
||||||
* @return the pageDir
|
* @return the pageDir
|
||||||
*/
|
*/
|
||||||
protected String getPageDir() {
|
protected String getPageDir() {
|
||||||
return getPageDir(getTestDir());
|
return getPageDir(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected File getPageDirFile() {
|
protected File getPageDirFile() {
|
||||||
|
@ -886,7 +890,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
||||||
* @return the largeMessagesDir
|
* @return the largeMessagesDir
|
||||||
*/
|
*/
|
||||||
protected String getLargeMessagesDir() {
|
protected String getLargeMessagesDir() {
|
||||||
return getLargeMessagesDir(getTestDir());
|
return getLargeMessagesDir(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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="users.properties"
|
||||||
|
org.apache.activemq.jaas.properties.role="roles.properties";
|
||||||
|
};
|
||||||
|
|
||||||
|
LDAPLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:1024"
|
||||||
|
connectionUsername="uid=admin,ou=system"
|
||||||
|
connectionPassword=secret
|
||||||
|
connectionProtocol=s
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=system"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleBase="ou=system"
|
||||||
|
roleName=cn
|
||||||
|
roleSearchMatching="(member=uid={1},ou=system)"
|
||||||
|
roleSearchSubtree=false
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
UnAuthenticatedLDAPLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:1024"
|
||||||
|
connectionUsername="uid=admin,ou=system"
|
||||||
|
connectionPassword=""
|
||||||
|
connectionProtocol=s
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=system"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleBase="ou=system"
|
||||||
|
roleName=dummyRoleName
|
||||||
|
roleSearchMatching="(uid={1})"
|
||||||
|
roleSearchSubtree=false
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
ExpandedLDAPLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:1024"
|
||||||
|
connectionUsername="uid=admin,ou=system"
|
||||||
|
connectionPassword=secret
|
||||||
|
connectionProtocol=s
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=system"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleBase="ou=system"
|
||||||
|
roleName=cn
|
||||||
|
roleSearchMatching="(uid={1})"
|
||||||
|
roleSearchSubtree=false
|
||||||
|
expandRoles=true
|
||||||
|
expandRolesMatching="(member={0})"
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
GuestLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule required
|
||||||
|
debug=true
|
||||||
|
org.apache.activemq.jaas.guest.user="foo"
|
||||||
|
org.apache.activemq.jaas.guest.role="bar";
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
GuestLoginWithDefaults {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule required
|
||||||
|
debug=true;
|
||||||
|
};
|
||||||
|
|
||||||
|
OpenLdapConfiguration {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:389"
|
||||||
|
connectionUsername="cn=mqbroker,ou=Services,ou=system,dc=fusesource,dc=com"
|
||||||
|
connectionPassword="sunflower"
|
||||||
|
connectionProtocol="s"
|
||||||
|
topicSearchMatchingFormat="cn={0},ou=Topic,ou=Destination,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
topicSearchSubtreeBool=true
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=User,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleSearchMatching="(uid={1})"
|
||||||
|
queueSearchMatchingFormat="cn={0},ou=Queue,ou=Destination,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
queueSearchSubtreeBool=true
|
||||||
|
roleBase="ou=Group,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
roleName=cn
|
||||||
|
roleSearchMatching="(member:=uid={1})"
|
||||||
|
roleSearchSubtree=true
|
||||||
|
;
|
||||||
|
};
|
|
@ -0,0 +1,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 "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.
|
||||||
|
#
|
||||||
|
|
||||||
|
programmers=first
|
||||||
|
accounting=second
|
||||||
|
employees=first,second
|
|
@ -0,0 +1,39 @@
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
## 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.
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
dn: uid=first,ou=system
|
||||||
|
uid: first
|
||||||
|
userPassword: secret
|
||||||
|
objectClass: account
|
||||||
|
objectClass: simpleSecurityObject
|
||||||
|
objectClass: top
|
||||||
|
|
||||||
|
###################
|
||||||
|
## Define groups ##
|
||||||
|
###################
|
||||||
|
|
||||||
|
dn: cn=admins,ou=system
|
||||||
|
cn: admins
|
||||||
|
member: uid=first,ou=system
|
||||||
|
objectClass: groupOfNames
|
||||||
|
objectClass: top
|
||||||
|
|
||||||
|
dn: cn=users,ou=system
|
||||||
|
cn: users
|
||||||
|
member: cn=admins,ou=system
|
||||||
|
objectClass: groupOfNames
|
||||||
|
objectClass: top
|
|
@ -0,0 +1,19 @@
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
first=secret
|
||||||
|
second=password
|
|
@ -129,17 +129,21 @@ Sockets Layer (SSL) transport.
|
||||||
|
|
||||||
For more information on configuring the SSL transport, please see [Configuring the Transport](configuring-transports.md).
|
For more information on configuring the SSL transport, please see [Configuring the Transport](configuring-transports.md).
|
||||||
|
|
||||||
## Basic user credentials
|
## User credentials
|
||||||
|
|
||||||
Apache ActiveMQ Artemis ships with a security manager implementation that reads user
|
Apache ActiveMQ Artemis ships with two security manager implementations:
|
||||||
credentials, i.e. user names, passwords and role information from properties
|
|
||||||
files on the classpath called `artemis-users.properties` and `artemis-roles.properties`. This is the default security manager.
|
- The legacy, deprecated `ActiveMQSecurityManager` that reads user credentials, i.e. user names, passwords and role
|
||||||
|
information from properties files on the classpath called `artemis-users.properties` and `artemis-roles.properties`.
|
||||||
|
This is the default security manager.
|
||||||
|
|
||||||
If you wish to use this security manager, then users, passwords and
|
- The flexible, pluggable `ActiveMQJAASSecurityManager` which supports any standard JAAS login module. Artemis ships
|
||||||
roles can easily be added into these files.
|
with several login modules which will be discussed further down.
|
||||||
|
|
||||||
To configure this manager then it needs to be added to the `bootstrap.xml` configuration.
|
### Non-JAAS Security Manager
|
||||||
Lets take a look at what this might look like:
|
|
||||||
|
If you wish to use the legacy, deprecated `ActiveMQSecurityManager`, then it needs to be added to the `bootstrap.xml`
|
||||||
|
configuration. Lets take a look at what this might look like:
|
||||||
|
|
||||||
<basic-security>
|
<basic-security>
|
||||||
<users>file:${activemq.home}/config/non-clustered/artemis-users.properties</users>
|
<users>file:${activemq.home}/config/non-clustered/artemis-users.properties</users>
|
||||||
|
@ -149,28 +153,276 @@ Lets take a look at what this might look like:
|
||||||
|
|
||||||
The first 2 elements `users` and `roles` define what properties files should be used to load in the users and passwords.
|
The first 2 elements `users` and `roles` define what properties files should be used to load in the users and passwords.
|
||||||
|
|
||||||
The next thing to note is the element `defaultuser`. This defines what
|
The next thing to note is the element `defaultuser`. This defines what user will be assumed when the client does not
|
||||||
user will be assumed when the client does not specify a
|
specify a username/password when creating a session. In this case they will be the user `guest`. Multiple roles can be
|
||||||
username/password when creating a session. In this case they will be the
|
specified for a default user in the `artemis-roles.properties`.
|
||||||
user `guest`. Multiple roles can be specified for a default user in the
|
|
||||||
`artemis-roles.properties`.
|
|
||||||
|
|
||||||
Lets now take alook at the `artemis-users.properties` file, this is basically
|
Lets now take a look at the `artemis-users.properties` file, this is basically just a set of key value pairs that define
|
||||||
just a set of key value pairs that define the users and their password, like so:
|
the users and their password, like so:
|
||||||
|
|
||||||
bill=activemq
|
bill=activemq
|
||||||
andrew=activemq1
|
andrew=activemq1
|
||||||
frank=activemq2
|
frank=activemq2
|
||||||
sam=activemq3
|
sam=activemq3
|
||||||
|
|
||||||
The `artemis-roles.properties` defines what groups these users belong too
|
The `artemis-roles.properties` defines what groups these users belong too where the key is the user and the value is a
|
||||||
where the key is the user and the value is a comma separated list of the groups
|
comma separated list of the groups the user belongs to, like so:
|
||||||
the user belongs to, like so:
|
|
||||||
|
|
||||||
bill=user
|
bill=user
|
||||||
andrew=europe-user,user
|
andrew=europe-user,user
|
||||||
frank=us-user,news-user,user
|
frank=us-user,news-user,user
|
||||||
sam=news-user,user
|
sam=news-user,user
|
||||||
|
|
||||||
|
### JAAS Security Manager
|
||||||
|
|
||||||
|
When using JAAS much of the configuration depends on which login module is used. However, there are a few commonalities
|
||||||
|
for every case. Just like in the non-JAAS use-case, the first place to look is in `bootstrap.xml`. Here is an example
|
||||||
|
using the `PropertiesLogin` JAAS login module which reads user, password, and role information from properties files
|
||||||
|
much like the non-JAAS security manager implementation:
|
||||||
|
|
||||||
|
<jaas-security login-module="PropertiesLogin"/>
|
||||||
|
|
||||||
|
No matter what login module you're using, you'll need to specify it here in `bootstrap.xml`. The `login-module` attribute
|
||||||
|
here refers to the relevant login module entry in `login.config`. For example:
|
||||||
|
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
|
The `login.config` file is a standard JAAS configuration file. You can read more about this file on
|
||||||
|
[Oracle's website](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jgss/tutorials/LoginConfigFile.html).
|
||||||
|
In short, the file defines:
|
||||||
|
|
||||||
|
- an alias for a configuration (e.g. `PropertiesLogin`)
|
||||||
|
|
||||||
|
- the implementation class (e.g. `org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule`)
|
||||||
|
|
||||||
|
- a flag which indicates whether the success of the LoginModule is `required`, `requisite`, `sufficient`, or `optional`
|
||||||
|
|
||||||
|
- a list of configuration options specific to the login module implementation
|
||||||
|
|
||||||
|
By default, the location and name of `login.config` is specified on the Artemis command-line which is set by
|
||||||
|
`etc/artemis.profile` on linux and `etc\artemis.profile.cmd` on Windows.
|
||||||
|
|
||||||
|
### JAAS Login Modules
|
||||||
|
|
||||||
|
#### GuestLoginModule
|
||||||
|
Allows users without credentials (and, depending on how it is configured, possibly also users with invalid credentials)
|
||||||
|
to access the broker. Normally, the guest login module is chained with another login module, such as a properties login
|
||||||
|
module. It is implemented by `org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule`.
|
||||||
|
|
||||||
|
- `org.apache.activemq.jaas.guest.user` - the user name to assign; default is "guest"
|
||||||
|
|
||||||
|
- `org.apache.activemq.jaas.guest.role` - the role name to assign; default is "guests"
|
||||||
|
|
||||||
|
- `credentialsInvalidate` - boolean flag; if `true`, reject login requests that include a password (i.e. guest login
|
||||||
|
succeeds only when the user does not provide a password); default is `false`
|
||||||
|
|
||||||
|
- `debug` - boolean flag; if `true`, enable debugging; this is used only for testing or debugging; normally, it
|
||||||
|
should be set to `false`, or omitted; default is `false`
|
||||||
|
|
||||||
|
There are two basic use cases for the guest login module, as follows:
|
||||||
|
|
||||||
|
- Guests with no credentials or invalid credentials.
|
||||||
|
|
||||||
|
- Guests with no credentials only.
|
||||||
|
|
||||||
|
The following snippet shows how to configure a JAAS login entry for the use case where users with no credentials or
|
||||||
|
invalid credentials are logged in as guests. In this example, the guest login module is used in combination with the
|
||||||
|
properties login module.
|
||||||
|
|
||||||
|
activemq-domain {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule sufficient
|
||||||
|
debug=true
|
||||||
|
org.apache.activemq.jaas.properties.user="artemis-users.properties"
|
||||||
|
org.apache.activemq.jaas.properties.role="artemis-roles.properties";
|
||||||
|
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule sufficient
|
||||||
|
debug=true
|
||||||
|
org.apache.activemq.jaas.guest.user="anyone"
|
||||||
|
org.apache.activemq.jaas.guest.role="restricted";
|
||||||
|
};
|
||||||
|
|
||||||
|
Depending on the user login data, authentication proceeds as follows:
|
||||||
|
|
||||||
|
- User logs in with a valid password — the properties login module successfully authenticates the user and returns
|
||||||
|
immediately. The guest login module is not invoked.
|
||||||
|
|
||||||
|
- User logs in with an invalid password — the properties login module fails to authenticate the user, and authentication
|
||||||
|
proceeds to the guest login module. The guest login module successfully authenticates the user and returns the guest principal.
|
||||||
|
|
||||||
|
- User logs in with a blank password — the properties login module fails to authenticate the user, and authentication
|
||||||
|
proceeds to the guest login module. The guest login module successfully authenticates the user and returns the guest principal.
|
||||||
|
|
||||||
|
The following snipped shows how to configure a JAAS login entry for the use case where only those users with no
|
||||||
|
credentials are logged in as guests. To support this use case, you must set the credentialsInvalidate option to true in
|
||||||
|
the configuration of the guest login module. You should also note that, compared with the preceding example, the order
|
||||||
|
of the login modules is reversed and the flag attached to the properties login module is changed to requisite.
|
||||||
|
|
||||||
|
activemq-guest-when-no-creds-only-domain {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule sufficient
|
||||||
|
debug=true
|
||||||
|
credentialsInvalidate=true
|
||||||
|
org.apache.activemq.jaas.guest.user="guest"
|
||||||
|
org.apache.activemq.jaas.guest.role="guests";
|
||||||
|
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule requisite
|
||||||
|
debug=true
|
||||||
|
org.apache.activemq.jaas.properties.user="artemis-users.properties"
|
||||||
|
org.apache.activemq.jaas.properties.role="artemis-roles.properties";
|
||||||
|
};
|
||||||
|
|
||||||
|
Depending on the user login data, authentication proceeds as follows:
|
||||||
|
|
||||||
|
- User logs in with a valid password — the guest login module fails to authenticate the user (because the user has
|
||||||
|
presented a password while the credentialsInvalidate option is enabled) and authentication proceeds to the properties
|
||||||
|
login module. The properties login module sucessfully authenticates the user and returns.
|
||||||
|
|
||||||
|
- User logs in with an invalid password — the guest login module fails to authenticate the user and authentication proceeds
|
||||||
|
to the properties login module. The properties login module also fails to authenticate the user. The nett result is
|
||||||
|
authentication failure.
|
||||||
|
|
||||||
|
- User logs in with a blank password — the guest login module successfully authenticates the user and returns immediately.
|
||||||
|
The properties login module is not invoked.
|
||||||
|
|
||||||
|
#### PropertiesLoginModule
|
||||||
|
The JAAS properties login module provides a simple store of authentication data, where the relevant user data is stored
|
||||||
|
in a pair of flat files. This is convenient for demonstrations and testing, but for an enterprise system, the integration
|
||||||
|
with LDAP is preferable. It is implemented by `org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule`.
|
||||||
|
|
||||||
|
- `org.apache.activemq.jaas.properties.user` - the path to the file which contains user and password properties
|
||||||
|
|
||||||
|
- `org.apache.activemq.jaas.properties.role` - the path to the file which contains user and role properties
|
||||||
|
|
||||||
|
- `debug` - boolean flag; if `true`, enable debugging; this is used only for testing or debugging; normally, it
|
||||||
|
should be set to `false`, or omitted; default is `false`
|
||||||
|
|
||||||
|
In the context of the properties login module, the `artemis-users.properties` file consists of a list of properties of the
|
||||||
|
form, `UserName=Password`. For example, to define the users `system`, `user`, and `guest`, you could create a file like
|
||||||
|
the following:
|
||||||
|
|
||||||
|
system=manager
|
||||||
|
user=password
|
||||||
|
guest=password
|
||||||
|
|
||||||
|
The `artemis-roles.properties` file consists of a list of properties of the form, `Role=UserList`, where UserList is a
|
||||||
|
comma-separated list of users. For example, to define the roles `admins`, `users`, and `guests`, you could create a file
|
||||||
|
like the following:
|
||||||
|
|
||||||
|
admins=system
|
||||||
|
users=system,user
|
||||||
|
guests=guest
|
||||||
|
|
||||||
|
#### LDAPLoginModule
|
||||||
|
The LDAP login module enables you to perform authentication and authorization by checking the incoming credentials against
|
||||||
|
user data stored in a central X.500 directory server. For systems that already have an X.500 directory server in place,
|
||||||
|
this means that you can rapidly integrate ActiveMQ Artemis with the existing security database and user accounts can be
|
||||||
|
managed using the X.500 system. It is implemented by `org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule`.
|
||||||
|
|
||||||
|
- `initialContextFactory` - must always be set to `com.sun.jndi.ldap.LdapCtxFactory`
|
||||||
|
|
||||||
|
- `connectionURL` - specify the location of the directory server using an ldap URL, ldap://Host:Port. You can
|
||||||
|
optionally qualify this URL, by adding a forward slash, `/`, followed by the DN of a particular node in the directory
|
||||||
|
tree. For example, ldap://ldapserver:10389/ou=system.
|
||||||
|
|
||||||
|
- `authentication` - specifies the authentication method used when binding to the LDAP server. Can take either of
|
||||||
|
the values, `simple` (username and password) or `none` (anonymous).
|
||||||
|
|
||||||
|
- `connectionUsername` - the DN of the user that opens the connection to the directory server. For example,
|
||||||
|
`uid=admin,ou=system`. Directory servers generally require clients to present username/password credentials in order
|
||||||
|
to open a connection.
|
||||||
|
|
||||||
|
- `connectionPassword` - the password that matches the DN from `connectionUsername`. In the directory server,
|
||||||
|
in the DIT, the password is normally stored as a `userPassword` attribute in the corresponding directory entry.
|
||||||
|
|
||||||
|
- `connectionProtocol` - currently, the only supported value is a blank string. In future, this option will allow
|
||||||
|
you to select the Secure Socket Layer (SSL) for the connection to the directory server. This option must be set
|
||||||
|
explicitly to an empty string, because it has no default value.
|
||||||
|
|
||||||
|
- `userBase` - selects a particular subtree of the DIT to search for user entries. The subtree is specified by a
|
||||||
|
DN, which specifes the base node of the subtree. For example, by setting this option to `ou=User,ou=ActiveMQ,ou=system`,
|
||||||
|
the search for user entries is restricted to the subtree beneath the `ou=User,ou=ActiveMQ,ou=system` node.
|
||||||
|
|
||||||
|
- `userSearchMatching` - specifies an LDAP search filter, which is applied to the subtree selected by `userBase`.
|
||||||
|
Before passing to the LDAP search operation, the string value you provide here is subjected to string substitution,
|
||||||
|
as implemented by the `java.text.MessageFormat` class. Essentially, this means that the special string, `{0}`, is
|
||||||
|
substituted by the username, as extracted from the incoming client credentials.
|
||||||
|
|
||||||
|
After substitution, the string is interpreted as an LDAP search filter, where the LDAP search filter syntax is
|
||||||
|
defined by the IETF standard, RFC 2254. A short introduction to the search filter syntax is available from Oracle's
|
||||||
|
JNDI tutorial, [Search Filters](http://download.oracle.com/javase/jndi/tutorial/basics/directory/filter.html).
|
||||||
|
|
||||||
|
For example, if this option is set to `(uid={0})` and the received username is `jdoe`, the search filter becomes
|
||||||
|
`(uid=jdoe)` after string substitution. If the resulting search filter is applied to the subtree selected by the
|
||||||
|
user base, `ou=User,ou=ActiveMQ,ou=system`, it would match the entry, `uid=jdoe,ou=User,ou=ActiveMQ,ou=system`
|
||||||
|
(and possibly more deeply nested entries, depending on the specified search depth—see the `userSearchSubtree` option).
|
||||||
|
|
||||||
|
- `userSearchSubtree` - specify the search depth for user entries, relative to the node specified by `userBase`.
|
||||||
|
This option is a boolean. `false` indicates it will try to match one of the child entries of the `userBase` node
|
||||||
|
(maps to `javax.naming.directory.SearchControls.ONELEVEL_SCOPE`). `true` indicates it will try to match any entry
|
||||||
|
belonging to the subtree of the `userBase` node (maps to `javax.naming.directory.SearchControls.SUBTREE_SCOPE`).
|
||||||
|
|
||||||
|
- `userRoleName` - specifies the name of the multi-valued attribute of the user entry that contains a list of
|
||||||
|
role names for the user (where the role names are interpreted as group names by the broker's authorization plug-in).
|
||||||
|
If you omit this option, no role names are extracted from the user entry.
|
||||||
|
|
||||||
|
- `roleBase` - if you want to store role data directly in the directory server, you can use a combination of role
|
||||||
|
options (`roleBase`, `roleSearchMatching`, `roleSearchSubtree`, and `roleName`) as an alternative to (or in addition
|
||||||
|
to) specifying the `userRoleName` option. This option selects a particular subtree of the DIT to search for role/group
|
||||||
|
entries. The subtree is specified by a DN, which specifes the base node of the subtree. For example, by setting this
|
||||||
|
option to `ou=Group,ou=ActiveMQ,ou=system`, the search for role/group entries is restricted to the subtree beneath
|
||||||
|
the `ou=Group,ou=ActiveMQ,ou=system` node.
|
||||||
|
|
||||||
|
- `roleName` - specifies the attribute type of the role entry that contains the name of the role/group (e.g. C, O,
|
||||||
|
OU, etc.). If you omit this option, the role search feature is effectively disabled.
|
||||||
|
|
||||||
|
- `roleSearchMatching` - specifies an LDAP search filter, which is applied to the subtree selected by `roleBase`.
|
||||||
|
This works in a similar manner to the `userSearchMatching` option, except that it supports two substitution strings,
|
||||||
|
as follows:
|
||||||
|
|
||||||
|
- `{0}` - substitutes the full DN of the matched user entry (that is, the result of the user search). For
|
||||||
|
example, for the user, `jdoe`, the substituted string could be `uid=jdoe,ou=User,ou=ActiveMQ,ou=system`.
|
||||||
|
|
||||||
|
- `{1}` - substitutes the received username. For example, `jdoe`.
|
||||||
|
|
||||||
|
For example, if this option is set to `(member=uid={1})` and the received username is `jdoe`, the search filter
|
||||||
|
becomes `(member=uid=jdoe)` after string substitution (assuming ApacheDS search filter syntax). If the resulting
|
||||||
|
search filter is applied to the subtree selected by the role base, `ou=Group,ou=ActiveMQ,ou=system`, it matches all
|
||||||
|
role entries that have a `member` attribute equal to `uid=jdoe` (the value of a `member` attribute is a DN).
|
||||||
|
|
||||||
|
This option must always be set, even if role searching is disabled, because it has no default value.
|
||||||
|
|
||||||
|
If you use OpenLDAP, the syntax of the search filter is `(member:=uid=jdoe)`.
|
||||||
|
|
||||||
|
- `roleSearchSubtree` - specify the search depth for role entries, relative to the node specified by `roleBase`.
|
||||||
|
This option can take boolean values, as follows:
|
||||||
|
|
||||||
|
- `false` (default) - try to match one of the child entries of the roleBase node (maps to
|
||||||
|
`javax.naming.directory.SearchControls.ONELEVEL_SCOPE`).
|
||||||
|
|
||||||
|
- `true` — try to match any entry belonging to the subtree of the roleBase node (maps to
|
||||||
|
`javax.naming.directory.SearchControls.SUBTREE_SCOPE`).
|
||||||
|
|
||||||
|
- `debug` - boolean flag; if `true`, enable debugging; this is used only for testing or debugging; normally, it
|
||||||
|
should be set to `false`, or omitted; default is `false`
|
||||||
|
|
||||||
|
Add user entries under the node specified by the `userBase` option. When creating a new user entry in the directory,
|
||||||
|
choose an object class that supports the `userPassword` attribute (for example, the `person` or `inetOrgPerson` object
|
||||||
|
classes are typically suitable). After creating the user entry, add the `userPassword` attribute, to hold the user's
|
||||||
|
password.
|
||||||
|
|
||||||
|
If you want to store role data in dedicated role entries (where each node represents a particular role), create a role
|
||||||
|
entry as follows. Create a new child of the `roleBase` node, where the `objectClass` of the child is `groupOfNames`. Set
|
||||||
|
the `cn` (or whatever attribute type is specified by `roleName`) of the new child node equal to the name of the
|
||||||
|
role/group. Define a `member` attribute for each member of the role/group, setting the `member` value to the DN of the
|
||||||
|
corresponding user (where the DN is specified either fully, `uid=jdoe,ou=User,ou=ActiveMQ,ou=system`, or partially,
|
||||||
|
`uid=jdoe`).
|
||||||
|
|
||||||
|
If you want to add roles to user entries, you would need to customize the directory schema, by adding a suitable
|
||||||
|
attribute type to the user entry's object class. The chosen attribute type must be capable of handling multiple values.
|
||||||
|
|
||||||
## Changing the username/password for clustering
|
## Changing the username/password for clustering
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ under the License.
|
||||||
<module>request-reply</module>
|
<module>request-reply</module>
|
||||||
<module>scheduled-message</module>
|
<module>scheduled-message</module>
|
||||||
<module>security</module>
|
<module>security</module>
|
||||||
|
<module>security-jaas</module>
|
||||||
<module>send-acknowledgements</module>
|
<module>send-acknowledgements</module>
|
||||||
<module>spring-integration</module>
|
<module>spring-integration</module>
|
||||||
<module>ssl-enabled</module>
|
<module>ssl-enabled</module>
|
||||||
|
@ -142,6 +143,7 @@ under the License.
|
||||||
<module>rest</module>
|
<module>rest</module>
|
||||||
<module>scheduled-message</module>
|
<module>scheduled-message</module>
|
||||||
<module>security</module>
|
<module>security</module>
|
||||||
|
<module>security-jaas</module>
|
||||||
<module>send-acknowledgements</module>
|
<module>send-acknowledgements</module>
|
||||||
<module>spring-integration</module>
|
<module>spring-integration</module>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
<?xml version='1.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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.apache.activemq.examples.broker</groupId>
|
||||||
|
<artifactId>jms-examples</artifactId>
|
||||||
|
<version>1.1.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>security-jaas</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>ActiveMQ Artemis JMS JAAS Security Example</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.activemq</groupId>
|
||||||
|
<artifactId>artemis-jms-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.activemq</groupId>
|
||||||
|
<artifactId>artemis-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>create</id>
|
||||||
|
<goals>
|
||||||
|
<goal>create</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<ignore>${noServer}</ignore>
|
||||||
|
<brokerSecurity>jaas</brokerSecurity>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>start</id>
|
||||||
|
<goals>
|
||||||
|
<goal>cli</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<ignore>${noServer}</ignore>
|
||||||
|
<spawn>true</spawn>
|
||||||
|
<testURI>tcp://localhost:61616</testURI>
|
||||||
|
<testUser>bill</testUser>
|
||||||
|
<testPassword>activemq</testPassword>
|
||||||
|
<args>
|
||||||
|
<param>run</param>
|
||||||
|
</args>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>runClient</id>
|
||||||
|
<goals>
|
||||||
|
<goal>runClient</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<clientClass>org.apache.activemq.artemis.jms.example.JaasSecurityExample</clientClass>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>stop</id>
|
||||||
|
<goals>
|
||||||
|
<goal>cli</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<ignore>${noServer}</ignore>
|
||||||
|
<args>
|
||||||
|
<param>stop</param>
|
||||||
|
</args>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.activemq.examples.broker</groupId>
|
||||||
|
<artifactId>security-jaas</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,324 @@
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ActiveMQ Artemis JMS Security Example</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../../common/common.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../../common/prettify.css" />
|
||||||
|
<script type="text/javascript" src="../../../common/prettify.js"></script>
|
||||||
|
</head>
|
||||||
|
<body onload="prettyPrint()">
|
||||||
|
<h1>JMS JAAS Security Example</h1>
|
||||||
|
|
||||||
|
<pre>To run the example, simply type <b>mvn verify</b> from this directory, <br>or <b>mvn -PnoServer verify</b> if you want to start and create the server manually.</pre>
|
||||||
|
|
||||||
|
|
||||||
|
<p>This example shows how to configure and use JAAS security using ActiveMQ Artemis.</p>
|
||||||
|
|
||||||
|
<p>With security properly configured, ActiveMQ Artemis can restrict client access to its resources, including
|
||||||
|
connection creation, message sending/receiving, etc. This is done by configuring users and roles as well as permissions in
|
||||||
|
the configuration files.</p>
|
||||||
|
|
||||||
|
<p>ActiveMQ Artemis supports wild-card security configuration. This feature makes security configuration very
|
||||||
|
flexible and enables fine-grained control over permissions in an efficient way.</p>
|
||||||
|
|
||||||
|
<p>For a full description of how to configure security with ActiveMQ Artemis, please consult the user
|
||||||
|
manual.</p>
|
||||||
|
|
||||||
|
<p>This example demonstrates how to configure users/roles using a JAAS login module, how to configure topics with
|
||||||
|
proper permissions using wild-card expressions, and how they take effects in a simple program. </p>
|
||||||
|
|
||||||
|
<p>First we need to configure users with roles. Since this example is using the <code>PropertiesLogin</code> JAAS
|
||||||
|
login module the users and roles are configured in <code>artemis-users.properties</code> and
|
||||||
|
<code>artemis-roles.properties</code> which are referenced from the login module's configuration in <code>login.config</code>.
|
||||||
|
This example has four users configured as below:</p>
|
||||||
|
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
bill=activemq
|
||||||
|
andrew=activemq1
|
||||||
|
frank=activemq2
|
||||||
|
sam=activemq3
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>And various roles for those users:</p>
|
||||||
|
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
user=bill,andrew,frank,sam
|
||||||
|
europe-user=andrew
|
||||||
|
us-user=frank
|
||||||
|
news-user=frank,sam
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Each user has three properties available: user name, password, and roles it belongs to. It should be noted that
|
||||||
|
a user can belong to more than one role. In the above configuration, all users belong to role 'user'. User 'andrew' also
|
||||||
|
belongs to role 'europe-user', user 'frank' also belongs to 'us-user' and 'news-user' and user 'sam' also belongs to 'news-user'.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
User name and password consists of a valid account that can be used to establish connections to a ActiveMQ Artemis server, while
|
||||||
|
roles are used in controlling the access privileges against ActiveMQ Artemis topics and queues. You can achieve this control by
|
||||||
|
configuring proper permissions in <code>broker.xml</code>, like the following
|
||||||
|
</p>
|
||||||
|
<pre class="prettyprint"><code>
|
||||||
|
<security-settings>
|
||||||
|
<!-- any user can have full control of generic topics -->
|
||||||
|
<security-setting match="jms.topic.#">
|
||||||
|
<permission type="createDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteDurableQueue" roles="user"/>
|
||||||
|
<permission type="createNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="send" roles="user"/>
|
||||||
|
<permission type="consume" roles="user"/>
|
||||||
|
</security-setting>
|
||||||
|
|
||||||
|
<security-setting match="jms.topic.news.europe.#">
|
||||||
|
<permission type="createDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteDurableQueue" roles="user"/>
|
||||||
|
<permission type="createNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="send" roles="europe-user"/>
|
||||||
|
<permission type="consume" roles="news-user"/>
|
||||||
|
</security-setting>
|
||||||
|
|
||||||
|
<security-setting match="jms.topic.news.us.#">
|
||||||
|
<permission type="createDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteDurableQueue" roles="user"/>
|
||||||
|
<permission type="createNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="send" roles="us-user"/>
|
||||||
|
<permission type="consume" roles="news-user"/>
|
||||||
|
</security-setting>
|
||||||
|
</security-settings>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Permissions can be defined on any group of queues, by using a wildcard. You can easily specify
|
||||||
|
wildcards to apply certain permissions to a set of matching queues and topics. In the above configuration
|
||||||
|
we have created four sets of permissions, each set matches against a special group of targets, indicated by wild-card match attributes.</p>
|
||||||
|
|
||||||
|
<p>You can provide a very broad permission control as a default and then add more strict control
|
||||||
|
over specific addresses. By the above we define the following access rules:</p>
|
||||||
|
|
||||||
|
<li>Only role 'us-user' can create/delete and pulish messages to topics whose names match wild-card pattern 'news.us.#'.</li>
|
||||||
|
<li>Only role 'europe-user' can create/delete and publish messages to topics whose names match wild-card pattern 'news.europe.#'.</li>
|
||||||
|
<li>Only role 'news-user' can subscribe messages to topics whose names match wild-card pattern 'news.us.#' and 'news.europe.#'.</li>
|
||||||
|
<li>For any other topics that don't match any of the above wild-card patterns, permissions are granted to users of role 'user'.</li>
|
||||||
|
|
||||||
|
<p>To illustrate the effect of permissions, three topics are deployed. Topic 'genericTopic' matches 'jms.topic.#' wild-card, topic 'news.europe.europeTopic' matches
|
||||||
|
jms.topic.news.europe.#' wild-cards, and topic 'news.us.usTopic' matches 'jms.topic.news.us.#'.</p>
|
||||||
|
|
||||||
|
<p>With ActiveMQ Artemis, the security manager is also configurable. You can use JAASSecurityManager or JBossASSecurityManager based on you need. Please
|
||||||
|
check out the activemq-beans.xml for how to do. In this example we just use the basic ActiveMQSecurityManagerImpl which reads users/roles/passwords from the xml
|
||||||
|
file <code>activemq-users.xml</code>.
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Example step-by-step</h2>
|
||||||
|
<p><i>To run the example, simply type <code>mvn verify -Pexample</code> from this directory</i></p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>First we need to get an initial context so we can look-up the JMS connection factory and destination objects from JNDI. This initial context will get it's properties from the <code>client-jndi.properties</code> file in the directory <code>../common/config</code></li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
InitialContext initialContext = getContext(0);
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We perform lookup on the topics</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
Topic genericTopic = (Topic) initialContext.lookup("/topic/genericTopic");
|
||||||
|
Topic europeTopic = (Topic) initialContext.lookup("/topic/europeTopic");
|
||||||
|
Topic usTopic = (Topic) initialContext.lookup("/topic/usTopic");
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We perform a lookup on the Connection Factory</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("/ConnectionFactory");
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We try to create a JMS Connection without user/password. It will fail.</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cf.createConnection();
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
catch (JMSSecurityException e)
|
||||||
|
{
|
||||||
|
System.out.println("Default user cannot get a connection. Details: " + e.getMessage());
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>Bill tries to make a connection using wrong password</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
billConnection = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
billConnection = createConnection("bill", "activemq1", cf);
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
catch (JMSException e)
|
||||||
|
{
|
||||||
|
System.out.println("User bill failed to connect. Details: " + e.getMessage());
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>Bill makes a good connection.</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
billConnection = createConnection("bill", "activemq", cf);
|
||||||
|
billConnection.start();
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>Andrew makes a good connection</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
andrewConnection = createConnection("andrew", "activemq1", cf);
|
||||||
|
andrewConnection.start();
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>Frank makes a good connection</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
frankConnection = createConnection("frank", "activemq2", cf);
|
||||||
|
frankConnection.start();
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>Sam makes a good connection</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
samConnection = createConnection("sam", "activemq3", cf);
|
||||||
|
samConnection.start();
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check every user can publish/subscribe genericTopics</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserSendAndReceive(genericTopic, billConnection, "bill");
|
||||||
|
checkUserSendAndReceive(genericTopic, andrewConnection, "andrew");
|
||||||
|
checkUserSendAndReceive(genericTopic, frankConnection, "frank");
|
||||||
|
checkUserSendAndReceive(genericTopic, samConnection, "sam");
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.europe.europeTopic for bill: can't send and can't receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserNoSendNoReceive(europeTopic, billConnection, "bill", andrewConnection, frankConnection);
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.europe.europeTopic for andrew: can send but can't receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserSendNoReceive(europeTopic, andrewConnection, "andrew", frankConnection);
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.europe.europeTopic for frank: can't send but can receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserReceiveNoSend(europeTopic, frankConnection, "frank", andrewConnection);
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.europe.europeTopic for sam: can't send but can receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserReceiveNoSend(europeTopic, samConnection, "sam", andrewConnection);
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.us.usTopic for bill: can't send and can't receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserNoSendNoReceive(usTopic, billConnection, "bill");
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.us.usTopic for andrew: can't send and can't receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserNoSendNoReceive(usTopic, andrewConnection, "andrew");
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.us.usTopic for frank: can both send and receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserSendAndReceive(usTopic, frankConnection, "frank");
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>We check permissions on news.us.usTopic for sam: can't send but can receive</li>
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
checkUserReceiveNoSend(usTopic, samConnection, "sam", frankConnection);
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<li>And finally, <b>always</b> remember to close your JMS connections and resources after use, in a <code>finally</code> block. Closing a JMS connection will automatically close all of its sessions, consumers, producer and browser objects</li>
|
||||||
|
|
||||||
|
<pre class="prettyprint">
|
||||||
|
<code>
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (billConnection != null)
|
||||||
|
{
|
||||||
|
billConnection.close();
|
||||||
|
}
|
||||||
|
if (andrewConnection != null)
|
||||||
|
{
|
||||||
|
andrewConnection.close();
|
||||||
|
}
|
||||||
|
if (frankConnection != null)
|
||||||
|
{
|
||||||
|
frankConnection.close();
|
||||||
|
}
|
||||||
|
if (samConnection != null)
|
||||||
|
{
|
||||||
|
samConnection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also the initialContext
|
||||||
|
if (initialContext != null)
|
||||||
|
{
|
||||||
|
initialContext.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</ol>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,282 @@
|
||||||
|
/*
|
||||||
|
* 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.jms.example;
|
||||||
|
|
||||||
|
import javax.jms.Connection;
|
||||||
|
import javax.jms.ConnectionFactory;
|
||||||
|
import javax.jms.JMSException;
|
||||||
|
import javax.jms.JMSSecurityException;
|
||||||
|
import javax.jms.MessageConsumer;
|
||||||
|
import javax.jms.MessageProducer;
|
||||||
|
import javax.jms.Session;
|
||||||
|
import javax.jms.TextMessage;
|
||||||
|
import javax.jms.Topic;
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
|
||||||
|
public class JaasSecurityExample {
|
||||||
|
|
||||||
|
public static void main(final String[] args) throws Exception {
|
||||||
|
boolean result = true;
|
||||||
|
Connection failConnection = null;
|
||||||
|
Connection billConnection = null;
|
||||||
|
Connection andrewConnection = null;
|
||||||
|
Connection frankConnection = null;
|
||||||
|
Connection samConnection = null;
|
||||||
|
|
||||||
|
InitialContext initialContext = null;
|
||||||
|
try {
|
||||||
|
// /Step 1. Create an initial context to perform the JNDI lookup.
|
||||||
|
initialContext = new InitialContext();
|
||||||
|
|
||||||
|
// Step 2. perform lookup on the topics
|
||||||
|
Topic genericTopic = (Topic) initialContext.lookup("topic/genericTopic");
|
||||||
|
Topic europeTopic = (Topic) initialContext.lookup("topic/europeTopic");
|
||||||
|
Topic usTopic = (Topic) initialContext.lookup("topic/usTopic");
|
||||||
|
|
||||||
|
// Step 3. perform a lookup on the Connection Factory
|
||||||
|
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("ConnectionFactory");
|
||||||
|
|
||||||
|
// Step 4. Try to create a JMS Connection without user/password. It will fail.
|
||||||
|
try {
|
||||||
|
failConnection = cf.createConnection();
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
catch (JMSSecurityException e) {
|
||||||
|
System.out.println("Default user cannot get a connection. Details: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5. bill tries to make a connection using wrong password
|
||||||
|
billConnection = null;
|
||||||
|
try {
|
||||||
|
billConnection = createConnection("bill", "activemq1", cf);
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
catch (JMSException e) {
|
||||||
|
System.out.println("User bill failed to connect. Details: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6. bill makes a good connection.
|
||||||
|
billConnection = createConnection("bill", "activemq", cf);
|
||||||
|
billConnection.start();
|
||||||
|
|
||||||
|
// Step 7. andrew makes a good connection.
|
||||||
|
andrewConnection = createConnection("andrew", "activemq1", cf);
|
||||||
|
andrewConnection.start();
|
||||||
|
|
||||||
|
// Step 8. frank makes a good connection.
|
||||||
|
frankConnection = createConnection("frank", "activemq2", cf);
|
||||||
|
frankConnection.start();
|
||||||
|
|
||||||
|
// Step 9. sam makes a good connection.
|
||||||
|
samConnection = createConnection("sam", "activemq3", cf);
|
||||||
|
samConnection.start();
|
||||||
|
|
||||||
|
// Step 10. Check every user can publish/subscribe genericTopics.
|
||||||
|
System.out.println("------------------------Checking permissions on " + genericTopic + "----------------");
|
||||||
|
checkUserSendAndReceive(genericTopic, billConnection, "bill");
|
||||||
|
checkUserSendAndReceive(genericTopic, andrewConnection, "andrew");
|
||||||
|
checkUserSendAndReceive(genericTopic, frankConnection, "frank");
|
||||||
|
checkUserSendAndReceive(genericTopic, samConnection, "sam");
|
||||||
|
System.out.println("-------------------------------------------------------------------------------------");
|
||||||
|
|
||||||
|
System.out.println("------------------------Checking permissions on " + europeTopic + "----------------");
|
||||||
|
|
||||||
|
// Step 11. Check permissions on news.europe.europeTopic for bill: can't send and can't receive
|
||||||
|
checkUserNoSendNoReceive(europeTopic, billConnection, "bill");
|
||||||
|
|
||||||
|
// Step 12. Check permissions on news.europe.europeTopic for andrew: can send but can't receive
|
||||||
|
checkUserSendNoReceive(europeTopic, andrewConnection, "andrew", frankConnection);
|
||||||
|
|
||||||
|
// Step 13. Check permissions on news.europe.europeTopic for frank: can't send but can receive
|
||||||
|
checkUserReceiveNoSend(europeTopic, frankConnection, "frank", andrewConnection);
|
||||||
|
|
||||||
|
// Step 14. Check permissions on news.europe.europeTopic for sam: can't send but can receive
|
||||||
|
checkUserReceiveNoSend(europeTopic, samConnection, "sam", andrewConnection);
|
||||||
|
System.out.println("-------------------------------------------------------------------------------------");
|
||||||
|
|
||||||
|
System.out.println("------------------------Checking permissions on " + usTopic + "----------------");
|
||||||
|
|
||||||
|
// Step 15. Check permissions on news.us.usTopic for bill: can't send and can't receive
|
||||||
|
checkUserNoSendNoReceive(usTopic, billConnection, "bill");
|
||||||
|
|
||||||
|
// Step 16. Check permissions on news.us.usTopic for andrew: can't send and can't receive
|
||||||
|
checkUserNoSendNoReceive(usTopic, andrewConnection, "andrew");
|
||||||
|
|
||||||
|
// Step 17. Check permissions on news.us.usTopic for frank: can both send and receive
|
||||||
|
checkUserSendAndReceive(usTopic, frankConnection, "frank");
|
||||||
|
|
||||||
|
// Step 18. Check permissions on news.us.usTopic for sam: can't send but can receive
|
||||||
|
checkUserReceiveNoSend(usTopic, samConnection, "sam", frankConnection);
|
||||||
|
System.out.println("-------------------------------------------------------------------------------------");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// Step 19. Be sure to close our JMS resources!
|
||||||
|
if (failConnection != null) {
|
||||||
|
failConnection.close();
|
||||||
|
}
|
||||||
|
if (billConnection != null) {
|
||||||
|
billConnection.close();
|
||||||
|
}
|
||||||
|
if (andrewConnection != null) {
|
||||||
|
andrewConnection.close();
|
||||||
|
}
|
||||||
|
if (frankConnection != null) {
|
||||||
|
frankConnection.close();
|
||||||
|
}
|
||||||
|
if (samConnection != null) {
|
||||||
|
samConnection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also the initialContext
|
||||||
|
if (initialContext != null) {
|
||||||
|
initialContext.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the user can receive message but cannot send message.
|
||||||
|
private static void checkUserReceiveNoSend(final Topic topic,
|
||||||
|
final Connection connection,
|
||||||
|
final String user,
|
||||||
|
final Connection sendingConn) throws JMSException {
|
||||||
|
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
MessageProducer producer = session.createProducer(topic);
|
||||||
|
MessageConsumer consumer = session.createConsumer(topic);
|
||||||
|
TextMessage msg = session.createTextMessage("hello-world-1");
|
||||||
|
|
||||||
|
try {
|
||||||
|
producer.send(msg);
|
||||||
|
throw new IllegalStateException("Security setting is broken! User " + user +
|
||||||
|
" can send message [" +
|
||||||
|
msg.getText() +
|
||||||
|
"] to topic " +
|
||||||
|
topic);
|
||||||
|
}
|
||||||
|
catch (JMSException e) {
|
||||||
|
System.out.println("User " + user + " cannot send message [" + msg.getText() + "] to topic: " + topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now send a good message
|
||||||
|
Session session1 = sendingConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
producer = session1.createProducer(topic);
|
||||||
|
producer.send(msg);
|
||||||
|
|
||||||
|
TextMessage receivedMsg = (TextMessage) consumer.receive(2000);
|
||||||
|
|
||||||
|
if (receivedMsg != null) {
|
||||||
|
System.out.println("User " + user + " can receive message [" + receivedMsg.getText() + "] from topic " + topic);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalStateException("Security setting is broken! User " + user + " cannot receive message from topic " + topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
session1.close();
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the user can send message but cannot receive message
|
||||||
|
private static void checkUserSendNoReceive(final Topic topic,
|
||||||
|
final Connection connection,
|
||||||
|
final String user,
|
||||||
|
final Connection receivingConn) throws JMSException {
|
||||||
|
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
MessageProducer producer = session.createProducer(topic);
|
||||||
|
try {
|
||||||
|
session.createConsumer(topic);
|
||||||
|
}
|
||||||
|
catch (JMSException e) {
|
||||||
|
System.out.println("User " + user + " cannot receive any message from topic " + topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
Session session1 = receivingConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
MessageConsumer goodConsumer = session1.createConsumer(topic);
|
||||||
|
|
||||||
|
TextMessage msg = session.createTextMessage("hello-world-2");
|
||||||
|
producer.send(msg);
|
||||||
|
|
||||||
|
TextMessage receivedMsg = (TextMessage) goodConsumer.receive(2000);
|
||||||
|
if (receivedMsg != null) {
|
||||||
|
System.out.println("User " + user + " can send message [" + receivedMsg.getText() + "] to topic " + topic);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalStateException("Security setting is broken! User " + user +
|
||||||
|
" cannot send message [" +
|
||||||
|
msg.getText() +
|
||||||
|
"] to topic " +
|
||||||
|
topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
session1.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the user has neither send nor receive permission on topic
|
||||||
|
private static void checkUserNoSendNoReceive(final Topic topic,
|
||||||
|
final Connection connection,
|
||||||
|
final String user) throws JMSException {
|
||||||
|
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
MessageProducer producer = session.createProducer(topic);
|
||||||
|
|
||||||
|
try {
|
||||||
|
session.createConsumer(topic);
|
||||||
|
}
|
||||||
|
catch (JMSException e) {
|
||||||
|
System.out.println("User " + user + " cannot create consumer on topic " + topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextMessage msg = session.createTextMessage("hello-world-3");
|
||||||
|
try {
|
||||||
|
producer.send(msg);
|
||||||
|
throw new IllegalStateException("Security setting is broken! User " + user +
|
||||||
|
" can send message [" +
|
||||||
|
msg.getText() +
|
||||||
|
"] to topic " +
|
||||||
|
topic);
|
||||||
|
}
|
||||||
|
catch (JMSException e) {
|
||||||
|
System.out.println("User " + user + " cannot send message [" + msg.getText() + "] to topic: " + topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the user connection has both send and receive permissions on the topic
|
||||||
|
private static void checkUserSendAndReceive(final Topic topic,
|
||||||
|
final Connection connection,
|
||||||
|
final String user) throws JMSException {
|
||||||
|
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
TextMessage msg = session.createTextMessage("hello-world-4");
|
||||||
|
MessageProducer producer = session.createProducer(topic);
|
||||||
|
MessageConsumer consumer = session.createConsumer(topic);
|
||||||
|
producer.send(msg);
|
||||||
|
TextMessage receivedMsg = (TextMessage) consumer.receive(5000);
|
||||||
|
if (receivedMsg != null) {
|
||||||
|
System.out.println("User " + user + " can send message: [" + msg.getText() + "] to topic: " + topic);
|
||||||
|
System.out.println("User " + user + " can receive message: [" + msg.getText() + "] from topic: " + topic);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalStateException("Error! User " + user + " cannot receive the message! ");
|
||||||
|
}
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Connection createConnection(final String username,
|
||||||
|
final String password,
|
||||||
|
final ConnectionFactory cf) throws JMSException {
|
||||||
|
return cf.createConnection(username, password);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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 "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=bill,andrew,frank,sam
|
||||||
|
europe-user=andrew
|
||||||
|
us-user=frank
|
||||||
|
news-user=frank,sam
|
|
@ -0,0 +1,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 "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.
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
bill=activemq
|
||||||
|
andrew=activemq1
|
||||||
|
frank=activemq2
|
||||||
|
sam=activemq3
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version='1.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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="urn:activemq"
|
||||||
|
xsi:schemaLocation="urn:activemq /schema/artemis-server.xsd">
|
||||||
|
|
||||||
|
<jms xmlns="urn:activemq:jms">
|
||||||
|
<topic name="genericTopic"/>
|
||||||
|
|
||||||
|
<topic name="news.europe.europeTopic"/>
|
||||||
|
|
||||||
|
<topic name="news.us.usTopic"/>
|
||||||
|
</jms>
|
||||||
|
|
||||||
|
<core xmlns="urn:activemq:core">
|
||||||
|
|
||||||
|
<bindings-directory>./data/messaging/bindings</bindings-directory>
|
||||||
|
|
||||||
|
<journal-directory>./data/messaging/journal</journal-directory>
|
||||||
|
|
||||||
|
<large-messages-directory>./data/messaging/largemessages</large-messages-directory>
|
||||||
|
|
||||||
|
<paging-directory>./data/messaging/paging</paging-directory>
|
||||||
|
|
||||||
|
<!-- Acceptors -->
|
||||||
|
<acceptors>
|
||||||
|
<acceptor name="netty-acceptor">tcp://localhost:61616</acceptor>
|
||||||
|
</acceptors>
|
||||||
|
|
||||||
|
<!-- Other config -->
|
||||||
|
|
||||||
|
<security-settings>
|
||||||
|
<!-- any user can have full control of generic topics -->
|
||||||
|
<security-setting match="jms.topic.#">
|
||||||
|
<permission type="createDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteDurableQueue" roles="user"/>
|
||||||
|
<permission type="createNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="send" roles="user"/>
|
||||||
|
<permission type="consume" roles="user"/>
|
||||||
|
</security-setting>
|
||||||
|
|
||||||
|
<security-setting match="jms.topic.news.europe.#">
|
||||||
|
<permission type="createDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteDurableQueue" roles="user"/>
|
||||||
|
<permission type="createNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="send" roles="europe-user"/>
|
||||||
|
<permission type="consume" roles="news-user"/>
|
||||||
|
</security-setting>
|
||||||
|
|
||||||
|
<security-setting match="jms.topic.news.us.#">
|
||||||
|
<permission type="createDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteDurableQueue" roles="user"/>
|
||||||
|
<permission type="createNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="deleteNonDurableQueue" roles="user"/>
|
||||||
|
<permission type="send" roles="us-user"/>
|
||||||
|
<permission type="consume" roles="news-user"/>
|
||||||
|
</security-setting>
|
||||||
|
</security-settings>
|
||||||
|
|
||||||
|
</core>
|
||||||
|
</configuration>
|
|
@ -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.
|
||||||
|
|
||||||
|
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
|
||||||
|
connectionFactory.ConnectionFactory=tcp://localhost:61616
|
||||||
|
topic.topic/genericTopic=genericTopic
|
||||||
|
topic.topic/europeTopic=news.europe.europeTopic
|
||||||
|
topic.topic/usTopic=news.us.usTopic
|
2
pom.xml
2
pom.xml
|
@ -123,6 +123,8 @@
|
||||||
<geronimo.jms.2.spec.version>1.0-alpha-2</geronimo.jms.2.spec.version>
|
<geronimo.jms.2.spec.version>1.0-alpha-2</geronimo.jms.2.spec.version>
|
||||||
|
|
||||||
<javac-compiler-id>javac-with-errorprone</javac-compiler-id>
|
<javac-compiler-id>javac-with-errorprone</javac-compiler-id>
|
||||||
|
|
||||||
|
<directory-version>1.5.7</directory-version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
|
|
|
@ -261,6 +261,24 @@
|
||||||
<version>1.0.1</version>
|
<version>1.0.1</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -0,0 +1,347 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.artemis.tests.integration.security;
|
||||||
|
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.NameClassPair;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.api.core.ActiveMQException;
|
||||||
|
import org.apache.activemq.artemis.api.core.SimpleString;
|
||||||
|
import org.apache.activemq.artemis.api.core.TransportConfiguration;
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ClientConsumer;
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ClientProducer;
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ClientSession;
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ServerLocator;
|
||||||
|
import org.apache.activemq.artemis.core.config.Configuration;
|
||||||
|
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
|
||||||
|
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory;
|
||||||
|
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory;
|
||||||
|
import org.apache.activemq.artemis.core.security.Role;
|
||||||
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
|
import org.apache.activemq.artemis.core.server.ActiveMQServers;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
||||||
|
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||||
|
import org.apache.directory.server.annotations.CreateLdapServer;
|
||||||
|
import org.apache.directory.server.annotations.CreateTransport;
|
||||||
|
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
|
||||||
|
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
|
||||||
|
import org.apache.directory.server.core.integ.FrameworkRunner;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@RunWith(FrameworkRunner.class)
|
||||||
|
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)})
|
||||||
|
@ApplyLdifFiles("test.ldif")
|
||||||
|
public class LDAPSecurityTest extends AbstractLdapTestUnit {
|
||||||
|
|
||||||
|
static {
|
||||||
|
String path = System.getProperty("java.security.auth.login.config");
|
||||||
|
if (path == null) {
|
||||||
|
URL resource = LDAPSecurityTest.class.getClassLoader().getResource("login.config");
|
||||||
|
if (resource != null) {
|
||||||
|
path = resource.getFile();
|
||||||
|
System.setProperty("java.security.auth.login.config", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerLocator locator;
|
||||||
|
|
||||||
|
public static final String TARGET_TMP = "./target/tmp";
|
||||||
|
private static final String PRINCIPAL = "uid=admin,ou=system";
|
||||||
|
private static final String CREDENTIALS = "secret";
|
||||||
|
|
||||||
|
|
||||||
|
public LDAPSecurityTest() {
|
||||||
|
File parent = new File(TARGET_TMP);
|
||||||
|
parent.mkdirs();
|
||||||
|
temporaryFolder = new TemporaryFolder(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder;
|
||||||
|
private String testDir;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
locator = ActiveMQClient.createServerLocatorWithHA(new TransportConfiguration(InVMConnectorFactory.class.getCanonicalName()));
|
||||||
|
testDir = temporaryFolder.getRoot().getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testRunning() throws Exception {
|
||||||
|
Hashtable env = new Hashtable();
|
||||||
|
env.put(Context.PROVIDER_URL, "ldap://localhost:1024");
|
||||||
|
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||||
|
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, PRINCIPAL);
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, CREDENTIALS);
|
||||||
|
DirContext ctx = new InitialDirContext(env);
|
||||||
|
|
||||||
|
HashSet set = new HashSet();
|
||||||
|
|
||||||
|
NamingEnumeration list = ctx.list("ou=system");
|
||||||
|
|
||||||
|
while (list.hasMore()) {
|
||||||
|
NameClassPair ncp = (NameClassPair) list.next();
|
||||||
|
set.add(ncp.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertTrue(set.contains("uid=admin"));
|
||||||
|
Assert.assertTrue(set.contains("ou=users"));
|
||||||
|
Assert.assertTrue(set.contains("ou=groups"));
|
||||||
|
Assert.assertTrue(set.contains("ou=configuration"));
|
||||||
|
Assert.assertTrue(set.contains("prefNodeName=sysPrefRoot"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthentication() throws Exception {
|
||||||
|
ActiveMQServer server = getActiveMQServer();
|
||||||
|
server.start();
|
||||||
|
ClientSessionFactory cf = locator.createSessionFactory();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Assert.fail("should not throw exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
cf.close();
|
||||||
|
locator.close();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthenticationBadPassword() throws Exception {
|
||||||
|
ActiveMQServer server = getActiveMQServer();
|
||||||
|
server.start();
|
||||||
|
ClientSessionFactory cf = locator.createSessionFactory();
|
||||||
|
|
||||||
|
try {
|
||||||
|
cf.createSession("first", "badpassword", false, true, true, false, 0);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
cf.close();
|
||||||
|
locator.close();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthorizationNegative() throws Exception {
|
||||||
|
final SimpleString ADDRESS = new SimpleString("address");
|
||||||
|
final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
|
||||||
|
final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
|
||||||
|
|
||||||
|
ActiveMQServer server = getActiveMQServer();
|
||||||
|
Set<Role> roles = new HashSet<>();
|
||||||
|
roles.add(new Role("programmers", false, false, false, false, false, false, false));
|
||||||
|
server.getConfiguration().getSecurityRoles().put("#", roles);
|
||||||
|
server.start();
|
||||||
|
server.createQueue(ADDRESS, DURABLE_QUEUE, null, true, false);
|
||||||
|
server.createQueue(ADDRESS, NON_DURABLE_QUEUE, null, false, false);
|
||||||
|
|
||||||
|
ClientSessionFactory cf = locator.createSessionFactory();
|
||||||
|
ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
|
||||||
|
|
||||||
|
// CREATE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(DURABLE_QUEUE);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, NON_DURABLE_QUEUE, false);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(NON_DURABLE_QUEUE);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRODUCE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(ADDRESS);
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONSUME
|
||||||
|
try {
|
||||||
|
ClientConsumer consumer = session.createConsumer(DURABLE_QUEUE);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// MANAGE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
cf.close();
|
||||||
|
locator.close();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthorizationPositive() throws Exception {
|
||||||
|
final SimpleString ADDRESS = new SimpleString("address");
|
||||||
|
final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
|
||||||
|
final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
|
||||||
|
|
||||||
|
ActiveMQServer server = getActiveMQServer();
|
||||||
|
Set<Role> roles = new HashSet<>();
|
||||||
|
roles.add(new Role("admins", true, true, true, true, true, true, true));
|
||||||
|
server.getConfiguration().getSecurityRoles().put("#", roles);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
ClientSessionFactory cf = locator.createSessionFactory();
|
||||||
|
ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
|
||||||
|
|
||||||
|
// CREATE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, NON_DURABLE_QUEUE, false);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(NON_DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
|
||||||
|
// PRODUCE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(ADDRESS);
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONSUME
|
||||||
|
try {
|
||||||
|
session.createConsumer(DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// MANAGE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
cf.close();
|
||||||
|
locator.close();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActiveMQServer getActiveMQServer() {
|
||||||
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||||
|
securityManager.setConfigurationName("LDAPLogin");
|
||||||
|
Configuration configuration = new ConfigurationImpl().setSecurityEnabled(true).addAcceptorConfiguration(new TransportConfiguration(InVMAcceptorFactory.class.getCanonicalName()))
|
||||||
|
.setJournalDirectory(ActiveMQTestBase.getJournalDir(testDir, 0, false))
|
||||||
|
.setBindingsDirectory(ActiveMQTestBase.getBindingsDir(testDir, 0, false))
|
||||||
|
.setPagingDirectory(ActiveMQTestBase.getPageDir(testDir, 0, false))
|
||||||
|
.setLargeMessagesDirectory(ActiveMQTestBase.getLargeMessagesDir(testDir, 0, false));
|
||||||
|
return ActiveMQServers.newActiveMQServer(configuration, ManagementFactory.getPlatformMBeanServer(), securityManager, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ package org.apache.activemq.artemis.tests.integration.security;
|
||||||
|
|
||||||
import javax.transaction.xa.XAResource;
|
import javax.transaction.xa.XAResource;
|
||||||
import javax.transaction.xa.Xid;
|
import javax.transaction.xa.Xid;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -30,24 +32,37 @@ import org.apache.activemq.artemis.api.core.client.ClientProducer;
|
||||||
import org.apache.activemq.artemis.api.core.client.ClientSession;
|
import org.apache.activemq.artemis.api.core.client.ClientSession;
|
||||||
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
|
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
|
||||||
import org.apache.activemq.artemis.api.core.client.ServerLocator;
|
import org.apache.activemq.artemis.api.core.client.ServerLocator;
|
||||||
import org.apache.activemq.artemis.tests.util.CreateMessage;
|
|
||||||
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
|
||||||
import org.apache.activemq.artemis.core.config.Configuration;
|
import org.apache.activemq.artemis.core.config.Configuration;
|
||||||
import org.apache.activemq.artemis.core.security.CheckType;
|
import org.apache.activemq.artemis.core.security.CheckType;
|
||||||
import org.apache.activemq.artemis.core.security.Role;
|
import org.apache.activemq.artemis.core.security.Role;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
|
import org.apache.activemq.artemis.core.server.ActiveMQServers;
|
||||||
import org.apache.activemq.artemis.core.server.Queue;
|
import org.apache.activemq.artemis.core.server.Queue;
|
||||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl;
|
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl;
|
||||||
import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
|
import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManagerImpl;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManagerImpl;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
||||||
|
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||||
|
import org.apache.activemq.artemis.tests.util.CreateMessage;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class SecurityTest extends ActiveMQTestBase {
|
public class SecurityTest extends ActiveMQTestBase {
|
||||||
|
|
||||||
|
static {
|
||||||
|
String path = System.getProperty("java.security.auth.login.config");
|
||||||
|
if (path == null) {
|
||||||
|
URL resource = SecurityTest.class.getClassLoader().getResource("login.config");
|
||||||
|
if (resource != null) {
|
||||||
|
path = resource.getFile();
|
||||||
|
System.setProperty("java.security.auth.login.config", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* create session tests
|
* create session tests
|
||||||
*/
|
*/
|
||||||
|
@ -67,6 +82,301 @@ public class SecurityTest extends ActiveMQTestBase {
|
||||||
locator = createInVMNonHALocator();
|
locator = createInVMNonHALocator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthentication() throws Exception {
|
||||||
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||||
|
securityManager.setConfigurationName("PropertiesLogin");
|
||||||
|
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||||
|
server.start();
|
||||||
|
ClientSessionFactory cf = createSessionFactory(locator);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Assert.fail("should not throw exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthenticationBadPassword() throws Exception {
|
||||||
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||||
|
securityManager.setConfigurationName("PropertiesLogin");
|
||||||
|
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||||
|
server.start();
|
||||||
|
ClientSessionFactory cf = createSessionFactory(locator);
|
||||||
|
|
||||||
|
try {
|
||||||
|
cf.createSession("first", "badpassword", false, true, true, false, 0);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthenticationGuest() throws Exception {
|
||||||
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||||
|
securityManager.setConfigurationName("GuestLogin");
|
||||||
|
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||||
|
server.start();
|
||||||
|
ClientSessionFactory cf = createSessionFactory(locator);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientSession session = cf.createSession("first", "secret", false, true, true, false, 0);
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Assert.fail("should not throw exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthorizationNegative() throws Exception {
|
||||||
|
final SimpleString ADDRESS = new SimpleString("address");
|
||||||
|
final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
|
||||||
|
final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
|
||||||
|
|
||||||
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||||
|
securityManager.setConfigurationName("PropertiesLogin");
|
||||||
|
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||||
|
Set<Role> roles = new HashSet<>();
|
||||||
|
roles.add(new Role("programmers", false, false, false, false, false, false, false));
|
||||||
|
server.getConfiguration().getSecurityRoles().put("#", roles);
|
||||||
|
server.start();
|
||||||
|
server.createQueue(ADDRESS, DURABLE_QUEUE, null, true, false);
|
||||||
|
server.createQueue(ADDRESS, NON_DURABLE_QUEUE, null, false, false);
|
||||||
|
|
||||||
|
ClientSessionFactory cf = createSessionFactory(locator);
|
||||||
|
ClientSession session = addClientSession(cf.createSession("first", "secret", false, true, true, false, 0));
|
||||||
|
|
||||||
|
// CREATE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(DURABLE_QUEUE);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, NON_DURABLE_QUEUE, false);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(NON_DURABLE_QUEUE);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRODUCE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(ADDRESS);
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONSUME
|
||||||
|
try {
|
||||||
|
ClientConsumer consumer = session.createConsumer(DURABLE_QUEUE);
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// MANAGE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
Assert.fail("should throw exception here");
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthorizationPositive() throws Exception {
|
||||||
|
final SimpleString ADDRESS = new SimpleString("address");
|
||||||
|
final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
|
||||||
|
final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
|
||||||
|
|
||||||
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||||
|
securityManager.setConfigurationName("PropertiesLogin");
|
||||||
|
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||||
|
Set<Role> roles = new HashSet<>();
|
||||||
|
roles.add(new Role("programmers", true, true, true, true, true, true, true));
|
||||||
|
server.getConfiguration().getSecurityRoles().put("#", roles);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
ClientSessionFactory cf = createSessionFactory(locator);
|
||||||
|
ClientSession session = addClientSession(cf.createSession("first", "secret", false, true, true, false, 0));
|
||||||
|
|
||||||
|
// CREATE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, NON_DURABLE_QUEUE, false);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(NON_DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
|
||||||
|
// PRODUCE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(ADDRESS);
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONSUME
|
||||||
|
try {
|
||||||
|
session.createConsumer(DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// MANAGE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJAASSecurityManagerAuthorizationPositiveGuest() throws Exception {
|
||||||
|
final SimpleString ADDRESS = new SimpleString("address");
|
||||||
|
final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
|
||||||
|
final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
|
||||||
|
|
||||||
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
|
||||||
|
securityManager.setConfigurationName("GuestLogin");
|
||||||
|
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||||
|
Set<Role> roles = new HashSet<>();
|
||||||
|
roles.add(new Role("bar", true, true, true, true, true, true, true));
|
||||||
|
server.getConfiguration().getSecurityRoles().put("#", roles);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
ClientSessionFactory cf = createSessionFactory(locator);
|
||||||
|
ClientSession session = addClientSession(cf.createSession("junk", "junk", false, true, true, false, 0));
|
||||||
|
|
||||||
|
// CREATE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.createQueue(ADDRESS, NON_DURABLE_QUEUE, false);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE_NON_DURABLE_QUEUE
|
||||||
|
try {
|
||||||
|
session.deleteQueue(NON_DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
|
||||||
|
|
||||||
|
// PRODUCE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(ADDRESS);
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONSUME
|
||||||
|
try {
|
||||||
|
session.createConsumer(DURABLE_QUEUE);
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// MANAGE
|
||||||
|
try {
|
||||||
|
ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
|
||||||
|
producer.send(session.createMessage(true));
|
||||||
|
}
|
||||||
|
catch (ActiveMQException e) {
|
||||||
|
Assert.fail("should not throw exception here");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateSessionWithNullUserPass() throws Exception {
|
public void testCreateSessionWithNullUserPass() throws Exception {
|
||||||
ActiveMQServer server = createServer();
|
ActiveMQServer server = createServer();
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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="users.properties"
|
||||||
|
org.apache.activemq.jaas.properties.role="roles.properties";
|
||||||
|
};
|
||||||
|
|
||||||
|
LDAPLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:1024"
|
||||||
|
connectionUsername="uid=admin,ou=system"
|
||||||
|
connectionPassword=secret
|
||||||
|
connectionProtocol=s
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=system"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleBase="ou=system"
|
||||||
|
roleName=cn
|
||||||
|
roleSearchMatching="(member=uid={1},ou=system)"
|
||||||
|
roleSearchSubtree=false
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
UnAuthenticatedLDAPLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:1024"
|
||||||
|
connectionUsername="uid=admin,ou=system"
|
||||||
|
connectionPassword=""
|
||||||
|
connectionProtocol=s
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=system"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleBase="ou=system"
|
||||||
|
roleName=dummyRoleName
|
||||||
|
roleSearchMatching="(uid={1})"
|
||||||
|
roleSearchSubtree=false
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
ExpandedLDAPLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:1024"
|
||||||
|
connectionUsername="uid=admin,ou=system"
|
||||||
|
connectionPassword=secret
|
||||||
|
connectionProtocol=s
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=system"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleBase="ou=system"
|
||||||
|
roleName=cn
|
||||||
|
roleSearchMatching="(uid={1})"
|
||||||
|
roleSearchSubtree=false
|
||||||
|
expandRoles=true
|
||||||
|
expandRolesMatching="(member={0})"
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
GuestLogin {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule required
|
||||||
|
debug=true
|
||||||
|
org.apache.activemq.jaas.guest.user="foo"
|
||||||
|
org.apache.activemq.jaas.guest.role="bar";
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
GuestLoginWithDefaults {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule required
|
||||||
|
debug=true;
|
||||||
|
};
|
||||||
|
|
||||||
|
OpenLdapConfiguration {
|
||||||
|
org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required
|
||||||
|
debug=true
|
||||||
|
initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
|
||||||
|
connectionURL="ldap://localhost:389"
|
||||||
|
connectionUsername="cn=mqbroker,ou=Services,ou=system,dc=fusesource,dc=com"
|
||||||
|
connectionPassword="sunflower"
|
||||||
|
connectionProtocol="s"
|
||||||
|
topicSearchMatchingFormat="cn={0},ou=Topic,ou=Destination,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
topicSearchSubtreeBool=true
|
||||||
|
authentication=simple
|
||||||
|
userBase="ou=User,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
userSearchMatching="(uid={0})"
|
||||||
|
userSearchSubtree=false
|
||||||
|
roleSearchMatching="(uid={1})"
|
||||||
|
queueSearchMatchingFormat="cn={0},ou=Queue,ou=Destination,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
queueSearchSubtreeBool=true
|
||||||
|
roleBase="ou=Group,ou=ActiveMQ,ou=system,dc=fusesource,dc=com"
|
||||||
|
roleName=cn
|
||||||
|
roleSearchMatching="(member:=uid={1})"
|
||||||
|
roleSearchSubtree=true
|
||||||
|
;
|
||||||
|
};
|
|
@ -0,0 +1,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 "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.
|
||||||
|
#
|
||||||
|
|
||||||
|
programmers=first
|
||||||
|
accounting=second
|
||||||
|
employees=first,second
|
|
@ -0,0 +1,39 @@
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
## 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.
|
||||||
|
## ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
dn: uid=first,ou=system
|
||||||
|
uid: first
|
||||||
|
userPassword: secret
|
||||||
|
objectClass: account
|
||||||
|
objectClass: simpleSecurityObject
|
||||||
|
objectClass: top
|
||||||
|
|
||||||
|
###################
|
||||||
|
## Define groups ##
|
||||||
|
###################
|
||||||
|
|
||||||
|
dn: cn=admins,ou=system
|
||||||
|
cn: admins
|
||||||
|
member: uid=first,ou=system
|
||||||
|
objectClass: groupOfNames
|
||||||
|
objectClass: top
|
||||||
|
|
||||||
|
dn: cn=users,ou=system
|
||||||
|
cn: users
|
||||||
|
member: cn=admins,ou=system
|
||||||
|
objectClass: groupOfNames
|
||||||
|
objectClass: top
|
|
@ -0,0 +1,19 @@
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
first=secret
|
||||||
|
second=password
|
Loading…
Reference in New Issue