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 01:07:11 UTC

[qpid-broker-j] 01/02: 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 master
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git

commit 4c7aeb273736baebd49cf5c0807359ca3f15ed7e
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
---
 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 e846559..d06f629 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 6b8fcda..2f17c3b 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