/* * 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.SecurityAuthConfig; 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.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.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @understands searching for users(from authentication sources) */ @Service public class UserSearchService { private final LdapUserSearch ldapUserSearch; private final PasswordFileUserSearch passwordFileUserSearch; private final AuthorizationMetadataStore store; private final AuthorizationExtension authorizationExtension; private GoConfigService goConfigService; private AuthenticationPluginRegistry authenticationPluginRegistry; private AuthenticationExtension authenticationExtension; private static final Logger LOGGER = Logger.getLogger(UserSearchService.class); private static final int MINIMUM_SEARCH_STRING_LENGTH = 2; @Autowired public UserSearchService(LdapUserSearch ldapUserSearch, PasswordFileUserSearch passwordFileUserSearch, AuthorizationExtension authorizationExtension, GoConfigService goConfigService, AuthenticationPluginRegistry authenticationPluginRegistry, AuthenticationExtension authenticationExtension) { this.ldapUserSearch = ldapUserSearch; this.passwordFileUserSearch = passwordFileUserSearch; this.store = AuthorizationMetadataStore.instance(); this.authorizationExtension = authorizationExtension; this.goConfigService = goConfigService; this.authenticationPluginRegistry = authenticationPluginRegistry; this.authenticationExtension = authenticationExtension; } public List<UserSearchModel> search(String searchText, HttpLocalizedOperationResult result) { List<UserSearchModel> userSearchModels = new ArrayList<>(); if (isInputValid(searchText, result)) { return userSearchModels; } boolean passwordSearchFailed = searchPasswordFile(searchText, result, userSearchModels); searchLdap(searchText, result, userSearchModels, passwordSearchFailed); searchUsingPlugins(searchText, userSearchModels); if (userSearchModels.size() == 0 && !result.hasMessage()) { result.setMessage(LocalizedMessage.string("NO_SEARCH_RESULTS_ERROR")); } return userSearchModels; } private void searchLdap(String searchText, HttpLocalizedOperationResult result, List<UserSearchModel> userSearchModels, boolean passwordSearchFailed) { if (goConfigService.isLdapConfigured()) { List<User> users = new ArrayList<>(); try { users = ldapUserSearch.search(searchText); } catch (LdapUserSearch.NotAllResultsShownException ex) { result.setMessage(LocalizedMessage.string("NOT_ALL_RESULTS_SHOWN")); users = ex.getUsers(); } catch (Exception ex) { LOGGER.error(String.format("User search for %s on ldap failed with Exception.", searchText), ex); if (passwordSearchFailed) { result.badRequest(LocalizedMessage.string("USER_SEARCH_FAILED")); } else { result.setMessage(LocalizedMessage.string("LDAP_ERROR")); } } userSearchModels.addAll(convertUsersToUserSearchModel(users, UserSourceType.LDAP)); } } private boolean searchPasswordFile(String searchText, HttpLocalizedOperationResult result, List<UserSearchModel> userSearchModels) { boolean passwordSearchFailed = false; if (!goConfigService.isPasswordFileConfigured()) { return false; } try { List<User> passwordFileUsers = passwordFileUserSearch.search(searchText); List<UserSearchModel> models = convertUsersToUserSearchModel(passwordFileUsers, UserSourceType.PASSWORD_FILE); userSearchModels.addAll(models); } catch (Exception e) { passwordSearchFailed = true; result.setMessage(LocalizedMessage.string("PASSWORD_SEARCH_FAILED")); LOGGER.error(String.format("User search for %s on password failed with IOException.", searchText), e); } return passwordSearchFailed; } private void searchUsingPlugins(String searchText, List<UserSearchModel> userSearchModels) { List<User> searchResults = new ArrayList<>(); for (final String pluginId : getAuthorizationAndAuthenticationPlugins()) { try { List<com.thoughtworks.go.plugin.access.authentication.models.User> users = getUsersConfiguredViaPlugin(pluginId, searchText); if (users != null && !users.isEmpty()) { for (com.thoughtworks.go.plugin.access.authentication.models.User user : users) { String displayName = user.getDisplayName() == null ? "" : user.getDisplayName(); String emailId = user.getEmailId() == null ? "" : user.getEmailId(); searchResults.add(new User(user.getUsername(), displayName, emailId)); } } } catch (Exception e) { LOGGER.warn("Error occurred while performing user search using plugin: " + pluginId, e); } } userSearchModels.addAll(convertUsersToUserSearchModel(searchResults, UserSourceType.PLUGIN)); } private List<com.thoughtworks.go.plugin.access.authentication.models.User> getUsersConfiguredViaPlugin(String pluginId, String searchTerm) { List<com.thoughtworks.go.plugin.access.authentication.models.User> users = new ArrayList<>(); if (authorizationExtension.canHandlePlugin(pluginId)) { List<SecurityAuthConfig> authConfigs = goConfigService.security().securityAuthConfigs().findByPluginId(pluginId); users.addAll(authorizationExtension.searchUsers(pluginId, searchTerm, authConfigs)); } if (authenticationExtension.canHandlePlugin(pluginId)) { users.addAll(authenticationExtension.searchUser(pluginId, searchTerm)); } return users; } private Set<String> getAuthorizationAndAuthenticationPlugins() { Set<String> authPlugins = new HashSet<>(); Set<String> pluginsThatSupportsUserSearch = store.getPluginsThatSupportsUserSearch(); Set<String> authenticationPlugins = authenticationPluginRegistry.getAuthenticationPlugins(); authPlugins.addAll(pluginsThatSupportsUserSearch); authPlugins.addAll(authenticationPlugins); return authPlugins; } private boolean isInputValid(String searchText, HttpLocalizedOperationResult result) { if (searchText.trim().length() < MINIMUM_SEARCH_STRING_LENGTH) { result.badRequest(LocalizedMessage.string("SEARCH_STRING_TOO_SMALL")); return true; } return false; } private List<UserSearchModel> convertUsersToUserSearchModel(List<User> users, UserSourceType source) { List<UserSearchModel> userSearchModels = new ArrayList<>(); for (User user : users) { userSearchModels.add(new UserSearchModel(user, source)); } return userSearchModels; } }