package com.constellio.app.modules.es.connectors.ldap; import static com.constellio.app.modules.es.connectors.ldap.ConnectorLDAPDocumentType.USER; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.where; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.naming.NamingException; import javax.naming.ldap.LdapContext; import org.apache.commons.lang.StringUtils; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import com.constellio.app.modules.es.connectors.ConnectorDeleterJob; import com.constellio.app.modules.es.connectors.spi.ConnectorJob; import com.constellio.app.modules.es.connectors.spi.DefaultAbstractConnector; import com.constellio.app.modules.es.model.connectors.ConnectorDocument; import com.constellio.app.modules.es.model.connectors.ldap.ConnectorLDAPInstance; import com.constellio.app.modules.es.model.connectors.ldap.ConnectorLDAPUserDocument; import com.constellio.model.conf.ldap.LDAPDirectoryType; import com.constellio.model.conf.ldap.RegexFilter; import com.constellio.model.conf.ldap.services.LDAPConnectionFailure; import com.constellio.model.entities.records.Record; import com.constellio.model.services.search.query.logical.LogicalSearchQuery; public class ConnectorLDAP extends DefaultAbstractConnector { private static final Logger LOGGER = LogManager.getLogger(ConnectorLDAP.class); private List<String> allObjectsToFetch; private List<String> allObjectsToRemoveConstellioIds; final private ConnectorLDAPServices ldapServices; private int maxJobPerBatch; private ConnectorLDAPInstance ldapInstance; private String url; private int documentsPerJob; public ConnectorLDAP(ConnectorLDAPServices ldapServices) { this.ldapServices = ldapServices; } public ConnectorLDAP() { this.ldapServices = new ConnectorLDAPServicesImpl(); } @Override public List<ConnectorJob> getJobs() { if (isFetchStarting()) { initFetch(); } else if (isFetchEnded()) { setFetchAsStarting(); return new ArrayList<>(); } List<ConnectorJob> nextJobs = nextJobs(); if (nextJobs.isEmpty()) { setFetchAsStarting(); } return nextJobs; } private List<ConnectorJob> nextJobs() { List<ConnectorJob> returnJobs = new ArrayList<>(); int i = 0; boolean moreJobs = true; while (i < maxJobPerBatch && moreJobs) { ConnectorJob addJob = createAddJob(); if (addJob != null) { i++; returnJobs.add(addJob); } else { moreJobs = false; } } moreJobs = true; while (i < maxJobPerBatch && moreJobs) { ConnectorJob addJob = createRemoveJob(); if (addJob != null) { i++; returnJobs.add(addJob); } else { moreJobs = false; } } return returnJobs; } private void setFetchAsStarting() { this.allObjectsToFetch = null; } private boolean isFetchEnded() { if (this.allObjectsToFetch.isEmpty() && this.allObjectsToRemoveConstellioIds.isEmpty()) { return true; } else { return false; } } void initFetch() { this.allObjectsToFetch = new ArrayList<>(); maxJobPerBatch = ldapInstance.getNumberOfJobsInParallel(); documentsPerJob = ldapInstance.getDocumentsPerJobs(); if (documentsPerJob == 0) { throw new InvalidDocumentsBatchRuntimeException("At least one document per job"); } if (maxJobPerBatch == 0) { throw new InvalidJobsBatchRuntimeException("At least one job per batch"); } if (!isFetchOnlyForUsers(this.ldapInstance)) { throw new RuntimeException("The current version support fetch only for LDAP users"); } ConnectorLDAPDocumentType documentType = USER; this.url = getUrl(); LdapContext ctx = null; try { ctx = connectToLDAP(url, ldapInstance, ldapServices); Set<String> contextList = new HashSet<>(); contextList.addAll(ldapInstance.getUsersBaseContextList()); RegexFilter filter = new RegexFilter(ldapInstance.getIncludeRegex(), ldapInstance.getExcludeRegex()); ConnectorLDAPSearchResult searchResult = this.ldapServices .getAllObjectsUsingFilter(ctx, documentType.getObjectClass(), documentType.getObjectCategory(), contextList, filter); allObjectsToFetch.addAll(searchResult.getDocumentIds()); if (searchResult.isErrorDuringSearch()) { LOGGER.warn("No document fetched from ldap server, no document will be removed from constellio"); } else { allObjectsToRemoveConstellioIds = getObjectsToRemoveConstellioIds(allObjectsToFetch, ldapInstance); } ctx.close(); } catch (Exception e) { LOGGER.error(e); if (ctx != null) { try { ctx.close(); } catch (Exception e1) { LOGGER.warn("Error when closing context ", e); } } this.allObjectsToFetch = new ArrayList<>(); this.allObjectsToRemoveConstellioIds = new ArrayList<>(); } } private boolean isFetchStarting() { return this.allObjectsToFetch == null || this.allObjectsToRemoveConstellioIds == null; } private ConnectorJob createRemoveJob() { if (this.allObjectsToRemoveConstellioIds.isEmpty()) { return null; } List<String> documentToDeleteConstellioIds = this.allObjectsToRemoveConstellioIds .subList(0, Math.min(this.documentsPerJob, this.allObjectsToRemoveConstellioIds.size())); if (this.documentsPerJob < this.allObjectsToRemoveConstellioIds.size()) { this.allObjectsToRemoveConstellioIds = this.allObjectsToRemoveConstellioIds.subList(this.documentsPerJob, this.allObjectsToRemoveConstellioIds.size()); } else { this.allObjectsToRemoveConstellioIds = new ArrayList<>(); } return new ConnectorDeleterJob(this, documentToDeleteConstellioIds); } private ConnectorJob createAddJob() { if (this.allObjectsToFetch.isEmpty()) { return null; } List<String> documentsToCrawlLDAPIds = this.allObjectsToFetch .subList(0, Math.min(this.documentsPerJob, this.allObjectsToFetch.size())); if (this.documentsPerJob < this.allObjectsToFetch.size()) { this.allObjectsToFetch = this.allObjectsToFetch.subList(this.documentsPerJob, this.allObjectsToFetch.size()); } else { this.allObjectsToFetch = new ArrayList<>(); } return new ConnectorLDAPCrawlerJob(this, ldapInstance, USER, url, documentsToCrawlLDAPIds); } @Override protected void initialize(Record instance) { this.ldapInstance = getEs().wrapConnectorLDAPInstance(instance); } List<String> getObjectsToRemoveConstellioIds(List<String> allObjectsToFetch, ConnectorLDAPInstance connectorInstance) { List<String> returnSet = new ArrayList<>(); List<ConnectorDocument<?>> allConnectorInstanceDocument = es .searchConnectorDocuments( new LogicalSearchQuery( where(es.connectorLdapUserDocument.connector()).isEqualTo(connectorInstance))); for (ConnectorDocument document : allConnectorInstanceDocument) { String dn = ((ConnectorLDAPUserDocument) document).getDistinguishedName(); if (!allObjectsToFetch.contains(dn)) { returnSet.add(document.getId()); } } return returnSet; } static LdapContext connectToLDAP(String url, ConnectorLDAPInstance connectorInstance, ConnectorLDAPServices ldapServices) { String user = connectorInstance.getConnectionUsername(); String password = connectorInstance.getPassword(); boolean followReferences = (connectorInstance.getFollowReferences() == null) ? false : connectorInstance.getFollowReferences(); boolean activeDirectory = (connectorInstance.getDirectoryType() == null) ? true : connectorInstance.getDirectoryType().equals( LDAPDirectoryType.ACTIVE_DIRECTORY); return ldapServices.connectToLDAP(url, user, password, followReferences, activeDirectory); } boolean isFetchOnlyForUsers(ConnectorLDAPInstance ldapInstance) { if ((ldapInstance.getFetchGroups() != null && ldapInstance.getFetchGroups()) || (ldapInstance.getFetchComputers() != null && ldapInstance.getFetchComputers())) { return false; } return ldapInstance.getFetchUsers(); } private String getUrl() { List<String> urls = ldapInstance.getUrls(); if (urls.size() != 1) { throw new RuntimeException( "Unsupported in the current version. Connector instance should have only one url, given urls are " + StringUtils.join(urls, ",")); } //should have at least one value since metadata is a default requirement return urls.get(0); } @Override public List<String> getConnectorDocumentTypes() { return asList(ConnectorLDAPUserDocument.SCHEMA_TYPE); } @Override public void start() { LOGGER.info("started"); } @Override public void stop() { LOGGER.info("stopped"); } @Override public void afterJobs(List<ConnectorJob> jobs) { } @Override public void resume() { LOGGER.info("resumed"); } @Override public void onAllDocumentsDeleted() { } public ConnectorLDAPServices getLdapServices() { return ldapServices; } public static class InvalidDocumentsBatchRuntimeException extends RuntimeException { public InvalidDocumentsBatchRuntimeException(String s) { super(s); } } public static class InvalidJobsBatchRuntimeException extends RuntimeException { public InvalidJobsBatchRuntimeException(String s) { super(s); } } }