You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by rl...@apache.org on 2017/04/18 17:34:28 UTC

ambari git commit: AMBARI-18938. NPE when authenticating via a Centrify LDAP proxy (rlevas)

Repository: ambari
Updated Branches:
  refs/heads/branch-2.4 2db62f4be -> f4d7a3fdb


AMBARI-18938. NPE when authenticating via a Centrify LDAP proxy (rlevas)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/f4d7a3fd
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/f4d7a3fd
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/f4d7a3fd

Branch: refs/heads/branch-2.4
Commit: f4d7a3fdbf26ef1900647a31121f2b7ceb5b1a2b
Parents: 2db62f4
Author: Robert Levas <rl...@hortonworks.com>
Authored: Tue Apr 18 13:34:17 2017 -0400
Committer: Robert Levas <rl...@hortonworks.com>
Committed: Tue Apr 18 13:34:22 2017 -0400

----------------------------------------------------------------------
 .../AmbariLdapAuthenticationProvider.java       |  23 +-
 .../AmbariLdapBindAuthenticator.java            | 228 +++++++++++++++++--
 .../AmbariLdapBindAuthenticatorTest.java        | 209 ++++++++++-------
 3 files changed, 340 insertions(+), 120 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/f4d7a3fd/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
index 6905757..b5776a3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -24,7 +24,6 @@ import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.orm.dao.UserDAO;
 import org.apache.ambari.server.orm.entities.UserEntity;
 import org.apache.ambari.server.security.ClientSecurityType;
