You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by or...@apache.org on 2019/09/29 22:18:39 UTC
[qpid-broker-j] 09/12: QPID-8363: [Broker-J] Add support for GSSAPI
authentication into SimpleLDAP authentication provider
This is an automated email from the ASF dual-hosted git repository.
orudyy pushed a commit to branch 7.1.x
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git
commit 097031e32ad5334ddd1831d563ef0d1680f2a7bd
Author: Alex Rudyy <or...@apache.org>
AuthorDate: Sun Sep 15 00:14:19 2019 +0100
QPID-8363: [Broker-J] Add support for GSSAPI authentication into SimpleLDAP authentication provider
(cherry picked from commit 4c7aeb273736baebd49cf5c0807359ca3f15ed7e)
---
broker-core/pom.xml | 5 +
.../auth/manager/LdapAuthenticationMethod.java | 27 +-
.../manager/SimpleLDAPAuthenticationManager.java | 20 +-
.../SimpleLDAPAuthenticationManagerImpl.java | 207 ++++++---
.../SimpleLDAPAuthenticationManagerTest.java | 495 +++++++++++++++++++++
broker-core/src/test/resources/login.config | 55 +++
broker-core/src/test/resources/users.ldif | 50 +++
.../authenticationprovider/simpleldap/add.html | 30 +-
.../authenticationprovider/simpleldap/show.html | 8 +
.../authenticationprovider/simpleldap/add.js | 36 +-
.../authenticationprovider/simpleldap/show.js | 7 +-
...oker-Security-Authentication-Providers-LDAP.xml | 10 +
pom.xml | 18 +
13 files changed, 878 insertions(+), 90 deletions(-)
diff --git a/broker-core/pom.xml b/broker-core/pom.xml
index a7f0325..7f7bd78 100644
--- a/broker-core/pom.xml
+++ b/broker-core/pom.xml
@@ -92,6 +92,11 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-all</artifactId>
+ </dependency>
+
</dependencies>
<build>
diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/show.js b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/LdapAuthenticationMethod.java
similarity index 56%
copy from broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/show.js
copy to broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/LdapAuthenticationMethod.java
index 9e13956..50335e5 100644
--- a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/show.js
+++ b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/LdapAuthenticationMethod.java
@@ -17,24 +17,23 @@
* under the License.
*/
-define(["qpid/common/util", "dojo/domReady!"], function (util)
+package org.apache.qpid.server.security.auth.manager;
+
+public enum LdapAuthenticationMethod
{
+ NONE("none"),
+ SIMPLE("simple"),
+ GSSAPI("GSSAPI");
+
+ private final String _method;
- function SimpleLdapAuthenticationProvider(data)
+ LdapAuthenticationMethod(String method)
{
- this.fields = [];
- var attributes = data.parent.management.metadata.getMetaData("AuthenticationProvider", "SimpleLDAP").attributes;
- for (var name in attributes)
- {
- this.fields.push(name);
- }
- util.buildUI(data.containerNode, data.parent, "authenticationprovider/simpleldap/show.html", this.fields, this);
+ _method = method;
}
- SimpleLdapAuthenticationProvider.prototype.update = function (data)
+ public String getMethod()
{
- util.updateUI(data, this.fields, this);
+ return _method;
}
-
- return SimpleLdapAuthenticationProvider;
-});
+}
diff --git a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java
index 4820675..fe650f7 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java
@@ -43,10 +43,15 @@ public interface SimpleLDAPAuthenticationManager<X extends SimpleLDAPAuthenticat
String PROVIDER_AUTH_URL = "providerAuthUrl";
String SEARCH_CONTEXT = "searchContext";
String LDAP_CONTEXT_FACTORY = "ldapContextFactory";
- String SEARCH_USERNAME = "getSearchUsername";
- String SEARCH_PASSWORD = "getSearchPassword";
+ String SEARCH_USERNAME = "searchUsername";
+ String SEARCH_PASSWORD = "searchPassword";
String TRUST_STORE = "trustStore";
-
+ String SEARCH_FILTER = "searchFilter";
+ String GROUP_SEARCH_CONTEXT = "groupSearchContext";
+ String GROUP_SEARCH_FILTER = "groupSearchFilter";
+ String AUTHENTICATION_METHOD ="authenticationMethod";
+ String LOGIN_CONFIG_SCOPE = "loginConfigScope";
+ String LOGIN_CONFIG_SCOPE_DEFAULT = "qpid-broker-j";
@ManagedAttribute( description = "LDAP server URL", mandatory = true)
String getProviderUrl();
@@ -90,6 +95,15 @@ public interface SimpleLDAPAuthenticationManager<X extends SimpleLDAPAuthenticat
@ManagedAttribute( description = "Defines the group search scope. If true the search for group entries is performed in the entire subtree of the group search context")
boolean isGroupSubtreeSearchScope();
+ @ManagedAttribute(description = "Method of authentication to use when binding into LDAP. Supported methods: NONE, SIMPLE, GSSAPI.",
+ defaultValue = "NONE")
+ LdapAuthenticationMethod getAuthenticationMethod();
+
+ @ManagedAttribute(description = "The scope of JAAS configuration from login module to use to obtain Kerberos"
+ + " initiator credentials when the authentication method is GSSAPI",
+ defaultValue = LOGIN_CONFIG_SCOPE_DEFAULT)
+ String getLoginConfigScope();
+
@DerivedAttribute
List<String> getTlsProtocolWhiteList();
diff --git a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerImpl.java b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerImpl.java
index 863f421..7a18c6c 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerImpl.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerImpl.java
@@ -25,6 +25,8 @@ import static java.util.Collections.unmodifiableList;
import java.security.GeneralSecurityException;
import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -47,6 +49,9 @@ import javax.naming.directory.SearchResult;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
import com.google.common.util.concurrent.ListenableFuture;
import org.slf4j.Logger;
@@ -69,23 +74,27 @@ import org.apache.qpid.server.security.auth.sasl.SaslNegotiator;
import org.apache.qpid.server.security.auth.sasl.SaslSettings;
import org.apache.qpid.server.security.auth.sasl.plain.PlainNegotiator;
import org.apache.qpid.server.security.group.GroupPrincipal;
+import org.apache.qpid.server.transport.network.security.ssl.SSLUtil;
import org.apache.qpid.server.util.CipherSuiteAndProtocolRestrictingSSLSocketFactory;
import org.apache.qpid.server.util.ParameterizedTypes;
+import org.apache.qpid.server.util.ServerScopedRuntimeException;
import org.apache.qpid.server.util.StringUtil;
-import org.apache.qpid.server.transport.network.security.ssl.SSLUtil;
-public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationManager<SimpleLDAPAuthenticationManagerImpl>
+public class SimpleLDAPAuthenticationManagerImpl
+ extends AbstractAuthenticationManager<SimpleLDAPAuthenticationManagerImpl>
implements SimpleLDAPAuthenticationManager<SimpleLDAPAuthenticationManagerImpl>
{
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLDAPAuthenticationManagerImpl.class);
private static final List<String> CONNECTIVITY_ATTRS = unmodifiableList(Arrays.asList(PROVIDER_URL,
- PROVIDER_AUTH_URL,
- SEARCH_CONTEXT,
- LDAP_CONTEXT_FACTORY,
- SEARCH_USERNAME,
- SEARCH_PASSWORD,
- TRUST_STORE));
+ PROVIDER_AUTH_URL,
+ SEARCH_CONTEXT,
+ LDAP_CONTEXT_FACTORY,
+ SEARCH_USERNAME,
+ SEARCH_PASSWORD,
+ TRUST_STORE,
+ LOGIN_CONFIG_SCOPE,
+ AUTHENTICATION_METHOD));
/**
* Environment key to instruct {@link InitialDirContext} to override the socket factory.
@@ -131,6 +140,12 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
@ManagedAttributeField
private boolean _groupSubtreeSearchScope;
+ @ManagedAttributeField
+ private LdapAuthenticationMethod _authenticationMethod;
+
+ @ManagedAttributeField
+ private String _loginConfigScope;
+
private List<String> _tlsProtocolWhiteList;
private List<String> _tlsProtocolBlackList;
@@ -154,9 +169,7 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
protected void validateOnCreate()
{
super.validateOnCreate();
-
- Class<? extends SocketFactory> sslSocketFactoryOverrideClass = createSslSocketFactoryOverrideClass(_trustStore);
- validateInitialDirContext(sslSocketFactoryOverrideClass, _providerUrl, _searchUsername, _searchPassword);
+ validateInitialDirContext(this);
}
@Override
@@ -166,11 +179,8 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
if (!disjoint(changedAttributes, CONNECTIVITY_ATTRS))
{
- SimpleLDAPAuthenticationManager changed = (SimpleLDAPAuthenticationManager)proxyForValidation;
- TrustStore changedTruststore = changed.getTrustStore();
- Class<? extends SocketFactory> sslSocketFactoryOverrideClass = createSslSocketFactoryOverrideClass(changedTruststore);
- validateInitialDirContext(sslSocketFactoryOverrideClass, changed.getProviderUrl(), changed.getSearchUsername(),
- changed.getSearchPassword());
+ SimpleLDAPAuthenticationManager changed = (SimpleLDAPAuthenticationManager) proxyForValidation;
+ validateInitialDirContext(changed);
}
}
@@ -279,6 +289,18 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
}
@Override
+ public LdapAuthenticationMethod getAuthenticationMethod()
+ {
+ return _authenticationMethod;
+ }
+
+ @Override
+ public String getLoginConfigScope()
+ {
+ return _loginConfigScope;
+ }
+
+ @Override
public List<String> getMechanisms()
{
return singletonList(PlainNegotiator.MECHANISM);
@@ -307,10 +329,24 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
private AuthenticationResult doLDAPNameAuthentication(String userId, String password)
{
+ Subject gssapiIdentity = null;
+ if (LdapAuthenticationMethod.GSSAPI.equals(getAuthenticationMethod()))
+ {
+ try
+ {
+ gssapiIdentity = doGssApiLogin(getLoginConfigScope());
+ }
+ catch (LoginException e)
+ {
+ LOGGER.warn("JAAS Login failed", e);
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+ }
+
final String name;
try
{
- name = getNameFromId(userId);
+ name = getNameFromId(userId, gssapiIdentity);
}
catch (NamingException e)
{
@@ -334,7 +370,7 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
InitialDirContext ctx = null;
try
{
- ctx = createInitialDirContext(env, _sslSocketFactoryOverrideClass);
+ ctx = createInitialDirContext(env, _sslSocketFactoryOverrideClass, gssapiIdentity);
Set<Principal> groups = Collections.emptySet();
if (isGroupSearchRequired())
@@ -342,9 +378,9 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
if (!providerAuthUrl.equals(getProviderUrl()))
{
closeSafely(ctx);
- ctx = createSearchInitialDirContext();
+ ctx = createSearchInitialDirContext(gssapiIdentity);
}
- groups = findGroups(ctx, name);
+ groups = findGroups(ctx, name, gssapiIdentity);
}
//Authentication succeeded
@@ -402,7 +438,8 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
return value != null && !"".equals(value);
}
- private Set<Principal> findGroups(DirContext context, String userDN) throws NamingException
+ private Set<Principal> findGroups(DirContext context, String userDN, final Subject gssapiIdentity)
+ throws NamingException
{
Set<Principal> groupPrincipals = new HashSet<>();
if (getGroupAttributeName() != null && !"".equals(getGroupAttributeName()))
@@ -436,10 +473,11 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
searchControls.setSearchScope(isGroupSubtreeSearchScope()
? SearchControls.SUBTREE_SCOPE
: SearchControls.ONELEVEL_SCOPE);
- NamingEnumeration<?> groupEnumeration = context.search(getGroupSearchContext(),
- getGroupSearchFilter(),
- new String[]{encode(userDN)},
- searchControls);
+ PrivilegedExceptionAction<NamingEnumeration<?>> search = () -> context.search(getGroupSearchContext(),
+ getGroupSearchFilter(),
+ new String[]{encode(userDN)},
+ searchControls);
+ NamingEnumeration<?> groupEnumeration = invokeContextOperationAs(gssapiIdentity, search);
while (groupEnumeration.hasMore())
{
SearchResult result = (SearchResult) groupEnumeration.next();
@@ -491,8 +529,9 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
return env;
}
- private InitialDirContext createInitialDirContext(Hashtable<String, Object> env,
- Class<? extends SocketFactory> sslSocketFactoryOverrideClass) throws NamingException
+ private InitialDirContext createInitialDirContext(final Hashtable<String, Object> env,
+ final Class<? extends SocketFactory> sslSocketFactoryOverrideClass,
+ final Subject gssapiIdentity) throws NamingException
{
ClassLoader existingContextClassLoader = null;
@@ -508,7 +547,7 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
Thread.currentThread().setContextClassLoader(sslSocketFactoryOverrideClass.getClassLoader());
revertContentClassLoader = true;
}
- return new InitialDirContext(env);
+ return invokeContextOperationAs(gssapiIdentity, () -> new InitialDirContext(env));
}
finally
{
@@ -564,27 +603,45 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
", providerUrl=" + _providerUrl + ", providerAuthUrl=" + _providerAuthUrl +
", searchContext=" + _searchContext + ", state=" + getState() +
", searchFilter=" + _searchFilter + ", ldapContextFactory=" + _ldapContextFactory +
- ", bindWithoutSearch=" + _bindWithoutSearch + ", trustStore=" + _trustStore +
- ", searchUsername=" + _searchUsername + "]";
+ ", bindWithoutSearch=" + _bindWithoutSearch + ", trustStore=" + _trustStore +
+ ", searchUsername=" + _searchUsername + ", loginConfigScope=" + _loginConfigScope +
+ ", authenticationMethod=" + _authenticationMethod + "]";
}
- private void validateInitialDirContext(Class<? extends SocketFactory> sslSocketFactoryOverrideClass,
- final String providerUrl,
- final String searchUsername, final String searchPassword)
+ private void validateInitialDirContext(final SimpleLDAPAuthenticationManager<?> authenticationProvider)
{
- Hashtable<String,Object> env = createInitialDirContextEnvironment(providerUrl);
+ final TrustStore truststore = authenticationProvider.getTrustStore();
+ final Class<? extends SocketFactory> sslSocketFactoryOverrideClass =
+ createSslSocketFactoryOverrideClass(truststore);
- setupSearchContext(env, searchUsername, searchPassword);
+ final Hashtable<String, Object> env =
+ createInitialDirContextEnvironment(authenticationProvider.getProviderUrl());
+ setAuthenticationProperties(env,
+ authenticationProvider.getSearchUsername(),
+ authenticationProvider.getSearchPassword(),
+ authenticationProvider.getAuthenticationMethod());
InitialDirContext ctx = null;
try
{
- ctx = createInitialDirContext(env, sslSocketFactoryOverrideClass);
+ Subject gssapiIdentity = null;
+ if (LdapAuthenticationMethod.GSSAPI.equals(authenticationProvider.getAuthenticationMethod()))
+ {
+ gssapiIdentity = doGssApiLogin(authenticationProvider.getLoginConfigScope());
+ }
+ ctx = createInitialDirContext(env, sslSocketFactoryOverrideClass, gssapiIdentity);
}
catch (NamingException e)
{
- LOGGER.error("Failed to establish connectivity to the ldap server for '{}'", providerUrl, e);
- throw new IllegalConfigurationException("Failed to establish connectivity to the ldap server." , e);
+ LOGGER.debug("Failed to establish connectivity to the ldap server for '{}'",
+ authenticationProvider.getProviderUrl(),
+ e);
+ throw new IllegalConfigurationException("Failed to establish connectivity to the ldap server.", e);
+ }
+ catch (LoginException e)
+ {
+ LOGGER.debug("JAAS login failed ", e);
+ throw new IllegalConfigurationException("JAAS login failed.", e);
}
finally
{
@@ -592,14 +649,26 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
}
}
- private void setupSearchContext(final Hashtable<String, Object> env,
- final String searchUsername, final String searchPassword)
+ private void setAuthenticationProperties(final Hashtable<String, Object> env,
+ final String userName,
+ final String password,
+ final LdapAuthenticationMethod authenticationMethod)
{
- if(_searchUsername != null && _searchUsername.trim().length()>0)
+ if (LdapAuthenticationMethod.GSSAPI.equals(authenticationMethod))
+ {
+ env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+ }
+ else if (LdapAuthenticationMethod.SIMPLE.equals(authenticationMethod))
{
env.put(Context.SECURITY_AUTHENTICATION, "simple");
- env.put(Context.SECURITY_PRINCIPAL, searchUsername);
- env.put(Context.SECURITY_CREDENTIALS, searchPassword);
+ if (userName != null)
+ {
+ env.put(Context.SECURITY_PRINCIPAL, userName);
+ }
+ if (password != null)
+ {
+ env.put(Context.SECURITY_CREDENTIALS, password);
+ }
}
else
{
@@ -607,11 +676,12 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
}
}
- private String getNameFromId(String id) throws NamingException
+ private String getNameFromId(final String id, final Subject gssapiIdentity)
+ throws NamingException
{
- if(!isBindWithoutSearch())
+ if (!isBindWithoutSearch())
{
- InitialDirContext ctx = createSearchInitialDirContext();
+ InitialDirContext ctx = createSearchInitialDirContext(gssapiIdentity);
try
{
@@ -622,7 +692,13 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
NamingEnumeration<?> namingEnum = null;
LOGGER.debug("Searching for '{}'", id);
- namingEnum = ctx.search(_searchContext, _searchFilter, new String[]{id}, searchControls);
+ namingEnum = invokeContextOperationAs(gssapiIdentity,
+ (PrivilegedExceptionAction<NamingEnumeration<?>>) () -> ctx.search(
+ _searchContext,
+ _searchFilter,
+ new String[]{id},
+ searchControls));
+
if (namingEnum.hasMore())
{
SearchResult result = (SearchResult) namingEnum.next();
@@ -645,14 +721,45 @@ public class SimpleLDAPAuthenticationManagerImpl extends AbstractAuthenticationM
{
return id;
}
+ }
+ private <T> T invokeContextOperationAs(final Subject identity, final PrivilegedExceptionAction<T> action)
+ throws NamingException
+ {
+ try
+ {
+ return Subject.doAs(identity, action);
+ }
+ catch (PrivilegedActionException e)
+ {
+ final Exception exception = e.getException();
+ if (exception instanceof NamingException)
+ {
+ throw (NamingException) exception;
+ }
+ else if (exception instanceof RuntimeException)
+ {
+ throw (RuntimeException) exception;
+ }
+ else
+ {
+ throw new ServerScopedRuntimeException(exception);
+ }
+ }
+ }
+
+ private Subject doGssApiLogin(final String configScope) throws LoginException
+ {
+ LoginContext loginContext = new LoginContext(configScope);
+ loginContext.login();
+ return loginContext.getSubject();
}
- private InitialDirContext createSearchInitialDirContext() throws NamingException
+ private InitialDirContext createSearchInitialDirContext(final Subject gssapiIdentity) throws NamingException
{
Hashtable<String, Object> env = createInitialDirContextEnvironment(_providerUrl);
- setupSearchContext(env, _searchUsername, _searchPassword);
- return createInitialDirContext(env, _sslSocketFactoryOverrideClass);
+ setAuthenticationProperties(env, _searchUsername, _searchPassword, _authenticationMethod);
+ return createInitialDirContext(env, _sslSocketFactoryOverrideClass, gssapiIdentity);
}
diff --git a/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerTest.java b/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerTest.java
new file mode 100644
index 0000000..b5dd2a1
--- /dev/null
+++ b/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerTest.java
@@ -0,0 +1,495 @@
+/*
+ * 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.qpid.server.security.auth.manager;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.qpid.server.security.auth.manager.CachingAuthenticationProvider.AUTHENTICATION_CACHE_MAX_SIZE;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.util.Strings;
+import org.apache.directory.server.annotations.CreateKdcServer;
+import org.apache.directory.server.annotations.CreateLdapServer;
+import org.apache.directory.server.annotations.CreateTransport;
+import org.apache.directory.server.annotations.SaslMechanism;
+import org.apache.directory.server.core.annotations.ApplyLdifFiles;
+import org.apache.directory.server.core.annotations.CreateDS;
+import org.apache.directory.server.core.annotations.CreatePartition;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.integ.CreateLdapServerRule;
+import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor;
+import org.apache.directory.server.factory.ServerAnnotationProcessor;
+import org.apache.directory.server.kerberos.kdc.KdcServer;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
+import org.apache.directory.server.kerberos.shared.keytab.Keytab;
+import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler;
+import org.apache.directory.shared.kerberos.KerberosTime;
+import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
+import org.apache.directory.shared.kerberos.components.EncryptionKey;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.BrokerTestHelper;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.security.auth.SocketConnectionPrincipal;
+import org.apache.qpid.server.security.auth.sasl.SaslNegotiator;
+import org.apache.qpid.server.security.auth.sasl.SaslSettings;
+import org.apache.qpid.test.utils.JvmVendor;
+import org.apache.qpid.test.utils.SystemPropertySetter;
+import org.apache.qpid.test.utils.TestFileUtils;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+@CreateDS(
+ name = "testDS",
+ partitions =
+ {
+ @CreatePartition(name = "test", suffix = "dc=qpid,dc=org")
+ },
+ additionalInterceptors =
+ {
+ KeyDerivationInterceptor.class
+ }
+)
+@CreateLdapServer(
+ transports =
+ {
+ @CreateTransport(protocol = "LDAP")
+ },
+ allowAnonymousAccess = true,
+ saslHost = "localhost",
+ saslPrincipal = "ldap/localhost@QPID.ORG",
+ saslMechanisms =
+ {
+ @SaslMechanism(name = SupportedSaslMechanisms.PLAIN, implClass = PlainMechanismHandler.class),
+ @SaslMechanism(name = SupportedSaslMechanisms.GSSAPI, implClass = GssapiMechanismHandler.class)
+ }
+)
+@CreateKdcServer(
+ transports =
+ {
+ @CreateTransport(protocol = "TCP", port = 0)
+ },
+ searchBaseDn = "ou=users,dc=qpid,dc=org")
+@ApplyLdifFiles("users.ldif")
+public class SimpleLDAPAuthenticationManagerTest extends UnitTestBase
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLDAPAuthenticationManagerTest.class);
+ private static final String ROOT = "dc=qpid,dc=org";
+ private static final String USERS_DN = "ou=users," + ROOT;
+ private static final String SEARCH_CONTEXT_VALUE = USERS_DN;
+ private static final String SEARCH_FILTER_VALUE = "(uid={0})";
+ private static final String LDAP_URL_TEMPLATE = "ldap://localhost:%d";
+ private static final String USER_1_NAME = "test1";
+ private static final String USER_1_PASSWORD = "password1";
+ private static final String USER_1_DN = "cn=integration-test1,ou=users,dc=qpid,dc=org";
+ private static final String GROUP_SEARCH_CONTEXT_VALUE = "ou=groups,dc=qpid,dc=org";
+ private static final String GROUP_SEARCH_FILTER_VALUE = "(member={0})";
+ private static final String LDAP_SERVICE_NAME = "ldap";
+ private static final String REALM = "QPID.ORG";
+ private static final String HOSTNAME = "localhost";
+ private static final String BROKER_PRINCIPAL = "service/" + HOSTNAME;
+ private static final String LINE_SEPARATOR = System.lineSeparator();
+ private static final String LOGIN_CONFIG = "login.config";
+ private static final String LOGIN_SCOPE = "ldap-gssapi-bind";
+ private static final AtomicBoolean KERBEROS_SETUP = new AtomicBoolean();
+
+ @ClassRule
+ public static CreateLdapServerRule LDAP = new CreateLdapServerRule();
+
+ @ClassRule
+ public static final SystemPropertySetter SYSTEM_PROPERTY_SETTER = new SystemPropertySetter();
+
+ private SimpleLDAPAuthenticationManager _authenticationProvider;
+
+ @Before
+ public void setUp()
+ {
+ _authenticationProvider = createAuthenticationProvider();
+ }
+
+ @After
+ public void tearDown()
+ {
+ if (_authenticationProvider != null)
+ {
+ _authenticationProvider.close();
+ }
+ }
+
+ @Test
+ public void testAuthenticateSuccess()
+ {
+ final AuthenticationResult result = _authenticationProvider.authenticate(USER_1_NAME, USER_1_PASSWORD);
+ assertEquals(AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus());
+ assertEquals(USER_1_DN, result.getMainPrincipal().getName());
+ }
+
+ @Test
+ public void testAuthenticateFailure()
+ {
+ final AuthenticationResult result = _authenticationProvider.authenticate(USER_1_NAME, USER_1_PASSWORD + "_");
+ assertEquals(AuthenticationResult.AuthenticationStatus.ERROR, result.getStatus());
+ }
+
+ @Test
+ public void testSaslPlainNegotiatorPlain()
+ {
+ final SaslSettings saslSettings = mock(SaslSettings.class);
+ when(saslSettings.getLocalFQDN()).thenReturn(HOSTNAME);
+
+ final SaslNegotiator negotiator = _authenticationProvider.createSaslNegotiator("PLAIN", saslSettings, null);
+ assertNotNull("Could not create SASL negotiator for mechanism 'PLAIN'", negotiator);
+
+ final AuthenticationResult result = negotiator.handleResponse(new byte[0]);
+ assertEquals("Unexpected authentication status",
+ AuthenticationResult.AuthenticationStatus.CONTINUE,
+ result.getStatus());
+
+ final AuthenticationResult result2 =
+ negotiator.handleResponse(String.format("\0%s\0%s", USER_1_NAME, USER_1_PASSWORD).getBytes(UTF_8));
+
+ assertEquals("Unexpected authentication status",
+ AuthenticationResult.AuthenticationStatus.SUCCESS,
+ result2.getStatus());
+ }
+
+ @Test
+ public void testGroups()
+ {
+ _authenticationProvider.close();
+ final Map<String, Object> groupSetUp = new HashMap<>();
+ groupSetUp.put(SimpleLDAPAuthenticationManager.GROUP_SEARCH_CONTEXT, GROUP_SEARCH_CONTEXT_VALUE);
+ groupSetUp.put(SimpleLDAPAuthenticationManager.GROUP_SEARCH_FILTER, GROUP_SEARCH_FILTER_VALUE);
+ _authenticationProvider = createAuthenticationProvider(groupSetUp);
+
+ final AuthenticationResult result = _authenticationProvider.authenticate(USER_1_NAME, USER_1_PASSWORD);
+ assertEquals(AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus());
+ assertEquals(USER_1_DN, result.getMainPrincipal().getName());
+
+ final Set<Principal> principals = result.getPrincipals();
+ assertNotNull(principals);
+
+ final Principal groupPrincipal = principals.stream()
+ .filter(p -> "cn=group1,ou=groups,dc=qpid,dc=org".equalsIgnoreCase(p.getName()))
+ .findFirst()
+ .orElse(null);
+ assertNotNull(groupPrincipal);
+ }
+
+ @Test
+ public void testAuthenticateSuccessWhenCachingEnabled()
+ {
+ _authenticationProvider.close();
+ _authenticationProvider = createCachingAuthenticationProvider();
+
+ final SocketConnectionPrincipal principal = mock(SocketConnectionPrincipal.class);
+ when(principal.getRemoteAddress()).thenReturn(new InetSocketAddress(HOSTNAME, 5672));
+ final Subject subject =
+ new Subject(true, Collections.singleton(principal), Collections.emptySet(), Collections.emptySet());
+ final AuthenticationResult result = Subject.doAs(subject,
+ (PrivilegedAction<AuthenticationResult>) () -> _authenticationProvider
+ .authenticate(USER_1_NAME, USER_1_PASSWORD));
+ assertEquals(AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus());
+ assertEquals(USER_1_DN, result.getMainPrincipal().getName());
+ }
+
+ @Test
+ public void testGssapiBindWithKeyTab() throws Exception
+ {
+ setUpKerberosAndJaas();
+
+ final Map<String, Object> attributes = new HashMap<>();
+ attributes.put(SimpleLDAPAuthenticationManager.AUTHENTICATION_METHOD, LdapAuthenticationMethod.GSSAPI.name());
+ attributes.put(SimpleLDAPAuthenticationManager.LOGIN_CONFIG_SCOPE, LOGIN_SCOPE);
+ final SimpleLDAPAuthenticationManagerImpl authenticationProvider = createAuthenticationProvider(attributes);
+ final AuthenticationResult result = authenticationProvider.authenticate(USER_1_NAME, USER_1_PASSWORD);
+ assertEquals(AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus());
+ assertEquals(USER_1_DN, result.getMainPrincipal().getName());
+ }
+
+ @Test
+ public void testChangeAuthenticationToGssapi() throws Exception
+ {
+ setUpKerberosAndJaas();
+
+ final Map<String, Object> attributes = new HashMap<>();
+ attributes.put(SimpleLDAPAuthenticationManager.AUTHENTICATION_METHOD, LdapAuthenticationMethod.GSSAPI.name());
+ attributes.put(SimpleLDAPAuthenticationManager.LOGIN_CONFIG_SCOPE, LOGIN_SCOPE);
+ _authenticationProvider.setAttributes(attributes);
+
+ final AuthenticationResult result = _authenticationProvider.authenticate(USER_1_NAME, USER_1_PASSWORD);
+ assertEquals(AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus());
+ assertEquals(USER_1_DN, result.getMainPrincipal().getName());
+ }
+
+ @Test
+ public void testChangeAuthenticationToGssapiWithInvalidScope() throws Exception
+ {
+ setUpKerberosAndJaas();
+
+ final Map<String, Object> attributes = new HashMap<>();
+ attributes.put(SimpleLDAPAuthenticationManager.AUTHENTICATION_METHOD, LdapAuthenticationMethod.GSSAPI.name());
+ attributes.put(SimpleLDAPAuthenticationManager.LOGIN_CONFIG_SCOPE, "non-existing");
+ try
+ {
+ _authenticationProvider.setAttributes(attributes);
+ fail("Exception is expected");
+ }
+ catch (IllegalConfigurationException e)
+ {
+ // pass
+ }
+ }
+
+ @Test
+ public void testChangeAuthenticationToGssapiWhenConfigIsBroken() throws Exception
+ {
+ setUpKerberosAndJaas();
+
+ final Map<String, Object> attributes = new HashMap<>();
+ attributes.put(SimpleLDAPAuthenticationManager.AUTHENTICATION_METHOD, LdapAuthenticationMethod.GSSAPI.name());
+ attributes.put(SimpleLDAPAuthenticationManager.LOGIN_CONFIG_SCOPE, "ldap-gssapi-bind-broken");
+ try
+ {
+ _authenticationProvider.setAttributes(attributes);
+ fail("Exception is expected");
+ }
+ catch (IllegalConfigurationException e)
+ {
+ // pass
+ }
+ }
+
+ @Test
+ public void testChangeAuthenticationToGssapiNoScopeProvided() throws Exception
+ {
+ setUpKerberosAndJaas();
+
+ final Map<String, Object> attributes = new HashMap<>();
+ attributes.put(SimpleLDAPAuthenticationManager.AUTHENTICATION_METHOD, LdapAuthenticationMethod.GSSAPI.name());
+ _authenticationProvider.setAttributes(attributes);
+
+ final AuthenticationResult result = _authenticationProvider.authenticate(USER_1_NAME, USER_1_PASSWORD);
+ assertEquals(AuthenticationResult.AuthenticationStatus.SUCCESS, result.getStatus());
+ assertEquals(USER_1_DN, result.getMainPrincipal().getName());
+ }
+
+ private SimpleLDAPAuthenticationManagerImpl createAuthenticationProvider()
+ {
+ return createAuthenticationProvider(Collections.emptyMap());
+ }
+
+ private SimpleLDAPAuthenticationManagerImpl createCachingAuthenticationProvider()
+ {
+ final Map<String, String> context = Collections.singletonMap(AUTHENTICATION_CACHE_MAX_SIZE, "1");
+ final Map<String, Object> attributes =
+ Collections.singletonMap(SimpleLDAPAuthenticationManager.CONTEXT, context);
+ return createAuthenticationProvider(attributes);
+ }
+
+ private SimpleLDAPAuthenticationManagerImpl createAuthenticationProvider(final Map<String, Object> settings)
+ {
+ final Broker<?> broker = BrokerTestHelper.createBrokerMock();
+ final Map<String, Object> attributes = new HashMap<>();
+ attributes.put(SimpleLDAPAuthenticationManager.NAME, getTestName());
+ attributes.put(SimpleLDAPAuthenticationManager.SEARCH_CONTEXT, SEARCH_CONTEXT_VALUE);
+ attributes.put(SimpleLDAPAuthenticationManager.PROVIDER_URL,
+ String.format(LDAP_URL_TEMPLATE, LDAP.getLdapServer().getPort()));
+ attributes.put(SimpleLDAPAuthenticationManager.SEARCH_FILTER, SEARCH_FILTER_VALUE);
+ attributes.put(SimpleLDAPAuthenticationManager.CONTEXT,
+ Collections.singletonMap(AUTHENTICATION_CACHE_MAX_SIZE, "0"));
+ attributes.putAll(settings);
+ final SimpleLDAPAuthenticationManagerImpl authenticationProvider =
+ new SimpleLDAPAuthenticationManagerImpl(attributes, broker);
+ authenticationProvider.open();
+ return authenticationProvider;
+ }
+
+
+ private void setUpKerberosAndJaas() throws Exception
+ {
+ assumeThat(getJvmVendor(), not(JvmVendor.IBM));
+ if (KERBEROS_SETUP.compareAndSet(false, true))
+ {
+ setUpKerberos();
+ setUpJaas();
+ }
+ }
+
+ private void setUpKerberos() throws Exception
+ {
+ final LdapServer ldapServer = LDAP.getLdapServer();
+ final KdcServer kdcServer =
+ ServerAnnotationProcessor.getKdcServer(LDAP.getDirectoryService(), ldapServer.getPort() + 1);
+ kdcServer.getConfig().setPaEncTimestampRequired(false);
+ final KerberosPrincipal servicePrincipal =
+ new KerberosPrincipal(LDAP_SERVICE_NAME + "/" + HOSTNAME + "@" + REALM,
+ KerberosPrincipal.KRB_NT_SRV_HST);
+ final String servicePrincipalName = servicePrincipal.getName();
+ ldapServer.setSaslHost(servicePrincipalName.substring(servicePrincipalName.indexOf("/") + 1,
+ servicePrincipalName.indexOf("@")));
+ ldapServer.setSaslPrincipal(servicePrincipalName);
+ ldapServer.setSearchBaseDn(USERS_DN);
+
+ final String krb5confPath = createKrb5Conf(kdcServer.getTransports()[0].getPort());
+ SYSTEM_PROPERTY_SETTER.setSystemProperty("java.security.krb5.conf", krb5confPath);
+
+ createPrincipal("KDC", "KDC", "krbtgt", UUID.randomUUID().toString(), "krbtgt/" + REALM + "@" + REALM);
+ createPrincipal("Service", "LDAP Service", "ldap", UUID.randomUUID().toString(), servicePrincipalName);
+ }
+
+ private void setUpJaas() throws LdapException, IOException
+ {
+ createKeyTab(BROKER_PRINCIPAL);
+ final URL resource = SimpleLDAPAuthenticationManagerTest.class.getClassLoader().getResource(LOGIN_CONFIG);
+ LOGGER.debug("JAAS config:" + resource);
+ assertNotNull(resource);
+ SYSTEM_PROPERTY_SETTER.setSystemProperty("java.security.auth.login.config", resource.getPath());
+ SYSTEM_PROPERTY_SETTER.setSystemProperty("sun.security.krb5.debug", "true");
+ }
+
+ private String createKrb5Conf(final int port) throws IOException
+ {
+ final File file = createFile("krb5", ".conf");
+ final String config = String.format("[libdefaults]%1$s"
+ + " default_realm = %2$s%1$s"
+ + " udp_preference_limit = 1%1$s"
+ + "[realms]%1$s"
+ + " %2$s = {%1$s"
+ + " kdc = %3$s%1$s"
+ + " }%1$s"
+ + "[domain_realm]%1$s"
+ + " .%4$s = %2$s%1$s"
+ + " %4$s = %2$s%1$s",
+ LINE_SEPARATOR,
+ REALM,
+ HOSTNAME + ":" + port,
+ Strings.toLowerCaseAscii(REALM));
+ LOGGER.debug("krb5.conf:" + config);
+ TestFileUtils.saveTextContentInFile(config, file);
+ return file.getAbsolutePath();
+ }
+
+ private void createPrincipal(final String sn,
+ final String cn,
+ final String uid,
+ final String userPassword,
+ final String kerberosPrincipalName) throws LdapException
+ {
+ final DirectoryService directoryService = LDAP.getDirectoryService();
+ final Entry entry = new DefaultEntry(directoryService.getSchemaManager());
+ entry.setDn(String.format("uid=%s,%s", uid, USERS_DN));
+ entry.add("objectClass", "top", "person", "inetOrgPerson", "krb5principal", "krb5kdcentry");
+ entry.add("cn", cn);
+ entry.add("sn", sn);
+ entry.add("uid", uid);
+ entry.add("userPassword", userPassword);
+ entry.add("krb5PrincipalName", kerberosPrincipalName);
+ entry.add("krb5KeyVersionNumber", "0");
+ directoryService.getAdminSession().add(entry);
+ }
+
+ private void createPrincipal(String uid, String userPassword) throws LdapException
+ {
+ createPrincipal(uid, uid, uid, userPassword, uid + "@" + REALM);
+ }
+
+ private void createPrincipal(final File keyTabFile, final String... principals) throws LdapException, IOException
+ {
+ final Keytab keytab = new Keytab();
+ final List<KeytabEntry> entries = new ArrayList<>();
+ final String password = UUID.randomUUID().toString();
+ for (final String principal : principals)
+ {
+ createPrincipal(principal, password);
+ final String principalName = principal + "@" + REALM;
+ final KerberosTime timestamp = new KerberosTime();
+ final Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principalName, password);
+ keys.forEach((type, key) -> entries.add(new KeytabEntry(principalName,
+ 1,
+ timestamp,
+ (byte) key.getKeyVersion(),
+ key)));
+ }
+ keytab.setEntries(entries);
+ keytab.write(keyTabFile);
+ }
+
+ private void createKeyTab(String... principals) throws LdapException, IOException
+ {
+ final File keyTabFile = createFile("kerberos", ".keytab");
+ createPrincipal(keyTabFile, principals);
+ }
+
+ private File createFile(final String prefix, final String suffix) throws IOException
+ {
+ final Path targetDir = FileSystems.getDefault().getPath("target");
+ final File file = new File(targetDir.toFile(), prefix + suffix);
+ if (file.exists())
+ {
+ if (!file.delete())
+ {
+ throw new IOException(String.format("Cannot delete existing file '%s'", file.getAbsolutePath()));
+ }
+ }
+ if (!file.createNewFile())
+ {
+ throw new IOException(String.format("Cannot create file '%s'", file.getAbsolutePath()));
+ }
+ return file;
+ }
+}
diff --git a/broker-core/src/test/resources/login.config b/broker-core/src/test/resources/login.config
new file mode 100644
index 0000000..d458516
--- /dev/null
+++ b/broker-core/src/test/resources/login.config
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+ldap-gssapi-bind {
+ com.sun.security.auth.module.Krb5LoginModule required
+ useKeyTab=true
+ debug=true
+ storeKey=true
+ doNotPrompt=true
+ isInitiator=true
+ refreshKrb5Config=true
+ realm="QPID.ORG"
+ principal="service/localhost"
+ keyTab="target/kerberos.keytab";
+};
+
+ldap-gssapi-bind-broken {
+ com.sun.security.auth.module.Krb5LoginModule required
+ useKeyTab=true
+ debug=true
+ storeKey=true
+ doNotPrompt=true
+ isInitiator=true
+ refreshKrb5Config=true
+ realm="QPID.ORG"
+ principal="service/localhost"
+ keyTab="target/kerberos-non-existing.keytab";
+};
+
+qpid-broker-j {
+ com.sun.security.auth.module.Krb5LoginModule required
+ useKeyTab=true
+ debug=true
+ storeKey=true
+ doNotPrompt=true
+ isInitiator=true
+ refreshKrb5Config=true
+ realm="QPID.ORG"
+ principal="service/localhost"
+ keyTab="target/kerberos.keytab";
+};
diff --git a/broker-core/src/test/resources/users.ldif b/broker-core/src/test/resources/users.ldif
new file mode 100644
index 0000000..0d76baa
--- /dev/null
+++ b/broker-core/src/test/resources/users.ldif
@@ -0,0 +1,50 @@
+#
+# 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.
+#
+
+version: 1
+dn: dc=qpid,dc=org
+objectClass: domain
+objectClass: top
+dc: tests
+
+dn: ou=users,dc=qpid,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: Users
+
+dn: ou=groups,dc=qpid,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: Groups
+
+dn: cn=integration-test1,ou=users,dc=qpid,dc=org
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: integration-test1
+sn: ldap-integration-test1
+uid: test1
+userPassword: password1
+
+dn: cn=group1,ou=groups,dc=qpid,dc=org
+cn: Group1
+member: cn=integration-test1,ou=Users,dc=qpid,dc=org
+objectClass: groupOfNames
+objectClass: top
diff --git a/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/add.html b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/add.html
index 5b88a3c..3601540 100644
--- a/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/add.html
+++ b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/add.html
@@ -68,11 +68,38 @@
promptMessage: 'Fully qualified class name for LDAP Context Factory'"/>
</div>
</div>
-
+ <div class="clear">
+ <div class="formLabel-labelCell tableContainer-labelCell">Authentication Method:</div>
+ <div class="tableContainer-valueCell formLabel-controlCell">
+ <select class="authenticationMethod" data-dojo-type="dijit/form/FilteringSelect"
+ id="ldapAuthenticationMethod"
+ data-dojo-props="
+ required: false,
+ name: 'authenticationMethod',
+ placeHolder: 'Select authentication method',
+ promptMessage: 'Select authentication method to bind into LDAP server',
+ title: 'Select authentication method to bind into LDAP server',
+ searchAttr: 'name'">
+ </select>
+ </div>
+ </div>
+ <div class="clear">
+ <div class="formLabel-labelCell tableContainer-labelCell">Login Config Scope:</div>
+ <div class="formLabel-controlCell tableContainer-valueCell">
+ <input type="text" class="loginConfigScope"
+ data-dojo-type="dijit/form/ValidationTextBox"
+ data-dojo-props="
+ name: 'loginConfigScope',
+ placeHolder: 'loginConfigScope',
+ title: 'Login configuration name for GSSAPI authentication',
+ promptMessage: 'Login configuration name for GSSAPI authentication'"/>
+ </div>
+ </div>
<div class="clear">
<div class="formLabel-labelCell tableContainer-labelCell">Search username:</div>
<div class="formLabel-controlCell tableContainer-valueCell">
<input type="text" class="searchUsername"
+ id="ldapSearchUsername"
data-dojo-type="dijit/form/ValidationTextBox"
data-dojo-props="
name: 'searchUsername',
@@ -85,6 +112,7 @@
<div class="formLabel-labelCell tableContainer-labelCell">Search password:</div>
<div class="formLabel-controlCell tableContainer-valueCell">
<input type="password" class="searchPassword"
+ id="ldapSearchPassword"
data-dojo-type="dijit/form/ValidationTextBox"
data-dojo-props="
name: 'searchPassword',
diff --git a/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/show.html b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/show.html
index d71e01f..94bf306 100644
--- a/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/show.html
+++ b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/simpleldap/show.html
@@ -41,6 +41,14 @@
<div class="formLabel-labelCell">Search password:</div>
<div><span class="searchPassword" ></span></div>
</div>
+ <div class="clear">
+ <div class="formLabel-labelCell">Authentication method:</div>
+ <div><span class="authenticationMethod" ></span></div>
+ </div>
+ <div class="clear">
+ <div class="formLabel-labelCell">Login config scope:</div>
+ <div><span class="loginConfigScope" ></span></div>
+ </div>
<div class="clear"></div>
<div class="formBox">
diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/add.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/add.js
index 06bd2d5..edc8243 100644
--- a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/add.js
+++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/add.js
@@ -81,6 +81,7 @@ define(["dojo/query",
.then(function (trustStores)
{
that._initTrustStores(trustStores, data.containerNode);
+ that._initAuthenticationMethods(data.parent.management.metadata, data.containerNode);
if (data.data)
{
that._initFields(data.data, data.containerNode, data.parent.management.metadata);
@@ -137,28 +138,23 @@ define(["dojo/query",
var trustStore = registry.byNode(query(".trustStore", containerNode)[0]);
trustStore.set("store", trustStoresStore);
},
- _initFields: function (data, containerNode, metadata)
+ _initAuthenticationMethods: function (metadata, containerNode)
{
var attributes = metadata.getMetaData("AuthenticationProvider", "SimpleLDAP").attributes;
- for (var name in attributes)
- {
- var domNode = query("." + name, containerNode)[0];
- if (domNode)
- {
- var widget = registry.byNode(domNode);
- if (widget)
- {
- if (widget instanceof dijit.form.CheckBox)
- {
- widget.set("checked", data[name]);
- }
- else
- {
- widget.set("value", data[name]);
- }
- }
- }
- }
+ var store = util.makeTypeStore(attributes.authenticationMethod.validValues);
+ var authenticationMethod = registry.byId("ldapAuthenticationMethod");
+ authenticationMethod.set("store", store);
+ authenticationMethod.on("change", function (value) {
+ registry.byId('ldapSearchUsername').set("required", (value === "SIMPLE"));
+ });
+ },
+ _initFields: function (data, containerNode, metadata)
+ {
+ util.applyToWidgets(containerNode,
+ "AuthenticationProvider",
+ "SimpleLDAP",
+ data,
+ metadata);
}
};
});
diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/show.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/show.js
index 9e13956..096aa36 100644
--- a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/show.js
+++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/simpleldap/show.js
@@ -26,7 +26,10 @@ define(["qpid/common/util", "dojo/domReady!"], function (util)
var attributes = data.parent.management.metadata.getMetaData("AuthenticationProvider", "SimpleLDAP").attributes;
for (var name in attributes)
{
- this.fields.push(name);
+ if (attributes.hasOwnProperty(name))
+ {
+ this.fields.push(name);
+ }
}
util.buildUI(data.containerNode, data.parent, "authenticationprovider/simpleldap/show.html", this.fields, this);
}
@@ -34,7 +37,7 @@ define(["qpid/common/util", "dojo/domReady!"], function (util)
SimpleLdapAuthenticationProvider.prototype.update = function (data)
{
util.updateUI(data, this.fields, this);
- }
+ };
return SimpleLdapAuthenticationProvider;
});
diff --git a/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers-LDAP.xml b/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers-LDAP.xml
index ed81c09..91ce9dc 100644
--- a/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers-LDAP.xml
+++ b/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers-LDAP.xml
@@ -57,6 +57,16 @@
certificate).</para>
</listitem>
<listitem>
+ <para><emphasis>Authentication method</emphasis> is a method of authentication to use on binding into LDAP
+ when <literal>bind without search</literal> mode is not selected.
+ Supported methods are NONE, SIMPLE, GSSAPI. The latter requires setting of <emphasis>Login Config Scope</emphasis>
+ which is a name of JAAS login module from JASS login configuration file specified using JVM system
+ property <emphasis>java.security.auth.login.config</emphasis> or Java security properties file. If
+ <emphasis>Login Config Scope</emphasis> is not specified with <literal>GSSAPI</literal>
+ <emphasis>Authentication method</emphasis>, the scope <emphasis>qpid-broker-j</emphasis> will be used.
+ </para>
+ </listitem>
+ <listitem>
<para>Additional group information can be obtained from LDAP.
There are two common ways of representing group membership in LDAP.
<itemizedlist>
diff --git a/pom.xml b/pom.xml
index a76c4b8..e59d3ab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -152,6 +152,7 @@
<maven-jar-plugin-version>3.1.0</maven-jar-plugin-version>
<maven-surefire-report-plugin-version>2.22.0</maven-surefire-report-plugin-version>
<h2.version>1.4.199</h2.version>
+ <apache-directory-version>2.0.0-M23</apache-directory-version>
</properties>
<modules>
@@ -724,6 +725,23 @@
<artifactId>maven-resolver-transport-http</artifactId>
<version>${maven-resolver-version}</version>
</dependency>
+ <!-- apacheds test dependency -->
+ <dependency>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-all</artifactId>
+ <version>${apache-directory-version}</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-schema</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.directory.api</groupId>
+ <artifactId>api-ldap-schema-data</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
</dependencies>
</dependencyManagement>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org