package com.constellio.model.services.users.sync; import java.util.*; import com.constellio.data.threads.*; import com.google.common.base.Joiner; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Transformer; import org.apache.commons.lang3.StringUtils; import org.joda.time.LocalTime; import org.joda.time.format.DateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.data.dao.managers.StatefulService; import com.constellio.model.conf.ldap.LDAPConfigurationManager; 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.LDAPServices; import com.constellio.model.conf.ldap.services.LDAPServices.LDAPUsersAndGroups; import com.constellio.model.conf.ldap.services.LDAPServicesFactory; import com.constellio.model.conf.ldap.user.LDAPGroup; import com.constellio.model.conf.ldap.user.LDAPUser; import com.constellio.model.entities.CorePermissions; import com.constellio.model.entities.security.global.GlobalGroup; import com.constellio.model.entities.security.global.GlobalGroupStatus; import com.constellio.model.entities.security.global.UserCredential; import com.constellio.model.entities.security.global.UserCredentialStatus; import com.constellio.model.services.schemas.validators.EmailValidator; import com.constellio.model.services.security.authentification.LDAPAuthenticationService; import com.constellio.model.services.users.GlobalGroupsManager; import com.constellio.model.services.users.UserServices; import com.constellio.model.services.users.UserServicesRuntimeException; import com.constellio.model.services.users.UserServicesRuntimeException.UserServicesRuntimeException_NoSuchUser; import com.constellio.model.services.users.UserUtils; public class LDAPUserSyncManager implements StatefulService { private final static Logger LOGGER = LoggerFactory.getLogger(LDAPUserSyncManager.class); private final LDAPConfigurationManager ldapConfigurationManager; UserServices userServices; GlobalGroupsManager globalGroupsManager; LDAPUserSyncConfiguration userSyncConfiguration; LDAPServerConfiguration serverConfiguration; BackgroundThreadsManager backgroundThreadsManager; boolean processingSynchronizationOfUsers = false; private ConstellioJobManager constellioJobManager; public LDAPUserSyncManager(UserServices userServices, GlobalGroupsManager globalGroupsManager, LDAPConfigurationManager ldapConfigurationManager, final ConstellioJobManager constellioJobManager) { this.userServices = userServices; this.globalGroupsManager = globalGroupsManager; this.ldapConfigurationManager = ldapConfigurationManager; this.constellioJobManager = constellioJobManager; } @Override public void initialize() { this.serverConfiguration = ldapConfigurationManager.getLDAPServerConfiguration(); this.userSyncConfiguration = ldapConfigurationManager.getLDAPUserSyncConfiguration(false); if (!(userSyncConfiguration == null || (userSyncConfiguration.getDurationBetweenExecution() == null && userSyncConfiguration.getScheduleTime() == null))) { configureAndScheduleJob(); } } public void reloadLDAPUserSynchConfiguration() { this.userSyncConfiguration = ldapConfigurationManager.getLDAPUserSyncConfiguration(false); this.serverConfiguration = ldapConfigurationManager.getLDAPServerConfiguration(); } @Override public void close() { } private void configureAndScheduleJob() { // LDAPUserSyncManagerJob.action = new Runnable() { @Override public void run() { synchronizeIfPossible(new LDAPSynchProgressionInfo()); } }; // LDAPUserSyncManagerJob.intervals = new HashSet<Integer>(); if (userSyncConfiguration.getDurationBetweenExecution() != null) { LDAPUserSyncManagerJob.intervals.add( new Long(userSyncConfiguration.getDurationBetweenExecution().getStandardSeconds()).intValue() ); } // if (userSyncConfiguration.getScheduleTime() != null) { // final List<LocalTime> scheduleTimeList = new ArrayList( CollectionUtils.collect( userSyncConfiguration.getScheduleTime(), new Transformer() { @Override public Object transform(Object input) { return DateTimeFormat.forPattern(LDAPUserSyncConfiguration.TIME_PATTERN).parseLocalTime((String) input); } } ) ); final Map<Integer, List<Integer>> minuteHoursMap = new HashMap<>(); for (final LocalTime time : scheduleTimeList) { if (!minuteHoursMap.containsKey(time.getMinuteOfHour())) { minuteHoursMap.put(time.getMinuteOfHour(), new ArrayList<Integer>()); } minuteHoursMap.get(time.getMinuteOfHour()).add(time.getHourOfDay()); } LDAPUserSyncManagerJob.cronExpressions = new HashSet<>(minuteHoursMap.entrySet().size()); for (final Map.Entry<Integer, List<Integer>> minuteHours : minuteHoursMap.entrySet()) { LDAPUserSyncManagerJob.cronExpressions.add(String.format("0 %d %s ? * *", minuteHours.getKey(), Joiner.on(",").join(minuteHours.getValue()))); } } // ldapConfigurationManager.setNextUsersSyncFireTime(constellioJobManager.addJob(new LDAPUserSyncManagerJob(), true)); } public synchronized void synchronizeIfPossible(){ synchronizeIfPossible(null); } public synchronized void synchronizeIfPossible(LDAPSynchProgressionInfo ldapSynchProgressionInfo) { if (!processingSynchronizationOfUsers) { processingSynchronizationOfUsers = true; try { synchronize(ldapSynchProgressionInfo); } finally { processingSynchronizationOfUsers = false; } } } private synchronized void synchronize(LDAPSynchProgressionInfo ldapSynchProgressionInfo) { this.userSyncConfiguration = ldapConfigurationManager.getLDAPUserSyncConfiguration(true); this.serverConfiguration = ldapConfigurationManager.getLDAPServerConfiguration(); List<String> usersIdsBeforeSynchronisation = getAllUsersNames(); List<String> groupsIdsBeforeSynchronisation = getGroupsIds(); List<String> usersIdsAfterSynchronisation = new ArrayList<>(); List<String> groupsIdsAfterSynchronisation = new ArrayList<>(); LDAPServices ldapServices = LDAPServicesFactory.newLDAPServices(serverConfiguration.getDirectoryType()); List<String> selectedCollectionsCodes = userSyncConfiguration.getSelectedCollectionsCodes(); //FIXME cas rare mais possible nom d utilisateur/de groupe non unique (se trouvant dans des urls differentes) for (String url : getNonEmptyUrls(serverConfiguration)) { LDAPUsersAndGroups importedUsersAndgroups = ldapServices .importUsersAndGroups(serverConfiguration, userSyncConfiguration, url); if (ldapSynchProgressionInfo != null) { ldapSynchProgressionInfo.totalGroupsAndUsers = importedUsersAndgroups.getUsers().size() + importedUsersAndgroups.getGroups().size(); } Set<LDAPGroup> ldapGroups = importedUsersAndgroups.getGroups(); Set<LDAPUser> ldapUsers = importedUsersAndgroups.getUsers(); UpdatedUsersAndGroups updatedUsersAndGroups = updateUsersAndGroups(ldapUsers, ldapGroups, selectedCollectionsCodes, ldapSynchProgressionInfo); usersIdsAfterSynchronisation.addAll(updatedUsersAndGroups.getUsersNames()); groupsIdsAfterSynchronisation.addAll(updatedUsersAndGroups.getGroupsCodes()); } //remove inexistingUsers List<String> removedUsersIds = (List<String>) CollectionUtils .subtract(usersIdsBeforeSynchronisation, usersIdsAfterSynchronisation); removeUsersExceptAdmins(removedUsersIds); //remove inexistingGroups List<String> removedGroupsIds = (List<String>) CollectionUtils .subtract(groupsIdsBeforeSynchronisation, groupsIdsAfterSynchronisation); removeGroups(removedGroupsIds); } private List<String> getNonEmptyUrls(LDAPServerConfiguration serverConfiguration) { if (serverConfiguration.getDirectoryType() == LDAPDirectoryType.AZURE_AD) { return Arrays.asList(new String[]{null}); } else { return serverConfiguration.getUrls(); } } private UpdatedUsersAndGroups updateUsersAndGroups(Set<LDAPUser> ldapUsers, Set<LDAPGroup> ldapGroups, List<String> selectedCollectionsCodes, LDAPSynchProgressionInfo ldapSynchProgressionInfo) { UpdatedUsersAndGroups updatedUsersAndGroups = new UpdatedUsersAndGroups(); for (LDAPGroup ldapGroup : ldapGroups) { GlobalGroup group = createGlobalGroupFromLdapGroup(ldapGroup, selectedCollectionsCodes); try { userServices.addUpdateGlobalGroup(group); updatedUsersAndGroups.addGroupCode(group.getCode()); } catch (Throwable e) { LOGGER.error("Group ignored due to error when trying to add it " + group.getCode(), e); } if (ldapSynchProgressionInfo != null) { ldapSynchProgressionInfo.processedGroupsAndUsers++; } } for (LDAPUser ldapUser : ldapUsers) { if (!ldapUser.getName().toLowerCase().equals("admin")) { UserCredential userCredential = createUserCredentialsFromLdapUser(ldapUser, selectedCollectionsCodes); if (userCredential.getUsername() == null) { LOGGER.error("Invalid user ignored (missing username). Id: " + ldapUser.getId() + ", Username : " + userCredential.getUsername()); } else { try { // Keep locally created groups of existing users final List<String> newUserGlobalGroups = new ArrayList<>(userCredential.getGlobalGroups()); final UserCredential previousUserCredential = userServices.getUserCredential(userCredential.getUsername()); if (previousUserCredential != null) { for (final String userGlobalGroup : previousUserCredential.getGlobalGroups()) { final GlobalGroup previousGlobalGroup = globalGroupsManager.getGlobalGroupWithCode(userGlobalGroup); if (previousGlobalGroup != null && previousGlobalGroup.isLocallyCreated()) { newUserGlobalGroups.add(previousGlobalGroup.getCode()); } } } userCredential.withGlobalGroups(newUserGlobalGroups); userServices.addUpdateUserCredential(userCredential); updatedUsersAndGroups.addUsername(UserUtils.cleanUsername(ldapUser.getName())); } catch (Throwable e) { LOGGER.error("User ignored due to error when trying to add it " + userCredential.getUsername(), e); } } } if (ldapSynchProgressionInfo != null) { ldapSynchProgressionInfo.processedGroupsAndUsers++; } } return updatedUsersAndGroups; } private GlobalGroup createGlobalGroupFromLdapGroup(LDAPGroup ldapGroup, List<String> selectedCollectionsCodes) { String code = ldapGroup.getDistinguishedName(); String name = ldapGroup.getSimpleName(); Set<String> usersAutomaticallyAddedToCollections; try { GlobalGroup group = userServices.getGroup(code); usersAutomaticallyAddedToCollections = new HashSet<>(group.getUsersAutomaticallyAddedToCollections()); usersAutomaticallyAddedToCollections.addAll(selectedCollectionsCodes); } catch (UserServicesRuntimeException.UserServicesRuntimeException_NoSuchGroup e) { usersAutomaticallyAddedToCollections = new HashSet<>(); } return userServices.createGlobalGroup(code, name, new ArrayList<>(usersAutomaticallyAddedToCollections), null, GlobalGroupStatus.ACTIVE, false); } private UserCredential createUserCredentialsFromLdapUser(LDAPUser ldapUser, List<String> selectedCollectionsCodes) { String username = ldapUser.getName(); String firstName = notNull(ldapUser.getGivenName()); String lastName = notNull(ldapUser.getFamilyName()); String email = notNull(validateEmail(ldapUser.getEmail())); List<String> globalGroups = new ArrayList<>(); for (LDAPGroup ldapGroup : ldapUser.getUserGroups()) { String groupSimpleName = ldapGroup.getSimpleName(); if (userSyncConfiguration.isGroupAccepted(groupSimpleName)) { globalGroups.add(ldapGroup.getDistinguishedName()); } } List<String> msExchDelegateListBL = new ArrayList<>(); if (ldapUser.getMsExchDelegateListBL() != null) { msExchDelegateListBL.addAll(ldapUser.getMsExchDelegateListBL()); } Set<String> collections; try { UserCredential tmpUser = userServices.getUser(username); collections = new HashSet<>(tmpUser.getCollections()); collections.addAll(selectedCollectionsCodes); } catch (UserServicesRuntimeException.UserServicesRuntimeException_NoSuchUser e) { collections = new HashSet<>(selectedCollectionsCodes); } UserCredentialStatus userStatus; if (ldapUser.getEnabled()) { userStatus = UserCredentialStatus.ACTIVE; } else { userStatus = UserCredentialStatus.DELETED; } UserCredential returnUserCredentials = userServices.createUserCredential( username, firstName, lastName, email, globalGroups, new ArrayList<>(collections), userStatus, "", msExchDelegateListBL, ldapUser.getId()).withDN(ldapUser.getId()); try { UserCredential currentUserCredential = userServices.getUser(username); if (currentUserCredential.isSystemAdmin()) { returnUserCredentials = returnUserCredentials.withSystemAdminPermission(); } returnUserCredentials = returnUserCredentials.withAccessTokens(currentUserCredential.getAccessTokens()); } catch (UserServicesRuntimeException_NoSuchUser e) { //OK } return returnUserCredentials; } private String validateEmail(String email) { if (StringUtils.isBlank(email)) { return email; } else if (!EmailValidator.isValid(email)) { LOGGER.warn("Invalid email set to null : " + email); return null; } else { return email; } } private String notNull(String value) { return (value == null) ? "" : value; } private void removeUsersExceptAdmins(List<String> removedUsersIds) { for (String userId : removedUsersIds) { if (!userId.equals(LDAPAuthenticationService.ADMIN_USERNAME)) { if (!userServices.has(userId).globalPermissionInAnyCollection(CorePermissions.MANAGE_SECURITY)) { UserCredential userCredential = userServices.getUser(userId); userServices.removeUserCredentialAndUser(userCredential); } } } } private void removeGroups(List<String> removedGroupsIds) { UserCredential admin = userServices.getUser(LDAPAuthenticationService.ADMIN_USERNAME); for (String groupId : removedGroupsIds) { GlobalGroup group = userServices.getGroup(groupId); userServices.logicallyRemoveGroupHierarchy(admin, group); } } private List<String> getGroupsIds() { List<String> groups = new ArrayList<>(); List<GlobalGroup> globalGroups = globalGroupsManager.getAllGroups(); for (GlobalGroup globalGroup : globalGroups) { if (!globalGroup.isLocallyCreated()) { groups.add(globalGroup.getCode()); } } return groups; } private List<String> getAllUsersNames() { List<String> usernames = new ArrayList<>(); List<UserCredential> userCredentials = userServices.getAllUserCredentials();//getUserCredentials(); for (UserCredential userCredential : userCredentials) { usernames.add(userCredential.getUsername()); } return usernames; } public boolean isSynchronizing() { return this.processingSynchronizationOfUsers; } private class UpdatedUsersAndGroups { private Set<String> usersNames = new HashSet<>(); private Set<String> groupsCodes = new HashSet<>(); public void addUsername(String username) { usersNames.add(username); } public Set<String> getUsersNames() { return usersNames; } public Set<String> getGroupsCodes() { return groupsCodes; } public void addGroupCode(String groupCode) { groupsCodes.add(groupCode); } } public static class LDAPSynchProgressionInfo { int totalGroupsAndUsers = 0; int processedGroupsAndUsers = 0; public int getProgressPercentage() { if (totalGroupsAndUsers == 0) { return 0; } else { return processedGroupsAndUsers / totalGroupsAndUsers; } } } public final static class LDAPUserSyncManagerJob extends ConstellioJob { private static Runnable action; private static Set<Integer> intervals; private static Set<String> cronExpressions; private static Date startTime; @Override protected String name() { return LDAPUserSyncManagerJob.class.getSimpleName(); } @Override protected Runnable action() { return action; } @Override protected boolean unscheduleOnException() { return false; } @Override protected Set<Integer> intervals() { return intervals; } @Override protected Set<String> cronExpressions() { return cronExpressions; } } }