/*
* Copyright 2017 ThoughtWorks, Inc.
*
* Licensed 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 com.thoughtworks.go.server.security;
import com.thoughtworks.go.config.GoConfigDao;
import com.thoughtworks.go.server.security.providers.LdapAuthenticationProvider;
import com.thoughtworks.go.util.GoConfigFileHelper;
import com.unboundid.ldif.LDIFRecord;
import org.apache.commons.lang.ArrayUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import static com.thoughtworks.go.server.security.GoAuthority.ROLE_SUPERVISOR;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsArrayContaining.hasItemInArray;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:WEB-INF/applicationContext-global.xml",
"classpath:WEB-INF/applicationContext-dataLocalAccess.xml",
"classpath:WEB-INF/applicationContext-acegi-security.xml"
}
)
public class LdapAuthenticationTest {
@Autowired
private GoConfigDao goConfigDao;
@Autowired
private LdapAuthenticationProvider ldapAuthenticationProvider;
private GoConfigFileHelper configFileHelper;
private InMemoryLdapServerForTests ldapServer;
private LDIFRecord employeesOrgUnit;
private static final int PORT = 12389;
private static final String LDAP_URL = "ldap://localhost:" + PORT;
private static final String BASE_DN = "dc=corp,dc=somecompany,dc=com";
private static final String MANAGER_DN = "cn=Active Directory Ldap User,ou=SomeSystems,ou=Accounts,ou=Principal," + BASE_DN;
private static final String MANAGER_PASSWORD = "some-password";
private static final String SEARCH_BASE = "ou=Employees,ou=Company,ou=Principal," + BASE_DN;
private static final String SEARCH_FILTER = "(sAMAccountName={0})";
@Before
public void setUp() throws Exception {
configFileHelper = new GoConfigFileHelper();
configFileHelper.usingCruiseConfigDao(goConfigDao);
configFileHelper.initializeConfigFile();
configFileHelper.addLdapSecurity(LDAP_URL, MANAGER_DN, MANAGER_PASSWORD, SEARCH_BASE, SEARCH_FILTER);
ldapServer = new InMemoryLdapServerForTests(BASE_DN, MANAGER_DN, MANAGER_PASSWORD).start(PORT);
ldapServer.addOrganizationalUnit("Principal", "ou=Principal," + BASE_DN);
ldapServer.addOrganizationalUnit("Company", "ou=Company,ou=Principal," + BASE_DN);
employeesOrgUnit = ldapServer.addOrganizationalUnit("Employees", "ou=Employees,ou=Company,ou=Principal," + BASE_DN);
}
@After
public void tearDown() throws Exception {
ldapServer.stop();
}
@Test
public void shouldSupportAuthenticationIfLdapConfigExist() {
assertThat(ldapAuthenticationProvider.supports(UsernamePasswordAuthenticationToken.class), is(true));
}
@Test
public void shouldAuthenticateForInvalidUser() {
assertFailedAuthentication("invalid_user", "");
}
@Test
public void shouldAuthenticateValidUser() throws Exception {
ldapServer.addUser(employeesOrgUnit, "foleys", "some-password", "Shilpa Foley", "foleys@somecompany.com");
assertAuthenticationOfValidAdminUser("foleys", "some-password");
}
@Test
public void shouldReturnAdministratorRoleForSpecifiedLdapUser() throws Exception {
configFileHelper.initializeConfigFile();
configFileHelper.addLdapSecurityWithAdmin(LDAP_URL, MANAGER_DN, MANAGER_PASSWORD, SEARCH_BASE, SEARCH_FILTER, "foleys");
shouldAuthenticateValidUser();
}
@Test
public void commonLdapUserShouldOnlyHaveAuthorityOfUserAndNotAdmin() throws Exception {
ldapServer.addUser(employeesOrgUnit, "foleys", "some-password", "Shilpa Foley", "foleys@somecompany.com");
configFileHelper.initializeConfigFile();
configFileHelper.addLdapSecurityWithAdmin(LDAP_URL, MANAGER_DN, MANAGER_PASSWORD, SEARCH_BASE, SEARCH_FILTER, "another_admin");
Authentication authentication = new UsernamePasswordAuthenticationToken("foleys", "some-password");
Authentication result = ldapAuthenticationProvider.authenticate(authentication);
assertThat(result.isAuthenticated(), is(true));
GrantedAuthority[] authorities = result.getAuthorities();
assertThat("foleys should have only user authority. Found: " + ArrayUtils.toString(authorities), authorities.length, is(1));
assertThat(authorities[0].getAuthority(), is("ROLE_USER"));
}
@Test
public void shouldAuthenticateConcurrently() throws Exception {
ldapServer.addUser(employeesOrgUnit, "foleys", "some-password", "Shilpa Foley", "foleys@somecompany.com");
ExecutorService pool = Executors.newFixedThreadPool(100);
List<Callable<String>> allCallables = new ArrayList<>();
for (int i = 0; i < 100; i++) {
final boolean even = i % 2 == 0;
allCallables.add(new Callable<String>() {
@Override
public String call() throws Exception {
if (even) {
assertAuthenticationOfValidAdminUser("foleys", "some-password");
} else {
assertFailedAuthentication("invalid_user", "");
}
return "";
}
});
}
List<Future<String>> futures = pool.invokeAll(allCallables);
pool.shutdown();
boolean finishedWithoutTimeout = pool.awaitTermination(10, TimeUnit.SECONDS);
assertThat(finishedWithoutTimeout, is(true));
// Assert no exceptions, by getting result.
for (Future<String> future : futures) {
future.get();
}
}
private void assertAuthenticationOfValidAdminUser(String userName, String password) {
Authentication authentication = new UsernamePasswordAuthenticationToken(userName, password);
Authentication result = ldapAuthenticationProvider.authenticate(authentication);
assertThat(result.isAuthenticated(), is(true));
assertThat(userName + " should have " + ROLE_SUPERVISOR + " authority", result.getAuthorities(),
hasItemInArray(ROLE_SUPERVISOR.asAuthority())); // by default, every user is administrator
}
private void assertFailedAuthentication(String userName, String password) {
Authentication authentication = new UsernamePasswordAuthenticationToken(userName, password);
try {
ldapAuthenticationProvider.authenticate(authentication);
fail("Expected authentication to fail for user: " + userName);
} catch (BadCredentialsException e) {
}
}
}