/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ambari.server.security.authorization; 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; import java.util.Properties; 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 org.apache.ambari.server.configuration.Configuration; import org.apache.commons.lang.StringUtils; import org.easymock.EasyMockSupport; import org.junit.Test; import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.ldap.support.LdapUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; public class AmbariLdapBindAuthenticatorTest extends EasyMockSupport { @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 { testAuthenticate("username", "ldapUsername", false); } @Test public void testAuthenticateWithLoginAliasForceToLower() throws Exception { testAuthenticate("username", "ldapUsername", true); } @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); LdapName basePath = LdapUtils.newLdapName(basePathString); LdapContextSource ldapCtxSource = createMock(LdapContextSource.class); expect(ldapCtxSource.getBaseLdapName()) .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(); replayAll(); Configuration configuration = new Configuration(); AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(ldapCtxSource, configuration); bindAuthenticator.setUserSearch(userSearch); 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()); } verifyAll(); } 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); LdapName basePath = LdapUtils.newLdapName(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.getBaseLdapName()) .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(); RequestContextHolder.setRequestAttributes(servletRequestAttributes); Properties properties = new Properties(); if (forceUsernameToLower) { properties.setProperty(Configuration.LDAP_USERNAME_FORCE_LOWERCASE.getKey(), "true"); } Configuration configuration = new Configuration(properties); AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(ldapCtxSource, configuration); bindAuthenticator.setUserSearch(userSearch); DirContextOperations user = bindAuthenticator.authenticate(new UsernamePasswordAuthenticationToken(ambariUsername, "password")); verifyAll(); String ldapUserNameAttribute = configuration.getLdapServerProperties().getUsernameAttribute(); assertEquals(ldapUsername, user.getStringAttribute(ldapUserNameAttribute)); } }