org.nuxeo.ecm.directory.ldap.LDAPServerDescriptor.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.directory.ldap.LDAPServerDescriptor.java

Source

/*
 * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *
 */
package org.nuxeo.ecm.directory.ldap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.naming.InvalidNameException;
import javax.naming.NamingException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.nuxeo.common.xmap.annotation.XNode;
import org.nuxeo.common.xmap.annotation.XNodeList;
import org.nuxeo.common.xmap.annotation.XObject;
import org.nuxeo.ecm.directory.DirectoryException;
import org.nuxeo.ecm.directory.ldap.dns.DNSServiceEntry;
import org.nuxeo.ecm.directory.ldap.dns.DNSServiceResolver;
import org.nuxeo.ecm.directory.ldap.dns.DNSServiceResolverImpl;

import com.sun.jndi.ldap.LdapURL;

@XObject(value = "server")
public class LDAPServerDescriptor {

    public static final Log log = LogFactory.getLog(LDAPServerDescriptor.class);

    protected static final String LDAPS_SCHEME = "ldaps";

    protected static final String LDAP_SCHEME = "ldap";

    @XNode("@name")
    public String name;

    public String ldapUrls;

    public String bindDn;

    @XNode("connectionTimeout")
    public int connectionTimeout = 10000; // timeout after 10 seconds

    @XNode("poolingEnabled")
    public boolean poolingEnabled = true;

    @XNode("verifyServerCert")
    public boolean verifyServerCert = true;

    /**
     * @since 5.7
     */
    @XNode("retries")
    public int retries = 5;

    protected LinkedHashSet<LdapEntry> ldapEntries;

    protected boolean isDynamicServerList = false;

    protected boolean useSsl = false;

    protected final DNSServiceResolver srvResolver = DNSServiceResolverImpl.getInstance();

    public boolean isDynamicServerList() {
        return isDynamicServerList;
    }

    public String getName() {
        return name;
    }

    public String bindPassword = "";

    @XNode("bindDn")
    public void setBindDn(String bindDn) {
        if (null != bindDn && bindDn.trim().equals("")) {
            // empty bindDn means anonymous authentication
            this.bindDn = null;
        } else {
            this.bindDn = bindDn;
        }
    }

    public String getBindDn() {
        return bindDn;
    }

    @XNode("bindPassword")
    public void setBindPassword(String bindPassword) {
        if (bindPassword == null) {
            // no password means empty pasword
            this.bindPassword = "";
        } else {
            this.bindPassword = bindPassword;
        }
    }

    public String getBindPassword() {
        return bindPassword;
    }

    public String getLdapUrls() {
        if (ldapUrls != null) {
            return ldapUrls;
        }

        // Leverage JNDI support for clustered servers by concatenating
        // all the provided URLs for fail-over
        StringBuilder calculatedLdapUrls = new StringBuilder();
        for (LdapEntry entry : ldapEntries) {
            calculatedLdapUrls.append(entry);
            calculatedLdapUrls.append(' ');
        }

        /*
         * If the configuration does not contain any domain entries then cache the urls, domain entries should always be
         * re-queried however as the LDAP server list should change dynamically
         */
        if (!isDynamicServerList) {
            return ldapUrls = calculatedLdapUrls.toString().trim();
        }
        return calculatedLdapUrls.toString().trim();
    }

    @XNodeList(value = "ldapUrl", componentType = LDAPUrlDescriptor.class, type = LDAPUrlDescriptor[].class)
    public void setLdapUrls(LDAPUrlDescriptor[] ldapUrls) throws DirectoryException {
        if (ldapUrls == null) {
            throw new DirectoryException("At least one <ldapUrl/> server declaration is required");
        }
        ldapEntries = new LinkedHashSet<LdapEntry>();

        Set<LDAPUrlDescriptor> processed = new HashSet<LDAPUrlDescriptor>();

        List<String> urls = new ArrayList<String>(ldapUrls.length);
        for (LDAPUrlDescriptor url : ldapUrls) {
            LdapURL ldapUrl;
            try {
                /*
                 * Empty string translates to ldap://localhost:389 through JNDI
                 */
                if (StringUtils.isEmpty(url.getValue())) {
                    urls.add(url.getValue());
                    ldapEntries.add(new LdapEntryDescriptor(url));
                    continue;
                }

                /*
                 * Parse the URI to make sure it is valid
                 */
                ldapUrl = new LdapURL(url.getValue());
                if (!processed.add(url)) {
                    continue;
                }
            } catch (NamingException e) {
                throw new DirectoryException(e);
            }

            useSsl = useSsl || ldapUrl.useSsl();

            /*
             * RFC-2255 - The "ldap" prefix indicates an entry or entries residing in the LDAP server running on the
             * given hostname at the given port number. The default LDAP port is TCP port 389. If no hostport is given,
             * the client must have some apriori knowledge of an appropriate LDAP server to contact.
             */
            if (ldapUrl.getHost() == null) {
                /*
                 * RFC-2782 - Check to see if an LDAP SRV record is defined in the DNS server
                 */
                String domain = convertDNtoFQDN(ldapUrl.getDN());
                if (domain != null) {
                    /*
                     * Dynamic URL - retrieve from SRV record
                     */
                    List<String> discoveredUrls;
                    try {
                        discoveredUrls = discoverLdapServers(domain, ldapUrl.useSsl(), url.getSrvPrefix());
                    } catch (NamingException e) {
                        throw new DirectoryException(String.format("SRV record DNS lookup failed for %s.%s: %s",
                                url.getSrvPrefix(), domain, e.getMessage()), e);
                    }

                    /*
                     * Discovered URLs could be empty, lets check at the end though
                     */
                    urls.addAll(discoveredUrls);

                    /*
                     * Store entries in an ordered set and remember that we were dynamic
                     */
                    ldapEntries.add(new LdapEntryDomain(url, domain, ldapUrl.useSsl()));
                    isDynamicServerList = true;
                } else {
                    throw new DirectoryException("Invalid LDAP SRV reference, this should be of the form"
                            + " ldap:///dc=example,dc=org");
                }
            } else {
                /*
                 * Static URL - store the value
                 */
                urls.add(url.getValue());

                /*
                 * Store entries in an ordered set
                 */
                ldapEntries.add(new LdapEntryDescriptor(url));
            }
        }

        /*
         * Oops no valid URLs to connect to :(
         */
        if (urls.isEmpty()) {
            throw new DirectoryException("No valid server urls returned from DNS query");
        }
    }

    /**
     * Whether this server descriptor defines a secure ldap connection
     */
    public boolean useSsl() {
        return useSsl;
    }

    /**
     * Retrieve server URLs from DNS SRV record
     *
     * @param domain The domain to query
     * @param useSsl Whether the connection to this domain should be secure
     * @return List of servers or empty list
     * @throws NamingException if DNS lookup fails
     */
    protected List<String> discoverLdapServers(String domain, boolean useSsl, String srvPrefix)
            throws NamingException {
        List<String> result = new ArrayList<String>();
        List<DNSServiceEntry> servers = getSRVResolver().resolveLDAPDomainServers(domain, srvPrefix);

        for (DNSServiceEntry serviceEntry : servers) {
            /*
             * Rebuild the URL
             */
            StringBuilder realUrl = (useSsl) ? new StringBuilder(LDAPS_SCHEME + "://")
                    : new StringBuilder(LDAP_SCHEME + "://");
            realUrl.append(serviceEntry);
            result.add(realUrl.toString());
        }
        return result;
    }

    /**
     * Convert domain from the ldap form dc=nuxeo,dc=org to the DNS domain name form nuxeo.org
     *
     * @param dn base DN of the domain
     * @return the FQDN or null is DN is not matching the expected structure
     * @throws DirectoryException is the DN is invalid
     */
    protected String convertDNtoFQDN(String dn) throws DirectoryException {
        try {
            LdapDN ldapDN = new LdapDN(dn);
            Enumeration<String> components = ldapDN.getAll();
            List<String> domainComponents = new ArrayList<String>();
            while (components.hasMoreElements()) {
                String component = components.nextElement();
                if (component.startsWith("dc=")) {
                    domainComponents.add(component.substring(3));
                } else {
                    break;
                }
            }
            Collections.reverse(domainComponents);
            return StringUtils.join(domainComponents, ".");
        } catch (InvalidNameException e) {
            throw new DirectoryException(e);
        }
    }

    public boolean isPoolingEnabled() {
        return poolingEnabled;
    }

    public boolean isVerifyServerCert() {
        return verifyServerCert;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    protected DNSServiceResolver getSRVResolver() {
        return srvResolver;
    }

    /**
     * Common internal interface for Ldap entries
     *
     * @author Bob Browning
     */
    protected interface LdapEntry {
        String getUrl() throws NamingException;
    }

    /**
     * Server URL implementation of {@link LdapEntry}
     *
     * @author Bob Browning
     */
    protected class LdapEntryDescriptor implements LdapEntry {

        protected LDAPUrlDescriptor url;

        public LdapEntryDescriptor(LDAPUrlDescriptor descriptor) {
            url = descriptor;
        }

        @Override
        public String toString() {
            try {
                return getUrl();
            } catch (NamingException e) {
                log.error(e, e);
                return "[DNS lookup failed]";
            }
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof LdapEntryDescriptor) {
                return url.equals(obj);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return url.hashCode();
        }

        @Override
        public String getUrl() throws NamingException {
            return url.getValue();
        }

    }

    /**
     * Domain implementation of {@link LdapEntry} using DNS SRV record
     *
     * @author Bob Browning
     */
    protected final class LdapEntryDomain extends LdapEntryDescriptor {

        protected final String domain;

        protected final boolean useSsl;

        public LdapEntryDomain(LDAPUrlDescriptor descriptor, final String domain, boolean useSsl) {
            super(descriptor);
            this.domain = domain;
            this.useSsl = useSsl;
        }

        @Override
        public String getUrl() throws NamingException {
            List<DNSServiceEntry> servers = getSRVResolver().resolveLDAPDomainServers(domain, url.getSrvPrefix());

            StringBuilder result = new StringBuilder();
            for (DNSServiceEntry serviceEntry : servers) {
                /*
                 * Rebuild the URL
                 */
                result.append(useSsl ? LDAPS_SCHEME + "://" : LDAP_SCHEME + "://");
                result.append(serviceEntry);
                result.append(' ');
            }
            return result.toString().trim();
        }

        private LDAPServerDescriptor getOuterType() {
            return LDAPServerDescriptor.this;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result + getOuterType().hashCode();
            result = prime * result + ((domain == null) ? 0 : domain.hashCode());
            result = prime * result + (useSsl ? 1231 : 1237);
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!super.equals(obj)) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            LdapEntryDomain other = (LdapEntryDomain) obj;
            if (!getOuterType().equals(other.getOuterType())) {
                return false;
            }
            if (domain == null) {
                if (other.domain != null) {
                    return false;
                }
            } else if (!domain.equals(other.domain)) {
                return false;
            }
            if (useSsl != other.useSsl) {
                return false;
            }
            return true;
        }
    }

    /**
     * @since 5.7
     */
    public int getRetries() {
        return retries;
    }

}