/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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.jbpm.services.task.utils; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class providing LDAP search capabilities. */ public class LdapSearcher { private static final Logger log = LoggerFactory.getLogger(LdapSearcher.class); public static final String SEARCH_SCOPE = "ldap.search.scope"; private static final String DEFAULT_INITIAL_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; private static final String DEFAULT_SECURITY_AUTHENTICATION = "simple"; private final List<SearchResult> searchResults = new ArrayList<>(); private final Properties config; /** * @param config LDAP connection properties * @see javax.naming.Context */ public LdapSearcher(Properties config) { this.config = config; } /** * Search LDAP and stores the results in searchResults field. * @param context the name of the context where the search starts (the depth depends on ldap.search.scope) * @param filterExpr the filter expression to use for the search. The expression may contain variables of the form * "<code>{i}</code>" where <code>i</code> is a non-negative integer. May not be null. * @param filterArgs the array of arguments to substitute for the variables in <code>filterExpr</code>. The value of * <code>filterArgs[i]</code> will replace each occurrence of "<code>{i}</code>". If null, an equivalent of an empty * array is used. * @return this */ public LdapSearcher search(String context, String filterExpr, Object... filterArgs) { searchResults.clear(); LdapContext ldapContext = null; NamingEnumeration<SearchResult> ldapResult = null; try { ldapContext = buildLdapContext(); ldapResult = ldapContext.search(context, filterExpr, filterArgs, createSearchControls()); while (ldapResult.hasMore()) { searchResults.add(ldapResult.next()); } } catch (NamingException ex) { throw new RuntimeException("LDAP search has failed", ex); } finally { if (ldapResult != null) { try { ldapResult.close(); } catch (NamingException ex) { log.error("Failed to close LDAP results enumeration", ex); } } if (ldapContext != null) { try { ldapContext.close(); } catch (NamingException ex) { log.error("Failed to close LDAP context", ex); } } } return this; } public SearchResult getSingleSearchResult() { return searchResults.isEmpty() ? null : searchResults.get(0); } public List<SearchResult> getSearchResults() { return searchResults; } public String getSingleAttributeResult(String attributeId) { List<String> attributeResults = getAttributeResults(attributeId); return attributeResults.isEmpty() ? null : attributeResults.get(0); } public List<String> getAttributeResults(String attributeId) { return searchResults.stream() .map(searchResult -> getAttribute(searchResult, attributeId)) .collect(Collectors.toList()); } private String getAttribute(SearchResult searchResult, String attributeId) { if (searchResult == null) { return null; } Attribute entry = searchResult.getAttributes().get(attributeId); if (entry == null) { log.warn("The attribute with ID '{}' has not been found.", attributeId); return null; } try { return entry.get().toString(); } catch (NamingException ex) { log.error("Failed to get attribute value", ex); return null; } } private LdapContext buildLdapContext() throws NamingException { config.putIfAbsent(Context.INITIAL_CONTEXT_FACTORY, DEFAULT_INITIAL_CONTEXT_FACTORY); config.putIfAbsent(Context.SECURITY_AUTHENTICATION, DEFAULT_SECURITY_AUTHENTICATION); String protocol = config.getProperty(Context.SECURITY_PROTOCOL); config.putIfAbsent(Context.PROVIDER_URL, createDefaultProviderUrl(protocol)); if (log.isDebugEnabled()) { log.debug("Using following InitialLdapContext properties:"); log.debug("Initial Context Factory: {}", config.getProperty(Context.INITIAL_CONTEXT_FACTORY)); log.debug("Authentication Type: {}", config.getProperty(Context.SECURITY_AUTHENTICATION)); log.debug("Protocol: {}", config.getProperty(Context.SECURITY_PROTOCOL)); log.debug("Provider URL: {}", config.getProperty(Context.PROVIDER_URL)); log.debug("User DN: {}", config.getProperty(Context.SECURITY_PRINCIPAL)); log.debug("Password: {}", config.getProperty(Context.SECURITY_CREDENTIALS)); } return new InitialLdapContext(config, null); } private String createDefaultProviderUrl(String protocol) { String port = "ssl".equalsIgnoreCase(protocol) ? "636" : "389"; return "ldap://localhost:" + port; } private SearchControls createSearchControls() { SearchControls searchControls = new SearchControls(); String searchScope = config.getProperty(SEARCH_SCOPE); if (searchScope != null) { searchControls.setSearchScope(parseSearchScope(searchScope)); } return searchControls; } private int parseSearchScope(String searchScope) { log.debug("Search scope: {}", searchScope); try { return SearchScope.valueOf(searchScope).ordinal(); } catch (IllegalArgumentException ex) { return SearchScope.ONELEVEL_SCOPE.ordinal(); } } public enum SearchScope { OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE } }