All Downloads are FREE. Search and download functionalities are using the official Maven repository.

apoc.load.LoadLdap Maven / Gradle / Ivy

package apoc.load;

import apoc.Extended;
import apoc.util.Util;
import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.util.ssl.SSLUtil;
import com.unboundid.util.ssl.TrustAllTrustManager;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;

import javax.net.ssl.SSLSocketFactory;
import java.security.GeneralSecurityException;
import java.util.*;
import java.util.stream.Stream;

import static apoc.ApocConfig.apocConfig;

@Extended
public class LoadLdap {

    @Context
    public Log log;

    @Procedure(name = "apoc.load.ldap", mode = Mode.READ)
    @Description("apoc.load.ldap(\"key\" or {connectionMap},{searchMap}) Load entries from an ldap source (yield entry)")
    public Stream ldapQuery(@Name("connection") final Object conn, @Name("search") final Map search) {

        LDAPManager mgr = new LDAPManager(getConnectionMap(conn, log));

        return mgr.executeSearch(search);
    }

    public static Map getConnectionMap(Object conn, Log log) {
        if (conn instanceof String) {
            //String value = "ldap.forumsys.com cn=read-only-admin,dc=example,dc=com password";
            String key = "apoc.loadldap.%s.config".formatted(conn);
            String value = apocConfig().getString(key);
            // format   
            if (value == null) {
                // fallback: if `apoc.loadldap..config` is not set
                // we check for a config with key `apoc.loadldap.config`
                String keyOld = "apoc.loadldap%s.config".formatted(conn);
                value = apocConfig().getString(keyOld);

                // if the value is set and log == null (that is, not from the test LoadLdapTest.testLoadLDAPConfig),
                // we print a log warn, since the correct way should be with a dot before 
                if (value != null && log != null) {
                    log.warn("""
                            Not to cause breaking-change, the current config `%s` is valid,
                            but in future releases it will be removed in favor of `%s` (with dot before `%s`),
                            as documented here: https://neo4j.com/labs/apoc/5/database-integration/load-ldap/#_credentials.
                            """
                            .formatted(keyOld, key, conn)
                    );
                }
            }

            // if neither `apoc.loadldap..config` nor `apoc.loadldap.config` is set.
            // we throw an error
            if (value == null) {
                throw new RuntimeException("No " + key + " ldap access configuration specified");
            }
            Map config = new HashMap<>();
            String[] sConf = value.split(" ");
            config.put("ldapHost", sConf[0]);
            config.put("loginDN", sConf[1]);
            config.put("loginPW", sConf[2]);

            return config;

        } else {
            return (Map ) conn;
        }
    }

    public static class LDAPManager {
        private static final String LDAP_HOST_P = "ldapHost";
        private static final String LDAP_LOGIN_DN_P = "loginDN";
        private static final String LDAP_LOGIN_PW_P = "loginPW";
        private static final String LDAP_SSL = "ssl";
        private static final String SEARCH_BASE_P = "searchBase";
        private static final String SEARCH_SCOPE_P = "searchScope";
        private static final String SEARCH_FILTER_P = "searchFilter";
        private static final String SEARCH_ATTRIBUTES_P = "attributes";

        private static final String SCOPE_BASE = "SCOPE_BASE";
        private static final String SCOPE_ONE = "SCOPE_ONE";
        private static final String SCOPE_SUB = "SCOPE_SUB";

        private int ldapPort;
        private String ldapHost;
        private String loginDN;
        private String password;
        private boolean ssl;
        private LDAPConnection lc;
        private List attributeList;

        public LDAPManager(Map connParms) {

            String sLdapHostPort = (String) connParms.get(LDAP_HOST_P);
            if (sLdapHostPort.indexOf(":") > -1) {
                this.ldapHost = sLdapHostPort.substring(0, sLdapHostPort.indexOf(":"));
                this.ldapPort = Integer.parseInt(sLdapHostPort.substring(sLdapHostPort.indexOf(":") + 1));
            } else {
                this.ldapHost = sLdapHostPort;
                this.ldapPort = 389; // default
            }

            this.loginDN = (String) connParms.get(LDAP_LOGIN_DN_P);
            this.password = (String) connParms.get(LDAP_LOGIN_PW_P);
            this.ssl = Util.toBoolean(connParms.get(LDAP_SSL));
        }

        public Stream executeSearch(Map search) {
            try {
                return doSearch(search).getSearchEntries()
                        .stream()
                        .map(i -> getMapFromEntry(i, attributeList))
                        .map(LDAPResult::new)
                        .onClose(() -> closeIt(lc));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        private Map getMapFromEntry(SearchResultEntry entry, List attributes) {
            Map map = new LinkedHashMap<>(attributes.size() + 1);
            map.put("dn", entry.getDN());

            if (attributes.isEmpty()) {
                entry.getAttributes()
                        .forEach(i -> {
                            Object value = readValue(i);
                            map.put(i.getName(), value);
                        });
            } else {
                for (String attribute : attributes) {
                    Object value = readValue(entry.getAttribute(attribute));
                    if (value != null) map.put(attribute, value);
                }
            }

            return map;
        }

        private Object readValue(Attribute att) {
            if (att == null) return null;
            if (att.size() == 1) {
                // single value
                // for now everything is string
                return att.getValue();
            } else {
                return att.getValues();
            }
        }

        public SearchResult doSearch(Map search) {
            // parse search parameters
            String searchBase = (String) search.get(SEARCH_BASE_P);
            String searchFilter = (String) search.getOrDefault(SEARCH_FILTER_P, "(objectClass=*)");
            String sScope = (String) search.get(SEARCH_SCOPE_P);
            attributeList = (List) search.get(SEARCH_ATTRIBUTES_P);
            if (attributeList == null) attributeList = new ArrayList<>();
            
            int searchScope = switch (sScope) {
                case SCOPE_BASE -> SearchScope.BASE_INT_VALUE;
                case SCOPE_ONE -> SearchScope.ONE_INT_VALUE;
                case SCOPE_SUB -> SearchScope.SUB_INT_VALUE;
                default -> throw new RuntimeException("Invalid scope:" + sScope + ". value scopes are SCOPE_BASE, SCOPE_ONE and SCOPE_SUB");
            };
            // getting an ldap connection
            try {
                lc = getConnection();
                // execute query
                SearchResult searchResults;
                SearchScope scope = SearchScope.valueOf(searchScope);
                if (attributeList.isEmpty()) {
                    searchResults = lc.search(searchBase, scope, searchFilter);
                } else {
                    searchResults = lc.search(searchBase, scope, searchFilter, attributeList.toArray(new String[0]));
                }
                return searchResults;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public static void closeIt(LDAPConnection lc) {
            try {
                lc.close();
            } catch (Exception e) {
                // ignore
            }
        }

        private LDAPConnection getConnection() throws GeneralSecurityException, LDAPException {

            SSLSocketFactory socketFactory = getSocketFactory();
            lc = new LDAPConnection(socketFactory);
            
            lc.connect(ldapHost, ldapPort);
            lc.bind(loginDN, password);
            
            return lc;
        }

        private SSLSocketFactory getSocketFactory() throws GeneralSecurityException {
            if (ssl || ldapPort == 636) {
                SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
                return sslUtil.createSSLSocketFactory();
            } else {
                return null;
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy