ARTEMIS-3140 Extra options in LDAP login module

Adds support for extra configuration options to LDAP login module to
prepare for supporting any future/custom string configuration in LDAP
directory context creation.

Details:

 - Changed LDAPLoginModule to pass any string configuration not
recognized by the module itself to the InitialDirContext contruction
environment.
 - Changed the static LDAPLoginModule configuration key fields to an
enum to be able to loop through the specified keys (e.g. to filter out
the internal LDAPLoginModule configuration keys from the keys passed to
InitialDirContext).
 - Few fixes for issues reported by static analysis tools.
 - Tested that LDAP authentication with TLS+GSSAPI works against a
recent Windows AD server with Java
OpenJDK11U-jdk_x64_windows_hotspot_11.0.13_8 by setting the property
com.sun.jndi.ldap.tls.cbtype (see ARTEMIS-3140) in JAAS login.conf.
 - Moved LDAPLoginModuleTest to the correct package to be able to
access LDAPLoginModule package privates from the test code.
 - Added a test to LDAPLoginModuleTest for the task changes.
 - Updated documentation to reflect the changes.
This commit is contained in:
pahamala 2021-10-28 14:19:52 +03:00 committed by Justin Bertram
parent 2167ac2e30
commit a0c4cba7e1
3 changed files with 216 additions and 138 deletions

View File

@ -17,7 +17,6 @@
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;
@ -65,36 +64,59 @@ public class LDAPLoginModule implements AuditLoginModule {
private static final Logger logger = Logger.getLogger(LDAPLoginModule.class);
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";
private static final String SASL_LOGIN_CONFIG_SCOPE = "saslLoginConfigScope";
private static final String AUTHENTICATE_USER = "authenticateUser";
private static final String REFERRAL = "referral";
private static final String IGNORE_PARTIAL_RESULT_EXCEPTION = "ignorePartialResultException";
private static final String PASSWORD_CODEC = "passwordCodec";
private static final String CONNECTION_POOL = "connectionPool";
private static final String CONNECTION_TIMEOUT = "connectionTimeout";
private static final String READ_TIMEOUT = "readTimeout";
enum ConfigKey {
DEBUG("debug"),
INITIAL_CONTEXT_FACTORY("initialContextFactory"),
CONNECTION_URL("connectionURL"),
CONNECTION_USERNAME("connectionUsername"),
CONNECTION_PASSWORD("connectionPassword"),
CONNECTION_PROTOCOL("connectionProtocol"),
AUTHENTICATION("authentication"),
USER_BASE("userBase"),
USER_SEARCH_MATCHING("userSearchMatching"),
USER_SEARCH_SUBTREE("userSearchSubtree"),
ROLE_BASE("roleBase"),
ROLE_NAME("roleName"),
ROLE_SEARCH_MATCHING("roleSearchMatching"),
ROLE_SEARCH_SUBTREE("roleSearchSubtree"),
USER_ROLE_NAME("userRoleName"),
EXPAND_ROLES("expandRoles"),
EXPAND_ROLES_MATCHING("expandRolesMatching"),
SASL_LOGIN_CONFIG_SCOPE("saslLoginConfigScope"),
AUTHENTICATE_USER("authenticateUser"),
REFERRAL("referral"),
IGNORE_PARTIAL_RESULT_EXCEPTION("ignorePartialResultException"),
PASSWORD_CODEC("passwordCodec"),
CONNECTION_POOL("connectionPool"),
CONNECTION_TIMEOUT("connectionTimeout"),
READ_TIMEOUT("readTimeout");
private final String name;
ConfigKey(String name) {
this.name = name;
}
String getName() {
return name;
}
static boolean contains(String key) {
for (ConfigKey k: values()) {
if (k.name.equals(key)) {
return true;
}
}
return false;
}
}
protected DirContext context;
private Subject subject;
private CallbackHandler handler;
private LDAPLoginProperty[] config;
private final Set<LDAPLoginProperty> config = new HashSet<>();
private String username;
private final Set<RolePrincipal> groups = new HashSet<>();
private boolean userAuthenticated = false;
@ -113,37 +135,20 @@ public class LDAPLoginModule implements AuditLoginModule {
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)),
new LDAPLoginProperty(PASSWORD_CODEC, (String) options.get(PASSWORD_CODEC)),
new LDAPLoginProperty(SASL_LOGIN_CONFIG_SCOPE, (String) options.get(SASL_LOGIN_CONFIG_SCOPE)),
new LDAPLoginProperty(AUTHENTICATE_USER, (String) options.get(AUTHENTICATE_USER)),
new LDAPLoginProperty(REFERRAL, (String) options.get(REFERRAL)),
new LDAPLoginProperty(IGNORE_PARTIAL_RESULT_EXCEPTION, (String) options.get(IGNORE_PARTIAL_RESULT_EXCEPTION)),
new LDAPLoginProperty(CONNECTION_POOL, (String) options.get(CONNECTION_POOL)),
new LDAPLoginProperty(CONNECTION_TIMEOUT, (String) options.get(CONNECTION_TIMEOUT)),
new LDAPLoginProperty(READ_TIMEOUT, (String) options.get(READ_TIMEOUT))};
if (isLoginPropertySet(AUTHENTICATE_USER)) {
authenticateUser = Boolean.valueOf(getLDAPPropertyValue(AUTHENTICATE_USER));
// copy all options to config, ignoring non-string entries
config.clear();
for (Map.Entry<String, ?> entry : options.entrySet()) {
if (entry.getValue() instanceof String) {
config.add(new LDAPLoginProperty(entry.getKey(), (String) entry.getValue()));
}
}
isRoleAttributeSet = isLoginPropertySet(ROLE_NAME);
roleAttributeName = getLDAPPropertyValue(ROLE_NAME);
codecClass = getLDAPPropertyValue(PASSWORD_CODEC);
if (isLoginPropertySet(ConfigKey.AUTHENTICATE_USER)) {
authenticateUser = Boolean.parseBoolean(getLDAPPropertyValue(ConfigKey.AUTHENTICATE_USER));
}
isRoleAttributeSet = isLoginPropertySet(ConfigKey.ROLE_NAME);
roleAttributeName = getLDAPPropertyValue(ConfigKey.ROLE_NAME);
codecClass = getLDAPPropertyValue(ConfigKey.PASSWORD_CODEC);
}
private String getPlainPassword(String password) {
@ -174,13 +179,15 @@ public class LDAPLoginModule implements AuditLoginModule {
String password = null;
username = ((NameCallback) callbacks[0]).getName();
if (username == null)
if (username == null) {
return false;
}
if (((PasswordCallback) callbacks[1]).getPassword() != null)
if (((PasswordCallback) callbacks[1]).getPassword() != null) {
password = new String(((PasswordCallback) callbacks[1]).getPassword());
}
/**
/*
* https://tools.ietf.org/html/rfc4513#section-6.3.1
*
* Clients that use the results from a simple Bind operation to make
@ -188,8 +195,9 @@ public class LDAPLoginModule implements AuditLoginModule {
* requests (by verifying that the supplied password is not empty) and
* react appropriately.
*/
if (password == null || password.length() == 0)
if (password == null || password.length() == 0) {
throw new FailedLoginException("Password cannot be null or empty");
}
// authenticate will throw LoginException
// in case of failed authentication
@ -227,9 +235,8 @@ public class LDAPLoginModule implements AuditLoginModule {
}
}
for (RolePrincipal gp : groups) {
principals.add(gp);
}
principals.addAll(groups);
clear();
return result;
}
@ -259,7 +266,6 @@ public class LDAPLoginModule implements AuditLoginModule {
}
protected boolean authenticate(String username, String password) throws LoginException {
List<String> roles = new ArrayList<>();
try {
String dn = resolveDN(username, roles);
@ -271,11 +277,6 @@ public class LDAPLoginModule implements AuditLoginModule {
} else {
throw new FailedLoginException("Password does not match for user: " + username);
}
} catch (CommunicationException e) {
closeContext();
FailedLoginException ex = new FailedLoginException("Error contacting LDAP");
ex.initCause(e);
throw ex;
} catch (NamingException e) {
closeContext();
FailedLoginException ex = new FailedLoginException("Error contacting LDAP");
@ -299,10 +300,6 @@ public class LDAPLoginModule implements AuditLoginModule {
private String resolveDN(String username, List<String> roles) throws FailedLoginException {
String dn = null;
MessageFormat userSearchMatchingFormat;
boolean userSearchSubtreeBool;
boolean ignorePartialResultExceptionBool;
if (logger.isDebugEnabled()) {
logger.debug("Create the LDAP initial context.");
}
@ -314,13 +311,13 @@ public class LDAPLoginModule implements AuditLoginModule {
throw ex;
}
if (!isLoginPropertySet(USER_SEARCH_MATCHING)) {
if (!isLoginPropertySet(ConfigKey.USER_SEARCH_MATCHING)) {
return username;
}
userSearchMatchingFormat = new MessageFormat(getLDAPPropertyValue(USER_SEARCH_MATCHING));
userSearchSubtreeBool = Boolean.valueOf(getLDAPPropertyValue(USER_SEARCH_SUBTREE)).booleanValue();
ignorePartialResultExceptionBool = Boolean.valueOf(getLDAPPropertyValue(IGNORE_PARTIAL_RESULT_EXCEPTION)).booleanValue();
MessageFormat userSearchMatchingFormat = new MessageFormat(getLDAPPropertyValue(ConfigKey.USER_SEARCH_MATCHING));
boolean userSearchSubtreeBool = Boolean.parseBoolean(getLDAPPropertyValue(ConfigKey.USER_SEARCH_SUBTREE));
boolean ignorePartialResultExceptionBool = Boolean.parseBoolean(getLDAPPropertyValue(ConfigKey.IGNORE_PARTIAL_RESULT_EXCEPTION));
try {
@ -334,8 +331,8 @@ public class LDAPLoginModule implements AuditLoginModule {
// setup attributes
List<String> list = new ArrayList<>();
if (isLoginPropertySet(USER_ROLE_NAME)) {
list.add(getLDAPPropertyValue(USER_ROLE_NAME));
if (isLoginPropertySet(ConfigKey.USER_ROLE_NAME)) {
list.add(getLDAPPropertyValue(ConfigKey.USER_ROLE_NAME));
}
String[] attribs = new String[list.size()];
list.toArray(attribs);
@ -344,13 +341,13 @@ public class LDAPLoginModule implements AuditLoginModule {
if (logger.isDebugEnabled()) {
logger.debug("Get the user DN.");
logger.debug("Looking for the user in LDAP with ");
logger.debug(" base DN: " + getLDAPPropertyValue(USER_BASE));
logger.debug(" base DN: " + getLDAPPropertyValue(ConfigKey.USER_BASE));
logger.debug(" filter: " + filter);
}
NamingEnumeration<SearchResult> results = null;
try {
results = Subject.doAs(brokerGssapiIdentity, (PrivilegedExceptionAction< NamingEnumeration<SearchResult>>) () -> context.search(getLDAPPropertyValue(USER_BASE), filter, constraints));
results = Subject.doAs(brokerGssapiIdentity, (PrivilegedExceptionAction< NamingEnumeration<SearchResult>>) () -> context.search(getLDAPPropertyValue(ConfigKey.USER_BASE), filter, constraints));
} catch (PrivilegedActionException e) {
Exception cause = e.getException();
FailedLoginException ex = new FailedLoginException("Error executing search query to resolve DN");
@ -382,7 +379,7 @@ public class LDAPLoginModule implements AuditLoginModule {
NameParser parser = context.getNameParser("");
Name contextName = parser.parse(context.getNameInNamespace());
Name baseName = parser.parse(getLDAPPropertyValue(USER_BASE));
Name baseName = parser.parse(getLDAPPropertyValue(ConfigKey.USER_BASE));
Name entryName = parser.parse(result.getName());
Name name = contextName.addAll(baseName);
name = name.addAll(entryName);
@ -415,8 +412,8 @@ public class LDAPLoginModule implements AuditLoginModule {
if (attrs == null) {
throw new FailedLoginException("User found, but LDAP entry malformed: " + username);
}
if (isLoginPropertySet(USER_ROLE_NAME)) {
Attribute roleNames = attrs.get(getLDAPPropertyValue(USER_ROLE_NAME));
if (isLoginPropertySet(ConfigKey.USER_ROLE_NAME)) {
Attribute roleNames = attrs.get(getLDAPPropertyValue(ConfigKey.USER_ROLE_NAME));
if (roleNames != null) {
NamingEnumeration<?> e = roleNames.getAll();
while (e.hasMore()) {
@ -436,11 +433,6 @@ public class LDAPLoginModule implements AuditLoginModule {
}
}
}
} catch (CommunicationException e) {
closeContext();
FailedLoginException ex = new FailedLoginException("Error contacting LDAP");
ex.initCause(e);
throw ex;
} catch (NamingException e) {
closeContext();
FailedLoginException ex = new FailedLoginException("Error contacting LDAP");
@ -456,18 +448,14 @@ public class LDAPLoginModule implements AuditLoginModule {
String username,
List<String> currentRoles) throws NamingException {
if (!isLoginPropertySet(ROLE_SEARCH_MATCHING)) {
if (!isLoginPropertySet(ConfigKey.ROLE_SEARCH_MATCHING)) {
return;
}
MessageFormat roleSearchMatchingFormat;
boolean roleSearchSubtreeBool;
boolean expandRolesBool;
boolean ignorePartialResultExceptionBool;
roleSearchMatchingFormat = new MessageFormat(getLDAPPropertyValue(ROLE_SEARCH_MATCHING));
roleSearchSubtreeBool = Boolean.valueOf(getLDAPPropertyValue(ROLE_SEARCH_SUBTREE)).booleanValue();
expandRolesBool = Boolean.valueOf(getLDAPPropertyValue(EXPAND_ROLES)).booleanValue();
ignorePartialResultExceptionBool = Boolean.valueOf(getLDAPPropertyValue(IGNORE_PARTIAL_RESULT_EXCEPTION)).booleanValue();
MessageFormat roleSearchMatchingFormat = new MessageFormat(getLDAPPropertyValue(ConfigKey.ROLE_SEARCH_MATCHING));
boolean roleSearchSubtreeBool = Boolean.parseBoolean(getLDAPPropertyValue(ConfigKey.ROLE_SEARCH_SUBTREE));
boolean expandRolesBool = Boolean.parseBoolean(getLDAPPropertyValue(ConfigKey.EXPAND_ROLES));
boolean ignorePartialResultExceptionBool = Boolean.parseBoolean(getLDAPPropertyValue(ConfigKey.IGNORE_PARTIAL_RESULT_EXCEPTION));
final String filter = roleSearchMatchingFormat.format(new String[]{doRFC2254Encoding(dn), doRFC2254Encoding(username)});
@ -480,14 +468,14 @@ public class LDAPLoginModule implements AuditLoginModule {
if (logger.isDebugEnabled()) {
logger.debug("Get user roles.");
logger.debug("Looking for the user roles in LDAP with ");
logger.debug(" base DN: " + getLDAPPropertyValue(ROLE_BASE));
logger.debug(" base DN: " + getLDAPPropertyValue(ConfigKey.ROLE_BASE));
logger.debug(" filter: " + filter);
}
HashSet<String> haveSeenNames = new HashSet<>();
Queue<String> pendingNameExpansion = new LinkedList<>();
NamingEnumeration<SearchResult> results = null;
try {
results = Subject.doAs(brokerGssapiIdentity, (PrivilegedExceptionAction< NamingEnumeration<SearchResult>>) () -> context.search(getLDAPPropertyValue(ROLE_BASE), filter, constraints));
results = Subject.doAs(brokerGssapiIdentity, (PrivilegedExceptionAction< NamingEnumeration<SearchResult>>) () -> context.search(getLDAPPropertyValue(ConfigKey.ROLE_BASE), filter, constraints));
} catch (PrivilegedActionException e) {
Exception cause = e.getException();
NamingException ex = new NamingException("Error executing search query to resolve roles");
@ -513,18 +501,18 @@ public class LDAPLoginModule implements AuditLoginModule {
}
}
if (expandRolesBool) {
MessageFormat expandRolesMatchingFormat = new MessageFormat(getLDAPPropertyValue(EXPAND_ROLES_MATCHING));
MessageFormat expandRolesMatchingFormat = new MessageFormat(getLDAPPropertyValue(ConfigKey.EXPAND_ROLES_MATCHING));
while (!pendingNameExpansion.isEmpty()) {
String name = pendingNameExpansion.remove();
final String expandFilter = expandRolesMatchingFormat.format(new String[]{name});
if (logger.isDebugEnabled()) {
logger.debug("Get 'expanded' user roles.");
logger.debug("Looking for the 'expanded' user roles in LDAP with ");
logger.debug(" base DN: " + getLDAPPropertyValue(ROLE_BASE));
logger.debug(" base DN: " + getLDAPPropertyValue(ConfigKey.ROLE_BASE));
logger.debug(" filter: " + expandFilter);
}
try {
results = Subject.doAs(brokerGssapiIdentity, (PrivilegedExceptionAction< NamingEnumeration<SearchResult>>) () -> context.search(getLDAPPropertyValue(ROLE_BASE), expandFilter, constraints));
results = Subject.doAs(brokerGssapiIdentity, (PrivilegedExceptionAction< NamingEnumeration<SearchResult>>) () -> context.search(getLDAPPropertyValue(ConfigKey.ROLE_BASE), expandFilter, constraints));
} catch (PrivilegedActionException e) {
Exception cause = e.getException();
NamingException ex = new NamingException("Error executing search query to expand roles");
@ -554,7 +542,7 @@ public class LDAPLoginModule implements AuditLoginModule {
}
protected String doRFC2254Encoding(String inputString) {
StringBuffer buf = new StringBuffer(inputString.length());
StringBuilder buf = new StringBuilder(inputString.length());
for (int i = 0; i < inputString.length(); i++) {
char c = inputString.charAt(i);
switch (c) {
@ -603,17 +591,17 @@ public class LDAPLoginModule implements AuditLoginModule {
}
}
if (isLoginPropertySet(CONNECTION_USERNAME)) {
context.addToEnvironment(Context.SECURITY_PRINCIPAL, getLDAPPropertyValue(CONNECTION_USERNAME));
if (isLoginPropertySet(ConfigKey.CONNECTION_USERNAME)) {
context.addToEnvironment(Context.SECURITY_PRINCIPAL, getLDAPPropertyValue(ConfigKey.CONNECTION_USERNAME));
} else {
context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
}
if (isLoginPropertySet(CONNECTION_PASSWORD)) {
context.addToEnvironment(Context.SECURITY_CREDENTIALS, getPlainPassword(getLDAPPropertyValue(CONNECTION_PASSWORD)));
if (isLoginPropertySet(ConfigKey.CONNECTION_PASSWORD)) {
context.addToEnvironment(Context.SECURITY_CREDENTIALS, getPlainPassword(getLDAPPropertyValue(ConfigKey.CONNECTION_PASSWORD)));
} else {
context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
}
context.addToEnvironment(Context.SECURITY_AUTHENTICATION, getLDAPPropertyValue(AUTHENTICATION));
context.addToEnvironment(Context.SECURITY_AUTHENTICATION, getLDAPPropertyValue(ConfigKey.AUTHENTICATION));
return isValid;
}
@ -634,25 +622,25 @@ public class LDAPLoginModule implements AuditLoginModule {
if (context == null) {
try {
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, getLDAPPropertyValue(INITIAL_CONTEXT_FACTORY));
env.put(Context.SECURITY_PROTOCOL, getLDAPPropertyValue(CONNECTION_PROTOCOL));
env.put(Context.PROVIDER_URL, getLDAPPropertyValue(CONNECTION_URL));
env.put(Context.SECURITY_AUTHENTICATION, getLDAPPropertyValue(AUTHENTICATION));
if (isLoginPropertySet(CONNECTION_POOL)) {
env.put("com.sun.jndi.ldap.connect.pool", getLDAPPropertyValue(CONNECTION_POOL));
env.put(Context.INITIAL_CONTEXT_FACTORY, getLDAPPropertyValue(ConfigKey.INITIAL_CONTEXT_FACTORY));
env.put(Context.SECURITY_PROTOCOL, getLDAPPropertyValue(ConfigKey.CONNECTION_PROTOCOL));
env.put(Context.PROVIDER_URL, getLDAPPropertyValue(ConfigKey.CONNECTION_URL));
env.put(Context.SECURITY_AUTHENTICATION, getLDAPPropertyValue(ConfigKey.AUTHENTICATION));
if (isLoginPropertySet(ConfigKey.CONNECTION_POOL)) {
env.put("com.sun.jndi.ldap.connect.pool", getLDAPPropertyValue(ConfigKey.CONNECTION_POOL));
}
if (isLoginPropertySet(CONNECTION_TIMEOUT)) {
env.put("com.sun.jndi.ldap.connect.timeout", getLDAPPropertyValue(CONNECTION_TIMEOUT));
if (isLoginPropertySet(ConfigKey.CONNECTION_TIMEOUT)) {
env.put("com.sun.jndi.ldap.connect.timeout", getLDAPPropertyValue(ConfigKey.CONNECTION_TIMEOUT));
}
if (isLoginPropertySet(READ_TIMEOUT)) {
env.put("com.sun.jndi.ldap.read.timeout", getLDAPPropertyValue(READ_TIMEOUT));
if (isLoginPropertySet(ConfigKey.READ_TIMEOUT)) {
env.put("com.sun.jndi.ldap.read.timeout", getLDAPPropertyValue(ConfigKey.READ_TIMEOUT));
}
// handle LDAP referrals
// valid values are "throw", "ignore" and "follow"
String referral = "ignore";
if (getLDAPPropertyValue(REFERRAL) != null) {
referral = getLDAPPropertyValue(REFERRAL);
if (getLDAPPropertyValue(ConfigKey.REFERRAL) != null) {
referral = getLDAPPropertyValue(ConfigKey.REFERRAL);
}
env.put(Context.REFERRAL, referral);
@ -660,9 +648,9 @@ public class LDAPLoginModule implements AuditLoginModule {
logger.debug("Referral handling: " + referral);
}
if ("GSSAPI".equalsIgnoreCase(getLDAPPropertyValue(AUTHENTICATION))) {
if ("GSSAPI".equalsIgnoreCase(getLDAPPropertyValue(ConfigKey.AUTHENTICATION))) {
final String configScope = isLoginPropertySet(SASL_LOGIN_CONFIG_SCOPE) ? getLDAPPropertyValue(SASL_LOGIN_CONFIG_SCOPE) : "broker-sasl-gssapi";
final String configScope = isLoginPropertySet(ConfigKey.SASL_LOGIN_CONFIG_SCOPE) ? getLDAPPropertyValue(ConfigKey.SASL_LOGIN_CONFIG_SCOPE) : "broker-sasl-gssapi";
try {
LoginContext loginContext = new LoginContext(configScope);
loginContext.login();
@ -676,19 +664,21 @@ public class LDAPLoginModule implements AuditLoginModule {
} else {
if (isLoginPropertySet(CONNECTION_USERNAME)) {
env.put(Context.SECURITY_PRINCIPAL, getLDAPPropertyValue(CONNECTION_USERNAME));
if (isLoginPropertySet(ConfigKey.CONNECTION_USERNAME)) {
env.put(Context.SECURITY_PRINCIPAL, getLDAPPropertyValue(ConfigKey.CONNECTION_USERNAME));
} else {
throw new NamingException("Empty username is not allowed");
}
if (isLoginPropertySet(CONNECTION_PASSWORD)) {
env.put(Context.SECURITY_CREDENTIALS, getPlainPassword(getLDAPPropertyValue(CONNECTION_PASSWORD)));
if (isLoginPropertySet(ConfigKey.CONNECTION_PASSWORD)) {
env.put(Context.SECURITY_CREDENTIALS, getPlainPassword(getLDAPPropertyValue(ConfigKey.CONNECTION_PASSWORD)));
} else {
throw new NamingException("Empty password is not allowed");
}
}
extendInitialEnvironment(config, env);
try {
context = Subject.doAs(brokerGssapiIdentity, (PrivilegedExceptionAction<DirContext>) () -> new InitialDirContext(env));
} catch (PrivilegedActionException e) {
@ -703,17 +693,31 @@ public class LDAPLoginModule implements AuditLoginModule {
}
}
private String getLDAPPropertyValue(String propertyName) {
protected void extendInitialEnvironment(Set<LDAPLoginProperty> moduleConfig, Hashtable<String, String> initialContextEnv) {
// sub-classes may override the method if the default implementation is not sufficient:
// add all non-module configs to initial DirContext environment to support passing
// any custom/future property to InitialDirContext construction
for (LDAPLoginProperty prop: moduleConfig) {
String propName = prop.getPropertyName();
if (initialContextEnv.get(propName) == null && !ConfigKey.contains(propName)) {
initialContextEnv.put(propName, prop.getPropertyValue());
}
}
}
private String getLDAPPropertyValue(ConfigKey key) {
for (LDAPLoginProperty conf : config)
if (conf.getPropertyName().equals(propertyName))
if (conf.getPropertyName().equals(key.getName())) {
return conf.getPropertyValue();
}
return null;
}
private boolean isLoginPropertySet(String propertyName) {
private boolean isLoginPropertySet(ConfigKey key) {
for (LDAPLoginProperty conf : config) {
if (conf.getPropertyName().equals(propertyName) && (conf.getPropertyValue() != null && !"".equals(conf.getPropertyValue())))
if (conf.getPropertyName().equals(key.getName()) && (conf.getPropertyValue() != null && !"".equals(conf.getPropertyValue()))) {
return true;
}
}
return false;
}

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.security.jaas;
package org.apache.activemq.artemis.spi.core.security.jaas;
import javax.naming.Context;
import javax.naming.NameClassPair;
@ -37,14 +37,12 @@ import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
import org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule;
import org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginProperty;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
@ -309,9 +307,9 @@ public class LDAPLoginModuleTest extends AbstractLdapTestUnit {
}
loginModule.initialize(new Subject(), callbackHandler, null, options);
LDAPLoginProperty[] ldapProps = (LDAPLoginProperty[]) configMap.get(loginModule);
Set<LDAPLoginProperty> ldapProps = (Set<LDAPLoginProperty>) configMap.get(loginModule);
for (String key: options.keySet()) {
assertTrue("val set: " + key, presentInArray(ldapProps, key));
assertTrue("val set: " + key, presentIn(ldapProps, key));
}
}
@ -364,8 +362,81 @@ public class LDAPLoginModuleTest extends AbstractLdapTestUnit {
}
context.logout();
}
@Test
public void testEnvironmentProperties() throws Exception {
HashMap<String, Object> options = new HashMap<>();
private boolean presentInArray(LDAPLoginProperty[] ldapProps, String propertyName) {
// set module configs
for (LDAPLoginModule.ConfigKey configKey: LDAPLoginModule.ConfigKey.values()) {
if (configKey.getName().equals("initialContextFactory")) {
options.put(configKey.getName(), "com.sun.jndi.ldap.LdapCtxFactory");
} else if (configKey.getName().equals("connectionURL")) {
options.put(configKey.getName(), "ldap://localhost:1024");
} else if (configKey.getName().equals("referral")) {
options.put(configKey.getName(), "ignore");
} else if (configKey.getName().equals("connectionTimeout")) {
options.put(configKey.getName(), "10000");
} else if (configKey.getName().equals("readTimeout")) {
options.put(configKey.getName(), "11000");
} else if (configKey.getName().equals("authentication")) {
options.put(configKey.getName(), "simple");
} else if (configKey.getName().equals("connectionUsername")) {
options.put(configKey.getName(), PRINCIPAL);
} else if (configKey.getName().equals("connectionPassword")) {
options.put(configKey.getName(), CREDENTIALS);
} else if (configKey.getName().equals("connectionProtocol")) {
options.put(configKey.getName(), "s");
} else if (configKey.getName().equals("debug")) {
options.put(configKey.getName(), "true");
} else {
options.put(configKey.getName(), configKey.getName() + "_value_set");
}
}
// add extra configs
options.put("com.sun.jndi.ldap.tls.cbtype", "tls-server-end-point");
options.put("randomConfig", "some-value");
// add non-strings configs
options.put("non.string.1", new Object());
options.put("non.string.2", 1);
// create context
LDAPLoginModule loginModule = new LDAPLoginModule();
loginModule.initialize(new Subject(), null, null, options);
loginModule.openContext();
// get created environment
Hashtable<?, ?> environment = loginModule.context.getEnvironment();
// cleanup
loginModule.closeContext();
// module config keys should not be passed to environment
for (LDAPLoginModule.ConfigKey configKey: LDAPLoginModule.ConfigKey.values()) {
assertEquals("value should not be set for key: " + configKey.getName(), null, environment.get(configKey.getName()));
}
// extra, non-module configs should be passed to environment
assertEquals("value should be set for key: " + "com.sun.jndi.ldap.tls.cbtype", "tls-server-end-point", environment.get("com.sun.jndi.ldap.tls.cbtype"));
assertEquals("value should be set for key: " + "randomConfig", "some-value", environment.get("randomConfig"));
// non-string configs should not be passed to environment
assertEquals("value should not be set for key: " + "non.string.1", null, environment.get("non.string.1"));
assertEquals("value should not be set for key: " + "non.string.2", null, environment.get("non.string.2"));
// environment configs should be set
assertEquals("value should be set for key: " + Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory", environment.get(Context.INITIAL_CONTEXT_FACTORY));
assertEquals("value should be set for key: " + Context.PROVIDER_URL, "ldap://localhost:1024", environment.get(Context.PROVIDER_URL));
assertEquals("value should be set for key: " + Context.REFERRAL, "ignore", environment.get(Context.REFERRAL));
assertEquals("value should be set for key: " + "com.sun.jndi.ldap.connect.timeout", "10000", environment.get("com.sun.jndi.ldap.connect.timeout"));
assertEquals("value should be set for key: " + "com.sun.jndi.ldap.read.timeout", "11000", environment.get("com.sun.jndi.ldap.read.timeout"));
assertEquals("value should be set for key: " + Context.SECURITY_AUTHENTICATION, "simple", environment.get(Context.SECURITY_AUTHENTICATION));
assertEquals("value should be set for key: " + Context.SECURITY_PRINCIPAL, PRINCIPAL, environment.get(Context.SECURITY_PRINCIPAL));
assertEquals("value should be set for key: " + Context.SECURITY_CREDENTIALS, CREDENTIALS, environment.get(Context.SECURITY_CREDENTIALS));
assertEquals("value should be set for key: " + Context.SECURITY_PROTOCOL, "s", environment.get(Context.SECURITY_PROTOCOL));
}
private boolean presentIn(Set<LDAPLoginProperty> ldapProps, String propertyName) {
for (LDAPLoginProperty conf : ldapProps) {
if (conf.getPropertyName().equals(propertyName) && (conf.getPropertyValue() != null && !"".equals(conf.getPropertyValue())))
return true;

View File

@ -819,6 +819,9 @@ system. It is implemented by
testing or debugging; normally, it should be set to `false`, or omitted;
default is `false`
Any additional configuration option not recognized by the LDAP login module itself
is passed as-is to the underlying LDAP connection logic.
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