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/e73e783a
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e73e783a
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e73e783a

Branch: refs/heads/branch-feature-AMBARI-18634
Commit: e73e783a8b5377b809a829c362900d3cad15d69f
Parents: 4871533
Author: Robert Levas <rle...@hortonworks.com>
Authored: Tue Nov 22 15:28:12 2016 -0500
Committer: Robert Levas <rle...@hortonworks.com>
Committed: Tue Nov 22 15:28:12 2016 -0500

----------------------------------------------------------------------
 .../AmbariLdapAuthenticationProvider.java       |  23 +-
 .../AmbariLdapBindAuthenticator.java            | 233 ++++++++++++++++---
 .../AmbariLdapBindAuthenticatorTest.java        | 226 +++++++++++-------
 3 files changed, 354 insertions(+), 128 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/e73e783a/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/e73e783a/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 b34ef6a..b4ef889 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.
@@ -76,11 +95,10 @@ public class AmbariLdapBindAuthenticator extends 
BindAuthenticator {
       // If the ldap username needs to be processed (like converted to all 
lowercase characters),
       // process it before setting it in the session via 
AuthorizationHelper#addLoginNameAlias
       String processedLdapUserName;
-      if(ldapServerProperties.isForceUsernameToLowercase()) {
+      if (ldapServerProperties.isForceUsernameToLowercase()) {
         processedLdapUserName = ldapUserName.toLowerCase();
         LOG.info("Forcing ldap username to be lowercase characters: {} ==> 
{}", ldapUserName, processedLdapUserName);
-      }
-      else {
+      } else {
         processedLdapUserName = ldapUserName;
       }
 
@@ -91,10 +109,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 +282,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 +302,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 +318,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/e73e783a/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 cea4b66..aed6b57 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,179 @@
  */
 package org.apache.ambari.server.security.authorization;
 
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.junit.Assert.assertEquals;
-
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+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 = "AmbariLdapBindAuthenticatorTest",
-  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")})
-@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:"; + 
getLdapServer().getPort()});
-    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.getKey(), "ldap");
-    properties.setProperty(Configuration.SERVER_PERSISTENCE_TYPE.getKey(), 
"in-memory");
-    
properties.setProperty(Configuration.METADATA_DIR_PATH.getKey(),"src/test/resources/stacks");
-    
properties.setProperty(Configuration.SERVER_VERSION_FILE.getKey(),"src/test/resources/version");
-    properties.setProperty(Configuration.OS_VERSION.getKey(),"centos5");
-    properties.setProperty(Configuration.SHARED_RESOURCES_DIR.getKey(), 
"src/test/resources/");
-    properties.setProperty(Configuration.LDAP_BASE_DN.getKey(), 
"dc=ambari,dc=apache,dc=org");
-
-    if(forceUsernameToLower) {
-      
properties.setProperty(Configuration.LDAP_USERNAME_FORCE_LOWERCASE.getKey(), 
"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();
+
+    Attribute uidAttribute = createMock(Attribute.class);
+    expect(uidAttribute.size())
+        .andReturn(1)
+        .atLeastOnce();
+    expect(uidAttribute.get()).andReturn(ldapUsername).atLeastOnce();
+
+    Attributes searchedAttributes = createMock(Attributes.class);
+    expect(searchedAttributes.get("uid"))
+        .andReturn(uidAttribute)
+        .atLeastOnce();
+
+    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.getKey(), 
"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));
   }
 }

Reply via email to