package com.constellio.model.conf.ldap.services; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.LdapContext; import javax.naming.ldap.PagedResultsControl; import javax.naming.ldap.PagedResultsResponseControl; import com.constellio.model.conf.ldap.user.*; import com.google.common.base.Joiner; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Predicate; import org.apache.commons.collections.Transformer; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.model.conf.ldap.Filter; import com.constellio.model.conf.ldap.LDAPDirectoryType; import com.constellio.model.conf.ldap.config.LDAPServerConfiguration; import com.constellio.model.conf.ldap.config.LDAPUserSyncConfiguration; import com.constellio.model.conf.ldap.services.LDAPServicesException.CouldNotConnectUserToLDAP; import com.constellio.model.services.users.sync.LDAPFastBind; public class LDAPServicesImpl implements LDAPServices { Logger LOGGER = LoggerFactory.getLogger(LDAPServicesImpl.class); public LDAPServicesImpl() { } public Set<LDAPGroup> getAllGroups(LdapContext ctx, List<String> baseContextList) { Set<LDAPGroup> returnList = new HashSet<>(); if (baseContextList == null || baseContextList.isEmpty()) { return returnList; } else { returnList = new HashSet<>(); for (String baseContext : baseContextList) { Collection<? extends LDAPGroup> currentfetchedGroups; try { currentfetchedGroups = searchGroupsFromContext(ctx, baseContext); } catch (NamingException e) { throw new RuntimeException(e); } returnList.addAll(currentfetchedGroups); } } return returnList; } public Set<LDAPGroup> getGroupsUsingFilter(LdapContext ctx, List<String> baseContextList, final Filter filter) { Set<LDAPGroup> groups = getAllGroups(ctx, baseContextList); CollectionUtils.filter(groups, new Predicate() { @Override public boolean evaluate(Object object) { LDAPGroup ldapGroup = (LDAPGroup) object; return filter.isAccepted(ldapGroup.getSimpleName()); } }); return groups; } private List<LDAPGroup> searchGroupsFromContext(LdapContext ctx, String groupsContainer) throws NamingException { List<LDAPGroup> groups = new ArrayList<>(); try { int pageSize = 100; byte[] cookie = null; ctx.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.NONCRITICAL) }); do { //Query SearchControls searchCtls = new SearchControls(); searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); searchCtls.setReturningAttributes(LDAPGroup.FETCHED_ATTRIBUTES); NamingEnumeration results = ctx.search(groupsContainer, "(objectclass=group)", searchCtls); /* for each entry print out name + all attrs and values */ while (results != null && results.hasMore()) { SearchResult entry = (SearchResult) results.next(); LDAPGroup group = buildLDAPGroup(entry); groups.add(group); } // Examine the paged results control response Control[] controls = ctx.getResponseControls(); if (controls != null) { for (int i = 0; i < controls.length; i++) { if (controls[i] instanceof PagedResultsResponseControl) { PagedResultsResponseControl prrc = (PagedResultsResponseControl) controls[i]; cookie = prrc.getCookie(); } } } else { LOGGER.info("No controls were sent from the server"); } // Re-activate paged results ctx.setRequestControls(new Control[] { new PagedResultsControl(pageSize, cookie, Control.CRITICAL) }); } while (cookie != null); } catch (Exception e) { LOGGER.warn("PagedSearch failed.", e); } return groups; } private String buildUserSearchFilter(LDAPDirectoryType directoryType, List<String> userFilterGroups) { final StringBuilder filter = new StringBuilder(); // Construct conjunction with "objectclass=person" and disjunction groups filter filter.append("(").append("&").append("(objectclass=person)"); // Construct disjunction groups filter if (userFilterGroups != null && !userFilterGroups.isEmpty()) { final String userMembershipAttribute = LDAPDirectoryType.ACTIVE_DIRECTORY.equals(directoryType) ? ADUserBuilder.MEMBER_OF : EdirectoryUserBuilder.MEMBER_OF; filter.append("(").append("|").append( Joiner.on("").join( CollectionUtils.collect( userFilterGroups, new Transformer() { @Override public Object transform(Object groupDn) { return new StringBuilder("(").append(userMembershipAttribute).append("=").append(groupDn).append(")"); } } ) ) ).append(")"); } filter.append(")"); return filter.toString(); } public List<String> searchUsersIdsFromContext(LDAPDirectoryType directoryType, LdapContext ctx, String usersContainer, List<String> userFilterGroups) throws NamingException { List<String> usersIds = new ArrayList<>(); SearchControls ctls = new SearchControls(); String userIdAttributeName = LDAPUserBuilderFactory.getUserBuilder(directoryType).getUserIdAttribute(); ctls.setReturningAttributes(new String[] { userIdAttributeName }); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); String searchFilter = buildUserSearchFilter(directoryType, userFilterGroups); String[] returnAttributes = { "cn" }; ///////////////////////////// try { int pageSize = 100; byte[] cookie = null; ctx.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.NONCRITICAL) }); do { //Query SearchControls searchCtls = new SearchControls(); searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); searchCtls.setReturningAttributes(returnAttributes); NamingEnumeration results = ctx.search(usersContainer, searchFilter, searchCtls); /* for each entry print out name + all attrs and values */ while (results != null && results.hasMore()) { SearchResult entry = (SearchResult) results.next(); String currentUserId = entry.getNameInNamespace(); if (StringUtils.isNotBlank(currentUserId)) { usersIds.add(currentUserId); } } // Examine the paged results control response Control[] controls = ctx.getResponseControls(); if (controls != null) { for (int i = 0; i < controls.length; i++) { if (controls[i] instanceof PagedResultsResponseControl) { PagedResultsResponseControl prrc = (PagedResultsResponseControl) controls[i]; cookie = prrc.getCookie(); } } } else { LOGGER.warn("No controls were sent from the server"); } // Re-activate paged results ctx.setRequestControls(new Control[] { new PagedResultsControl(pageSize, cookie, Control.CRITICAL) }); } while (cookie != null); } catch (Exception e) { LOGGER.error("PagedSearch failed.", e); } Collections.sort(usersIds); return usersIds; //////////////////////////////////// /*NamingEnumeration<?> answer = ctx.search(usersContainer, "(objectclass=person)", ctls); while (answer.hasMore()) { SearchResult rslt = (SearchResult) answer.next(); // Attributes attrs = rslt.getAttributes(); // Attribute currentUserIdAttribute = attrs.get(userIdAttributeName); // String currentUserId = getFirstString(currentUserIdAttribute); String currentUserId = rslt.getNameInNamespace(); if (StringUtils.isNotBlank(currentUserId)) { usersIds.add(currentUserId); } } return usersIds;*/ } private LDAPGroup buildLDAPGroup(SearchResult entry) throws NamingException { Attributes attrs = entry.getAttributes(); Attribute groupNameAttribute = attrs.get(LDAPGroup.COMMON_NAME); Attribute groupDNameAttribute = attrs.get(LDAPGroup.DISTINGUISHED_NAME); String groupName; if (groupNameAttribute != null && groupNameAttribute.size() > 0) { groupName = (String) groupNameAttribute.get(0); } else { groupName = entry.getNameInNamespace(); } String distinguishedName; if (groupDNameAttribute != null && groupDNameAttribute.size() > 0) { distinguishedName = (String) groupDNameAttribute.get(0); } else { distinguishedName = entry.getNameInNamespace(); } LDAPGroup returnGroup = new LDAPGroup(groupName, distinguishedName); //String groupName = (String) groupNameAttribute.get(0); //TODO parent Attribute members = attrs.get(LDAPGroup.MEMBER); if (members != null) { for (int i = 0; i < members.size(); i++) { String userId = (String) members.get(i); returnGroup.addUser(userId); } } return returnGroup; } public LdapContext connectToLDAP(List<String> domains, String url, String user, String password, Boolean followReferences, boolean activeDirectory) { LDAPFastBind ldapFastBind = new LDAPFastBind(url, followReferences, activeDirectory); boolean authenticated = ldapFastBind.authenticate(user, password); if (!authenticated) { for (String domain : domains) { String username = user + "@" + domain; authenticated = ldapFastBind.authenticate(username, password); if (authenticated) { break; } } } if (!authenticated) { throw new LDAPConnectionFailure(domains.toArray(), url, user); } return ldapFastBind.ctx; } public LdapContext connectToLDAP(List<String> domains, List<String> urls, String user, String password, Boolean followReferences, boolean activeDirectory) { for (String url : urls) { LdapContext ctx; try { ctx = connectToLDAP(domains, url, user, password, followReferences, activeDirectory); if (ctx != null) { return ctx; } } catch (RuntimeException e) { LOGGER.warn("Connection to LDAP domain failed", e); } } return null; } public LDAPUser getUser(LDAPDirectoryType directoryType, String userId, LdapContext ctx) { Attributes attrs; try { LDAPUserBuilder userBuilder = LDAPUserBuilderFactory.getUserBuilder(directoryType); String[] fetchedAttributes = userBuilder.getFetchedAttributes(); attrs = ctx.getAttributes(userId, fetchedAttributes); LDAPUser user = userBuilder.buildUser(userId, attrs); return user; } catch (NamingException e) { throw new RuntimeException(e); } } public LDAPUser getUser(LDAPDirectoryType directoryType, String username, LdapContext ctx, List<String> searchBases) { // TODO: Verify the behaviour of this method String searchFilter = "(&(objectClass=user)(sAMAccountName=" + username + "))"; SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); for (String searchBase : searchBases) { try { NamingEnumeration<SearchResult> results = ctx.search(searchBase, searchFilter, searchControls); if (results.hasMoreElements()) { SearchResult searchResult = results.nextElement(); LDAPUserBuilder userBuilder = LDAPUserBuilderFactory.getUserBuilder(directoryType); Attributes attributes = searchResult.getAttributes(); return userBuilder.buildUser(attributes.get("objectSid").get().toString(), attributes); } } catch (NamingException e) { // Try next search base } } return null; } public String extractUsername(String userId) { return StringUtils.substringBetween(userId, "=", ","); } public boolean isUser(LDAPDirectoryType directoryType, String groupMemberId, LdapContext ctx) { if (directoryType == LDAPDirectoryType.E_DIRECTORY) { //FIXME return true; } try { LDAPUserBuilder userBuilder = LDAPUserBuilderFactory.getUserBuilder(directoryType); if (groupMemberId.contains("\\")) { groupMemberId = StringUtils.replace(groupMemberId, "\\", "\\\\"); } String searchFilter = "(&(objectClass=person)(" + userBuilder.getUserIdAttribute() + "=" + groupMemberId + "))"; SearchControls searchControls = new SearchControls(); searchControls.setReturningAttributes(new String[] {}); // specify the search scope searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); NamingEnumeration<SearchResult> found = ctx.search(groupMemberId, searchFilter, searchControls); return found != null && found.hasMoreElements(); } catch (NamingException e) { throw new RuntimeException(e); } } public Set<String> getUsersUsingFilter(LDAPDirectoryType directoryType, LdapContext ctx, List<String> usersWithoutGroupsBaseContextList, final Filter filter, final List<String> userFilterGroupsList) { Set<String> users = new HashSet<>(); for (String currentContext : usersWithoutGroupsBaseContextList) { try { users.addAll(searchUsersIdsFromContext(directoryType, ctx, currentContext, userFilterGroupsList)); } catch (NamingException e) { LOGGER.warn("NamingException when fetchingUsers", e); } } CollectionUtils.filter(users, new Predicate() { @Override public boolean evaluate(Object object) { String user = (String) object; return filter.isAccepted(user); } }); return users; } public String dnForUser(LdapContext dirContext, String username, List<String> baseSearch) { if (baseSearch == null || baseSearch.isEmpty()) { return null; } try { String[] returnAttribute = { "dn" }; SearchControls srchControls = new SearchControls(); srchControls.setReturningAttributes(returnAttribute); srchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); String searchFilter = "(&(objectClass=user)(sAMAccountName=" + username + "))"; //ceci ne fonctionne pas toujours ex : LDAPServicesAcceptanceTest#whenDnForUserThenOk //String searchFilter = "(&(objectClass=inetOrgPerson)(|(uid=" + cnORuid + ")(cn=" + cnORuid + ")))"; //FIXME search in all baseSearch elements NamingEnumeration<SearchResult> srchResponse = dirContext.search(baseSearch.get(0), searchFilter, srchControls); if (srchResponse.hasMore()) { return srchResponse.next().getNameInNamespace(); } } catch (NamingException namEx) { namEx.printStackTrace(); } return null; } public String dnForEdirectoryUser(LdapContext dirContext, String searchBase, String cnORuid) { try { String[] returnAttribute = { "dn" }; SearchControls srchControls = new SearchControls(); srchControls.setReturningAttributes(returnAttribute); srchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); String searchFilter = "(&(objectClass=inetOrgPerson)(|(uid=" + cnORuid + ")(cn=" + cnORuid + ")))"; NamingEnumeration<SearchResult> srchResponse = dirContext.search(searchBase, searchFilter, srchControls); if (srchResponse.hasMore()) { return srchResponse.next().getNameInNamespace(); } } catch (NamingException namEx) { namEx.printStackTrace(); } return null; } @Override public void authenticateUser(LDAPServerConfiguration ldapServerConfiguration, String user, String password) throws CouldNotConnectUserToLDAP { if(StringUtils.isBlank(password)){ LOGGER.warn("Invalid blank password"); throw new CouldNotConnectUserToLDAP(); } boolean activeDirectory = ldapServerConfiguration.getDirectoryType().equals(LDAPDirectoryType.ACTIVE_DIRECTORY); LdapContext ctx = connectToLDAP(ldapServerConfiguration.getDomains(), ldapServerConfiguration.getUrls(), user, password, ldapServerConfiguration.getFollowReferences(), activeDirectory); if (ctx == null) { throw new CouldNotConnectUserToLDAP(); } } @Override public List<String> getTestSynchronisationGroups(LDAPServerConfiguration ldapServerConfiguration, LDAPUserSyncConfiguration ldapUserSyncConfiguration) { Set<String> returnGroups = new HashSet<>(); boolean activeDirectory = ldapServerConfiguration.getDirectoryType().equals(LDAPDirectoryType.ACTIVE_DIRECTORY); List<String> urls = ldapServerConfiguration.getUrls(); for(String url: urls) { LdapContext ctx = connectToLDAP(ldapServerConfiguration.getDomains(), url, ldapUserSyncConfiguration.getUser(), ldapUserSyncConfiguration.getPassword(), ldapServerConfiguration.getFollowReferences(), activeDirectory); if (ctx != null) { Set<LDAPGroup> groups = getGroupsUsingFilter(ctx, ldapUserSyncConfiguration.getGroupBaseContextList(), ldapUserSyncConfiguration.getGroupFilter()); for (LDAPGroup group : groups) { returnGroups.add(group.getSimpleName()); } } } return new ArrayList<>(returnGroups); } @Override public LDAPUsersAndGroups importUsersAndGroups(LDAPServerConfiguration serverConfiguration, LDAPUserSyncConfiguration userSyncConfiguration, String url) { boolean activeDirectory = serverConfiguration.getDirectoryType().equals(LDAPDirectoryType.ACTIVE_DIRECTORY); LdapContext ldapContext = connectToLDAP(serverConfiguration.getDomains(), url, userSyncConfiguration.getUser(), userSyncConfiguration.getPassword(), serverConfiguration.getFollowReferences(), activeDirectory); // final Set<LDAPUser> ldapUsers = new HashSet<>(); final Set<LDAPGroup> ldapGroups = new HashSet<>(); // Get accepted groups list using groups search base and groups regex search filter final Set<LDAPGroup> acceptedGroups = getGroupsUsingFilter(ldapContext, userSyncConfiguration.getGroupBaseContextList(), userSyncConfiguration.getGroupFilter()); ldapGroups.addAll(acceptedGroups); // Get accepted users list using users search base, user groups filter and users regex search filter final List<LDAPUser> acceptedUsers = getAcceptedUsersNotLinkedToGroups(ldapContext, serverConfiguration, userSyncConfiguration); ldapUsers.addAll(acceptedUsers); // Add groups of accepted users to accepted groups list Set<LDAPGroup> groupsFromUsers = getGroupsFromUser(acceptedUsers); ldapGroups.addAll(groupsFromUsers); // if (userSyncConfiguration.isMembershipAutomaticDerivationActivated()) { // final Set<LDAPUser> acceptedUsersDerivedFromAcceptedGroups = getAcceptedUsersFromGroups(acceptedGroups, ldapContext, serverConfiguration, userSyncConfiguration); ldapUsers.addAll(acceptedUsersDerivedFromAcceptedGroups); // Add groups of derived accepted users to accepted groups list ldapGroups.addAll(getGroupsFromUser(acceptedUsersDerivedFromAcceptedGroups)); } // try { ldapContext.close(); } catch (NamingException e) { e.printStackTrace(); } return new LDAPUsersAndGroups(ldapUsers, ldapGroups); } @Override public List<String> getTestSynchronisationUsersNames(LDAPServerConfiguration ldapServerConfiguration, LDAPUserSyncConfiguration ldapUserSyncConfiguration) { Set<String> returnUsers = new HashSet<>(); boolean activeDirectory = ldapServerConfiguration.getDirectoryType().equals(LDAPDirectoryType.ACTIVE_DIRECTORY); List<String> urls = ldapServerConfiguration.getUrls(); for(String url: urls) { LdapContext ctx = connectToLDAP(ldapServerConfiguration.getDomains(), url, ldapUserSyncConfiguration.getUser(), ldapUserSyncConfiguration.getPassword(), ldapServerConfiguration.getFollowReferences(), activeDirectory); if (ctx != null) { Set<String> users = getUsersUsingFilter(ldapServerConfiguration.getDirectoryType(), ctx, ldapUserSyncConfiguration.getUsersWithoutGroupsBaseContextList(), ldapUserSyncConfiguration.getUserFilter(), ldapUserSyncConfiguration.getUserGroups()); returnUsers.addAll(users); } } return new ArrayList<>(returnUsers); } private Set<LDAPGroup> getAcceptedGroups(Set<LDAPGroup> ldapGroups, LDAPUserSyncConfiguration userSyncConfiguration) { Set<LDAPGroup> returnList = new HashSet<>(); for (LDAPGroup ldapGroup : ldapGroups) { String groupName = ldapGroup.getSimpleName(); if (userSyncConfiguration.isGroupAccepted(groupName)) { if (!ldapGroup.getMembers().isEmpty()) { returnList.add(ldapGroup); } } } return returnList; } public Set<LDAPUser> getAcceptedUsersFromGroups(Set<LDAPGroup> ldapGroups, LdapContext ldapContext, LDAPServerConfiguration serverConfiguration, LDAPUserSyncConfiguration userSyncConfiguration) { Set<LDAPUser> returnUsers = new HashSet<>(); Set<String> groupsMembersIds = new HashSet<>(); LDAPServicesImpl ldapServices = new LDAPServicesImpl(); for (LDAPGroup group : ldapGroups) { List<String> usersToAdd = group.getMembers(); groupsMembersIds.addAll(usersToAdd); } LDAPDirectoryType directoryType = serverConfiguration.getDirectoryType(); for (String memberId : groupsMembersIds) { if (ldapServices.isUser(directoryType, memberId, ldapContext)) { LDAPUser ldapUser = ldapServices.getUser(directoryType, memberId, ldapContext); String userName = ldapUser.getName(); if (userSyncConfiguration.isUserAccepted(userName)) { returnUsers.add(ldapUser); } removeNonAcceptedGroups(ldapUser, userSyncConfiguration); } } return returnUsers; } private List<LDAPUser> getAcceptedUsersNotLinkedToGroups(LdapContext ldapContext, LDAPServerConfiguration serverConfiguration, LDAPUserSyncConfiguration userSyncConfiguration) { List<LDAPUser> returnUsers = new ArrayList<>(); if (userSyncConfiguration.getUsersWithoutGroupsBaseContextList() == null || userSyncConfiguration .getUsersWithoutGroupsBaseContextList().isEmpty()) { return returnUsers; } Set<String> usersIds = new HashSet<>(); for (String baseContextName : userSyncConfiguration.getUsersWithoutGroupsBaseContextList()) { List<String> currentUsersIds; try { currentUsersIds = searchUsersIdsFromContext(serverConfiguration.getDirectoryType(), ldapContext, baseContextName, userSyncConfiguration.getUserFilterGroupsList()); } catch (NamingException e) { throw new RuntimeException(e); } usersIds.addAll(currentUsersIds); } //Accepted users: for (String userId : usersIds) { String userName = extractUsername(userId); if (userSyncConfiguration.isUserAccepted(userName)) { LDAPUser ldapUser = getUser(serverConfiguration.getDirectoryType(), userId, ldapContext); removeNonAcceptedGroups(ldapUser, userSyncConfiguration); returnUsers.add(ldapUser); } } return returnUsers; } private void removeNonAcceptedGroups(LDAPUser ldapUser, final LDAPUserSyncConfiguration userSyncConfiguration) { CollectionUtils.filter(ldapUser.getUserGroups(), new Predicate() { @Override public boolean evaluate(Object object) { LDAPGroup group = (LDAPGroup) object; return userSyncConfiguration.isGroupAccepted(group.getSimpleName()); } }); } private Set<LDAPGroup> getGroupsFromUser(Collection<LDAPUser> users) { Set<LDAPGroup> returnSet = new HashSet<>(); for (LDAPUser user : users) { returnSet.addAll(user.getUserGroups()); } return returnSet; } }