/* * 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.SecurityConfig; import com.thoughtworks.go.domain.User; import com.thoughtworks.go.i18n.LocalizedMessage; import com.thoughtworks.go.plugin.access.authentication.AuthenticationExtension; import com.thoughtworks.go.plugin.access.authentication.AuthenticationPluginRegistry; import com.thoughtworks.go.plugin.access.authorization.AuthorizationExtension; import com.thoughtworks.go.plugin.access.authorization.AuthorizationMetadataStore; import com.thoughtworks.go.plugin.domain.authorization.AuthorizationPluginInfo; import com.thoughtworks.go.plugin.domain.authorization.Capabilities; import com.thoughtworks.go.plugin.domain.authorization.SupportedAuthType; import com.thoughtworks.go.plugin.infra.plugininfo.GoPluginDescriptor; import com.thoughtworks.go.presentation.UserSearchModel; import com.thoughtworks.go.presentation.UserSourceType; import com.thoughtworks.go.server.service.GoConfigService; import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import java.io.IOException; import java.util.*; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; public class UserSearchServiceTest { @Mock private AuthenticationPluginRegistry authenticationPluginRegistry; @Mock private AuthenticationExtension authenticationExtension; @Mock private LdapUserSearch ldapUserSearch; @Mock private PasswordFileUserSearch passwordFileUserSearch; @Mock private AuthorizationExtension authorizationExtension; @Mock private GoConfigService goConfigService; private UserSearchService userSearchService; @Before public void setUp() { initMocks(this); when(goConfigService.isLdapConfigured()).thenReturn(true); userSearchService = new UserSearchService(ldapUserSearch, passwordFileUserSearch, authorizationExtension, goConfigService, authenticationPluginRegistry, authenticationExtension); } @After public void tearDown() throws Exception { AuthorizationMetadataStore.instance().clear(); } @Test public void shouldSearchForUsers() throws Exception { User foo = new User("foo", new ArrayList<>(), "foo@cruise.com", false); User bar = new User("bar-foo", new ArrayList<>(), "bar@go.com", true); when(ldapUserSearch.search("foo")).thenReturn(Arrays.asList(foo, bar)); List<UserSearchModel> models = userSearchService.search("foo", new HttpLocalizedOperationResult()); assertThat(models, is(Arrays.asList(new UserSearchModel(foo, UserSourceType.LDAP), new UserSearchModel(bar, UserSourceType.LDAP)))); } @Test public void shouldAddPluginSearchResults() throws Exception { String searchTerm = "foo"; User foo = new User("foo", new ArrayList<>(), "foo@cruise.com", false); User bar = new User("bar-foo", new ArrayList<>(), "bar@go.com", true); when(ldapUserSearch.search(searchTerm)).thenReturn(Arrays.asList(foo, bar)); List<String> pluginIds = Arrays.asList("plugin-id-1", "plugin-id-2", "plugin-id-3", "plugin-id-4"); addPluginSupportingUserSearch(pluginIds.get(0)); addPluginSupportingUserSearch(pluginIds.get(1)); addPluginSupportingUserSearch(pluginIds.get(2)); addPluginSupportingUserSearch(pluginIds.get(3)); when(authorizationExtension.canHandlePlugin(anyString())).thenReturn(true); when(goConfigService.security()).thenReturn(new SecurityConfig()); when(authorizationExtension.searchUsers("plugin-id-1", searchTerm, Collections.emptyList())).thenReturn(Arrays.asList(getPluginUser(1))); when(authorizationExtension.searchUsers("plugin-id-2", searchTerm, Collections.emptyList())).thenReturn(Arrays.asList(getPluginUser(2), getPluginUser(3))); when(authorizationExtension.searchUsers("plugin-id-3", searchTerm, Collections.emptyList())).thenReturn(new ArrayList<>()); when(authorizationExtension.searchUsers("plugin-id-4", searchTerm, Collections.emptyList())).thenReturn(Arrays.asList(new com.thoughtworks.go.plugin.access.authentication.models.User("username-" + 4, null, null))); List<UserSearchModel> models = userSearchService.search(searchTerm, new HttpLocalizedOperationResult()); assertThat(models, is(Arrays.asList(new UserSearchModel(foo, UserSourceType.LDAP), new UserSearchModel(bar, UserSourceType.LDAP), new UserSearchModel(getUser(1), UserSourceType.PLUGIN), new UserSearchModel(getUser(2), UserSourceType.PLUGIN), new UserSearchModel(getUser(3), UserSourceType.PLUGIN), new UserSearchModel(new User("username-" + 4, "", ""), UserSourceType.PLUGIN)))); } @Test public void shouldAddPluginSearchResultsWhenPluginImplementsAuthenticationExtension() { String searchTerm = "foo"; List<String> pluginIds = Arrays.asList("plugin-id-1", "plugin-id-2", "plugin-id-3", "plugin-id-4"); when(authenticationPluginRegistry.getAuthenticationPlugins()).thenReturn(new HashSet<String>(pluginIds)); when(authenticationExtension.canHandlePlugin(anyString())).thenReturn(true); when(authenticationExtension.searchUser("plugin-id-1", searchTerm)).thenReturn(Arrays.asList(getPluginUser(1))); when(authenticationExtension.searchUser("plugin-id-2", searchTerm)).thenReturn(Arrays.asList(getPluginUser(2), getPluginUser(3))); when(authenticationExtension.searchUser("plugin-id-3", searchTerm)).thenReturn(new ArrayList<com.thoughtworks.go.plugin.access.authentication.models.User>()); when(authenticationExtension.searchUser("plugin-id-4", searchTerm)).thenReturn(Arrays.asList(new com.thoughtworks.go.plugin.access.authentication.models.User("username-" + 4, null, null))); List<UserSearchModel> models = userSearchService.search(searchTerm, new HttpLocalizedOperationResult()); assertThat(models, is(Arrays.asList(new UserSearchModel(getUser(1), UserSourceType.PLUGIN), new UserSearchModel(getUser(2), UserSourceType.PLUGIN), new UserSearchModel(getUser(3), UserSourceType.PLUGIN), new UserSearchModel(new User("username-" + 4, "", ""), UserSourceType.PLUGIN)))); } @Test public void shouldReturnWarningMessageWhenPasswordSearchFails() throws Exception { HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); when(goConfigService.isPasswordFileConfigured()).thenReturn(true); when(passwordFileUserSearch.search("foo")).thenThrow(new RuntimeException("Password file not found")); when(ldapUserSearch.search("foo")).thenReturn(new ArrayList<>()); List<UserSearchModel> models = userSearchService.search("foo", result); assertThat(models.size(), is(0)); assertThat(result.localizable(), is(LocalizedMessage.string("PASSWORD_SEARCH_FAILED"))); } @Test public void shouldNotReturnWarningMessageWhenPasswordFileIsNotConfigured() throws Exception { HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); when(goConfigService.isPasswordFileConfigured()).thenReturn(false); when(ldapUserSearch.search("foo")).thenReturn(Arrays.asList(new User("foo"))); List<UserSearchModel> models = userSearchService.search("foo", result); assertThat(models.size(), is(1)); assertThat(result.hasMessage(), is(false)); } @Test public void search_shouldNotAttemptLdapSearchIfLdapIsNotConfigured() throws Exception { User foo = new User("foo", new ArrayList<>(), "foo@cruise.com", false); HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); when(goConfigService.isLdapConfigured()).thenReturn(false); when(passwordFileUserSearch.search("foo")).thenReturn(Arrays.asList(foo)); userSearchService.search("foo", result); verifyNoMoreInteractions(ldapUserSearch); } @Test public void shouldReturnWarningMessageWhenLdapSearchFails() throws Exception { User foo = new User("foo", new ArrayList<>(), "foo@cruise.com", false); HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); when(goConfigService.isPasswordFileConfigured()).thenReturn(true); when(ldapUserSearch.search("foo")).thenThrow(new RuntimeException("Ldap Error")); when(passwordFileUserSearch.search("foo")).thenReturn(Arrays.asList(foo)); List<UserSearchModel> models = userSearchService.search("foo", result); assertThat(models, is(Arrays.asList(new UserSearchModel(foo, UserSourceType.PASSWORD_FILE)))); assertThat(result.localizable(), is(LocalizedMessage.string("LDAP_ERROR"))); } @Test public void shouldReturnWarningMessageWhenSearchReturnsNoResults() throws Exception { HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); when(ldapUserSearch.search("foo")).thenReturn(new ArrayList()); when(passwordFileUserSearch.search("foo")).thenReturn(new ArrayList()); userSearchService.search("foo", result); assertThat(result.localizable(), is(LocalizedMessage.string("NO_SEARCH_RESULTS_ERROR"))); } @Test public void shouldFailSearchIfBothLdapAndPasswordFileFail() throws Exception { HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); when(goConfigService.isPasswordFileConfigured()).thenReturn(true); when(ldapUserSearch.search("foo")).thenThrow(new RuntimeException("Ldap Error")); when(passwordFileUserSearch.search("foo")).thenThrow(new IOException("Password file not found")); userSearchService.search("foo", result); assertThat(result.localizable(), is(LocalizedMessage.string("USER_SEARCH_FAILED"))); } @Test public void shouldNotInvokeSearchWhenUserSearchTextIsTooSmall() throws Exception { String smallSearchText = "f"; HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); userSearchService.search(smallSearchText, result); verify(ldapUserSearch, never()).search(smallSearchText); verify(passwordFileUserSearch, never()).search(smallSearchText); } @Test public void shouldNotInvokeSearchWhenUserSearchTextIsTooSmallAfterTrimming() throws Exception { String smallSearchText = "a "; HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); userSearchService.search(smallSearchText, result); verify(ldapUserSearch, never()).search(smallSearchText); verify(passwordFileUserSearch, never()).search(smallSearchText); } @Test public void shouldLimitSearchResultsAndWarnTheUser() throws Exception { HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); User foo = new User("fooUser", "Foo User", "foo@user.com"); User bar = new User("barUser", "boo User", "boo@user.com"); when(ldapUserSearch.search("foo")).thenThrow(new LdapUserSearch.NotAllResultsShownException(Arrays.asList(foo, bar))); when(passwordFileUserSearch.search("foo")).thenReturn(new ArrayList<>()); List<UserSearchModel> models = userSearchService.search("foo", result); assertThat(models.size(), is(2)); assertThat(result.localizable(), is(LocalizedMessage.string("NOT_ALL_RESULTS_SHOWN"))); } private User getUser(Integer userId) { return new User("username-" + userId, "display-name-" + userId, "test" + userId + "@test.com"); } private com.thoughtworks.go.plugin.access.authentication.models.User getPluginUser(Integer userId) { return new com.thoughtworks.go.plugin.access.authentication.models.User("username-" + userId, "display-name-" + userId, "test" + userId + "@test.com"); } private void addPluginSupportingUserSearch(String pluginId) { AuthorizationPluginInfo pluginInfo = new AuthorizationPluginInfo( new GoPluginDescriptor(pluginId, null, null, null, null, false), null, null, null, new Capabilities(SupportedAuthType.Password, true, true)); AuthorizationMetadataStore.instance().setPluginInfo(pluginInfo); } }