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));
}
}