-import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.dao.IncorrectResultSizeDataAccessException;
@@ -72,17 +71,21 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
 
         return new AmbariAuthentication(auth, userId);
       } catch (AuthenticationException e) {
-        LOG.debug("Got exception during LDAP authentification attempt", e);
+        LOG.debug("Got exception during LDAP authentication attempt", e);
         // Try to help in troubleshooting
         Throwable cause = e.getCause();
-        if (cause != null) {
-          // Below we check the cause of an AuthenticationException . If it is
-          // caused by another AuthenticationException, than probably
-          // the problem is with LDAP ManagerDN/password
-          if ((cause != e) && (cause instanceof
-                  org.springframework.ldap.AuthenticationException)) {
+        if ((cause != null) && (cause != e)) {
+          // Below we check the cause of an AuthenticationException to see what the actual cause is
+          // and then send an appropriate message to the caller.
+          if (cause instanceof org.springframework.ldap.CommunicationException) {
+            if (LOG.isDebugEnabled()) {
+              LOG.warn("Failed to communicate with the LDAP server: " + cause.getMessage(), e);
+            } else {
+              LOG.warn("Failed to communicate with the LDAP server: " + cause.getMessage());
+            }
+          } else if (cause instanceof org.springframework.ldap.AuthenticationException) {
             LOG.warn("Looks like LDAP manager credentials (that are used for " +
-                    "connecting to LDAP server) are invalid.", e);
+                "connecting to LDAP server) are invalid.", e);
           }
         }
         throw new InvalidUsernamePasswordCombinationException(e);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f4d7a3fd/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
index 9e534a6..e2bcd2d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -20,26 +20,36 @@ package org.apache.ambari.server.security.authorization;
 
 import java.util.List;
 
+import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
 
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.ldap.core.AttributesMapper;
+import org.springframework.ldap.core.ContextSource;
+import org.springframework.ldap.core.DirContextAdapter;
 import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.DistinguishedName;
 import org.springframework.ldap.core.LdapTemplate;
 import org.springframework.ldap.core.support.BaseLdapPathContextSource;
+import org.springframework.ldap.support.LdapUtils;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.ldap.authentication.BindAuthenticator;
+import org.springframework.security.ldap.authentication.AbstractLdapAuthenticator;
+import org.springframework.security.ldap.search.LdapUserSearch;
 
 
 /**
  * An authenticator which binds as a user and checks if user should get ambari
  * admin authorities according to LDAP group membership
  */
-public class AmbariLdapBindAuthenticator extends BindAuthenticator {
+public class AmbariLdapBindAuthenticator extends AbstractLdapAuthenticator {
   private static final Logger LOG = LoggerFactory.getLogger(AmbariLdapBindAuthenticator.class);
 
   private Configuration configuration;
@@ -55,9 +65,14 @@ public class AmbariLdapBindAuthenticator extends BindAuthenticator {
   @Override
   public DirContextOperations authenticate(Authentication authentication) {
 
-    DirContextOperations user = super.authenticate(authentication);
-    LdapServerProperties ldapServerProperties =
-      configuration.getLdapServerProperties();
+    if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
+      LOG.info("Unexpected authentication token type encountered ({}) - failing authentication.", authentication.getClass().getName());
+      throw new BadCredentialsException("Unexpected authentication token type encountered.");
+    }
+
+    DirContextOperations user = authenticate((UsernamePasswordAuthenticationToken) authentication);
+
+    LdapServerProperties ldapServerProperties = configuration.getLdapServerProperties();
     if (StringUtils.isNotEmpty(ldapServerProperties.getAdminGroupMappingRules())) {
       setAmbariAdminAttr(user, ldapServerProperties);
     }
@@ -65,9 +80,13 @@ public class AmbariLdapBindAuthenticator extends BindAuthenticator {
     // Users stored locally in ambari are matched against LDAP users by the ldap attribute configured to be used as user name.
     // (e.g. uid, sAMAccount -> ambari user name )
     String ldapUserName = user.getStringAttribute(ldapServerProperties.getUsernameAttribute());
-    String loginName  = authentication.getName(); // user login name the user has logged in
+    String loginName = authentication.getName(); // user login name the user has logged in
 
-    if (!ldapUserName.equals(loginName)) {
+    if (ldapUserName == null) {
+      LOG.warn("The user data does not contain a value for {}.", ldapServerProperties.getUsernameAttribute());
+    } else if (ldapUserName.isEmpty()) {
+      LOG.warn("The user data contains an empty value for {}.", ldapServerProperties.getUsernameAttribute());
+    } else if (!ldapUserName.equals(loginName)) {
       // if authenticated user name is different from ldap user name than user has logged in
       // with a login name that is different (e.g. user principal name) from the ambari user name stored in
       // ambari db. In this case add the user login name  as login alias for ambari user name.
@@ -91,10 +110,169 @@ public class AmbariLdapBindAuthenticator extends BindAuthenticator {
   }
 
   /**
-   *  Checks weather user is a member of ambari administrators group in LDAP. If
-   *  yes, sets user's ambari_admin attribute to true
-   * @param user
-   * @return
+   * Authenticates a user with a configured LDAP server using the user's username and password.
+   * <p>
+   * To authenticate a user:
+   * <ol>
+   * <li>
+   * The LDAP server is queried for the relevant user object where the
+   * supplied username matches the configured LDAP attribute that represents the user's username
+   * <ul><li>Example: (&(uid=user1)(objectClass=posixAccount))</li></ul>
+   * </li>
+   * <li>
+   * If found, the distinguished name (DN) of the user object is obtained from returned data and then
+   * used, along with the supplied password to perform an LDAP bind (see {@link #bind(DirContextOperations, String)})
+   * </li>
+   * </ol>
+   * <p>
+   * Failure to authenticate will result in a {@link BadCredentialsException} to be thrown.
+   *
+   * @param authentication the credentials to use for authentication
+   * @return the authenticated user details
+   * @see #bind(DirContextOperations, String)
+   */
+  private DirContextOperations authenticate(UsernamePasswordAuthenticationToken authentication) {
+    DirContextOperations user = null;
+
+    String username = authentication.getName();
+    Object credentials = authentication.getCredentials();
+    String password = (credentials instanceof String) ? (String) credentials : null;
+
+    if (StringUtils.isEmpty(username)) {
+      LOG.debug("Empty username encountered - failing authentication.");
+      throw new BadCredentialsException("Empty username encountered.");
+    }
+
+    LOG.debug("Authenticating {}", username);
+
+    if (StringUtils.isEmpty(password)) {
+      LOG.debug("Empty password encountered - failing authentication.");
+      throw new BadCredentialsException("Empty password encountered.");
+    }
+
+    LdapUserSearch userSearch = getUserSearch();
+    if (userSearch == null) {
+      LOG.debug("The user search facility has not been set - failing authentication.");
+      throw new BadCredentialsException("The user search facility has not been set.");
+    } else {
+      if (LOG.isTraceEnabled()) {
+        LOG.trace("Searching for user with username {}: {}", username, userSearch.toString());
+      }
+
+      // Find the user data where the supplied username matches the value of the configured LDAP
+      // attribute for the user's username. If a user is found, use the DN fro the returned data
+      // and the supplied password to attempt authentication.
+      DirContextOperations userFromSearch = userSearch.searchForUser(username);
+
+      if (userFromSearch == null) {
+        LOG.debug("LDAP user object not found for {}", username);
+      } else {
+        LOG.debug("Found LDAP user for {}: {}", username, userFromSearch.getDn());
+        user = bind(userFromSearch, password);
+
+        // If trace enabled, log the user's LDAP attributes.
+        if (LOG.isTraceEnabled()) {
+          Attributes attributes = user.getAttributes();
+          if (attributes != null) {
+            StringBuilder builder = new StringBuilder();
+            NamingEnumeration<String> ids = attributes.getIDs();
+            try {
+              while (ids.hasMore()) {
+                String id = ids.next();
+                builder.append("\n\t");
+                builder.append(attributes.get(id));
+              }
+            } catch (NamingException e) {
+              // Ignore this...
+            }
+            LOG.trace("User Attributes: {}", builder);
+          } else {
+            LOG.trace("User Attributes: not available");
+          }
+        }
+      }
+    }
+
+    // If a user was not authenticated, thrown a BadCredentialsException, else return the user data
+    if (user == null) {
+      LOG.debug("Invalid credentials for {} - failing authentication.", username);
+      throw new BadCredentialsException("Invalid credentials.");
+    } else {
+      LOG.debug("Successfully authenticated {}", username);
+    }
+
+    return user;
+  }
+
+  /**
+   * Attempt to authenticate a user with the configured LDAP server by performing an LDAP bind.
+   * <p>
+   * Using the distinguished name provided in the supplied user data and the supplied password,
+   * attempt to authenticate with the configured LDAP server. If authentication is successful, use the
+   * attributes from the supplied user data rather than the attributes associated with the bound context
+   * because some scenarios result in missing data within the bound context due to LDAP server implementations.
+   * <p>
+   * If authentication is not successful, throw a {@link BadCredentialsException}.
+   *
+   * @param user     the user data containing the relevant DN and associated attributes
+   * @param password the password
+   * @return the authenticated user details
+   * @throws BadCredentialsException if authentication fails
+   */
+  private DirContextOperations bind(DirContextOperations user, String password) {
+    ContextSource contextSource = getContextSource();
+
+    if (contextSource == null) {
+      String message = "Missing ContextSource - failing authentication.";
+      LOG.debug(message);
+      throw new InternalAuthenticationServiceException(message);
+    }
+
+    if (!(contextSource instanceof BaseLdapPathContextSource)) {
+      String message = String.format("Unexpected ContextSource type (%s) - failing authentication.", contextSource.getClass().getName());
+      LOG.debug(message);
+      throw new InternalAuthenticationServiceException(message);
+    }
+
+    BaseLdapPathContextSource baseLdapPathContextSource = (BaseLdapPathContextSource) contextSource;
+    DistinguishedName userDistinguishedName = new DistinguishedName(user.getDn());
+    DistinguishedName fullDn = new DistinguishedName(userDistinguishedName);
+    fullDn.prepend(baseLdapPathContextSource.getBaseLdapPath());
+
+    LOG.debug("Attempting to bind as {}", fullDn);
+
+    DirContext dirContext = null;
+
+    try {
+      // Perform the authentication.  The result is not used because it is expected that the supplied
+      // user data has all of the attributes for the authenticated user. If authentication fails, it
+      // expected that the supplied user data will be destroyed or orphaned.
+      dirContext = baseLdapPathContextSource.getContext(fullDn.toString(), password);
+
+      // Build a new DirContextAdapter using the attributes from the passed in user details since it
+      // is expected these details will be more complete of querying for them from the bound context.
+      // Some LDAP server implementations will no return all attributes to the bound context due to
+      // the filter being used in the query.
+      return new DirContextAdapter(user.getAttributes(), userDistinguishedName, baseLdapPathContextSource.getBaseLdapPath());
+    } catch (org.springframework.ldap.AuthenticationException e) {
+      String message = String.format("Failed to bind as %s - %s", user.getDn().toString(), e.getMessage());
+      if (LOG.isTraceEnabled()) {
+        LOG.trace(message, e);
+      } else if (LOG.isDebugEnabled()) {
+        LOG.debug(message);
+      }
+      throw new BadCredentialsException("The username or password is incorrect.");
+    } finally {
+      LdapUtils.closeContext(dirContext);
+    }
+  }
+
+  /**
+   * Checks weather user is a member of ambari administrators group in LDAP. If
+   * yes, sets user's ambari_admin attribute to true
+   *
+   * @param user the user details
+   * @return the updated user details
    */
   private DirContextOperations setAmbariAdminAttr(DirContextOperations user, LdapServerProperties ldapServerProperties) {
     String baseDn = ldapServerProperties.getBaseDN().toLowerCase();
@@ -105,10 +283,10 @@ public class AmbariLdapBindAuthenticator extends BindAuthenticator {
 
     //If groupBase is set incorrectly or isn't set - search in BaseDn
     int indexOfBaseDn = groupBase.indexOf(baseDn);
-    groupBase = indexOfBaseDn <= 0 ? "" : groupBase.substring(0,indexOfBaseDn - 1);
+    groupBase = indexOfBaseDn <= 0 ? "" : groupBase.substring(0, indexOfBaseDn - 1);
 
     String memberValue = StringUtils.isNotEmpty(adminGroupMappingMemberAttr)
-      ? user.getStringAttribute(adminGroupMappingMemberAttr) : user.getNameInNamespace();
+        ? user.getStringAttribute(adminGroupMappingMemberAttr) : user.getNameInNamespace();
     LOG.debug("LDAP login - set '{}' as member attribute for adminGroupMappingRules", memberValue);
 
     String setAmbariAdminAttrFilter = resolveAmbariAdminAttrFilter(ldapServerProperties, memberValue);
@@ -125,8 +303,8 @@ public class AmbariLdapBindAuthenticator extends BindAuthenticator {
     ldapTemplate.setIgnorePartialResultException(true);
     ldapTemplate.setIgnoreNameNotFoundException(true);
 
-    List<String> ambariAdminGroups = ldapTemplate.search(
-        groupBase, setAmbariAdminAttrFilter, attributesMapper);
+    @SuppressWarnings("unchecked")
+    List<String> ambariAdminGroups = ldapTemplate.search(groupBase, setAmbariAdminAttrFilter, attributesMapper);
 
     //user has admin role granted, if user is a member of at least 1 group,
     // which matches the rules in configuration
@@ -141,24 +319,24 @@ public class AmbariLdapBindAuthenticator extends BindAuthenticator {
     String groupMembershipAttr = ldapServerProperties.getGroupMembershipAttr();
     String groupObjectClass = ldapServerProperties.getGroupObjectClass();
     String adminGroupMappingRules =
-      ldapServerProperties.getAdminGroupMappingRules();
+        ldapServerProperties.getAdminGroupMappingRules();
     final String groupNamingAttribute =
-      ldapServerProperties.getGroupNamingAttr();
+        ldapServerProperties.getGroupNamingAttr();
     String groupSearchFilter = ldapServerProperties.getGroupSearchFilter();
 
     String setAmbariAdminAttrFilter;
     if (StringUtils.isEmpty(groupSearchFilter)) {
       String adminGroupMappingRegex = createAdminGroupMappingRegex(adminGroupMappingRules, groupNamingAttribute);
       setAmbariAdminAttrFilter = String.format("(&(%s=%s)(objectclass=%s)(|%s))",
-        groupMembershipAttr,
-        memberValue,
-        groupObjectClass,
-        adminGroupMappingRegex);
+          groupMembershipAttr,
+          memberValue,
+          groupObjectClass,
+          adminGroupMappingRegex);
     } else {
       setAmbariAdminAttrFilter = String.format("(&(%s=%s)%s)",
-        groupMembershipAttr,
-        memberValue,
-        groupSearchFilter);
+          groupMembershipAttr,
+          memberValue,
+          groupSearchFilter);
     }
     return setAmbariAdminAttrFilter;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/f4d7a3fd/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java
index ad57395..8e61df1 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java
@@ -1,5 +1,4 @@
-
-/**
+/*
  * 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
@@ -18,132 +17,172 @@
  */
 package org.apache.ambari.server.security.authorization;
 
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapName;
+
 import java.util.Properties;
 
 import org.apache.ambari.server.configuration.Configuration;
-import org.apache.directory.server.annotations.CreateLdapServer;
-import org.apache.directory.server.annotations.CreateTransport;
-import org.apache.directory.server.core.annotations.ApplyLdifFiles;
-import org.apache.directory.server.core.annotations.ContextEntry;
-import org.apache.directory.server.core.annotations.CreateDS;
-import org.apache.directory.server.core.annotations.CreatePartition;
-import org.apache.directory.server.core.integ.FrameworkRunner;
-import org.easymock.EasyMockRule;
-import org.easymock.Mock;
-import org.easymock.MockType;
-import org.junit.Before;
-import org.junit.Rule;
+import org.apache.commons.lang.StringUtils;
+import org.easymock.EasyMockSupport;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.DistinguishedName;
 import org.springframework.ldap.core.support.LdapContextSource;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
 import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
-import org.springframework.security.ldap.search.LdapUserSearch;
 import org.springframework.web.context.request.RequestAttributes;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
 import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.expectLastCall;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
+public class AmbariLdapBindAuthenticatorTest extends EasyMockSupport {
 
-@RunWith(FrameworkRunner.class)
-@CreateDS(allowAnonAccess = true,
-  name = "Test",
-  partitions = {
-    @CreatePartition(name = "Root",
-      suffix = "dc=apache,dc=org",
-      contextEntry = @ContextEntry(
-        entryLdif =
-          "dn: dc=apache,dc=org\n" +
-            "dc: apache\n" +
-            "objectClass: top\n" +
-            "objectClass: domain\n\n" +
-            "dn: dc=ambari,dc=apache,dc=org\n" +
-            "dc: ambari\n" +
-            "objectClass: top\n" +
-            "objectClass: domain\n\n"))
-  })
-@CreateLdapServer(allowAnonymousAccess = true,
-  transports = {@CreateTransport(protocol = "LDAP", port = 33389)})
-@ApplyLdifFiles("users.ldif")
-public class AmbariLdapBindAuthenticatorTest extends AmbariLdapAuthenticationProviderBaseTest {
-
-  @Rule
-  public EasyMockRule mocks = new EasyMockRule(this);
-
-  @Mock(type = MockType.NICE)
-  private ServletRequestAttributes servletRequestAttributes;
-
-  @Before
-  public void setUp() {
-    resetAll();
+  @Test
+  public void testAuthenticateWithoutLogin() throws Exception {
+    testAuthenticate("username", "username", false);
+  }
+
+  @Test
+  public void testAuthenticateWithNullLDAPUsername() throws Exception {
+    testAuthenticate("username", null, false);
   }
 
   @Test
   public void testAuthenticateWithLoginAliasDefault() throws Exception {
-    testAuthenticateWithLoginAlias(false);
+    testAuthenticate("username", "ldapUsername", false);
   }
 
   @Test
   public void testAuthenticateWithLoginAliasForceToLower() throws Exception {
-    testAuthenticateWithLoginAlias(true);
+    testAuthenticate("username", "ldapUsername", true);
   }
 
-  private void testAuthenticateWithLoginAlias(boolean forceUsernameToLower) throws Exception {
-    // Given
-
-    LdapContextSource ldapCtxSource = new LdapContextSource();
-    ldapCtxSource.setUrls(new String[] {"ldap://localhost:33389"});
-    ldapCtxSource.setBase("dc=ambari,dc=apache,dc=org");
-    ldapCtxSource.afterPropertiesSet();
+  @Test
+  public void testAuthenticateBadPassword() throws Exception {
+    String basePathString = "dc=apache,dc=org";
+    String ldapUserRelativeDNString = String.format("uid=%s,ou=people,ou=dev", "ldapUsername");
+    LdapName ldapUserRelativeDN = new LdapName(ldapUserRelativeDNString);
+    String ldapUserDNString = String.format("%s,%s", ldapUserRelativeDNString, basePathString);
+    DistinguishedName basePath = new DistinguishedName(basePathString);
+
+    LdapContextSource ldapCtxSource = createMock(LdapContextSource.class);
+    expect(ldapCtxSource.getBaseLdapPath())
+        .andReturn(basePath)
+        .atLeastOnce();
+    expect(ldapCtxSource.getContext(ldapUserDNString, "password"))
+        .andThrow(new org.springframework.ldap.AuthenticationException(null))
+        .once();
+
+    DirContextOperations searchedUserContext = createMock(DirContextOperations.class);
+    expect(searchedUserContext.getDn())
+        .andReturn(ldapUserRelativeDN)
+        .atLeastOnce();
+
+    FilterBasedLdapUserSearch userSearch = createMock(FilterBasedLdapUserSearch.class);
+    expect(userSearch.searchForUser(anyString())).andReturn(searchedUserContext).once();
 
-    Properties properties = new Properties();
-    properties.setProperty(Configuration.CLIENT_SECURITY_KEY, "ldap");
-    properties.setProperty(Configuration.SERVER_PERSISTENCE_TYPE_KEY, "in-memory");
-    properties.setProperty(Configuration.METADATA_DIR_PATH,"src/test/resources/stacks");
-    properties.setProperty(Configuration.SERVER_VERSION_FILE,"src/test/resources/version");
-    properties.setProperty(Configuration.OS_VERSION_KEY,"centos5");
-    properties.setProperty(Configuration.SHARED_RESOURCES_DIR_KEY, "src/test/resources/");
-    properties.setProperty(Configuration.LDAP_BASE_DN_KEY, "dc=ambari,dc=apache,dc=org");
-
-    if(forceUsernameToLower) {
-      properties.setProperty(Configuration.LDAP_USERNAME_FORCE_LOWERCASE_KEY, "true");
-    }
+    replayAll();
 
-    Configuration configuration = new Configuration(properties);
+    Configuration configuration = new Configuration();
 
     AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(ldapCtxSource, configuration);
-
-    LdapUserSearch userSearch = new FilterBasedLdapUserSearch("", "(&(cn={0})(objectClass=person))", ldapCtxSource);
     bindAuthenticator.setUserSearch(userSearch);
 
-    // JohnSmith is a login alias for deniedUser username
-    String loginAlias = "JohnSmith";
-    String userName = "deniedUser";
-
-    Authentication authentication = new UsernamePasswordAuthenticationToken(loginAlias, "password");
+    try {
+      bindAuthenticator.authenticate(new UsernamePasswordAuthenticationToken("username", "password"));
+      fail("Expected thrown exception: org.springframework.security.authentication.BadCredentialsException");
+    } catch (org.springframework.security.authentication.BadCredentialsException e) {
+      // expected
+    } catch (Throwable t) {
+      fail("Expected thrown exception: org.springframework.security.authentication.BadCredentialsException\nEncountered thrown exception " + t.getClass().getName());
+    }
 
-    RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+    verifyAll();
+  }
 
-    servletRequestAttributes.setAttribute(eq(loginAlias), eq(forceUsernameToLower ? userName.toLowerCase(): userName), eq(RequestAttributes.SCOPE_SESSION));
-    expectLastCall().once();
+  private void testAuthenticate(String ambariUsername, String ldapUsername, boolean forceUsernameToLower) throws Exception {
+    String basePathString = "dc=apache,dc=org";
+    String ldapUserRelativeDNString = String.format("uid=%s,ou=people,ou=dev", ldapUsername);
+    LdapName ldapUserRelativeDN = new LdapName(ldapUserRelativeDNString);
+    String ldapUserDNString = String.format("%s,%s", ldapUserRelativeDNString, basePathString);
+    DistinguishedName basePath = new DistinguishedName(basePathString);
+
+    @SuppressWarnings("unchecked")
+    NamingEnumeration<SearchResult> adminGroups = createMock(NamingEnumeration.class);
+    expect(adminGroups.hasMore())
+        .andReturn(false)
+        .atLeastOnce();
+    adminGroups.close();
+    expectLastCall().atLeastOnce();
+
+    DirContextOperations boundUserContext = createMock(DirContextOperations.class);
+    expect(boundUserContext.search(eq("ou=groups"), eq("(&(member=" + ldapUserDNString + ")(objectclass=group)(|(cn=Ambari Administrators)))"), anyObject(SearchControls.class)))
+        .andReturn(adminGroups)
+        .atLeastOnce();
+    boundUserContext.close();
+    expectLastCall().atLeastOnce();
+
+
+    LdapContextSource ldapCtxSource = createMock(LdapContextSource.class);
+    expect(ldapCtxSource.getBaseLdapPath())
+        .andReturn(basePath)
+        .atLeastOnce();
+    expect(ldapCtxSource.getContext(ldapUserDNString, "password"))
+        .andReturn(boundUserContext)
+        .once();
+    expect(ldapCtxSource.getReadOnlyContext())
+        .andReturn(boundUserContext)
+        .once();
+
+    Attributes searchedAttributes = new BasicAttributes("uid", ldapUsername);
+
+    DirContextOperations searchedUserContext = createMock(DirContextOperations.class);
+    expect(searchedUserContext.getDn())
+        .andReturn(ldapUserRelativeDN)
+        .atLeastOnce();
+    expect(searchedUserContext.getAttributes())
+        .andReturn(searchedAttributes)
+        .atLeastOnce();
+
+    FilterBasedLdapUserSearch userSearch = createMock(FilterBasedLdapUserSearch.class);
+    expect(userSearch.searchForUser(ambariUsername)).andReturn(searchedUserContext).once();
+
+    ServletRequestAttributes servletRequestAttributes = createMock(ServletRequestAttributes.class);
+
+    if (!StringUtils.isEmpty(ldapUsername) && !ambariUsername.equals(ldapUsername)) {
+      servletRequestAttributes.setAttribute(eq(ambariUsername), eq(forceUsernameToLower ? ldapUsername.toLowerCase() : ldapUsername), eq(RequestAttributes.SCOPE_SESSION));
+      expectLastCall().once();
+    }
 
     replayAll();
 
-    // When
+    RequestContextHolder.setRequestAttributes(servletRequestAttributes);
 
-    DirContextOperations user = bindAuthenticator.authenticate(authentication);
+    Properties properties = new Properties();
+    if (forceUsernameToLower) {
+      properties.setProperty(Configuration.LDAP_USERNAME_FORCE_LOWERCASE_KEY, "true");
+    }
+    Configuration configuration = new Configuration(properties);
 
-    // Then
+    AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(ldapCtxSource, configuration);
+    bindAuthenticator.setUserSearch(userSearch);
+    DirContextOperations user = bindAuthenticator.authenticate(new UsernamePasswordAuthenticationToken(ambariUsername, "password"));
 
     verifyAll();
 
     String ldapUserNameAttribute = configuration.getLdapServerProperties().getUsernameAttribute();
-
-    assertEquals(userName, user.getStringAttribute(ldapUserNameAttribute));
+    assertEquals(ldapUsername, user.getStringAttribute(ldapUserNameAttribute));
   }
 }