package org.ovirt.engine.core.bll.adbroker; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.common.businessentities.AdUser; import org.ovirt.engine.core.common.businessentities.ad_groups; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; import org.ovirt.engine.core.dal.dbbroker.DbFacade; /** * Helper class for AD issues * */ public class LdapBrokerUtils { private static LogCompat log = LogFactoryCompat.getLog(LdapBrokerUtils.class); /** * getDomainsList as stored in DB - trims the domains if needed * * @return */ public static List<String> getDomainsList(boolean filterInternalDomain) { String[] domains = Config.<String> GetValue(ConfigValues.DomainName).split("[,]", -1); List<String> domainsList = Arrays.asList(domains); List<String> results = new ArrayList<String>(); for (String domain : domains) { String trimmedDomain = domain.trim(); if (!trimmedDomain.isEmpty()) { results.add(domain.trim()); } } if (!filterInternalDomain) { results.add(Config.<String> GetValue(ConfigValues.AdminDomain).trim()); } return results; } /** * returns the full domain list * @return */ public static List<String> getDomainsList() { return getDomainsList(false); } /** * This method should parse a string in the following format: CN=groupname,OU=ouSub,OU=ouMain,DC=qumranet,DC=com to * the following format qumranet.com/ouMain/ouSub/groupname it should also handle '\,' and '\=' as ',' and '='. * * @param ldapname * @return */ public static String generateGroupDisplayValue(String ldapname) { if (ldapname == null) { return ""; } LdapName name; try { name = new LdapName(ldapname); } catch (InvalidNameException e) { // fail to generate a nice display value. Retuning the String we got. return ldapname; } StringBuilder sb = new StringBuilder(); List<Rdn> rdns = name.getRdns(); for (Rdn rdn : rdns) { String type = rdn.getType(); String val = (String) rdn.getValue(); if (type.equalsIgnoreCase("dc")) { sb.insert(0, "." + val); continue; } sb.append("/" + val); } // remove the first "." character. sb.delete(0, 1); return sb.toString(); } /** * This method performs group population for the given list of users. It will not execute LDAP queries for groups * that were already populated (passed by the updatedGroups parameters) * * @param users * users to populate their groups for * @param loginName * user to perform the LDAP queries for group population with * @param password * password to perform the LDAP queries for group population with * @param domainName * domain to perform the LDAP queries for group population with * @param updatedGroups * list of already populated groups that should not be repopulated. */ public static void performGroupPopulationForUsers(List<AdUser> users, String loginName, String password, String domainName, List<ad_groups> updatedGroups) { // A list that holds the results of the LDAP queries for groups - both from this method + from previous LDAP // queries for groups that populated groups // that are now in updatedGroups list List<GroupSearchResult> results = new ArrayList<GroupSearchResult>(); HashMap<String, java.util.HashMap<Guid, AdUser>> groupsAdUsersMap = new java.util.HashMap<String, java.util.HashMap<Guid, AdUser>>(); Set<String> currentGroupsForSearch = new HashSet<String>(); // Constructs a map that holds the groups that were already previously queried (for example, by // DbUserCacheManager.updateDbGroups Map<Guid, ad_groups> alreadyQueriedGroups = new HashMap<Guid, ad_groups>(); if (updatedGroups != null) { for (ad_groups adGroup : updatedGroups) { alreadyQueriedGroups.put(adGroup.getid(), adGroup); } } // Passes on all the users for (AdUser user : users) { // Passes on all known groups of a given user. for (Map.Entry<String, ad_groups> groupEntry : user.getGroups().entrySet()) { java.util.HashMap<Guid, AdUser> map; String groupName = groupEntry.getKey(); Guid groupId = groupEntry.getValue().getid(); String groupDN = groupEntry.getValue().getDistinguishedName(); // Checks the following for all groups of user // 1. If the group was already marked as candidate for population - dont mark it again , so // redundant population will not be carried out // 2. For a group that is marked as candidate for population - check if it was already populated // if so - add it to the search results list, and not to the groups to be queried if (!groupsAdUsersMap.containsKey(groupName)) { map = new java.util.HashMap<Guid, AdUser>(); groupsAdUsersMap.put(groupName, map); ad_groups alreadyUpdatedGroup = alreadyQueriedGroups.get(groupId); // If the group was already populated, transform it to LDAP query result object and add it to result // list if (alreadyUpdatedGroup != null) { results.add(new GroupSearchResult(alreadyUpdatedGroup)); } else { // the group was not already queried - make sure it will be queried. currentGroupsForSearch.add(groupDN); } } else { map = groupsAdUsersMap.get(groupName); } if (!map.containsKey(user.getUserId())) { map.put(user.getUserId(), user); } } } // Generate the LDAP query and pass the results (both the results from previous population and from // this population) to further processing GroupsDNQueryGenerator generator = new GroupsDNQueryGenerator(currentGroupsForSearch); List<LdapQueryData> partialQueries = generator.getLdapQueriesData(); for (LdapQueryData queryData : partialQueries) { List<GroupSearchResult> searchResults = performGroupQuery(loginName, password, domainName, queryData); if (searchResults != null) { // Add all LDAP results to the results list - it now contains objects retreived from ldap, and objects // that // were previously queried. results.addAll(searchResults); } } for (GroupSearchResult groupSearchResult : results) { ProceedGroupsSearchResult(groupSearchResult, groupsAdUsersMap, currentGroupsForSearch); } } /** * Performs a query on a group by using its DN as baseDN to perform an object-scope search (in order to optimize the * search * * @param loginName * login of AD user to perform the query with * @param password * password of AD user to perform the query with * @param domainName * domain of LDAP server to perform the query against * @param ldapSecurityAuth * security authentication type (either SIMPLE or GSSAPI - in case of SIMPLE no optimization occurs) * @param queryInfo * object that contain query information (query filter + base DN) * @return list of results */ public static List<GroupSearchResult> performGroupQuery(String loginName, String password, String domainName, LdapQueryData queryData) { LdapCredentials ldapCredentials = new LdapCredentials(LdapBrokerUtils.modifyLoginNameForKerberos(loginName, domainName), password); DirectorySearcher directorySearcher = new DirectorySearcher(ldapCredentials); try { List<GroupSearchResult> searchResults = directorySearcher.FindAll(queryData); return searchResults; } catch (DomainNotConfiguredException ex) { log.errorFormat("User {0} from domain {1} is a member of a group from {2} which is not configured. Please use the manage domains utility if you wish to add this domain.", loginName, domainName, queryData.getDomain()); return null; } } /** * Add Group reference to User * * @param user * @param groupName */ private static void AddGroupToUser(AdUser user, String groupName) { if (!user.getGroups().containsKey(groupName)) { ad_groups group = DbFacade.getInstance().getAdGroupDAO().getByName(groupName); if (group != null) { user.getGroups().put(groupName, group); } else { user.getGroups().put(groupName, new ad_groups()); } } } /** * Update all groups from single search result * * @param searchResult */ private static void ProceedGroupsSearchResult(GroupSearchResult searchResult, Map<String, java.util.HashMap<Guid, AdUser>> _groupsAdUsersMap, Set<String> currentGroupsForSearch) { List<String> memberOf = searchResult.getMemberOf(); String groupName = searchResult.getDistinguishedName(); groupName = generateGroupDisplayValue(groupName); java.util.HashMap<Guid, AdUser> groupUsers = _groupsAdUsersMap.get(groupName); if (memberOf == null) { return; } // The group may be a member of other groups - check for all the groups it is member in (all parent groups) for (String groupVal : memberOf) { String parentGroupName = generateGroupDisplayValue(groupVal); if (!_groupsAdUsersMap.containsKey(parentGroupName)) { currentGroupsForSearch.add(parentGroupName); java.util.HashMap<Guid, AdUser> map = new java.util.HashMap<Guid, AdUser>(); if (groupUsers != null) { for (AdUser user : groupUsers.values()) { map.put(user.getUserId(), user); AddGroupToUser(user, parentGroupName); } } _groupsAdUsersMap.put(parentGroupName, map); } else { java.util.HashMap<Guid, AdUser> parentGroupUser = _groupsAdUsersMap.get(parentGroupName); if (parentGroupUser != null && groupUsers != null) { for (Guid userId : groupUsers.keySet()) { if (!parentGroupUser.containsKey(userId)) { parentGroupUser.put(userId, groupUsers.get(userId)); AddGroupToUser(groupUsers.get(userId), parentGroupName); } } } } } } public static void performGroupPopulationForUsers(ArrayList<AdUser> adUsers, String domain, List<ad_groups> updatedGroups) { Domain domainObject = UsersDomainsCacheManagerService.getInstance().getDomain(domain.toLowerCase()); String user = domainObject.getUserName(); String password = domainObject.getPassword(); performGroupPopulationForUsers(adUsers, user, password, domain, updatedGroups); } public static String hadleNameEscaping(String name) { return StringUtils.countMatches(name, "\\") == 1 ? name.replace("\\", "\\\\\\") : name; } public static String modifyLoginNameForKerberos(String loginName, String domain) { String[] parts = loginName.split("[@]"); Domain requestedDomain = UsersDomainsCacheManagerService.getInstance().getDomain(domain); if (requestedDomain == null) { throw new DomainNotConfiguredException(domain); } LDAPSecurityAuthentication securityAuthentication = requestedDomain.getLdapSecurityAuthentication(); boolean isKerberosAuth = securityAuthentication.equals(LDAPSecurityAuthentication.GSSAPI); // if loginName is not in format of user@domain if (parts.length != 2) { // when Kerberos is the auth mechanism we must use UPN to otherwise // the default REALM, as confugured in krb5.conf will be picked return isKerberosAuth ? loginName + "@" + domain.toUpperCase() : loginName; } // In case the login name is in format of user@domain, it should be // transformed to user@realm - realm is a capitalized version of fully // qualified domain name StringBuilder result = new StringBuilder(); result.append(parts[0]); if (isKerberosAuth) { String realm = parts[1].toUpperCase(); result.append("@").append(realm); } return result.toString(); } public static String getGroupDomain(String ldapname) { if (ldapname == null) { return ""; } LdapName name; try { name = new LdapName(ldapname); } catch (InvalidNameException e) { // fail to generate a nice display value. Retuning the String we got. return ldapname; } StringBuilder sb = new StringBuilder(); List<Rdn> rdns = name.getRdns(); for (Rdn rdn : rdns) { String type = rdn.getType(); String val = (String) rdn.getValue(); if (type.equalsIgnoreCase("dc")) { sb.insert(0, "." + val); continue; } } // remove the first "." character. sb.delete(0, 1); return sb.toString(); } public static String getGuidFromNsUniqueId(String nsUniqueId) { // 12345678-12345678-12345678-12345678 --> // 12345678-1234-5678-1234-567812345678 StringBuilder sb = new StringBuilder(); sb.append(nsUniqueId.substring(0, 13)) .append("-") .append(nsUniqueId.substring(13, 22)) .append("-") .append(nsUniqueId.substring(22, 26)) .append(nsUniqueId.substring(27, 35)); return sb.toString(); } }