ARTEMIS-74 import JAAS auth from 5.x
This change allows the use of JAAS login modules for basic authentication and authorization.
This commit is contained in:
parent
e971f117b2
commit
6ed9c5ae91
|
@ -70,8 +70,16 @@ public class Create extends InputAbstract {
|
|||
public static final String ETC_LOGGING_PROPERTIES = "etc/logging.properties";
|
||||
public static final String ETC_BOOTSTRAP_XML = "etc/bootstrap.xml";
|
||||
public static final String ETC_BROKER_XML = "etc/broker.xml";
|
||||
|
||||
// The JAAS PropertiesLogin module uses role=user(s) syntax, but the basic security uses user=role(s) syntax so we need 2 different files here
|
||||
public static final String ETC_ARTEMIS_ROLES_PROPERTIES = "etc/artemis-roles.properties";
|
||||
public static final String ETC_ARTEMIS_ROLES_BASIC_PROPERTIES = "etc/artemis-roles-basic.properties";
|
||||
public static final String ETC_ARTEMIS_ROLES_JAAS_PROPERTIES = "etc/artemis-roles-jaas.properties";
|
||||
|
||||
public static final String ETC_ARTEMIS_USERS_PROPERTIES = "etc/artemis-users.properties";
|
||||
public static final String ETC_JAAS_BROKER_SECURITY_SETTINGS_TXT = "etc/jaas-broker-security-settings.txt";
|
||||
public static final String ETC_BASIC_BROKER_SECURITY_SETTINGS_TXT = "etc/basic-broker-security-settings.txt";
|
||||
public static final String ETC_LOGIN_CONFIG = "etc/login.config";
|
||||
public static final String ETC_REPLICATED_SETTINGS_TXT = "etc/replicated-settings.txt";
|
||||
public static final String ETC_SHARED_STORE_SETTINGS_TXT = "etc/shared-store-settings.txt";
|
||||
public static final String ETC_CLUSTER_SECURITY_SETTINGS_TXT = "etc/cluster-security-settings.txt";
|
||||
|
@ -161,10 +169,24 @@ public class Create extends InputAbstract {
|
|||
@Option(name = "--aio", description = "Force aio journal on the configuration regardless of the library being available or not.")
|
||||
boolean forceLibaio;
|
||||
|
||||
@Option(name = "--broker-security", description = "Use basic, file-based security or JAAS login module for broker security (Default: basic)")
|
||||
String brokerSecurity;
|
||||
|
||||
boolean IS_WINDOWS;
|
||||
|
||||
boolean IS_CYGWIN;
|
||||
|
||||
public String getBrokerSecurity() {
|
||||
if (brokerSecurity == null) {
|
||||
brokerSecurity = "basic";
|
||||
}
|
||||
return brokerSecurity;
|
||||
}
|
||||
|
||||
public void setBrokerSecurity(String security) {
|
||||
this.brokerSecurity = security;
|
||||
}
|
||||
|
||||
public int getMaxHops() {
|
||||
return maxHops;
|
||||
}
|
||||
|
@ -535,6 +557,29 @@ public class Create extends InputAbstract {
|
|||
|
||||
filters.put("${java-opts}", javaOptions);
|
||||
|
||||
if (isAllowAnonymous()) {
|
||||
filters.put("${bootstrap.guest}", "default-user=\"" + getUser() + "\"");
|
||||
}
|
||||
else {
|
||||
filters.put("${bootstrap.guest}", "");
|
||||
}
|
||||
|
||||
if (brokerSecurity != null && brokerSecurity.equalsIgnoreCase("jaas")) {
|
||||
filters.put("${broker-security-settings}", applyFilters(readTextFile(ETC_JAAS_BROKER_SECURITY_SETTINGS_TXT), filters));
|
||||
filters.put("${login-config}", "-Djava.security.auth.login.config=" + path(directory, false) + "/etc/login.config");
|
||||
write(ETC_LOGIN_CONFIG, filters, false);
|
||||
write(ETC_ARTEMIS_ROLES_JAAS_PROPERTIES, filters, false);
|
||||
File file = new File(directory, ETC_ARTEMIS_ROLES_JAAS_PROPERTIES);
|
||||
file.renameTo(new File(directory, ETC_ARTEMIS_ROLES_PROPERTIES));
|
||||
}
|
||||
else {
|
||||
filters.put("${broker-security-settings}", applyFilters(readTextFile(ETC_BASIC_BROKER_SECURITY_SETTINGS_TXT), filters));
|
||||
filters.put("${login-config}", "");
|
||||
write(ETC_ARTEMIS_ROLES_BASIC_PROPERTIES, filters, false);
|
||||
File file = new File(directory, ETC_ARTEMIS_ROLES_BASIC_PROPERTIES);
|
||||
file.renameTo(new File(directory, ETC_ARTEMIS_ROLES_PROPERTIES));
|
||||
}
|
||||
|
||||
if (IS_WINDOWS) {
|
||||
write(BIN_ARTEMIS_CMD, null, false);
|
||||
write(BIN_ARTEMIS_SERVICE_EXE);
|
||||
|
@ -553,13 +598,6 @@ public class Create extends InputAbstract {
|
|||
|
||||
write(ETC_LOGGING_PROPERTIES, null, false);
|
||||
|
||||
if (isAllowAnonymous()) {
|
||||
filters.put("${bootstrap.guest}", "default-user=\"" + getUser() + "\"");
|
||||
}
|
||||
else {
|
||||
filters.put("${bootstrap.guest}", "");
|
||||
}
|
||||
|
||||
if (noWeb) {
|
||||
filters.put("${bootstrap-web-settings}", "");
|
||||
}
|
||||
|
@ -571,7 +609,6 @@ public class Create extends InputAbstract {
|
|||
|
||||
write(ETC_BOOTSTRAP_XML, filters, false);
|
||||
write(ETC_BROKER_XML, filters, false);
|
||||
write(ETC_ARTEMIS_ROLES_PROPERTIES, filters, false);
|
||||
write(ETC_ARTEMIS_USERS_PROPERTIES, filters, false);
|
||||
|
||||
context.out.println("");
|
||||
|
|
|
@ -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_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
|
||||
|
|
|
@ -21,7 +21,7 @@ rem Cluster Properties: Used to pass arguments to ActiveMQ Artemis which can be
|
|||
rem set ARTEMIS_CLUSTER_PROPS=-Dactivemq.remoting.default.port=61617 -Dactivemq.remoting.amqp.port=5673 -Dactivemq.remoting.stomp.port=61614 -Dactivemq.remoting.hornetq.port=5446
|
||||
|
||||
rem Java Opts
|
||||
set JAVA_ARGS=-XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xms512M -Xmx1024M ${java-opts}
|
||||
set JAVA_ARGS=-XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xms512M -Xmx1024M ${login-config} ${java-opts}
|
||||
|
||||
rem Debug args: Uncomment to enable debug
|
||||
rem set DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
|
||||
|
|
|
@ -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">
|
||||
|
||||
<basic-security
|
||||
users="file:${artemis.instance}/etc/artemis-users.properties"
|
||||
roles="file:${artemis.instance}/etc/artemis-roles.properties"
|
||||
${bootstrap.guest}/>
|
||||
${broker-security-settings}
|
||||
|
||||
<server configuration="file:${artemis.instance}/etc/broker.xml"/>
|
||||
|
||||
|
|
|
@ -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_BOOTSTRAP_XML);
|
||||
openStream(Create.ETC_BROKER_XML);
|
||||
openStream(Create.ETC_ARTEMIS_ROLES_PROPERTIES);
|
||||
openStream(Create.ETC_ARTEMIS_ROLES_BASIC_PROPERTIES);
|
||||
openStream(Create.ETC_ARTEMIS_ROLES_JAAS_PROPERTIES);
|
||||
openStream(Create.ETC_ARTEMIS_USERS_PROPERTIES);
|
||||
openStream(Create.ETC_REPLICATED_SETTINGS_TXT);
|
||||
openStream(Create.ETC_REPLICATED_SETTINGS_TXT);
|
||||
|
@ -50,6 +51,8 @@ public class StreamClassPathTest {
|
|||
openStream(Create.ETC_CONNECTOR_SETTINGS_TXT);
|
||||
openStream(Create.ETC_BOOTSTRAP_WEB_SETTINGS_TXT);
|
||||
openStream(Create.ETC_JOURNAL_BUFFER_SETTINGS);
|
||||
openStream(Create.ETC_JAAS_BROKER_SECURITY_SETTINGS_TXT);
|
||||
openStream(Create.ETC_BASIC_BROKER_SECURITY_SETTINGS_TXT);
|
||||
}
|
||||
|
||||
private void openStream(String source) throws Exception {
|
||||
|
|
|
@ -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
|
||||
## contributor license agreements. See the NOTICE file distributed with
|
||||
## this work for additional information regarding copyright ownership.
|
||||
## The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
## The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
## (the "License"); you may not use this file except in compliance with
|
||||
## the License. You may obtain a copy of the License at
|
||||
##
|
||||
##
|
||||
## http://www.apache.org/licenses/LICENSE-2.0
|
||||
##
|
||||
## Unless required by applicable law or agreed to in writing, software
|
||||
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
## See the License for the specific language governing permissions and
|
||||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
## ---------------------------------------------------------------------------
|
||||
BrokerDTO
|
||||
SecurityDTO
|
||||
BasicSecurityDTO
|
||||
JaasSecurityDTO
|
||||
|
||||
|
|
|
@ -113,6 +113,9 @@ public class ArtemisCreatePlugin extends ArtemisAbstractPlugin {
|
|||
@Parameter(defaultValue = "ON_DEMAND")
|
||||
private String messageLoadBalancing;
|
||||
|
||||
@Parameter(defaultValue = "basic")
|
||||
private String brokerSecurity;
|
||||
|
||||
/**
|
||||
* For extra stuff not covered by the properties
|
||||
*/
|
||||
|
@ -200,7 +203,7 @@ public class ArtemisCreatePlugin extends ArtemisAbstractPlugin {
|
|||
|
||||
ArrayList<String> listCommands = new ArrayList<>();
|
||||
|
||||
add(listCommands, "create", "--allow-anonymous", "--silent", "--force", "--no-web", "--user", user, "--password", password, "--role", role, "--port-offset", "" + portOffset, "--data", dataFolder);
|
||||
add(listCommands, "create", "--allow-anonymous", "--silent", "--force", "--no-web", "--user", user, "--password", password, "--role", role, "--port-offset", "" + portOffset, "--data", dataFolder, "--broker-security", brokerSecurity);
|
||||
|
||||
if (allowAnonymous) {
|
||||
add(listCommands, "--allow-anonymous");
|
||||
|
|
|
@ -82,6 +82,24 @@
|
|||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-server-integ</artifactId>
|
||||
<version>${directory-version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-integ</artifactId>
|
||||
<version>${directory-version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
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() {
|
||||
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
|
||||
|
||||
File file = new File(testDir1);
|
||||
deleteDirectory(file);
|
||||
file.mkdirs();
|
||||
|
||||
recreateDirectory(getJournalDir(testDir1));
|
||||
recreateDirectory(getBindingsDir(testDir1));
|
||||
recreateDirectory(getPageDir(testDir1));
|
||||
recreateDirectory(getLargeMessagesDir(testDir1));
|
||||
recreateDirectory(getJournalDir(testDir1, index, backup));
|
||||
recreateDirectory(getBindingsDir(testDir1, index, backup));
|
||||
recreateDirectory(getPageDir(testDir1, index, backup));
|
||||
recreateDirectory(getLargeMessagesDir(testDir1, index, backup));
|
||||
recreateDirectory(getClientLargeMessagesDir(testDir1));
|
||||
recreateDirectory(getTemporaryDir(testDir1));
|
||||
}
|
||||
|
@ -815,7 +819,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
|||
* @return the journalDir
|
||||
*/
|
||||
public String getJournalDir() {
|
||||
return getJournalDir(getTestDir());
|
||||
return getJournalDir(0, false);
|
||||
}
|
||||
|
||||
protected static String getJournalDir(final String testDir1) {
|
||||
|
@ -834,7 +838,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
|||
* @return the bindingsDir
|
||||
*/
|
||||
protected String getBindingsDir() {
|
||||
return getBindingsDir(getTestDir());
|
||||
return getBindingsDir(0, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -859,7 +863,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
|||
* @return the pageDir
|
||||
*/
|
||||
protected String getPageDir() {
|
||||
return getPageDir(getTestDir());
|
||||
return getPageDir(0, false);
|
||||
}
|
||||
|
||||
protected File getPageDirFile() {
|
||||
|
@ -886,7 +890,7 @@ public abstract class ActiveMQTestBase extends Assert {
|
|||
* @return the largeMessagesDir
|
||||
*/
|
||||
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).
|
||||
|
||||
## Basic user credentials
|
||||
## User credentials
|
||||
|
||||
Apache ActiveMQ Artemis ships with a security manager implementation 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.
|
||||
Apache ActiveMQ Artemis ships with two security manager implementations:
|
||||
|
||||
- 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
|
||||
roles can easily be added into these files.
|
||||
- The flexible, pluggable `ActiveMQJAASSecurityManager` which supports any standard JAAS login module. Artemis ships
|
||||
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.
|
||||
Lets take a look at what this might look like:
|
||||
### Non-JAAS Security Manager
|
||||
|
||||
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>
|
||||
<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 next thing to note is the element `defaultuser`. This defines what
|
||||
user will be assumed when the client does not specify a
|
||||
username/password when creating a session. In this case they will be the
|
||||
user `guest`. Multiple roles can be specified for a default user in the
|
||||
`artemis-roles.properties`.
|
||||
The next thing to note is the element `defaultuser`. This defines what user will be assumed when the client does not
|
||||
specify a username/password when creating a session. In this case they will be the 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
|
||||
just a set of key value pairs that define the users and their password, like so:
|
||||
Lets now take a look at the `artemis-users.properties` file, this is basically just a set of key value pairs that define
|
||||
the users and their password, like so:
|
||||
|
||||
bill=activemq
|
||||
andrew=activemq1
|
||||
frank=activemq2
|
||||
sam=activemq3
|
||||
|
||||
The `artemis-roles.properties` defines what groups these users belong too
|
||||
where the key is the user and the value is a comma separated list of the groups
|
||||
the user belongs to, like so:
|
||||
The `artemis-roles.properties` defines what groups these users belong too where the key is the user and the value is a
|
||||
comma separated list of the groups the user belongs to, like so:
|
||||
|
||||
bill=user
|
||||
andrew=europe-user,user
|
||||
frank=us-user,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
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ under the License.
|
|||
<module>request-reply</module>
|
||||
<module>scheduled-message</module>
|
||||
<module>security</module>
|
||||
<module>security-jaas</module>
|
||||
<module>send-acknowledgements</module>
|
||||
<module>spring-integration</module>
|
||||
<module>ssl-enabled</module>
|
||||
|
@ -142,6 +143,7 @@ under the License.
|
|||
<module>rest</module>
|
||||
<module>scheduled-message</module>
|
||||
<module>security</module>
|
||||
<module>security-jaas</module>
|
||||
<module>send-acknowledgements</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>
|
||||
|
||||
<javac-compiler-id>javac-with-errorprone</javac-compiler-id>
|
||||
|
||||
<directory-version>1.5.7</directory-version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
|
|
|
@ -261,6 +261,24 @@
|
|||
<version>1.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-server-integ</artifactId>
|
||||
<version>${directory-version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-integ</artifactId>
|
||||
<version>${directory-version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<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.Xid;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URL;
|
||||
import java.util.HashSet;
|
||||
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.ClientSessionFactory;
|
||||
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.security.CheckType;
|
||||
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.core.server.Queue;
|
||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl;
|
||||
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.ActiveMQSecurityManager2;
|
||||
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.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -67,6 +82,301 @@ public class SecurityTest extends ActiveMQTestBase {
|
|||
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
|
||||
public void testCreateSessionWithNullUserPass() throws Exception {
|
||||
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