com.adito.activedirectory.ActiveDirectoryUserDatabaseConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for com.adito.activedirectory.ActiveDirectoryUserDatabaseConfiguration.java

Source

/*
*  Adito
*
*  Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either version 2 of
*  the License, or (at your option) any later version.
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public
*  License along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package com.adito.activedirectory;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.adito.boot.PropertyList;
import com.adito.properties.Property;
import com.adito.properties.impl.realms.RealmKey;
import com.adito.realms.Realm;
import com.adito.security.UserDatabaseException;

public final class ActiveDirectoryUserDatabaseConfiguration {
    private static final Log logger = LogFactory.getLog(ActiveDirectoryUserDatabaseConfiguration.class);
    private static final String COMMON_NAME = "CN=";
    private static final String CN_USERS = COMMON_NAME + "Users";
    private static final String CN_BUILTIN = COMMON_NAME + "Builtin";
    private static final String LDAP_PROTOCOL = "ldap://";
    private static final String PORT_SEPARATOR = ":";
    private static final String ESCAPE_BACKSLASH = "\\\\";
    private static final String ESCAPE_QUOTE = "\"";
    private static final String[] ESCAPED_CHARACTERS = { ESCAPE_BACKSLASH, "/", "#", ",,", "\\+", ESCAPE_QUOTE, "<",
            ">", ";" };
    private static final int SSL_SECURED_PORT = 636;
    private static final int CLEAR_TEXT_PORT = 389;

    /** The Kerberos GSSAPI authentication type. */
    public static final String GSSAPI_AUTHENTICATION_METHOD = "GSSAPI";
    /** The JNDI simple bind authentication type. */
    public static final String SIMPLE_AUTHENTICATION_METHOD = "simple";

    /** The plain text protocol. */
    public static final String PLAIN_PROTOCOL = "plain";
    /** The SSL protocol. */
    public static final String SSL_PROTOCOL = "ssl";

    private final Realm realm;
    private final ActiveDirectoryPropertyManager propertyManager;
    private String domain;
    private String controllerHost;
    private Collection<String> backupControllerHosts;
    private String serviceAuthenticationType;
    private String userAuthenticationType;
    private String serviceAccountName;
    private String serviceAccountPassword;
    private String baseDn;
    private String protocolType;
    private boolean followReferrals;
    private int userCacheSize;
    private int groupCacheSize;
    private boolean inMemoryCache;
    private int timeToLive;
    private final List<String> includedOuBasesList = new ArrayList<String>();
    private final List<String> excludedOuBasesList = new ArrayList<String>();
    private boolean hasFilteredOus;
    private boolean includeDistributionGroups;
    private boolean memberOfSupported;
    private boolean usernamesAreCaseSensitive;
    private final Collection<URI> activeDirectoryUrls = new ArrayList<URI>();
    private URI lastContactedActiveDirectoryUrl;
    private int pageSize;
    private int timeOut;
    private PagedResultTemplate template;

    public ActiveDirectoryUserDatabaseConfiguration(Realm realm, Properties propertyNames)
            throws IllegalArgumentException, Exception {
        this.realm = realm;
        propertyManager = new ActiveDirectoryPropertyManager(realm, propertyNames);
        initialize(propertyNames);
    }

    private void initialize(Properties propertyNames) throws Exception {
        // Get the domain and active directory root
        setControllerHost(getProperty("activeDirectory.controllerHost", propertyNames));
        setBackupControllerHosts(getPropertyList("activeDirectory.backupControllerHosts", propertyNames));
        setDomain(getProperty("activeDirectory.domain", propertyNames));
        setServiceAuthenticationType(getProperty("activeDirectory.serviceAuthenticationType", propertyNames));
        setUserAuthenticationType(getProperty("activeDirectory.userAuthenticationType", propertyNames));
        setServiceAccountName(getProperty("activeDirectory.serviceAccountUsername", propertyNames));
        setServiceAccountPassword(getProperty("activeDirectory.serviceAccountPassword", propertyNames));

        setFollowReferrals(getPropertyBoolean("activeDirectory.followReferrals", propertyNames));
        setUserCacheSize(getPropertyInt("activeDirectory.cacheUserMaxObjects", propertyNames));
        setGroupCacheSize(getPropertyInt("activeDirectory.cacheGroupMaxObjects", propertyNames));
        setInMemoryCache(getPropertyBoolean("activeDirectory.cacheInMemory", propertyNames));
        setTimeToLive(getPropertyInt("activeDirectory.userCacheTTL", propertyNames));

        Collection<String> includedOuFilterList = getPropertyList("activeDirectory.organizationalUnitFilter",
                propertyNames);
        Collection<String> excludedOuFilterList = getPropertyList(
                "activeDirectory.excludedOrganizationalUnitFilter", propertyNames);
        setValidOus(baseDn, includedOuFilterList, excludedOuFilterList);
        setIncludeStandardUsers(getPropertyBoolean("activeDirectory.includeStandardUsers", propertyNames));
        setIncludeBuiltInGroups(getPropertyBoolean("activeDirectory.includeBuiltInGroups", propertyNames));
        setIncludeDistributionGroups(
                getPropertyBoolean("activeDirectory.includeDistributionGroups", propertyNames));

        setMemberOfSupported(getPropertyBoolean("activeDirectory.memberOfSupported", propertyNames));
        setUsernamesAreCaseSensitive(
                getPropertyBoolean("activeDirectory.usernamesAreCaseSensitive", propertyNames));
        setPageSize(getPropertyInt("activeDirectory.pageSize", propertyNames));
        setTimeOut(getPropertyInt("activeDirectory.connection.timeout", propertyNames));
    }

    private String getProperty(String key, Properties propertyNames) {
        return Property.getProperty(getRealmKey(key, propertyNames));
    }

    private boolean getPropertyBoolean(String key, Properties propertyNames) {
        return Property.getPropertyBoolean(getRealmKey(key, propertyNames));
    }

    private int getPropertyInt(String key, Properties propertyNames) {
        return Property.getPropertyInt(getRealmKey(key, propertyNames));
    }

    private Collection<String> getPropertyList(String key, Properties propertyNames) {
        return Property.getPropertyList(getRealmKey(key, propertyNames));
    }

    private RealmKey getRealmKey(String key, Properties propertyNames) {
        String propertyOrDefault = propertyNames.getProperty(key, key);
        return new RealmKey(propertyOrDefault, realm);
    }

    void postInitialize() throws URISyntaxException {
        setActiveDirectoryUrls();
        if (!isServiceAuthenticationGssApi() && !serviceAccountName.toLowerCase().endsWith(baseDn)) {
            serviceAccountName = appendBaseDn(formatUsername(serviceAccountName));
        }
        includedOuBasesList.removeAll(excludedOuBasesList); // just to make sure

        Collection<String> escapedIncludedOuBasesList = getEscapedDns(includedOuBasesList, false);
        Collection<String> escapedExcludedOuBasesList = getEscapedDns(excludedOuBasesList, false);
        Collection<String> escapedOuSearchBase = getEscapedDns(includedOuBasesList, true);
        template = new PagedResultTemplate(escapedIncludedOuBasesList, escapedExcludedOuBasesList,
                escapedOuSearchBase, pageSize);
        refresh();
    }

    private static Collection<String> getEscapedDns(Collection<String> toEscapeDns, boolean requiresSecondEscape) {
        Collection<String> escapedDns = new HashSet<String>(toEscapeDns.size());
        for (String toEscapeDn : toEscapeDns) {
            String escapedDn = getEscapedDn(toEscapeDn, requiresSecondEscape);
            escapedDns.add(escapedDn);
        }
        return Collections.unmodifiableCollection(escapedDns);
    }

    static String getEscapedDn(String toEscape, boolean requiresSecondEscape) {
        for (int index = 0; index < ESCAPED_CHARACTERS.length; index++) {
            String character = ESCAPED_CHARACTERS[index];
            if (requiresSecondEscape) {
                if (character.equals(ESCAPE_BACKSLASH)) {
                    toEscape = toEscape.replaceAll(character,
                            ESCAPE_BACKSLASH + ESCAPE_BACKSLASH + ESCAPE_BACKSLASH + ESCAPE_BACKSLASH);
                } else if (character.equals(ESCAPE_QUOTE)) {
                    toEscape = toEscape.replaceAll(character, ESCAPE_BACKSLASH + ESCAPE_BACKSLASH + ESCAPE_QUOTE);
                } else {
                    toEscape = toEscape.replaceAll(character, ESCAPE_BACKSLASH + character);
                }
            } else {
                toEscape = toEscape.replaceAll(character, ESCAPE_BACKSLASH + character);
            }
        }
        return toEscape;
    }

    private static String formatUsername(String username) {
        return username.toUpperCase().startsWith(COMMON_NAME) ? username : COMMON_NAME + username;
    }

    public String appendBaseDn(String commonName) {
        if (commonName.toLowerCase().endsWith(baseDn.toLowerCase())) {
            return commonName;
        } else {
            return commonName.endsWith(",") ? commonName : commonName + "," + baseDn;
        }
    }

    void refresh() {
        propertyManager.refresh();
    }

    public String getDomain() {
        return domain;
    }

    private void setDomain(String domain) throws Exception {
        this.domain = domain.toUpperCase().trim();
        if (this.domain.equals("")) {
            throw new IllegalArgumentException("No active directory domain configured.");
        }
        setBaseDn(splitDomain(domain));
    }

    private void setControllerHost(String controllerHost) {
        if (controllerHost.equals("")) {
            throw new IllegalArgumentException("No active directory controller host configured.");
        }
        this.controllerHost = controllerHost;
    }

    private Collection<String> getBackupControllerHosts() {
        return backupControllerHosts;
    }

    private void setBackupControllerHosts(Collection<String> backupControllerHosts) {
        this.backupControllerHosts = backupControllerHosts;
    }

    String getServiceAuthenticationType() {
        return serviceAuthenticationType;
    }

    public void setServiceAuthenticationType(String serviceAuthenticationType) {
        this.serviceAuthenticationType = serviceAuthenticationType;
    }

    boolean isServiceAuthenticationGssApi() {
        return GSSAPI_AUTHENTICATION_METHOD.equals(getServiceAuthenticationType());
    }

    boolean isUserAuthenticationGssApi() {
        return GSSAPI_AUTHENTICATION_METHOD.equals(getUserAuthenticationType());
    }

    String getUserAuthenticationType() {
        return userAuthenticationType;
    }

    public void setUserAuthenticationType(String userAuthenticationType) {
        this.userAuthenticationType = userAuthenticationType;
    }

    public String getServiceAccountName() {
        return serviceAccountName;
    }

    void setServiceAccountName(String serviceAccountName) {
        this.serviceAccountName = serviceAccountName == null ? null : serviceAccountName.trim();
    }

    public String getServiceAccountPassword() {
        return serviceAccountPassword;
    }

    void setServiceAccountPassword(String serviceAccountPassword) {
        this.serviceAccountPassword = serviceAccountPassword;
    }

    private String getProtocolType() {
        return protocolType;
    }

    public boolean isSslProtcolType() {
        return "ssl".equals(getProtocolType());
    }

    public void setProtocolType(String protocolType) {
        this.protocolType = protocolType;
    }

    private boolean isFollowReferrals() {
        return followReferrals;
    }

    void setFollowReferrals(boolean followReferrals) {
        this.followReferrals = followReferrals;
    }

    public String getBaseDn() {
        return baseDn;
    }

    void setBaseDn(String baseDn) {
        this.baseDn = baseDn == null ? null : baseDn.toLowerCase().trim();
    }

    void setUserCacheSize(int userCacheSize) {
        this.userCacheSize = userCacheSize;
    }

    void setGroupCacheSize(int groupCacheSize) {
        this.groupCacheSize = groupCacheSize;
    }

    void setInMemoryCache(boolean inMemoryCache) {
        this.inMemoryCache = inMemoryCache;
    }

    int getTimeToLive() {
        return timeToLive;
    }

    void setTimeToLive(int timeToLive) {
        if (timeToLive < 1) {
            logger.warn(
                    "Cache TTL is less than 1 minute. This would cause serious performance problems. The minimum value of 1 minute will now be used");
            timeToLive = 1;
        }
        this.timeToLive = minutesToMillis(timeToLive);
    }

    private static int minutesToMillis(int minutes) {
        return minutes * 60 * 1000;
    }

    UserContainer createUserContainer() {
        return new UserContainer(userCacheSize, inMemoryCache, isUsernamesAreCaseSensitive(), getDomain());
    }

    GroupContainer createRoleContainer() {
        return new GroupContainer(groupCacheSize, inMemoryCache);
    }

    void setValidOus(String baseDn, Collection<String> includedOuFilterList,
            Collection<String> excludedOuFilterList) {
        includedOuBasesList.clear();
        includedOuBasesList.addAll(getFormattedOuFilterList(baseDn, includedOuFilterList));
        excludedOuBasesList.clear();
        excludedOuBasesList.addAll(getFormattedOuFilterList(baseDn, excludedOuFilterList));
        includedOuBasesList.removeAll(excludedOuBasesList);

        hasFilteredOus = !includedOuBasesList.isEmpty();
        if (!hasFilteredOus) {
            includedOuBasesList.add(baseDn);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Included OU Bases:");
            for (String dn : includedOuBasesList) {
                logger.debug(" " + dn);
            }

            logger.debug("Excluded OU Bases:");
            for (String dn : excludedOuBasesList) {
                logger.debug(" " + dn);
            }
        }
    }

    private static Collection<String> getFormattedOuFilterList(String baseDn, Collection<String> ouFilterList) {
        Collection<String> formattedOuFilterList = new HashSet<String>();
        for (String dn : ouFilterList) {
            if (!dn.trim().toLowerCase().endsWith(baseDn.trim().toLowerCase())) {
                dn = dn + "," + baseDn;
            }
            formattedOuFilterList.add(dn);
        }
        return formattedOuFilterList;
    }

    private void setIncludeStandardUsers(boolean includeStandardUsers) {
        if (includeStandardUsers) {
            if (hasFilteredOus) {
                includedOuBasesList.add(0, appendBaseDn(CN_USERS));
            }
        } else {
            excludedOuBasesList.add(0, appendBaseDn(CN_USERS));
        }
    }

    private void setIncludeBuiltInGroups(boolean includeBuiltInGroups) {
        if (includeBuiltInGroups) {
            if (hasFilteredOus) {
                includedOuBasesList.add(0, appendBaseDn(CN_BUILTIN));
            }
        } else {
            excludedOuBasesList.add(0, appendBaseDn(CN_BUILTIN));
        }
    }

    boolean isIncludeDistributionGroups() {
        return includeDistributionGroups;
    }

    private void setIncludeDistributionGroups(boolean includeDistributionGroups) {
        this.includeDistributionGroups = includeDistributionGroups;
    }

    boolean isMemberOfSupported() {
        return memberOfSupported;
    }

    private void setMemberOfSupported(boolean memberOfSupported) {
        this.memberOfSupported = memberOfSupported;
    }

    private boolean isUsernamesAreCaseSensitive() {
        return usernamesAreCaseSensitive;
    }

    private void setUsernamesAreCaseSensitive(boolean usernamesAreCaseSensitive) {
        this.usernamesAreCaseSensitive = usernamesAreCaseSensitive;
    }

    private void setActiveDirectoryUrls() throws URISyntaxException {
        activeDirectoryUrls.clear();
        lastContactedActiveDirectoryUrl = null;

        int controllerPort = getControllerPort();
        URI primaryUri = controllerHost.contains(PORT_SEPARATOR) ? buildURI(controllerHost)
                : buildURI(controllerHost, controllerPort);
        activeDirectoryUrls.add(primaryUri);

        for (String uri : getBackupControllerHosts()) {
            if (uri.contains(PORT_SEPARATOR)) {
                activeDirectoryUrls.add(buildURI(uri));
            } else {
                activeDirectoryUrls.add(buildURI(uri, controllerPort));
            }
        }

        setLastContactedActiveDirectoryUrl(primaryUri);
    }

    private int getControllerPort() {
        int indexOf = controllerHost.indexOf(PORT_SEPARATOR);
        if (indexOf == -1 || indexOf == controllerHost.length() - 1) {
            if (isServiceAuthenticationGssApi()) {
                return CLEAR_TEXT_PORT;
            }
            return isSslProtcolType() ? SSL_SECURED_PORT : CLEAR_TEXT_PORT;
        } else {
            String port = controllerHost.substring(indexOf + 1);
            Integer valueOf = Integer.valueOf(port);
            return valueOf;
        }
    }

    private static URI buildURI(String host, int port) throws URISyntaxException {
        return buildURI(host + PORT_SEPARATOR + port);
    }

    private static URI buildURI(String url) throws URISyntaxException {
        return new URI(LDAP_PROTOCOL + url);
    }

    String getContactableActiveDirectories() {
        URI lastContactedUrl = getLastContactedActiveDirectoryUrl();
        URI firstUrl = activeDirectoryUrls.isEmpty() ? null : activeDirectoryUrls.iterator().next();
        boolean isDifferent = !lastContactedUrl.equals(firstUrl);

        Collection<URI> hosts = new ArrayList<URI>(activeDirectoryUrls.size() + 1);
        if (isDifferent) {
            hosts.add(lastContactedUrl);
        }
        hosts.addAll(activeDirectoryUrls);
        return getHosts(hosts);
    }

    private static String getHosts(Collection<URI> urls) {
        StringBuffer buffer = new StringBuffer();
        for (Iterator<URI> itr = urls.iterator(); itr.hasNext();) {
            buffer.append(itr.next().toString());
            if (itr.hasNext()) {
                buffer.append(" ");
            }
        }
        return buffer.toString();
    }

    synchronized URI getLastContactedActiveDirectoryUrl() {
        return lastContactedActiveDirectoryUrl;
    }

    synchronized void setLastContactedActiveDirectoryUrl(String url) {
        try {
            setLastContactedActiveDirectoryUrl(new URI(url));
        } catch (URISyntaxException e) {
            // ignore
        }
    }

    private synchronized void setLastContactedActiveDirectoryUrl(URI url) {
        if (lastContactedActiveDirectoryUrl == null || !lastContactedActiveDirectoryUrl.equals(url)) {
            PropertyList kerbrosControllerSettings = getKerbrosControllerSettings(url);
            propertyManager.refresh(Collections.singletonMap("activeDirectory.backupControllerHosts",
                    kerbrosControllerSettings.getAsPropertyText()));
        }
        lastContactedActiveDirectoryUrl = url;
    }

    private PropertyList getKerbrosControllerSettings(URI contactedUrl) {
        PropertyList values = new PropertyList();
        values.add(getKerbrosController(contactedUrl));

        for (URI url : activeDirectoryUrls) {
            String kerbrosController = getKerbrosController(url);
            if (!values.contains(kerbrosController)) {
                values.add(kerbrosController);
            }
        }
        return values;
    }

    private static String getKerbrosController(URI url) {
        String toParse = url.toString();
        return toParse.substring(LDAP_PROTOCOL.length(), toParse.lastIndexOf(PORT_SEPARATOR));
    }

    void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    private int getTimeout() {
        return timeOut;
    }

    private void setTimeOut(int timeOut) {
        this.timeOut = timeOut * 1000;
    }

    PagedResultTemplate getPagedResultTemplate() {
        return this.template;
    }

    public Object doAs(PrivilegedAction<?> action) throws UserDatabaseException {
        Object result = null;
        if (isServiceAuthenticationGssApi()) {
            try {
                LoginContext context = getServiceAccountLoginContext();
                result = Subject.doAs(context.getSubject(), action);
                logoutContext(context);
            } catch (Exception e) {
                logger.error("Failure to create Login Context", e);
                throw new UserDatabaseException("", e);
            }
        } else {
            result = action.run();
        }

        if (result instanceof Throwable) {
            Throwable e = (Throwable) result;
            logger.error("Failure to doAs", e);
            throw new UserDatabaseException("", e);
        }
        return result;
    }

    private LoginContext getServiceAccountLoginContext() throws Exception {
        /*
         * Only attempt to load the service account context if it has not been
         * loaded, if the username has changed or if the password has changed
         */
        try {
            return createLoginContext(getServiceAccountName(), getServiceAccountPassword());
        } catch (LoginException e) {
            Throwable cause = e.getCause();
            // Check the class by name to allow non Sun Javas to compile
            if (cause != null && cause.getClass().getName().equals("sun.security.krb5.KrbException")) {
                throw new Exception("Failed to logon. Please check your Active Directory configuration.", e);
            }
            throw e;
        }
    }

    LoginContext createLoginContext(String username, String password) throws LoginException {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating login context for " + username);
        }

        UserPasswordCallbackHandler callbackHandler = new UserPasswordCallbackHandler();
        callbackHandler.setUserId(username);
        callbackHandler.setPassword(password);

        LoginContext context = new LoginContext(ActiveDirectoryUserDatabase.class.getName(), callbackHandler);
        context.login();
        return context;
    }

    static void logoutContext(LoginContext context) {
        try {
            if (context != null) {
                context.logout();
            }
        } catch (LoginException e) {
            // ignore
        }
    }

    InitialLdapContext getAuthenticatedContext(String url, Map<String, String> properties) throws NamingException {
        Hashtable<String, String> variables = new Hashtable<String, String>(properties);
        variables.put(Context.SECURITY_AUTHENTICATION, getServiceAuthenticationType());
        if (!isServiceAuthenticationGssApi()) {
            variables.put(Context.SECURITY_PRINCIPAL, getServiceAccountName());
            variables.put(Context.SECURITY_CREDENTIALS, getServiceAccountPassword());
        }
        return getInitialContext(url, variables);
    }

    InitialLdapContext getAuthenticatedContext(String url) throws NamingException {
        return getAuthenticatedContext(url, Collections.<String, String>emptyMap());
    }

    public InitialLdapContext getInitialContext(String url, Map<String, String> properties) throws NamingException {
        Hashtable<String, String> variables = new Hashtable<String, String>(properties);
        variables.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        variables.put(Context.PROVIDER_URL, url); // Must use fully qualified hostname

        if (isSslProtcolType()) {
            variables.put("java.naming.ldap.factory.socket", "com.adito.boot.CustomSSLSocketFactory");
            // Add the custom socket factory
        }

        if (isFollowReferrals()) {
            variables.put(Context.REFERRAL, "follow");
        }

        variables.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(getTimeout()));
        variables.put("java.naming.ldap.version", "3");
        variables.put("com.sun.jndi.ldap.connect.pool", "true");
        variables.put("javax.security.sasl.qop", "auth-conf,auth-int,auth");
        variables.put(Context.SECURITY_PROTOCOL, getProtocolType());

        InitialLdapContext context = new InitialLdapContext(variables, null);
        String usedUrl = (String) context.getEnvironment().get(Context.PROVIDER_URL);
        setLastContactedActiveDirectoryUrl(usedUrl);
        return context;
    }

    private static String splitDomain(String domain) {
        StringBuffer buffer = new StringBuffer();
        for (StringTokenizer tokenizer = new StringTokenizer(domain, "."); tokenizer.hasMoreTokens();) {
            if (buffer.length() > 0) {
                buffer.append(",");
            }
            buffer.append("DC=" + tokenizer.nextToken());
        }
        return buffer.toString();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder(super.toString());
        builder.append("[realm='").append(realm);
        builder.append("', domain='").append(getDomain());
        builder.append("', controllerHost='").append(controllerHost);
        builder.append("', backupControllerHosts='").append(getBackupControllerHosts());
        builder.append("', serviceAuthenticationType='").append(getServiceAuthenticationType());
        builder.append("', userAuthenticationType='").append(getUserAuthenticationType());
        builder.append("', serviceAccountName='").append(getServiceAccountName());
        builder.append("', serviceAccountPassword='************");
        builder.append("', baseDn='").append(getBaseDn());
        builder.append("', protocolType='").append(getProtocolType());
        builder.append("', followReferrals='").append(isFollowReferrals());
        builder.append("', userCacheSize='").append(userCacheSize);
        builder.append("', groupCacheSize='").append(groupCacheSize);
        builder.append("', inMemoryCache='").append(inMemoryCache);
        builder.append("', timeToLive='").append(timeToLive);
        builder.append("', includedOuBasesList='").append(includedOuBasesList);
        builder.append("', excludedOuBasesList='").append(excludedOuBasesList);
        builder.append("', hasFilteredOus='").append(hasFilteredOus);
        builder.append("', includeDistributionGroups='").append(includeDistributionGroups);
        builder.append("', usernamesAreCaseSensitive='").append(usernamesAreCaseSensitive);
        builder.append("', pageSize='").append(pageSize);
        builder.append("', timeOut='").append(timeOut).append("']");
        return builder.toString();
    }

    /**
     * Converts an Active Directory long value into a
     * <code>java.util.Date</code>.
     * 
     * @param timeStamp the time to convert
     * @return the <code>java.util.Date</code> representing the long
     */
    public static Date adTimeToJavaDate(long timeStamp) {
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(1601, 0, 1, 0, 0);
        timeStamp = timeStamp / 10000 + calendar.getTime().getTime();
        return new Date(timeStamp);
    }

    /**
     * Converts an Active Directory long value into a number of days.
     * 
     * @param timeStamp the time to convert
     * @return days representing the long
     */
    public static int adTimeToJavaDays(long timeStamp) {
        return (int) (timeStamp / -86400) / 10000000;
    }
}