/* * 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.ldap; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.NamingException; import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.security.authorization.AmbariLdapUtils; import org.apache.ambari.server.security.authorization.Group; import org.apache.ambari.server.security.authorization.LdapServerProperties; import org.apache.ambari.server.security.authorization.User; import org.apache.ambari.server.security.authorization.Users; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ldap.control.PagedResultsDirContextProcessor; import org.springframework.ldap.core.AttributesMapper; import org.springframework.ldap.core.ContextMapper; import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.ldap.filter.AndFilter; import org.springframework.ldap.filter.EqualsFilter; import org.springframework.ldap.filter.Filter; import org.springframework.ldap.filter.HardcodedFilter; import org.springframework.ldap.filter.LikeFilter; import org.springframework.ldap.filter.OrFilter; import org.springframework.ldap.support.LdapUtils; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.google.common.collect.Sets; import com.google.inject.Inject; /** * Provides users, groups and membership population from LDAP catalog. */ public class AmbariLdapDataPopulator { /** * Log. */ private static final Logger LOG = LoggerFactory.getLogger(AmbariLdapDataPopulator.class); /** * Ambari configuration. */ private Configuration configuration; /** * Highlevel facade for management of users and groups. */ private Users users; /** * LDAP specific properties. */ protected LdapServerProperties ldapServerProperties; /** * LDAP template for making search queries. */ private LdapTemplate ldapTemplate; // Constants private static final String UID_ATTRIBUTE = "uid"; private static final String OBJECT_CLASS_ATTRIBUTE = "objectClass"; private static final int USERS_PAGE_SIZE = 500; // REGEXP to check member attribute starts with "cn=" or "uid=" - case insensitive private static final String IS_MEMBER_DN_REGEXP = "^(?i)(uid|cn|%s|%s)=.*$"; private static final String MEMBER_ATTRIBUTE_REPLACE_STRING = "${member}"; private static final String MEMBER_ATTRIBUTE_VALUE_PLACEHOLDER = "{member}"; /** * Construct an AmbariLdapDataPopulator. * * @param configuration the Ambari configuration * @param users utility that provides access to Users */ @Inject public AmbariLdapDataPopulator(Configuration configuration, Users users) { this.configuration = configuration; this.users = users; this.ldapServerProperties = configuration.getLdapServerProperties(); } /** * Check if LDAP is enabled in server properties. * * @return true if enabled */ public boolean isLdapEnabled() { if (!configuration.isLdapConfigured()) { return false; } try { final LdapTemplate ldapTemplate = loadLdapTemplate(); ldapTemplate.search(ldapServerProperties.getBaseDN(), "uid=dummy_search", new AttributesMapper() { @Override public Object mapFromAttributes(Attributes arg0) throws NamingException { return null; } }); return true; } catch (Exception ex) { LOG.error("Could not connect to LDAP server - " + ex.getMessage()); return false; } } /** * Retrieves information about external groups and users and their synced/unsynced state. * * @return dto with information */ public LdapSyncDto getLdapSyncInfo() { final LdapSyncDto syncInfo = new LdapSyncDto(); final Map<String, Group> internalGroupsMap = getInternalGroups(); final Set<LdapGroupDto> externalGroups = getExternalLdapGroupInfo(); for (LdapGroupDto externalGroup : externalGroups) { if (internalGroupsMap.containsKey(externalGroup.getGroupName()) && internalGroupsMap.get(externalGroup.getGroupName()).isLdapGroup()) { externalGroup.setSynced(true); } else { externalGroup.setSynced(false); } } final Map<String, User> internalUsersMap = getInternalUsers(); final Set<LdapUserDto> externalUsers = getExternalLdapUserInfo(); for (LdapUserDto externalUser : externalUsers) { String userName = externalUser.getUserName(); if (internalUsersMap.containsKey(userName) && internalUsersMap.get(userName).isLdapUser()) { externalUser.setSynced(true); } else { externalUser.setSynced(false); } } syncInfo.setGroups(externalGroups); syncInfo.setUsers(externalUsers); return syncInfo; } /** * Performs synchronization of all groups. * * @throws AmbariException if synchronization failed for any reason */ public LdapBatchDto synchronizeAllLdapGroups(LdapBatchDto batchInfo) throws AmbariException { LOG.trace("Synchronize All LDAP groups..."); Set<LdapGroupDto> externalLdapGroupInfo = getExternalLdapGroupInfo(); final Map<String, Group> internalGroupsMap = getInternalGroups(); final Map<String, User> internalUsersMap = getInternalUsers(); for (LdapGroupDto groupDto : externalLdapGroupInfo) { String groupName = groupDto.getGroupName(); addLdapGroup(batchInfo, internalGroupsMap, groupName); refreshGroupMembers(batchInfo, groupDto, internalUsersMap, internalGroupsMap, null, false); } for (Entry<String, Group> internalGroup : internalGroupsMap.entrySet()) { if (internalGroup.getValue().isLdapGroup()) { batchInfo.getGroupsToBeRemoved().add(internalGroup.getValue().getGroupName()); } } return batchInfo; } /** * Performs synchronization of given sets of all users. * * @throws AmbariException if synchronization failed for any reason */ public LdapBatchDto synchronizeAllLdapUsers(LdapBatchDto batchInfo) throws AmbariException { LOG.trace("Synchronize All LDAP users..."); Set<LdapUserDto> externalLdapUserInfo = getExternalLdapUserInfo(); Map<String, User> internalUsersMap = getInternalUsers(); for (LdapUserDto userDto : externalLdapUserInfo) { String userName = userDto.getUserName(); if (internalUsersMap.containsKey(userName)) { final User user = internalUsersMap.get(userName); if (user != null && !user.isLdapUser()) { if (Configuration.LdapUsernameCollisionHandlingBehavior.SKIP == configuration.getLdapSyncCollisionHandlingBehavior()) { LOG.info("User '{}' skipped because it is local user", userName); batchInfo.getUsersSkipped().add(userName); } else { batchInfo.getUsersToBecomeLdap().add(userName); LOG.trace("Convert user '{}' to LDAP user.", userName); } } internalUsersMap.remove(userName); } else { batchInfo.getUsersToBeCreated().add(userName); } } for (Entry<String, User> internalUser : internalUsersMap.entrySet()) { if (internalUser.getValue().isLdapUser()) { batchInfo.getUsersToBeRemoved().add(internalUser.getValue().getUserName()); } } return batchInfo; } /** * Performs synchronization of given set of groupnames. * * @param groups set of groups to synchronize * @throws AmbariException if synchronization failed for any reason */ public LdapBatchDto synchronizeLdapGroups(Set<String> groups, LdapBatchDto batchInfo) throws AmbariException { LOG.trace("Synchronize LDAP groups..."); final Set<LdapGroupDto> specifiedGroups = new HashSet<>(); for (String group : groups) { Set<LdapGroupDto> groupDtos = getLdapGroups(group); if (groupDtos.isEmpty()) { throw new AmbariException("Couldn't sync LDAP group " + group + ", it doesn't exist"); } specifiedGroups.addAll(groupDtos); } final Map<String, Group> internalGroupsMap = getInternalGroups(); final Map<String, User> internalUsersMap = getInternalUsers(); for (LdapGroupDto groupDto : specifiedGroups) { String groupName = groupDto.getGroupName(); addLdapGroup(batchInfo, internalGroupsMap, groupName); refreshGroupMembers(batchInfo, groupDto, internalUsersMap, internalGroupsMap, null, true); } return batchInfo; } /** * Performs synchronization of given set of user names. * * @param users set of users to synchronize * @throws AmbariException if synchronization failed for any reason */ public LdapBatchDto synchronizeLdapUsers(Set<String> users, LdapBatchDto batchInfo) throws AmbariException { LOG.trace("Synchronize LDAP users..."); final Set<LdapUserDto> specifiedUsers = new HashSet<>(); for (String user : users) { Set<LdapUserDto> userDtos = getLdapUsers(user); if (userDtos.isEmpty()) { throw new AmbariException("Couldn't sync LDAP user " + user + ", it doesn't exist"); } specifiedUsers.addAll(userDtos); } final Map<String, User> internalUsersMap = getInternalUsers(); for (LdapUserDto userDto : specifiedUsers) { String userName = userDto.getUserName(); if (internalUsersMap.containsKey(userName)) { final User user = internalUsersMap.get(userName); if (user != null && !user.isLdapUser()) { if (Configuration.LdapUsernameCollisionHandlingBehavior.SKIP == configuration.getLdapSyncCollisionHandlingBehavior()) { LOG.info("User '{}' skipped because it is local user", userName); batchInfo.getUsersSkipped().add(userName); } else { batchInfo.getUsersToBecomeLdap().add(userName); } } internalUsersMap.remove(userName); } else { batchInfo.getUsersToBeCreated().add(userName); } } return batchInfo; } /** * Performs synchronization of existent users and groups. * * @throws AmbariException if synchronization failed for any reason */ public LdapBatchDto synchronizeExistingLdapGroups(LdapBatchDto batchInfo) throws AmbariException { LOG.trace("Synchronize Existing LDAP groups..."); final Map<String, Group> internalGroupsMap = getInternalGroups(); final Map<String, User> internalUsersMap = getInternalUsers(); final Set<Group> internalGroupSet = Sets.newHashSet(internalGroupsMap.values()); for (Group group : internalGroupSet) { if (group.isLdapGroup()) { Set<LdapGroupDto> groupDtos = getLdapGroups(group.getGroupName()); if (groupDtos.isEmpty()) { batchInfo.getGroupsToBeRemoved().add(group.getGroupName()); } else { LdapGroupDto groupDto = groupDtos.iterator().next(); refreshGroupMembers(batchInfo, groupDto, internalUsersMap, internalGroupsMap, null, true); } } } return batchInfo; } /** * Performs synchronization of existent users and groups. * * @throws AmbariException if synchronization failed for any reason */ public LdapBatchDto synchronizeExistingLdapUsers(LdapBatchDto batchInfo) throws AmbariException { LOG.trace("Synchronize Existing LDAP users..."); final Map<String, User> internalUsersMap = getInternalUsers(); for (User user : internalUsersMap.values()) { if (user.isLdapUser()) { Set<LdapUserDto> userDtos = getLdapUsers(user.getUserName()); if (userDtos.isEmpty()) { batchInfo.getUsersToBeRemoved().add(user.getUserName()); } } } return batchInfo; } /** * Check group members of the synced group: add missing ones and remove the ones absent in external LDAP. * * @param batchInfo batch update object * @param group ldap group * @param internalUsers map of internal users * @param groupMemberAttributes set of group member attributes that have already been refreshed * @param recursive if disabled, it won't refresh members recursively (its not needed in case of all groups are processed) * @throws AmbariException if group refresh failed */ protected void refreshGroupMembers(LdapBatchDto batchInfo, LdapGroupDto group, Map<String, User> internalUsers, Map<String, Group> internalGroupsMap, Set<String> groupMemberAttributes, boolean recursive) throws AmbariException { Set<String> externalMembers = new HashSet<>(); if (groupMemberAttributes == null) { groupMemberAttributes = new HashSet<>(); } for (String memberAttributeValue : group.getMemberAttributes()) { LdapUserDto groupMember = getLdapUserByMemberAttr(memberAttributeValue); if (groupMember != null) { externalMembers.add(groupMember.getUserName()); } else { // if we haven't already processed this group if (recursive && !groupMemberAttributes.contains(memberAttributeValue)) { // if the member is another group then add all of its members LdapGroupDto subGroup = getLdapGroupByMemberAttr(memberAttributeValue); if (subGroup != null) { groupMemberAttributes.add(memberAttributeValue); addLdapGroup(batchInfo, internalGroupsMap, subGroup.getGroupName()); refreshGroupMembers(batchInfo, subGroup, internalUsers, internalGroupsMap, groupMemberAttributes, true); } } } } String groupName = group.getGroupName(); final Map<String, User> internalMembers = getInternalMembers(groupName); for (String externalMember : externalMembers) { if (internalUsers.containsKey(externalMember)) { final User user = internalUsers.get(externalMember); if (user == null) { // user is fresh and is already added to batch info if (!internalMembers.containsKey(externalMember)) { batchInfo.getMembershipToAdd().add(new LdapUserGroupMemberDto(groupName, externalMember)); } continue; } if (!user.isLdapUser()) { if (Configuration.LdapUsernameCollisionHandlingBehavior.SKIP == configuration.getLdapSyncCollisionHandlingBehavior()) { // existing user can not be converted to ldap user, so skip it LOG.info("User '{}' skipped because it is local user", externalMember); batchInfo.getUsersSkipped().add(externalMember); continue; // and remove from group } else { batchInfo.getUsersToBecomeLdap().add(externalMember); } } if (!internalMembers.containsKey(externalMember)) { batchInfo.getMembershipToAdd().add(new LdapUserGroupMemberDto(groupName, externalMember)); } internalMembers.remove(externalMember); } else { batchInfo.getUsersToBeCreated().add(externalMember); batchInfo.getMembershipToAdd().add(new LdapUserGroupMemberDto(groupName, externalMember)); } } for (Entry<String, User> userToBeUnsynced : internalMembers.entrySet()) { final User user = userToBeUnsynced.getValue(); batchInfo.getMembershipToRemove().add(new LdapUserGroupMemberDto(groupName, user.getUserName())); } } /** * Get the set of LDAP groups for the given group name. * * @param groupName the group name * @return the set of LDAP groups for the given name */ protected Set<LdapGroupDto> getLdapGroups(String groupName) { Filter groupObjectFilter = new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getGroupObjectClass()); Filter groupNameFilter = new LikeFilter(ldapServerProperties.getGroupNamingAttr(), groupName); return getFilteredLdapGroups(ldapServerProperties.getBaseDN(), groupObjectFilter, groupNameFilter); } /** * Get the set of LDAP users for the given user name. * * @param username the user name * @return the set of LDAP users for the given name */ protected Set<LdapUserDto> getLdapUsers(String username) { Filter userObjectFilter = new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getUserObjectClass()); Filter userNameFilter = new LikeFilter(ldapServerProperties.getUsernameAttribute(), username); return getFilteredLdapUsers(ldapServerProperties.getBaseDN(), userObjectFilter, userNameFilter); } /** * Get the LDAP user member for the given member attribute. * * @param memberAttributeValue the member attribute value * @return the user for the given member attribute; null if not found */ protected LdapUserDto getLdapUserByMemberAttr(String memberAttributeValue) { Set<LdapUserDto> filteredLdapUsers; memberAttributeValue = getUniqueIdByMemberPattern(memberAttributeValue, ldapServerProperties.getSyncUserMemberReplacePattern()); Filter syncMemberFilter = createCustomMemberFilter(memberAttributeValue, ldapServerProperties.getSyncUserMemberFilter()); if (memberAttributeValue != null && syncMemberFilter != null) { LOG.trace("Use custom filter '{}' for getting member user with default baseDN ('{}')", syncMemberFilter.encode(), ldapServerProperties.getBaseDN()); filteredLdapUsers = getFilteredLdapUsers(ldapServerProperties.getBaseDN(), syncMemberFilter); } else if (memberAttributeValue != null && isMemberAttributeBaseDn(memberAttributeValue)) { LOG.trace("Member can be used as baseDn: {}", memberAttributeValue); Filter filter = new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getUserObjectClass()); filteredLdapUsers = getFilteredLdapUsers(memberAttributeValue, filter); } else { LOG.trace("Member cannot be used as baseDn: {}", memberAttributeValue); Filter filter = new AndFilter() .and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getUserObjectClass())) .and(new EqualsFilter(ldapServerProperties.getUsernameAttribute(), memberAttributeValue)); filteredLdapUsers = getFilteredLdapUsers(ldapServerProperties.getBaseDN(), filter); } return (filteredLdapUsers.isEmpty()) ? null : filteredLdapUsers.iterator().next(); } /** * Get the LDAP group member for the given member attribute. * * @param memberAttributeValue the member attribute value * @return the group for the given member attribute; null if not found */ protected LdapGroupDto getLdapGroupByMemberAttr(String memberAttributeValue) { Set<LdapGroupDto> filteredLdapGroups; memberAttributeValue = getUniqueIdByMemberPattern(memberAttributeValue, ldapServerProperties.getSyncGroupMemberReplacePattern()); Filter syncMemberFilter = createCustomMemberFilter(memberAttributeValue, ldapServerProperties.getSyncGroupMemberFilter()); if (memberAttributeValue != null && syncMemberFilter != null) { LOG.trace("Use custom filter '{}' for getting member group with default baseDN ('{}')", syncMemberFilter.encode(), ldapServerProperties.getBaseDN()); filteredLdapGroups = getFilteredLdapGroups(ldapServerProperties.getBaseDN(), syncMemberFilter); } else if (memberAttributeValue != null && isMemberAttributeBaseDn(memberAttributeValue)) { LOG.trace("Member can be used as baseDn: {}", memberAttributeValue); Filter filter = new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getGroupObjectClass()); filteredLdapGroups = getFilteredLdapGroups(memberAttributeValue, filter); } else { LOG.trace("Member cannot be used as baseDn: {}", memberAttributeValue); filteredLdapGroups = getFilteredLdapGroups(ldapServerProperties.getBaseDN(), new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getGroupObjectClass()), getMemberFilter(memberAttributeValue)); } return (filteredLdapGroups.isEmpty()) ? null : filteredLdapGroups.iterator().next(); } /** * Use custom member filter. Replace {member} with the member attribute. * E.g.: (&(objectclass=posixaccount)(dn={member})) -> (&(objectclass=posixaccount)(dn=cn=mycn,dc=apache,dc=org)) */ protected Filter createCustomMemberFilter(String memberAttributeValue, String syncMemberFilter) { Filter filter = null; if (StringUtils.isNotEmpty(syncMemberFilter)) { filter = new HardcodedFilter(syncMemberFilter.replace(MEMBER_ATTRIBUTE_VALUE_PLACEHOLDER, memberAttributeValue)); } return filter; } /** * Replace memberAttribute value by a custom pattern to get the DN or id (like memberUid) of a user/group. * E.g.: memberAttribute="<sid=...><guid=...>,cn=mycn,dc=org,dc=apache" * Apply on (?<sid>.*);(?<guid>.*);(?<member>.*) pattern, then the result will be: "${member}" */ protected String getUniqueIdByMemberPattern(String memberAttributeValue, String pattern) { if (StringUtils.isNotEmpty(memberAttributeValue) && StringUtils.isNotEmpty(pattern)) { try { Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(memberAttributeValue); LOG.debug("Apply replace pattern '{}' on '{}' membership attribbute value.", memberAttributeValue, pattern); if (m.matches()) { memberAttributeValue = m.replaceAll(MEMBER_ATTRIBUTE_REPLACE_STRING); LOG.debug("Membership attribute value after replace pattern applied: '{}'", memberAttributeValue); } else { LOG.warn("Membership attribute value pattern is not matched ({}) on '{}'", pattern, memberAttributeValue); } } catch (Exception e) { LOG.error("Error during replace memberAttribute '{}' with pattern '{}'", memberAttributeValue, pattern); } } return memberAttributeValue; } /** * Removes synced users which are not present in any of group. * * @throws AmbariException */ protected void cleanUpLdapUsersWithoutGroup() throws AmbariException { final List<User> allUsers = users.getAllUsers(); for (User user : allUsers) { if (user.isLdapUser() && user.getGroups().isEmpty()) { users.removeUser(user); } } } // Utility methods protected void addLdapGroup(LdapBatchDto batchInfo, Map<String, Group> internalGroupsMap, String groupName) { if (internalGroupsMap.containsKey(groupName)) { final Group group = internalGroupsMap.get(groupName); if (!group.isLdapGroup()) { batchInfo.getGroupsToBecomeLdap().add(groupName); LOG.trace("Convert group '{}' to LDAP group.", groupName); } internalGroupsMap.remove(groupName); batchInfo.getGroupsProcessedInternal().add(groupName); } else { if (!batchInfo.getGroupsProcessedInternal().contains(groupName)) { batchInfo.getGroupsToBeCreated().add(groupName); } } } /** * Determines that the member attribute can be used as a 'dn' */ protected boolean isMemberAttributeBaseDn(String memberAttributeValue) { Pattern pattern = Pattern.compile(String.format(IS_MEMBER_DN_REGEXP, ldapServerProperties.getUsernameAttribute(), ldapServerProperties.getGroupNamingAttr())); return pattern.matcher(memberAttributeValue).find(); } /** * Retrieves groups from external LDAP server. * * @return set of info about LDAP groups */ protected Set<LdapGroupDto> getExternalLdapGroupInfo() { EqualsFilter groupObjectFilter = new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getGroupObjectClass()); return getFilteredLdapGroups(ldapServerProperties.getBaseDN(), groupObjectFilter); } // get a filter based on the given member attribute private Filter getMemberFilter(String memberAttributeValue) { String dnAttribute = ldapServerProperties.getDnAttribute(); return new OrFilter().or(new EqualsFilter(dnAttribute, memberAttributeValue)). or(new EqualsFilter(UID_ATTRIBUTE, memberAttributeValue)); } private Set<LdapGroupDto> getFilteredLdapGroups(String baseDn, Filter... filters) { AndFilter andFilter = new AndFilter(); for (Filter filter : filters) { andFilter.and(filter); } return getFilteredLdapGroups(baseDn, andFilter); } private Set<LdapGroupDto> getFilteredLdapGroups(String baseDn, Filter filter) { final Set<LdapGroupDto> groups = new HashSet<>(); final LdapTemplate ldapTemplate = loadLdapTemplate(); LOG.trace("LDAP Group Query - Base DN: '{}' ; Filter: '{}'", baseDn, filter.encode()); ldapTemplate.search(baseDn, filter.encode(), new LdapGroupContextMapper(groups, ldapServerProperties)); return groups; } /** * Retrieves users from external LDAP server. * * @return set of info about LDAP users */ protected Set<LdapUserDto> getExternalLdapUserInfo() { EqualsFilter userObjectFilter = new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, ldapServerProperties.getUserObjectClass()); return getFilteredLdapUsers(ldapServerProperties.getBaseDN(), userObjectFilter); } private Set<LdapUserDto> getFilteredLdapUsers(String baseDn, Filter... filters) { AndFilter andFilter = new AndFilter(); for (Filter filter : filters) { andFilter.and(filter); } return getFilteredLdapUsers(baseDn, andFilter); } private Set<LdapUserDto> getFilteredLdapUsers(String baseDn, Filter filter) { final Set<LdapUserDto> users = new HashSet<>(); final LdapTemplate ldapTemplate = loadLdapTemplate(); PagedResultsDirContextProcessor processor = createPagingProcessor(); SearchControls searchControls = new SearchControls(); searchControls.setReturningObjFlag(true); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); LdapUserContextMapper ldapUserContextMapper = new LdapUserContextMapper(ldapServerProperties); String encodedFilter = filter.encode(); do { LOG.trace("LDAP User Query - Base DN: '{}' ; Filter: '{}'", baseDn, encodedFilter); List dtos = configuration.getLdapServerProperties().isPaginationEnabled() ? ldapTemplate.search(LdapUtils.newLdapName(baseDn), encodedFilter, searchControls, ldapUserContextMapper, processor) : ldapTemplate.search(LdapUtils.newLdapName(baseDn), encodedFilter, searchControls, ldapUserContextMapper); for (Object dto : dtos) { if (dto != null) { users.add((LdapUserDto) dto); } } } while (configuration.getLdapServerProperties().isPaginationEnabled() && processor.getCookie().getCookie() != null); return users; } /** * Creates a map of internal groups. * * @return map of GroupName-Group pairs */ protected Map<String, Group> getInternalGroups() { final List<Group> internalGroups = users.getAllGroups(); final Map<String, Group> internalGroupsMap = new HashMap<>(); for (Group group : internalGroups) { internalGroupsMap.put(group.getGroupName(), group); } return internalGroupsMap; } /** * Creates a map of internal users. * * @return map of UserName-User pairs */ protected Map<String, User> getInternalUsers() { final List<User> internalUsers = users.getAllUsers(); final Map<String, User> internalUsersMap = new HashMap<>(); LOG.trace("Get all users from Ambari Server."); for (User user : internalUsers) { internalUsersMap.put(user.getUserName(), user); } return internalUsersMap; } /** * Creates a map of internal users present in specified group. * * @param groupName group name * @return map of UserName-User pairs */ protected Map<String, User> getInternalMembers(String groupName) { final Collection<User> internalMembers = users.getGroupMembers(groupName); if (internalMembers == null) { return Collections.emptyMap(); } final Map<String, User> internalMembersMap = new HashMap<>(); for (User user : internalMembers) { internalMembersMap.put(user.getUserName(), user); } return internalMembersMap; } /** * Checks LDAP configuration for changes and reloads LDAP template if they occurred. * * @return LdapTemplate instance */ protected LdapTemplate loadLdapTemplate() { final LdapServerProperties properties = configuration .getLdapServerProperties(); if (ldapTemplate == null || !properties.equals(ldapServerProperties)) { LOG.info("Reloading properties"); ldapServerProperties = properties; final LdapContextSource ldapContextSource = createLdapContextSource(); // The LdapTemplate by design will close the connection after each call to the LDAP Server // In order to have the interaction work with large/paged results, said connection must be pooled and reused ldapContextSource.setPooled(true); final List<String> ldapUrls = ldapServerProperties.getLdapUrls(); ldapContextSource.setUrls(ldapUrls.toArray(new String[ldapUrls.size()])); if (!ldapServerProperties.isAnonymousBind()) { ldapContextSource.setUserDn(ldapServerProperties.getManagerDn()); ldapContextSource.setPassword(ldapServerProperties.getManagerPassword()); } try { ldapContextSource.afterPropertiesSet(); } catch (Exception e) { LOG.error("LDAP Context Source not loaded ", e); throw new UsernameNotFoundException("LDAP Context Source not loaded", e); } ldapContextSource.setReferral(ldapServerProperties.getReferralMethod()); ldapTemplate = createLdapTemplate(ldapContextSource); ldapTemplate.setIgnorePartialResultException(true); } return ldapTemplate; } /** * LdapContextSource factory method. * * @return new context source */ protected LdapContextSource createLdapContextSource() { return new LdapContextSource(); } /** * PagedResultsDirContextProcessor factory method. * * @return new processor; */ protected PagedResultsDirContextProcessor createPagingProcessor() { return new PagedResultsDirContextProcessor(USERS_PAGE_SIZE, null); } /** * LdapTemplate factory method. * * @param ldapContextSource the LDAP context source * @return new LDAP template */ protected LdapTemplate createLdapTemplate(LdapContextSource ldapContextSource) { return new LdapTemplate(ldapContextSource); } // // ContextMapper implementations // protected static class LdapGroupContextMapper implements ContextMapper { private final Set<LdapGroupDto> groups; private final LdapServerProperties ldapServerProperties; public LdapGroupContextMapper(Set<LdapGroupDto> groups, LdapServerProperties ldapServerProperties) { this.groups = groups; this.ldapServerProperties = ldapServerProperties; } @Override public Object mapFromContext(Object ctx) { final DirContextAdapter adapter = (DirContextAdapter) ctx; final String groupNameAttribute = adapter.getStringAttribute(ldapServerProperties.getGroupNamingAttr()); boolean outOfScope = AmbariLdapUtils.isLdapObjectOutOfScopeFromBaseDn(adapter, ldapServerProperties.getBaseDN()); if (outOfScope) { LOG.warn("Group '{}' is out of scope of the base DN. It will be skipped.", groupNameAttribute); return null; } if (groupNameAttribute != null) { final LdapGroupDto group = new LdapGroupDto(); group.setGroupName(groupNameAttribute.toLowerCase()); final String[] uniqueMembers = adapter.getStringAttributes(ldapServerProperties.getGroupMembershipAttr()); if (uniqueMembers != null) { for (String uniqueMember : uniqueMembers) { group.getMemberAttributes().add(uniqueMember.toLowerCase()); } } groups.add(group); } return null; } } protected static class LdapUserContextMapper implements ContextMapper { private final LdapServerProperties ldapServerProperties; public LdapUserContextMapper(LdapServerProperties ldapServerProperties) { this.ldapServerProperties = ldapServerProperties; } @Override public Object mapFromContext(Object ctx) { final DirContextAdapter adapter = (DirContextAdapter) ctx; final String usernameAttribute = adapter.getStringAttribute(ldapServerProperties.getUsernameAttribute()); final String uidAttribute = adapter.getStringAttribute(UID_ATTRIBUTE); boolean outOfScope = AmbariLdapUtils.isLdapObjectOutOfScopeFromBaseDn(adapter, ldapServerProperties.getBaseDN()); if (outOfScope) { LOG.warn("User '{}' is out of scope of the base DN. It will be skipped.", usernameAttribute); return null; } if (usernameAttribute != null || uidAttribute != null) { final LdapUserDto user = new LdapUserDto(); user.setUserName(usernameAttribute != null ? usernameAttribute.toLowerCase() : null); user.setUid(uidAttribute != null ? uidAttribute.toLowerCase() : null); user.setDn(adapter.getNameInNamespace().toLowerCase()); return user; } else { LOG.warn("Ignoring LDAP user " + adapter.getNameInNamespace() + " as it doesn't have required" + " attributes uid and " + ldapServerProperties.getUsernameAttribute()); } return null; } } }