com.vmware.identity.idm.server.provider.ldap.LdapWithAdMappingsProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.identity.idm.server.provider.ldap.LdapWithAdMappingsProvider.java

Source

/*
 *
 *  Copyright (c) 2012-2015 VMware, Inc.  All Rights Reserved.
 *
 *  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.
 *
 */
package com.vmware.identity.idm.server.provider.ldap;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import javax.security.auth.login.LoginException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

import com.vmware.identity.diagnostics.DiagnosticsContextFactory;
import com.vmware.identity.diagnostics.DiagnosticsLoggerFactory;
import com.vmware.identity.diagnostics.IDiagnosticsLogger;
import com.vmware.identity.idm.Attribute;
import com.vmware.identity.idm.AttributeValuePair;
import com.vmware.identity.idm.AuthenticationType;
import com.vmware.identity.idm.Group;
import com.vmware.identity.idm.GroupDetail;
import com.vmware.identity.idm.IDMException;
import com.vmware.identity.idm.IIdentityStoreData;
import com.vmware.identity.idm.IdentityStoreAttributeMapping;
import com.vmware.identity.idm.IdentityStoreType;
import com.vmware.identity.idm.InvalidArgumentException;
import com.vmware.identity.idm.InvalidPrincipalException;
import com.vmware.identity.idm.KnownSamlAttributes;
import com.vmware.identity.idm.PasswordExpiredException;
import com.vmware.identity.idm.PersonDetail;
import com.vmware.identity.idm.PersonUser;
import com.vmware.identity.idm.PrincipalId;
import com.vmware.identity.idm.SearchResult;
import com.vmware.identity.idm.SecurityDomain;
import com.vmware.identity.idm.UserAccountLockedException;
import com.vmware.identity.idm.ValidateUtil;
import com.vmware.identity.idm.server.IdentityManager;
import com.vmware.identity.idm.server.ServerUtils;
import com.vmware.identity.idm.server.performance.IIdmAuthStatRecorder;
import com.vmware.identity.idm.server.provider.BaseLdapProvider;
import com.vmware.identity.idm.server.provider.ILdapSchemaMapping;
import com.vmware.identity.idm.server.provider.NoSuchGroupException;
import com.vmware.identity.idm.server.provider.PrincipalGroupLookupInfo;
import com.vmware.identity.idm.server.provider.activedirectory.ADSchemaMapping;
import com.vmware.identity.idm.server.provider.activedirectory.SecurityIdentifier;
import com.vmware.identity.interop.domainmanager.DomainAdapterFactory;
import com.vmware.identity.interop.domainmanager.DomainControllerInfo;
import com.vmware.identity.interop.domainmanager.DomainManagerException;
import com.vmware.identity.interop.domainmanager.DomainManagerNativeException;
import com.vmware.identity.interop.domainmanager.IDomainAdapter;
import com.vmware.identity.interop.ldap.DirectoryStoreProtocol;
import com.vmware.identity.interop.ldap.ILdapConnectionEx;
import com.vmware.identity.interop.ldap.ILdapEntry;
import com.vmware.identity.interop.ldap.ILdapMessage;
import com.vmware.identity.interop.ldap.ILdapPagedSearchResult;
import com.vmware.identity.interop.ldap.LdapConstants;
import com.vmware.identity.interop.ldap.LdapFilterString;
import com.vmware.identity.interop.ldap.LdapScope;
import com.vmware.identity.interop.ldap.LdapValue;
import com.vmware.identity.interop.ldap.NoSuchObjectLdapException;
import com.vmware.identity.performanceSupport.IIdmAuthStat.ActivityKind;
import com.vmware.identity.performanceSupport.IIdmAuthStat.EventLevel;
import com.vmware.identity.performanceSupport.LdapQueryStat;

public class LdapWithAdMappingsProvider extends BaseLdapProvider

{

    /**
     *http://support.microsoft.com/kb/305144
     */
    private static enum AccountControlFlag {
        // 0x00000002 ADS_UF_ACCOUNTDISABLE
        //     The user account is disabled.
        FLAG_DISABLED_ACCOUNT(0x00000002),
        // 0x00000010 ADS_UF_LOCKOUT
        //     The account is currently locked out.
        FLAG_LOCKED_ACCOUNT(0x00000010),
        //ADS_UF_DONT_EXPIRE_PASSWD
        //     When set, the password will not expire on this account.
        FLAG_DONT_EXPIRE_PASSWD(0x00010000),
        // 0x00800000 ADS_UF_PASSWORD_EXPIRED
        //     The user password has expired. This flag is created by the system using data from the
        //     Pwd-Last-Set attribute and the domain policy.
        FLAG_PASSWORD_EXPIRED(0x00800000);

        final int value;

        AccountControlFlag(int v) {
            value = v;
        }

        public int getValue() {
            return value;
        }

        boolean isSet(int accountFlags) {
            return 0 != (this.value & accountFlags);
        }
    }

    private static final IDiagnosticsLogger logger = DiagnosticsLoggerFactory
            .getLogger(LdapWithAdMappingsProvider.class);

    private static final int DEFAULT_PAGE_SIZE = 1000;
    private static final int DEFAULT_RANGE_SIZE = 1000;

    private final ILdapSchemaMapping _ldapSchemaMapping;

    // Other constants
    private static final long INTVLS_100NS_IN_SEC = 10 * 1000 * 1000;
    private static final long INTVLS_100NS_1600TO1970 = 11644473600L * INTVLS_100NS_IN_SEC;

    private static Map<IdpKey, UpnSuffixCache> _alternativeUpnSuffixCache = new ConcurrentHashMap<IdpKey, UpnSuffixCache>();

    private static final long upnSuffixRefreshThreshold = 60000 * 60 * 6; // 6 hours
    // Writes and reads of "volatile" long and double values are always atomic

    private PwdLifeTimeValue _defaultDomainPolicyPwdLifeTimeCache = null;

    private static final ConnectionInfoCache _connInfoCache = new ConnectionInfoCache();

    // keep in sync with sso-config tool
    // use matching_rule_in_chain for nested groups searches
    public static final int FLAG_AD_MATCHING_RULE_IN_CHAIN = 0x1;
    // utilize group search base dn for nested groups resolution in token
    public static final int FLAG_DO_NOT_USE_BASE_DN_FOR_NESTED_GROUPS = 0x2;
    // resolve only direct parent groups
    public static final int FLAG_DIRECT_GROUPS_ONLY = 0x4;

    // Enable site affinity
    public static final int FLAG_ENABLE_SITE_AFFINITY = 0x8;

    //mapped user attribute names
    private final String ATTR_NAME_USER_ACCT_CTRL;
    private final String ATTR_NAME_SAM_ACCOUNT;
    private final String ATTR_USER_PRINCIPAL_NAME;

    private final String tenantName;

    private class IdpKey {

        private String tenant;
        private String idpName;

        public IdpKey(String tenant, String idpName) {
            this.tenant = tenant;
            this.idpName = idpName;
        }

        public String getTenant() {
            return tenant;
        }

        public String getIdpName() {
            return idpName;
        }

        @Override
        public int hashCode() {
            return Objects.hash(tenant, idpName);
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof IdpKey)) {
                return false;
            }

            IdpKey otherKey = (IdpKey) o;

            return tenant.equals(otherKey.getTenant()) && idpName.equals(otherKey.getIdpName());
        }
    }

    private class UpnSuffixCache {
        private Set<String> suffixes;
        private long retrievedAt;

        public UpnSuffixCache(Set<String> suffixes, long retrievedAt) {
            this.suffixes = suffixes;
            this.retrievedAt = retrievedAt;
        }

        public Set<String> getSuffixes() {
            return suffixes;
        }

        public long retrievedAt() {
            return retrievedAt;
        }
    }

    public LdapWithAdMappingsProvider(String tenantName, IIdentityStoreData store) {
        this(tenantName, store, null);
    }

    public LdapWithAdMappingsProvider(String tenantName, IIdentityStoreData store,
            Collection<X509Certificate> tenantTrustedCertificates) {
        super(store, tenantTrustedCertificates);
        Validate.isTrue(
                this.getStoreDataEx()
                        .getProviderType() == IdentityStoreType.IDENTITY_STORE_TYPE_LDAP_WITH_AD_MAPPING,
                "IIdentityStoreData must represent a store of 'IDENTITY_STORE_TYPE_LDAP_WITH_AD_MAPPING' type.");

        Validate.notEmpty(tenantName, "Tenant name must not be empty or null");
        this.tenantName = tenantName;

        if (((this.getStoreDataEx().getFlags())
                & (~(FLAG_AD_MATCHING_RULE_IN_CHAIN | FLAG_DO_NOT_USE_BASE_DN_FOR_NESTED_GROUPS
                        | FLAG_DIRECT_GROUPS_ONLY | FLAG_ENABLE_SITE_AFFINITY))) != 0) {
            logger.warn(String.format("Unrecognized flags: [%d].", this.getStoreDataEx().getFlags()));
        } else {
            logger.debug(String.format("The IdentitySource flag is set to %d", this.getStoreDataEx().getFlags()));
        }

        _ldapSchemaMapping = new ADSchemaMapping(this.getStoreDataEx().getIdentityStoreSchemaMapping());
        ATTR_NAME_SAM_ACCOUNT = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
        ATTR_USER_PRINCIPAL_NAME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);
        ATTR_NAME_USER_ACCT_CTRL = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);

    }

    @Override
    public void probeConnectionSettings() throws Exception {
        if (!enableSiteAffinity())
            super.probeConnectionSettings();

        if (enableSiteAffinity()) {
            try (ILdapConnectionEx connection = getConnection()) {
                checkDn(connection);
            }
        }
    }

    /* The logic of retrieving upnSuffixes for AD domain follows
     * http://support.microsoft.com/kb/269441
     */
    private Set<String> getAlterUpnSuffixes() throws Exception {
        Set<String> alterUpnSuffixes = new HashSet<String>();

        ILdapConnectionEx connection = null;
        ILdapMessage message = null;
        String ATTR_CONFIG_NAMING_CONTEXT = "configurationNamingContext";
        String ATTR_ROOT_DOMAIN_NAMING_CONTEXT = "rootDomainNamingContext";
        String ATTR_UPN_SUFFIXES = "uPNSuffixes";
        String configNamingContext = null;
        String rootDomainNamingContext = null;
        String[] attrNames = new String[2];

        try {
            // Search in RootDSE should not in GC (upnSuffix attribute is not represent in GC)
            Collection<String> connStrs = this.getStoreDataEx().getConnectionStrings();
            ArrayList<String> newConnStrs = new ArrayList<String>();
            for (String conn : connStrs) {
                URI uri = null;
                try {
                    uri = new URI(conn);
                    if (uri.getPort() == LdapConstants.LDAP_GC_PORT
                            || uri.getPort() == LdapConstants.LDAP_SSL_GC_PORT) {
                        int index = conn.lastIndexOf(':');
                        newConnStrs.add(conn.substring(0, index));
                    } else {
                        newConnStrs.add(conn);
                    }
                } catch (URISyntaxException e) {
                    logger.warn(String.format("bad uri: [%s]", conn));
                }
            }

            connection = getConnection(newConnStrs, false);

            try { // Retrieve RootDSE object
                attrNames[0] = ATTR_CONFIG_NAMING_CONTEXT;
                attrNames[1] = ATTR_ROOT_DOMAIN_NAMING_CONTEXT;

                message = connection.search("", LdapScope.SCOPE_BASE, "(objectClass=*)", attrNames, false);

                ILdapEntry[] entries = message.getEntries();
                if (entries != null) {
                    if (entries.length > 1) {
                        String msg = String.format("Multiple RootDSE objects are found for domain: %s",
                                this.getDomain());
                        throw new IllegalStateException(msg);
                    }

                    configNamingContext = getStringValue(entries[0].getAttributeValues(ATTR_CONFIG_NAMING_CONTEXT));

                    rootDomainNamingContext = getStringValue(
                            entries[0].getAttributeValues(ATTR_ROOT_DOMAIN_NAMING_CONTEXT));
                }
            } catch (Exception e) {
                // Do not bail in case we cannot retrieve information from RootDSE
                // It just mean log in names using upnSuffix will not work
                logger.warn(String.format("Failed to retrieve information from RootDSE in AD Over Ldap provider %s",
                        this.getDomain()), e);
            } finally {
                if (message != null) {
                    message.close();
                }
            }

            if (!ServerUtils.isNullOrEmpty(configNamingContext)) {
                try {
                    // Retrieve CN=partitions, configNamingContext
                    attrNames[0] = ATTR_UPN_SUFFIXES;
                    message = connection.search("CN=Partitions," + configNamingContext, LdapScope.SCOPE_BASE,
                            "(objectClass=*)", attrNames, false);

                    ILdapEntry[] entries = message.getEntries();
                    if (entries != null) {
                        if (entries.length > 1) {
                            String msg = String.format(
                                    "Multiple partition configuration objects are found for domain: %s",
                                    this.getDomain());
                            throw new IllegalStateException(msg);
                        }

                        Collection<String> suffixes = this
                                .getOptionalStringValues(entries[0].getAttributeValues(ATTR_UPN_SUFFIXES));
                        if (suffixes != null && suffixes.size() > 0) {
                            for (String suffix : suffixes) {
                                alterUpnSuffixes.add(ValidateUtil.getCanonicalUpnSuffix(suffix));
                            }
                        }
                    }
                } catch (Exception e) {
                    // Do not bail in case we cannot retrieve attribute upnSuffix
                    // It just mean log in names using upnSuffix will not work
                    logger.error(String.format("Failed to retrieve attribute upnSuffix in AD Over Ldap provider %s",
                            this.getDomain()), e);
                } finally {
                    if (message != null) {
                        message.close();
                    }
                }
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return alterUpnSuffixes;
    }

    @Override
    public Set<String> getRegisteredUpnSuffixes() {
        UpnSuffixCache upnCache = _alternativeUpnSuffixCache.get(getIdpKey());
        long currentTime = System.currentTimeMillis();

        if (upnCache == null || upnCache.getSuffixes().isEmpty()
                || currentTime - upnCache.retrievedAt() >= upnSuffixRefreshThreshold) {
            try {
                Set<String> upnSuffixes = getAlterUpnSuffixes();
                upnCache = new UpnSuffixCache(upnSuffixes, currentTime);
                _alternativeUpnSuffixCache.put(getIdpKey(), upnCache);
            } catch (Exception e) {
                // Do not bail in case we cannot retrieve upnSuffixes, it just means login names using upnSuffix will not work
                logger.error(String.format("Failed to retrieve upnSuffixes in AD over LDAP provider '%s'",
                        this.getDomain()), e);
            }
        }

        return upnCache == null ? Collections.<String>emptySet() : upnCache.getSuffixes();
    }

    private IdpKey getIdpKey() {
        return new IdpKey(tenantName, getName());
    }

    @Override
    public PrincipalId authenticate(PrincipalId principal, String password) throws LoginException {
        ValidateUtil.validateNotNull(principal, "principal");

        IIdmAuthStatRecorder idmAuthStatRecorder = this.createIdmAuthStatRecorderInstance(
                DiagnosticsContextFactory.getCurrentDiagnosticsContext().getTenantName(), ActivityKind.AUTHENTICATE,
                EventLevel.INFO, principal);
        idmAuthStatRecorder.start();

        principal = this.normalizeAliasInPrincipal(principal);
        ILdapConnectionEx connection = null;

        try {
            connection = super.getConnection(getUserDN(principal), password, AuthenticationType.PASSWORD, false);
        } catch (Exception ex) {
            throw ((LoginException) new LoginException("Login failed").initCause(ex));
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        idmAuthStatRecorder.end();
        return principal;
    }

    @Override
    public Collection<AttributeValuePair> getAttributes(PrincipalId principalId, Collection<Attribute> attributes)
            throws Exception {
        List<AttributeValuePair> result = new ArrayList<AttributeValuePair>();

        assert (attributes != null);

        IIdmAuthStatRecorder idmAuthStatRecorder = this.createIdmAuthStatRecorderInstance(
                DiagnosticsContextFactory.getCurrentDiagnosticsContext().getTenantName(),
                ActivityKind.GETATTRIBUTES, EventLevel.INFO, principalId);
        idmAuthStatRecorder.start();

        List<String> attrNames = new ArrayList<String>();
        final String ATTR_NAME_SAM_ACCOUNT = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
        final String ATTR_PRIMARY_GROUP_ID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrimaryGroupId);
        final String ATTR_OBJECT_SID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);

        Map<String, String> attrMap = this.getStoreDataEx().getAttributeMap();
        for (Attribute attr : attributes) {
            if (KnownSamlAttributes.ATTRIBUTE_USER_GROUPS.equalsIgnoreCase(attr.getName())) {
                attrNames.add(ATTR_PRIMARY_GROUP_ID);
            } else if (KnownSamlAttributes.ATTRIBUTE_USER_PRINCIPAL_NAME.equalsIgnoreCase(attr.getName())) {
                // we need to retrieve ATTR_NAME_CN
                // to make sure we use user name exactly as it is stored
                // in identity provider when constructing UPN
                attrNames.add(ATTR_NAME_SAM_ACCOUNT);
            } else if (KnownSamlAttributes.ATTRIBUTE_USER_SUBJECT_TYPE.equalsIgnoreCase(attr.getName()) == false) {
                if (attrMap == null) {
                    throw new IllegalArgumentException(String.format("Attribute mapping is null.", attr.getName()));
                }

                String mappedAttr = attrMap.get(attr.getName());
                if (mappedAttr == null) {
                    throw new IllegalArgumentException(
                            String.format("No attribute mapping found for [%s]", attr.getName()));
                }
                attrNames.add(mappedAttr);
            }
        }

        // we always need object sid
        attrNames.add(ATTR_OBJECT_SID);

        ILdapConnectionEx connection = null;
        AccountLdapEntryInfo ldapEntryInfo = null;
        ILdapEntry userLdapEntry = null;

        try {
            connection = getConnection();

            String baseDN = this.getStoreDataEx().getUserBaseDn();

            // find user using upn first (unique in forest);
            final String filter_by_upn = this.buildUserQueryByUpn(principalId);
            final String filter_by_acct = this.buildUserQueryByAccountName(principalId);

            ldapEntryInfo = findAccountLdapEntry(connection, filter_by_upn, filter_by_acct, baseDN,
                    attrNames.toArray(new String[attrNames.size()]), false, principalId, idmAuthStatRecorder);

            userLdapEntry = ldapEntryInfo.accountLdapEntry;

            byte[] resultObjectSID = ServerUtils.getBinaryValue(userLdapEntry.getAttributeValues(ATTR_OBJECT_SID));

            SecurityIdentifier sid = SecurityIdentifier.build(resultObjectSID);
            String userObjectId = sid.toString();

            AttributeValuePair pairGroupSids = new AttributeValuePair();
            pairGroupSids.setAttrDefinition(new Attribute(IdentityManager.INTERNAL_ATTR_GROUP_OBJECTIDS));
            if (ServerUtils.isNullOrEmpty(userObjectId) == false) {
                pairGroupSids.getValues().add(userObjectId);
            }

            for (Attribute attr : attributes) {
                AttributeValuePair pair = new AttributeValuePair();
                pair.setAttrDefinition(attr);

                if (KnownSamlAttributes.ATTRIBUTE_USER_GROUPS.equalsIgnoreCase(attr.getName())) {
                    Set<Group> groups = new HashSet<Group>();

                    int primaryGroupRID = ServerUtils
                            .getIntValue(userLdapEntry.getAttributeValues(ATTR_PRIMARY_GROUP_ID));

                    byte[] userObjectSID = ServerUtils
                            .getBinaryValue(userLdapEntry.getAttributeValues(ATTR_OBJECT_SID));

                    String primaryGroupDN = this.getPrimaryGroupDNAndGroupObject(connection, userObjectSID,
                            primaryGroupRID, groups, idmAuthStatRecorder);

                    populateNestedGroups(connection, userLdapEntry.getDN(), true, groups, idmAuthStatRecorder);
                    if ((!this.useDirectGroupsOnly()) && (!ServerUtils.isNullOrEmpty(primaryGroupDN))) {
                        populateNestedGroups(connection, primaryGroupDN, true, groups, idmAuthStatRecorder);
                    }

                    for (Group group : groups) {
                        pair.getValues().add(group.getNetbios());
                        pairGroupSids.getValues().add(group.getObjectId());
                    }
                } else if (KnownSamlAttributes.ATTRIBUTE_USER_SUBJECT_TYPE.equalsIgnoreCase(attr.getName())) {
                    // no solution users in OpenLdap.
                    pair.getValues().add("false");
                } else if (KnownSamlAttributes.ATTRIBUTE_USER_PRINCIPAL_NAME.equalsIgnoreCase(attr.getName())) {
                    String userName = getStringValue(userLdapEntry.getAttributeValues(ATTR_NAME_SAM_ACCOUNT));

                    // Always return iUpn
                    String upn = String.format("%s@%s", userName,
                            ServerUtils.getDomainFromDN(userLdapEntry.getDN()));
                    pair.getValues().add(upn);
                } else {
                    // attrMap is not null, and has proper mapping which is verified above when we build
                    // ldap attributes list
                    String attrName = attrMap.get(attr.getName());

                    LdapValue[] values = userLdapEntry.getAttributeValues(attrName);

                    if (values != null && values.length > 0) {
                        for (LdapValue value : values) {
                            if (!value.isEmpty()) {
                                String val = value.toString();
                                pair.getValues().add(val);
                            }
                        }
                    }
                }
                result.add(pair);
            }

            result.add(pairGroupSids);
        } finally {
            if (ldapEntryInfo != null) {
                ldapEntryInfo.close_messages();
            }
            if (connection != null) {
                connection.close();
            }
        }

        idmAuthStatRecorder.end();
        return result;
    }

    protected String getPrimaryGroupDNAndGroupObject(ILdapConnectionEx connection, byte[] userObjectSID,
            int groupRID, Set<Group> groups, IIdmAuthStatRecorder authStatRecorder) throws NoSuchGroupException {
        String groupDn = null;
        String searchBaseDn = (this.useGroupBaseDnForNestedGroups()) ? this.getStoreDataEx().getGroupBaseDn()
                : ServerUtils.getDomainDN(this.getDomain());

        SecurityIdentifier sid = SecurityIdentifier.build(userObjectSID);
        sid.setRID(groupRID);
        String groupSidString = sid.toString();

        final String ATTR_NAME_GROUP_CN = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);

        String[] attrNames = new String[] { ATTR_NAME_GROUP_CN };

        String filter = String.format(_ldapSchemaMapping.getGroupQueryByObjectUniqueId(),
                LdapFilterString.encode(groupSidString));

        long startTime = System.nanoTime();

        ILdapMessage message = connection.search(searchBaseDn, LdapScope.SCOPE_SUBTREE, filter, attrNames, false);

        if (authStatRecorder != null) {
            long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
            authStatRecorder
                    .add(new LdapQueryStat(filter, searchBaseDn, getConnectionString(connection), elapsed, 1));
        }

        try {
            ILdapEntry[] entries = message.getEntries();

            if ((entries != null && entries.length > 0)) {
                if (entries.length != 1) {
                    throw new IllegalStateException("Entry length > 1");
                }

                ILdapEntry entry = entries[0];

                String groupName = ServerUtils.getStringValue(entry.getAttributeValues(ATTR_NAME_GROUP_CN));

                String groupDomainName = ServerUtils.getDomainFromDN(entry.getDN());

                PrincipalId groupId = new PrincipalId(groupName, groupDomainName);

                Group g = new Group(groupId, null/*alias*/, groupSidString, null/*detail*/ );
                groups.add(g);

                groupDn = entry.getDN();
            } else // we did not find the entry
            {
                if (!(this.useGroupBaseDnForNestedGroups())) {
                    logger.error(String.format("Unable to find primary group with SID=[%s]", groupSidString));
                }
            }
        } finally {
            message.close();
        }

        return groupDn;
    }

    @Override
    public PersonUser findUser(PrincipalId id) throws Exception {
        if (!this.belongsToThisIdentityProvider(id.getDomain())) {
            String msg = String.format(
                    "Domain of PrincipleId %s matches neither of the domain: [%s],"
                            + " alias: [%s] nor the registered upnSuffixes of the data store",
                    id, getStoreData().getName(), getStoreDataEx().getAlias());
            throw new InvalidPrincipalException(msg, id.getUPN());
        }

        PersonUser user = null;

        ILdapConnectionEx connection = getConnection();
        AccountLdapEntryInfo ldapEntryInfo = null;

        try {
            final String ATTR_NAME_SAM_ACCOUNT = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
            final String ATTR_FIRST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeFirstName);
            final String ATTR_LAST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLastName);
            final String ATTR_EMAIL_ADDRESS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeEmail);
            final String ATTR_DESCRIPTION = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeDescription);
            final String ATTR_OBJECT_SID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
            final String ATTR_USER_PRINCIPAL_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);

            final String ATTR_NAME_USER_ACCT_CTRL = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_RESULTANT_PSO = _ldapSchemaMapping.getUserAttribute(
                    IdentityStoreAttributeMapping.AttributeIds.UserAttributePasswordSettingsObject);
            final String ATTR_PWD_LAST_SET = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePwdLastSet);
            final String ATTR_NAME_LOCKOUT_TIME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);

            String[] attrNames = { ATTR_NAME_SAM_ACCOUNT, ATTR_USER_PRINCIPAL_NAME, ATTR_FIRST_NAME, ATTR_LAST_NAME,
                    ATTR_EMAIL_ADDRESS, ATTR_DESCRIPTION, ATTR_NAME_USER_ACCT_CTRL, ATTR_RESULTANT_PSO,
                    ATTR_PWD_LAST_SET, ATTR_NAME_LOCKOUT_TIME, ATTR_OBJECT_SID };

            final String filter_by_upn = this.buildUserQueryByUpn(id);
            final String filter_by_acct = this.buildUserQueryByAccountName(id);

            // Search from Users by default
            String searchBaseDn = this.getStoreDataEx().getUserBaseDn();

            ldapEntryInfo = findAccountLdapEntry(connection, filter_by_upn, filter_by_acct, searchBaseDn, attrNames,
                    false, id);

            int accountFlags = this.getOptionalIntegerValue(
                    ldapEntryInfo.accountLdapEntry.getAttributeValues(ATTR_NAME_USER_ACCT_CTRL), 0);

            user = this.buildPersonUser(ldapEntryInfo.accountLdapEntry, accountFlags, true, connection);
        } finally {
            if (ldapEntryInfo != null) {
                ldapEntryInfo.close_messages();
            }
            if (connection != null) {
                connection.close();
            }
        }

        return user;
    }

    private long getPwdLifeTime(ILdapConnectionEx connection, String psoDn) {

        if (StringUtils.isNotEmpty(psoDn)) {
            try {
                return getPwdLifeTimeFromPSO(connection, psoDn);
            } catch (Exception e) {
                return getPwdLifeTimeFromDefaultDomainPolicy(connection);
            }
        } else {
            return getPwdLifeTimeFromDefaultDomainPolicy(connection);
        }
    }

    private long getPwdLifeTimeFromPSO(ILdapConnectionEx connection, String psoDn) {
        // we might be able to cache this, will be done later since
        // findUser is not a critical function for perf.

        // PSO time attributes are 64-bit, in -100 ns intervals
        // http://support.microsoft.com/kb/954414

        assert (StringUtils.isNotEmpty(psoDn));
        long maximumPasswordAge = PersonDetail.UNSPECIFIED_PASSWORD_LIFE_TIME_VALUE;
        final String ATTR_MSDS_MAXIMUM_PASSWORD_AGE = _ldapSchemaMapping.getPwdObjectAttribute(
                IdentityStoreAttributeMapping.AttributeIds.PasswordSettingsAttributeMaximumPwdAge);
        String[] attributes = { ATTR_MSDS_MAXIMUM_PASSWORD_AGE };

        ILdapMessage message = null;
        try {
            message = connection.search(psoDn, LdapScope.SCOPE_BASE, _ldapSchemaMapping.getPasswordSettingsQuery(),
                    attributes, false);
            ILdapEntry[] entries = message.getEntries();
            if (entries == null || entries.length == 0) {
                throw new IllegalStateException(String.format("password policy not found: [%s]", psoDn));
            } else if (entries.length > 1) {
                throw new IllegalStateException(
                        String.format("[%d] duplicated password policy found: [%s]", entries.length, psoDn));
            } else {
                assert (entries.length == 1);
                maximumPasswordAge = getOptionalLongValue(
                        entries[0].getAttributeValues(ATTR_MSDS_MAXIMUM_PASSWORD_AGE),
                        PersonDetail.UNSPECIFIED_PASSWORD_LIFE_TIME_VALUE);
            }
        } finally {
            if (message != null) {
                message.close();
            }
        }

        return (-maximumPasswordAge) / INTVLS_100NS_IN_SEC;
    }

    private long getPwdLifeTimeFromDefaultDomainPolicy(ILdapConnectionEx connection) {
        if (_defaultDomainPolicyPwdLifeTimeCache == null) {
            synchronized (this) {
                if (_defaultDomainPolicyPwdLifeTimeCache == null) {// instantiation of immutable object
                    _defaultDomainPolicyPwdLifeTimeCache = new PwdLifeTimeValue(
                            retrievePwdLifeTimeFromDefaultDomainPolicy(connection));
                }
            }
        }
        return _defaultDomainPolicyPwdLifeTimeCache.value;
    }

    private long retrievePwdLifeTimeFromDefaultDomainPolicy(ILdapConnectionEx connection) {
        final String ATTR_MAX_PWD_AGE = _ldapSchemaMapping
                .getDomainObjectAttribute(IdentityStoreAttributeMapping.AttributeIds.DomainAttributeMaxPwdAge);
        String domainDN = ServerUtils.getDomainDN(this.getStoreData().getName());
        long maxPwdAge = PersonDetail.UNSPECIFIED_PASSWORD_LIFE_TIME_VALUE;
        String[] attributes = { ATTR_MAX_PWD_AGE };
        ILdapMessage message = null;
        try {
            message = connection.search(domainDN, LdapScope.SCOPE_BASE, _ldapSchemaMapping.getDomainObjectQuery(),
                    attributes, false);
            ILdapEntry[] entries = message.getEntries();
            if (entries == null || entries.length == 0) {
                throw new IllegalStateException("Default Domain Policy not found");
            } else if (entries.length > 1) {
                throw new IllegalStateException(
                        String.format("[%d] duplicated default domain policy found", entries.length));
            } else {
                assert (entries.length == 1);
                maxPwdAge = getOptionalLongValue(entries[0].getAttributeValues(ATTR_MAX_PWD_AGE),
                        PersonDetail.UNSPECIFIED_PASSWORD_LIFE_TIME_VALUE);
            }
        } finally {
            if (message != null) {
                message.close();
            }
        }
        return (-maxPwdAge) / INTVLS_100NS_IN_SEC;
    }

    /**
     *  Create an immutable, internal private class to ensure the usage of
     *  immutable object for double-checked locking idiom (JMM guarantees this)
     */

    private final class PwdLifeTimeValue {
        private final long value;

        public PwdLifeTimeValue(long aValue) {
            value = aValue;
        }
    }

    @Override
    public PersonUser findUserByObjectId(String userEntryUuid) throws Exception {
        PersonUser user = null;

        ILdapConnectionEx connection = getConnection();

        try {
            final String ATTR_NAME_CN = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
            final String ATTR_FIRST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeFirstName);
            final String ATTR_LAST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLastName);
            final String ATTR_EMAIL_ADDRESS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeEmail);
            final String ATTR_DESCRIPTION = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeDescription);
            final String ATTR_ACCOUNT_FLAGS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_OBJECT_SID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
            final String ATTR_USER_PRINCIPAL_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);

            final String ATTR_NAME_LOCKOUT_TIME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);
            final String ATTR_RESULTANT_PSO = _ldapSchemaMapping.getUserAttribute(
                    IdentityStoreAttributeMapping.AttributeIds.UserAttributePasswordSettingsObject);
            final String ATTR_PWD_LAST_SET = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePwdLastSet);

            String[] attrNames = { ATTR_NAME_CN, ATTR_USER_PRINCIPAL_NAME, ATTR_FIRST_NAME, ATTR_LAST_NAME,
                    ATTR_EMAIL_ADDRESS, ATTR_DESCRIPTION, ATTR_ACCOUNT_FLAGS, ATTR_NAME_LOCKOUT_TIME,
                    ATTR_OBJECT_SID, ATTR_RESULTANT_PSO, ATTR_PWD_LAST_SET };

            String filter = String.format(_ldapSchemaMapping.getUserQueryByObjectUniqueId(),
                    LdapFilterString.encode(userEntryUuid));

            // Search from Users by default
            String searchBaseDn = this.getStoreDataEx().getUserBaseDn();

            ILdapMessage message = null;
            try {
                message = connection.search(searchBaseDn, LdapScope.SCOPE_SUBTREE, filter, attrNames, false);

                ILdapEntry[] entries = message.getEntries();

                if (entries == null || entries.length != 1) {
                    // Use doesn't exist or multiple user same name
                    throw new InvalidPrincipalException(
                            String.format("user with entruUUID %s doesn't exist or multiple users with same name",
                                    userEntryUuid),
                            userEntryUuid);
                }

                int currentFlag = getOptionalIntegerValue(entries[0].getAttributeValues(ATTR_ACCOUNT_FLAGS), 0);

                user = this.buildPersonUser(entries[0], currentFlag, true, connection);
            } catch (NoSuchObjectLdapException e) {
                throw new InvalidPrincipalException(
                        String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage()), searchBaseDn);
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } finally {
            connection.close();
        }

        return user;
    }

    @Override
    public Set<PersonUser> findUsers(String searchString, String domainName, int limit) throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllUsersQuery(),
                _ldapSchemaMapping.getUserQueryByCriteria(), searchString);
        return findUsersInternal(filter, domainName, limit);
    }

    @Override
    public Set<PersonUser> findUsersByName(String searchString, String domainName, int limit) throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllUsersQuery(),
                _ldapSchemaMapping.getUserQueryByCriteriaForName(), searchString);
        return findUsersInternal(filter, domainName, limit);
    }

    private Set<PersonUser> findUsersInternal(String filter, String domainName, int limit) throws Exception {
        if (ServerUtils.isNullOrEmpty(domainName)) {
            throw new InvalidArgumentException("findUsersInternal failed - domainName should not be null or empty");
        }

        Set<PersonUser> users = new HashSet<PersonUser>();

        if (limit == 0) {
            return users;
        }

        ILdapConnectionEx connection = getConnection();

        try {
            final String ATTR_NAME_CN = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
            final String ATTR_USER_PRINCIPAL_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);
            final String ATTR_DESCRIPTION = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeDescription);
            final String ATTR_FIRST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeFirstName);
            final String ATTR_LAST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLastName);
            final String ATTR_EMAIL_ADDRESS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeEmail);
            final String ATTR_ACCOUNT_FLAGS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_NAME_LOCKOUT_TIME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);
            final String ATTR_OBJECT_SID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);

            String[] attrNames = { ATTR_NAME_CN, ATTR_USER_PRINCIPAL_NAME, ATTR_DESCRIPTION, ATTR_FIRST_NAME,
                    ATTR_LAST_NAME, ATTR_EMAIL_ADDRESS, ATTR_ACCOUNT_FLAGS, ATTR_NAME_LOCKOUT_TIME,
                    ATTR_OBJECT_SID };

            String searchBaseDn = getStoreDataEx().getUserBaseDn();

            try {
                Collection<ILdapMessage> messages = connection.paged_search(searchBaseDn, LdapScope.SCOPE_SUBTREE,
                        filter, Arrays.asList(attrNames), DEFAULT_PAGE_SIZE, limit);

                if (messages != null && messages.size() > 0) {
                    for (ILdapMessage message : messages) {
                        try {
                            ILdapEntry[] entries = message.getEntries();

                            if (entries != null && entries.length > 0) {
                                for (ILdapEntry entry : entries) {
                                    int flag = getOptionalIntegerValue(entry.getAttributeValues(ATTR_ACCOUNT_FLAGS),
                                            0);

                                    users.add(buildPersonUser(entry, flag, false, null));
                                }
                            }
                        } finally {
                            if (message != null) {
                                message.close();
                            }
                        }
                    }
                }
            } catch (NoSuchObjectLdapException e) {//need to return detail message
                throw new InvalidPrincipalException(
                        String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage()), searchBaseDn);
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return users;
    }

    @Override
    public Set<PersonUser> findUsersInGroup(PrincipalId groupId, String searchString, int limit) throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllUsersQuery(),
                _ldapSchemaMapping.getUserQueryByCriteria(), searchString);
        return findUsersInGroupInternal(groupId, filter, limit);
    }

    @Override
    public Set<PersonUser> findUsersByNameInGroup(PrincipalId groupId, String searchString, int limit)
            throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllUsersQuery(),
                _ldapSchemaMapping.getUserQueryByCriteriaForName(), searchString);
        return findUsersInGroupInternal(groupId, filter, limit);
    }

    private Set<PersonUser> findUsersInGroupInternal(PrincipalId groupId, String filter, int limit)
            throws Exception {
        ValidateUtil.validateNotNull(groupId, "groupId");

        Set<PersonUser> users = new HashSet<PersonUser>();

        if (limit == 0) {
            // Short circuit since they're asking for a list of nothing anyway
            return users;
        }

        ILdapConnectionEx connection = null;

        try {
            connection = getConnection();

            int currRange = 1;
            boolean bContinueSearch = true;
            Collection<PersonUser> usersInOneRange = Collections.emptyList();
            MemberDnsResult membersResult = new MemberDnsResult(new ArrayList<String>(), false);

            int currLimit = limit;
            int rangeSize = DEFAULT_RANGE_SIZE;
            if (limit > 0 && limit < DEFAULT_RANGE_SIZE) {
                rangeSize = limit;
            }

            do {
                membersResult = findMemberDnsInGroupInRange(connection, _ldapSchemaMapping, groupId,
                        ServerUtils.getDomainDN(this.getDomain()), currRange, rangeSize);

                if (membersResult != null && !membersResult.memberDns.isEmpty()) {
                    try {
                        usersInOneRange = this.findUsersByDNsWithFilter(connection, membersResult.memberDns,
                                filter);
                    } catch (Exception ex) {
                        logger.warn(
                                String.format(
                                        "Find users in group %s By distinguishedName failed for range %d - %d ",
                                        groupId.getUPN(), (currRange - 1) * rangeSize, currRange * rangeSize - 1),
                                ex);
                    }
                    if (!usersInOneRange.isEmpty()) {
                        if (usersInOneRange.size() < currLimit || currLimit < 0) {
                            users.addAll(usersInOneRange);
                            if (currLimit > 0) {
                                currLimit = currLimit - usersInOneRange.size();
                            }
                        } else {
                            users.addAll(new ArrayList<PersonUser>(usersInOneRange).subList(0, currLimit));
                            bContinueSearch = false;
                        }
                    }
                }
                currRange++;
            } while (membersResult.memberDns.size() == rangeSize && bContinueSearch
                    && !membersResult.bNoMoreEntries);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return users;
    }

    @Override
    public Set<PersonUser> findDisabledUsers(String searchString, int limit) throws Exception {
        return getUsersWithAccountControlFlag(searchString, AccountControlFlag.FLAG_DISABLED_ACCOUNT, limit);
    }

    @Override
    public Set<PersonUser> findLockedUsers(String searchString, int limit) throws Exception {
        return getUsersWithAccountControlFlag(searchString, AccountControlFlag.FLAG_LOCKED_ACCOUNT, limit);
    }

    @Override
    public PrincipalGroupLookupInfo findDirectParentGroups(PrincipalId principalId) throws Exception {
        Set<Group> groups = new HashSet<Group>();

        ILdapConnectionEx connection = getConnection();
        PrincipalInfo info = null;

        try {
            final String ATTR_NAME_CN = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
            final String ATTR_DESCRIPTION = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
            final String ATTR_OBJECT_SID = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

            String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_OBJECT_SID };
            info = getPrincipalDN(connection, principalId);
            String filter = String.format(_ldapSchemaMapping.getDirectParentGroupsQuery(),
                    LdapFilterString.encode(info.getDn()));

            try {
                Collection<ILdapMessage> messages = connection.paged_search(getStoreDataEx().getGroupBaseDn(),
                        LdapScope.SCOPE_SUBTREE, filter, Arrays.asList(attrNames), DEFAULT_PAGE_SIZE, -1);

                if (messages != null && messages.size() > 0) {
                    for (ILdapMessage message : messages) {
                        try {
                            ILdapEntry[] entries = message.getEntries();

                            if (entries != null && entries.length > 0) {
                                for (ILdapEntry entry : entries) {
                                    groups.add(buildGroup(entry, null));
                                }
                            }
                        } finally {
                            message.close();
                        }
                    }
                }
            } catch (NoSuchObjectLdapException e) {
                throw new InvalidPrincipalException(
                        String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage()),
                        getStoreDataEx().getGroupBaseDn());
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return new PrincipalGroupLookupInfo(groups, (info != null) ? info.getObjectId() : null);
    }

    @Override
    public PrincipalGroupLookupInfo findNestedParentGroups(PrincipalId userId) throws Exception {
        Set<Group> groups = new HashSet<Group>();

        ILdapConnectionEx connection = getConnection();
        String userObjectId = null;

        AccountLdapEntryInfo ldapEntryInfo = null;
        try {
            String baseDN = getStoreDataEx().getUserBaseDn();

            // look up user by upn first (upn is unique in forest)
            final String filter_by_upn = this.buildUserQueryByUpn(userId);
            final String filter_by_acct = this.buildUserQueryByAccountName(userId);
            final String ATTR_USER_OBJECTID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
            String attributes[] = { ATTR_USER_OBJECTID };

            ldapEntryInfo = findAccountLdapEntry(connection, filter_by_upn, filter_by_acct, baseDN, attributes,
                    false, userId);
            ILdapEntry userLdapEntry = ldapEntryInfo.accountLdapEntry;

            // NestedParentGroups includes the direct parents, as well as the grandparents.
            populateNestedGroups(connection, userLdapEntry.getDN(), false, groups, null);
            userObjectId = getObjectSid(userLdapEntry, ATTR_USER_OBJECTID);
        } finally {
            if (ldapEntryInfo != null) {
                ldapEntryInfo.close_messages();
            }
            if (connection != null) {
                connection.close();
            }
        }

        return new PrincipalGroupLookupInfo(groups, userObjectId);
    }

    @Override
    public Group findGroup(PrincipalId groupId) throws Exception {
        final String ATTR_NAME_GROUP_CN = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
        final String ATTR_OBJECT_SID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        String[] attrNames = { ATTR_NAME_GROUP_CN, ATTR_DESCRIPTION, ATTR_OBJECT_SID };
        String filter = String.format(_ldapSchemaMapping.getGroupQueryByAccountName(),
                LdapFilterString.encode(groupId.getName()));

        ILdapConnectionEx connection = null;

        try {
            connection = getConnection();
            ILdapMessage message = null;

            try {
                message = connection.search(getStoreDataEx().getGroupBaseDn(), LdapScope.SCOPE_SUBTREE, filter,
                        attrNames, false);
                ILdapEntry[] entries = message.getEntries();
                if (entries == null || entries.length == 0) {
                    return null; // if no group found
                } else if (entries.length != 1) {
                    throw new IllegalStateException("duplicated groups with name " + groupId.getName());
                }

                return buildGroup(entries[0], null /* no default group dn */);
            } catch (NoSuchObjectLdapException e) {
                throw new InvalidPrincipalException(
                        String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage()),
                        getStoreDataEx().getGroupBaseDn());
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public Group findGroupByObjectId(String groupEntryUuid) throws Exception {
        final String ATTR_NAME_GROUP_CN = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
        final String ATTR_OBJECT_SID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        String[] attrNames = { ATTR_NAME_GROUP_CN, ATTR_DESCRIPTION, ATTR_OBJECT_SID };
        String filter = String.format(_ldapSchemaMapping.getGroupQueryByObjectUniqueId(),
                LdapFilterString.encode(groupEntryUuid));

        ILdapConnectionEx connection = null;

        try {
            connection = getConnection();
            ILdapMessage message = null;

            try {
                message = connection.search(getStoreDataEx().getGroupBaseDn(), LdapScope.SCOPE_SUBTREE, filter,
                        attrNames, false);
                ILdapEntry[] entries = message.getEntries();
                if (entries == null || entries.length != 1) {
                    // Use doesn't exist or multiple user same name
                    throw new InvalidPrincipalException(
                            String.format("Group with entryUuid %s doesn't exist or multiple groups with same name",
                                    groupEntryUuid),
                            groupEntryUuid);
                }

                return buildGroup(entries[0], null /* no default group dn */);
            } catch (NoSuchObjectLdapException e) {
                throw new InvalidPrincipalException(
                        String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage()),
                        getStoreDataEx().getGroupBaseDn());
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public Set<Group> findGroups(String searchString, String domainName, int limit) throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllGroupsQuery(),
                _ldapSchemaMapping.getGroupQueryByCriteria(), searchString);
        return findGroupsInternal(filter, domainName, limit);
    }

    @Override
    public Set<Group> findGroupsByName(String searchString, String domainName, int limit) throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllGroupsQuery(),
                _ldapSchemaMapping.getGroupQueryByCriteriaForName(), searchString);
        return findGroupsInternal(filter, domainName, limit);
    }

    private Set<Group> findGroupsInternal(String filter, String domainName, int limit) throws Exception {
        if (ServerUtils.isNullOrEmpty(domainName)) {
            throw new InvalidArgumentException(
                    "findGroupsInternal failed - domainName should not be null or empty");
        }

        Set<Group> groups = new HashSet<Group>();

        // Short circuit since they're asking for an empty limit anyway
        if (limit == 0) {
            return groups;
        }

        ILdapConnectionEx connection = null;
        try {
            connection = getConnection();

            try {
                final String ATTR_NAME_CN = _ldapSchemaMapping
                        .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
                final String ATTR_DESCRIPTION = _ldapSchemaMapping
                        .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
                final String ATTR_OBJECT_SID = _ldapSchemaMapping
                        .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

                String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_OBJECT_SID };

                Collection<ILdapMessage> messages = connection.paged_search(getStoreDataEx().getGroupBaseDn(),
                        LdapScope.SCOPE_SUBTREE, filter, Arrays.asList(attrNames), DEFAULT_PAGE_SIZE, limit);

                if (messages != null && messages.size() > 0) {
                    for (ILdapMessage message : messages) {
                        try {
                            ILdapEntry[] entries = message.getEntries();

                            if (entries != null && entries.length > 0) {
                                for (ILdapEntry entry : entries) {
                                    groups.add(buildGroup(entry, null));
                                }
                            }
                        } finally {
                            message.close();
                        }
                    }
                }
            } catch (NoSuchObjectLdapException e) {
                throw new InvalidPrincipalException(
                        String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage()),
                        getStoreDataEx().getGroupBaseDn());
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return groups;
    }

    @Override
    public Set<Group> findGroupsInGroup(PrincipalId groupId, String searchString, int limit) throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllGroupsQuery(),
                _ldapSchemaMapping.getGroupQueryByCriteria(), searchString);
        return findGroupsInGroupInternal(groupId, filter, limit);
    }

    @Override
    public Set<Group> findGroupsByNameInGroup(PrincipalId groupId, String searchString, int limit)
            throws Exception {
        String filter = createSearchFilter(_ldapSchemaMapping.getAllGroupsQuery(),
                _ldapSchemaMapping.getGroupQueryByCriteriaForName(), searchString);
        return findGroupsInGroupInternal(groupId, filter, limit);
    }

    private Set<Group> findGroupsInGroupInternal(PrincipalId groupId, String filter, int limit) throws Exception {
        ValidateUtil.validateNotNull(groupId, "groupId");

        Set<Group> groups = new HashSet<Group>();

        if (limit == 0) {
            // Short circuit since they're asking for a list of nothing anyway
            return groups;
        }

        ILdapConnectionEx connection = null;

        try {
            connection = getConnection();
            int currRange = 1;
            boolean bContinueSearch = true;
            Collection<Group> groupsInOneRange = Collections.emptyList();
            MemberDnsResult membersResult = new MemberDnsResult(new ArrayList<String>(), false);

            int currLimit = limit;
            int rangeSize = DEFAULT_RANGE_SIZE;
            if (limit > 0 && limit < DEFAULT_RANGE_SIZE) {
                rangeSize = limit;
            }

            do {
                membersResult = findMemberDnsInGroupInRange(connection, _ldapSchemaMapping, groupId,
                        ServerUtils.getDomainDN(this.getDomain()), currRange, rangeSize);
                if (membersResult != null && !membersResult.memberDns.isEmpty()) {
                    try {
                        groupsInOneRange = this.findGroupsByDNsWithFilter(connection, membersResult.memberDns,
                                filter);
                    } catch (Exception ex) {
                        logger.warn(
                                String.format(
                                        "Find groups in group %s By distinguishedName failed for range %d - %d ",
                                        groupId.getUPN(), (currRange - 1) * rangeSize, currRange * rangeSize - 1),
                                ex);
                    }
                    if (!groupsInOneRange.isEmpty()) {
                        if (groupsInOneRange.size() < currLimit || currLimit < 0) {
                            groups.addAll(groupsInOneRange);
                            if (currLimit > 0) {
                                currLimit = currLimit - groupsInOneRange.size();
                            }
                        } else {
                            groups.addAll(new ArrayList<Group>(groupsInOneRange).subList(0, currLimit));
                            bContinueSearch = false;
                        }
                    }
                }
                currRange++;
            } while (membersResult.memberDns.size() == rangeSize && bContinueSearch
                    && !membersResult.bNoMoreEntries);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return groups;
    }

    @Override
    public SearchResult find(String searchString, String domainName, int limit) throws Exception {
        Set<PersonUser> users = this.findUsers(searchString, domainName, limit < 0 ? -1 : (limit / 2 + limit % 2));
        int limitGroup = limit < 0 ? -1 : (limit - ((users != null) ? users.size() : 0));
        Set<Group> groups = null;
        if (limitGroup != 0) {
            groups = this.findGroups(searchString, domainName, limitGroup);
        }
        return new SearchResult(users, null, groups);
    }

    @Override
    public SearchResult findByName(String searchString, String domainName, int limit) throws Exception {
        return new SearchResult(findUsersByName(searchString, domainName, limit < 0 ? -1 : limit / 2),
                null, /* service principals */
                findGroupsByName(searchString, domainName, limit < 0 ? -1 : limit / 2 + limit % 2));
    }

    @Override
    public boolean IsActive(PrincipalId id) throws Exception {
        return !AccountControlFlag.FLAG_DISABLED_ACCOUNT.isSet(retrieveUserAccountFlags(id));
    }

    @Override
    public void checkUserAccountFlags(PrincipalId principalId) throws IDMException {
        int accountFlags = retrieveUserAccountFlags(principalId);
        if (AccountControlFlag.FLAG_LOCKED_ACCOUNT.isSet(accountFlags)) {
            throw new UserAccountLockedException(String.format("User account locked: %s", principalId));
        } else if (AccountControlFlag.FLAG_PASSWORD_EXPIRED.isSet(accountFlags)) {
            throw new PasswordExpiredException(String.format("User account expired: %s", principalId));
        }
    }

    private int retrieveUserAccountFlags(PrincipalId id) throws IDMException {
        int accountFlags = 0;
        ILdapConnectionEx connection = null;
        AccountLdapEntryInfo ldapEntryInfo = null;
        ILdapEntry userLdapEntry = null;

        try {
            connection = getConnection();
        } catch (Exception e) {
            throw new IDMException("Cannot established a connection to server", e);
        }

        try {
            String searchBaseDn = this.getStoreDataEx().getUserBaseDn();

            final String filter_by_upn = this.buildUserQueryByUpn(id);
            final String filter_by_acct = this.buildUserQueryByAccountName(id);

            final String ATTR_ACCOUNT_FLAGS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_NAME_LOCKOUT_TIME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);

            String attributes[] = { ATTR_ACCOUNT_FLAGS, ATTR_NAME_LOCKOUT_TIME };

            ldapEntryInfo = findAccountLdapEntry(connection, filter_by_upn, filter_by_acct, searchBaseDn,
                    attributes, false, id);

            userLdapEntry = ldapEntryInfo.accountLdapEntry;

            accountFlags = getOptionalIntegerValue(userLdapEntry.getAttributeValues(attributes[0]), 0);
            long lockoutTime = getOptionalLongValue(userLdapEntry.getAttributeValues(ATTR_NAME_LOCKOUT_TIME),
                    PersonDetail.UNSPECIFIED_LOCKOUT_TIME_VALUE);

            if (lockoutTime >= 1) {
                accountFlags = (accountFlags | AccountControlFlag.FLAG_LOCKED_ACCOUNT.value);
            }
        } finally {
            if (ldapEntryInfo != null) {
                ldapEntryInfo.close_messages();
            }
            if (connection != null) {
                connection.close();
            }
        }

        return accountFlags;
    }

    private String getUserDN(PrincipalId id) throws Exception {
        ILdapConnectionEx connection = null;
        AccountLdapEntryInfo ldapEntryInfo = null;
        try {
            connection = getConnection();

            String[] attrNames = { null };

            // find user using upn first (unique in forest)
            final String filter_by_upn = this.buildUserQueryByUpn(id);
            final String filter_by_acct = this.buildUserQueryByAccountName(id);

            // Search from Users by default
            String searchBaseDn = this.getStoreDataEx().getUserBaseDn();

            ldapEntryInfo = findAccountLdapEntry(connection, filter_by_upn, filter_by_acct, searchBaseDn, attrNames,
                    true, id);

            return ldapEntryInfo.accountLdapEntry.getDN();
        } finally {
            if (ldapEntryInfo != null) {
                ldapEntryInfo.close_messages();
            }
            if (connection != null) {
                connection.close();
            }
        }
    }

    Group buildGroup(ILdapEntry entry, String groupDN) {
        final String ATTR_NAME_GROUP_CN = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
        final String ATTR_OBJECT_SID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        LdapValue[] values = entry.getAttributeValues(ATTR_NAME_GROUP_CN);

        if (values == null || values.length != 1) {
            throw new IllegalStateException(
                    "No cn attribute or multiple cn attributes for the group entry: " + entry);
        }

        if (values[0].isEmpty()) {
            throw new IllegalStateException("Error : Group has no name: " + entry);
        }

        String groupName = values[0].toString();

        if (groupDN == null) {
            groupDN = entry.getDN();
        }

        byte[] resultObjectSID = ServerUtils.getBinaryValue(entry.getAttributeValues(ATTR_OBJECT_SID));

        SecurityIdentifier sid = SecurityIdentifier.build(resultObjectSID);
        String groupEntryObjectSid = sid.toString();

        String description = getOptionalLastStringValue(entry.getAttributeValues(ATTR_DESCRIPTION));

        PrincipalId gid = ServerUtils.getPrincipalId(null, groupName, ServerUtils.getDomainFromDN(groupDN));

        PrincipalId alias = ServerUtils.getPrincipalAliasId(groupName, this.getStoreDataEx().getAlias());

        return new Group(gid, alias, groupEntryObjectSid, new GroupDetail(description));
    }

    Set<Group> populateNestedGroups(ILdapConnectionEx connection, String dn, boolean groupNameOnly,
            Set<Group> groups, IIdmAuthStatRecorder authStatRecorder)
            throws NoSuchGroupException, InvalidPrincipalException {
        Validate.notNull(groups, "groups");

        final String ATTR_NAME_GROUP_CN = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
        final String ATTR_GROUP_SID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        ArrayList<String> attributeNames = new ArrayList<String>();
        attributeNames.add(ATTR_NAME_GROUP_CN);
        attributeNames.add(ATTR_GROUP_SID);
        if (!groupNameOnly) {
            attributeNames.add(ATTR_DESCRIPTION);
        }

        final boolean bRecurse = (!this.useDirectGroupsOnly()) && (!this.useMatchingRuleInChain());
        final String groupSearchBaseDn = (this.useGroupBaseDnForNestedGroups()
                ? this.getStoreDataEx().getGroupBaseDn()
                : ServerUtils.getDomainDN(this.getDomain()));
        final String searchQueryTemplate = this.useMatchingRuleInChain()
                ? _ldapSchemaMapping.getNestedParentGroupsQuery()
                : _ldapSchemaMapping.getDirectParentGroupsQuery();

        if (logger.isDebugEnabled()) {
            logger.debug(String.format(
                    "LdapWithAdMappingsProvider.populateNestedGroups -- GroupSearchBaseDn: %s; query template: %s",
                    groupSearchBaseDn, searchQueryTemplate));
        }
        int numberOfLdapSearches = 0;

        HashSet<String> groupsProcessed = new HashSet<String>();
        Stack<String> groupsToProcess = new Stack<String>();

        if (ServerUtils.isNullOrEmpty(dn) == false) {
            groupsToProcess.push(dn);
        }

        long startTimeForAllGrups = System.nanoTime();

        while (groupsToProcess.isEmpty() == false) {
            String currentDn = groupsToProcess.pop();
            groupsProcessed.add(currentDn);

            String filter = String.format(searchQueryTemplate,
                    (this.useMatchingRuleInChain() ? LdapFilterString.encodeMatchingRuleInChainDnFilter(currentDn)
                            : LdapFilterString.encode(currentDn)));

            String groupName = null;
            String groupDescription = null;
            String groupEntryObjectSid = null;

            ILdapPagedSearchResult prev_pagedResult = null;
            ILdapPagedSearchResult pagedResult = null;
            boolean isSearchFinished = false;

            try {
                int numOfQueriesPerGroup = 0;
                long startTimePerGroup = System.nanoTime();

                while (!isSearchFinished) {
                    if (logger.isTraceEnabled()) {
                        logger.trace(String.format(
                                "LdapWithAdMappingsProvider.populateNestedGroups -- running connection.search_one_page( %s )",
                                filter));
                    }
                    pagedResult = connection.search_one_page(groupSearchBaseDn, LdapScope.SCOPE_SUBTREE, filter,
                            attributeNames, DEFAULT_PAGE_SIZE, prev_pagedResult);

                    numOfQueriesPerGroup += 1;
                    numberOfLdapSearches += 1;

                    if (pagedResult != null) {
                        ILdapEntry[] entries = pagedResult.getEntries();
                        if ((entries != null) && (entries.length > 0)) {
                            for (ILdapEntry entry : entries) {
                                groupName = ServerUtils
                                        .getStringValue(entry.getAttributeValues(ATTR_NAME_GROUP_CN));

                                if (groupNameOnly == false) {
                                    groupDescription = ServerUtils
                                            .getStringValue(entry.getAttributeValues(ATTR_DESCRIPTION));
                                }

                                byte[] resultObjectSID = ServerUtils
                                        .getBinaryValue(entry.getAttributeValues(ATTR_GROUP_SID));

                                SecurityIdentifier sid = SecurityIdentifier.build(resultObjectSID);
                                groupEntryObjectSid = sid.toString();

                                String groupDomainName = ServerUtils.getDomainFromDN(entry.getDN());

                                PrincipalId groupId = new PrincipalId(groupName, groupDomainName);
                                PrincipalId groupAlias = null;
                                GroupDetail groupDetail = null;

                                if (groupNameOnly == false) {
                                    // If group lives in the registered Ad over Ldap IDP, we know the alias
                                    // Otherwise, we do not know the alias for the domain where group lives.
                                    if (groupDomainName.equalsIgnoreCase(this.getStoreData().getName())) {
                                        groupAlias = ServerUtils.getPrincipalAliasId(groupName, this.getAlias());
                                    }

                                    groupDetail = new GroupDetail(
                                            (groupDescription == null) ? "" : groupDescription);
                                }

                                Group g = new Group(groupId, groupAlias, groupEntryObjectSid, groupDetail);
                                groups.add(g);

                                if ((bRecurse == true) && (groupsProcessed.contains(entry.getDN()) == false)) {
                                    groupsToProcess.add(entry.getDN());
                                }
                            }
                        }
                    }
                    isSearchFinished = (pagedResult == null) || (pagedResult.isSearchFinished());
                    if (prev_pagedResult != null) {
                        prev_pagedResult.close();
                        prev_pagedResult = null;
                    }
                    prev_pagedResult = pagedResult;
                    pagedResult = null;
                } // while !isSearchFinished

                // If summarizeLdapQueries is set false, log each ldap query
                if (authStatRecorder != null && !authStatRecorder.summarizeLdapQueries()) {
                    authStatRecorder
                            .add(new LdapQueryStat(filter, groupSearchBaseDn, getConnectionString(connection),
                                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimePerGroup),
                                    numOfQueriesPerGroup));
                }
            } finally {
                if (prev_pagedResult != null) {
                    prev_pagedResult.close();
                    prev_pagedResult = null;
                }
                if (pagedResult != null) {
                    pagedResult.close();
                    pagedResult = null;
                }
            }

        } // groupstoprocess not empty

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("LdapWithAdMappingsProvider.populateNestedGroups -- ran [%d] ldap searches.",
                    numberOfLdapSearches));
        }

        // If summarizeLdapQueries is set true, log once only with summary
        if (authStatRecorder != null && authStatRecorder.summarizeLdapQueries()) {
            authStatRecorder.add(new LdapQueryStat(searchQueryTemplate, groupSearchBaseDn,
                    getConnectionString(connection),
                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimeForAllGrups), numberOfLdapSearches));
        }

        return groups;
    }

    protected Collection<PersonUser> findUsersByDNsWithFilter(ILdapConnectionEx connection,
            Collection<String> memberDNs, String filter) throws Exception {
        ArrayList<PersonUser> users = new ArrayList<PersonUser>();

        final String ATTR_NAME_CN = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeDescription);
        final String ATTR_FIRST_NAME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeFirstName);
        final String ATTR_LAST_NAME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLastName);
        final String ATTR_EMAIL_ADDRESS = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeEmail);
        final String ATTR_USER_PRINCIPAL_NAME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);
        final String ATTR_ACCOUNT_FLAGS = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
        final String ATTR_NAME_LOCKOUT_TIME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);
        final String ATTR_OBJECT_SID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);

        String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_FIRST_NAME, ATTR_LAST_NAME, ATTR_EMAIL_ADDRESS,
                ATTR_USER_PRINCIPAL_NAME, ATTR_ACCOUNT_FLAGS, ATTR_NAME_LOCKOUT_TIME, ATTR_OBJECT_SID };

        if (memberDNs != null && !memberDNs.isEmpty()) {
            filter = _ldapSchemaMapping.getDNFilter(filter, memberDNs);

            ILdapMessage message = null;
            try {
                message = connection.search(ServerUtils.getDomainDN(this.getDomain()), LdapScope.SCOPE_SUBTREE,
                        filter, attrNames, false);
                if (message.getEntries() != null && message.getEntries().length > 0) {
                    ILdapEntry[] userEntries = message.getEntries();

                    if (userEntries != null) {
                        for (ILdapEntry userEntry : userEntries) {
                            int flag = this
                                    .getOptionalIntegerValue(userEntry.getAttributeValues(ATTR_ACCOUNT_FLAGS), 0);

                            PersonUser user = this.buildPersonUser(userEntry, flag, false, null);
                            if (user != null) {
                                users.add(user);
                            }
                        }
                    }
                }
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        }

        return users;
    }

    private PersonUser buildPersonUser(ILdapEntry entry, int accountFlags, boolean provideExtendedAccountInfo,
            ILdapConnectionEx connection) {
        if (provideExtendedAccountInfo) {
            ValidateUtil.validateNotNull(connection, "connection");
        } else {
            ValidateUtil.validateNull(connection, "connection");
        }
        final String ATTR_FIRST_NAME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeFirstName);
        final String ATTR_LAST_NAME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLastName);
        final String ATTR_EMAIL_ADDRESS = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeEmail);
        final String ATTR_USER_PRINCIPAL_NAME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeDescription);
        final String ATTR_NAME_SAM_ACCOUNT = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);

        final String ATTR_NAME_LOCKOUT_TIME = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);
        final String ATTR_OBJECT_SID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
        final String ATTR_RESULTANT_PSO = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePasswordSettingsObject);
        final String ATTR_PWD_LAST_SET = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePwdLastSet);

        String accountName = getStringValue(entry.getAttributeValues(ATTR_NAME_SAM_ACCOUNT));

        String description = getOptionalLastStringValue(entry.getAttributeValues(ATTR_DESCRIPTION));

        String firstName = getOptionalStringValue(entry.getAttributeValues(ATTR_FIRST_NAME));

        String lastName = getOptionalStringValue(entry.getAttributeValues(ATTR_LAST_NAME));

        String email = getOptionalStringValue(entry.getAttributeValues(ATTR_EMAIL_ADDRESS));

        String upn = getOptionalStringValue(entry.getAttributeValues(ATTR_USER_PRINCIPAL_NAME));

        PrincipalId id = ServerUtils.getPrincipalId(upn, accountName, ServerUtils.getDomainFromDN(entry.getDN()));

        PrincipalId alias = ServerUtils.getPrincipalAliasId(accountName, getStoreDataEx().getAlias());

        String resultObjectSid = null;
        long pwdLastSet = PersonDetail.UNSPECIFIED_TS_VALUE;
        long lockoutTime = PersonDetail.UNSPECIFIED_LOCKOUT_TIME_VALUE;
        long pwdLifeTime = PersonDetail.UNSPECIFIED_PASSWORD_LIFE_TIME_VALUE;

        if (provideExtendedAccountInfo) {
            byte[] resultObjectSID = ServerUtils.getBinaryValue(entry.getAttributeValues(ATTR_OBJECT_SID));

            SecurityIdentifier sid = SecurityIdentifier.build(resultObjectSID);
            resultObjectSid = sid.toString();

            String resultantPSODn = getOptionalStringValue(entry.getAttributeValues(ATTR_RESULTANT_PSO));

            pwdLastSet = getOptionalLongValue(entry.getAttributeValues(ATTR_PWD_LAST_SET),
                    PersonDetail.UNSPECIFIED_TS_VALUE);

            lockoutTime = getOptionalLongValue(entry.getAttributeValues(ATTR_NAME_LOCKOUT_TIME),
                    PersonDetail.UNSPECIFIED_LOCKOUT_TIME_VALUE);

            if (pwdLastSet != PersonDetail.UNSPECIFIED_TS_VALUE) {
                // timestamp from AD is in 100 nanosecond intervals since January 1, 1601 (UTC)
                // Need to be converted to in secs since Unix epoch (January 1, 1970 (UTC)
                pwdLastSet = (pwdLastSet - INTVLS_100NS_1600TO1970) / INTVLS_100NS_IN_SEC;
            }

            // if pwdNeverExpired is set in user account control, treat it as non-defined pwd life time.
            if (!AccountControlFlag.FLAG_DONT_EXPIRE_PASSWD.isSet(accountFlags)) {
                pwdLifeTime = getPwdLifeTime(connection, resultantPSODn);
            }
        }

        PersonDetail detail = new PersonDetail.Builder().firstName(firstName).lastName(lastName)
                .userPrincipalName(upn).emailAddress(email).description(description)
                .pwdLastSet(pwdLastSet, IdentityStoreType.IDENTITY_STORE_TYPE_LDAP_WITH_AD_MAPPING)
                .pwdLifeTime(pwdLifeTime, IdentityStoreType.IDENTITY_STORE_TYPE_LDAP_WITH_AD_MAPPING).build();

        return new PersonUser(id, alias, resultObjectSid, detail,
                AccountControlFlag.FLAG_DISABLED_ACCOUNT.isSet(accountFlags),
                AccountControlFlag.FLAG_LOCKED_ACCOUNT.isSet(accountFlags) || (lockoutTime >= 1));
    }

    private class PrincipalInfo {
        private String _dn;
        private String _objectId;

        public PrincipalInfo(String dn, String objectId) {
            this._dn = dn;
            this._objectId = objectId;
        }

        public String getDn() {
            return this._dn;
        }

        public String getObjectId() {
            return this._objectId;
        }
    }

    private PrincipalInfo getPrincipalDN(ILdapConnectionEx connection, PrincipalId principalId) throws Exception {
        final String ATTR_USER_OBJECT_ID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
        final String ATTR_GROUP_OBJECT_ID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        String[] attrNames = { ATTR_USER_OBJECT_ID, ATTR_GROUP_OBJECT_ID, null };
        AccountLdapEntryInfo ldapEntryInfo = null;

        // search user/groups with samAccount first
        final String filter_by_acct = buildUserOrGroupFilterByAccountName(principalId);
        final String filter_by_upn = buildUserQueryByUpn(principalId);

        try {
            ldapEntryInfo = findAccountLdapEntry(connection, filter_by_upn, filter_by_acct,
                    ServerUtils.getDomainDN(getStoreData().getName()), attrNames, false, principalId);
            String objectId = getObjectSid(ldapEntryInfo.accountLdapEntry, ATTR_USER_OBJECT_ID);
            if (ServerUtils.isNullOrEmpty(objectId)) {
                objectId = getObjectSid(ldapEntryInfo.accountLdapEntry, ATTR_GROUP_OBJECT_ID);
            }
            return new PrincipalInfo(ldapEntryInfo.accountLdapEntry.getDN(), objectId);
        } finally {
            if (ldapEntryInfo != null) {
                ldapEntryInfo.close_messages();
            }
        }
    }

    private static String getObjectSid(ILdapEntry entry, String objectSidAttributeName) {
        String sidStr = null;
        if ((entry != null) && (ServerUtils.isNullOrEmpty(objectSidAttributeName) == false)) {
            byte[] resultObjectSID = ServerUtils.getBinaryValue(entry.getAttributeValues(objectSidAttributeName));

            if ((resultObjectSID != null) && (resultObjectSID.length != 0)) {
                SecurityIdentifier sid = SecurityIdentifier.build(resultObjectSID);
                sidStr = sid.toString();
            }
        }
        return sidStr;
    }

    Set<PersonUser> getUsersWithAccountControlFlag(String searchString, AccountControlFlag controlBit, int limit)
            throws Exception {
        Set<PersonUser> users = new HashSet<PersonUser>();

        ILdapConnectionEx connection = null;

        try {
            final String ATTR_NAME_CN = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
            final String ATTR_DESCRIPTION = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeDescription);
            final String ATTR_FIRST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeFirstName);
            final String ATTR_LAST_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLastName);
            final String ATTR_EMAIL_ADDRESS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeEmail);
            final String ATTR_USER_PRINCIPAL_NAME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);
            final String ATTR_ACCOUNT_FLAGS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_NAME_LOCKOUT_TIME = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);
            final String ATTR_OBJECT_SID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);

            connection = getConnection();
            String[] attrNames = { ATTR_NAME_CN, ATTR_USER_PRINCIPAL_NAME, ATTR_DESCRIPTION, ATTR_FIRST_NAME,
                    ATTR_LAST_NAME, ATTR_EMAIL_ADDRESS, ATTR_ACCOUNT_FLAGS, ATTR_NAME_LOCKOUT_TIME,
                    ATTR_OBJECT_SID };

            String searchBaseDn = getStoreDataEx().getUserBaseDn();

            //find all users -- perf hit? we might need index on userAccessControl.
            try {
                String filter = searchString.isEmpty() ? _ldapSchemaMapping.getAllUsersQuery()
                        : String.format(_ldapSchemaMapping.getUserQueryByCriteria(),
                                LdapFilterString.encode(searchString));

                Collection<ILdapMessage> messages = connection.paged_search(searchBaseDn, LdapScope.SCOPE_SUBTREE,
                        filter, Arrays.asList(attrNames), DEFAULT_PAGE_SIZE, limit);

                if (messages != null && messages.size() > 0) {
                    for (ILdapMessage message : messages) {
                        try {
                            ILdapEntry[] entries = message.getEntries();

                            if (entries != null && entries.length > 0) {
                                for (ILdapEntry entry : entries) {
                                    int flags = getOptionalIntegerValue(
                                            entry.getAttributeValues(ATTR_ACCOUNT_FLAGS), 0);

                                    long lockoutTime = getOptionalLongValue(
                                            entry.getAttributeValues(ATTR_NAME_LOCKOUT_TIME),
                                            PersonDetail.UNSPECIFIED_LOCKOUT_TIME_VALUE);

                                    boolean isBitSet = (controlBit.isSet(flags)
                                            || ((AccountControlFlag.FLAG_LOCKED_ACCOUNT.isSet(controlBit.value))
                                                    && (lockoutTime >= 1)));

                                    if (!isBitSet) {
                                        continue;
                                    }
                                    users.add(buildPersonUser(entry, flags, false, null));
                                }
                            }
                        } finally {
                            message.close();
                        }
                    }
                }
            } catch (NoSuchObjectLdapException e) {
                throw new InvalidPrincipalException(
                        String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage()), searchBaseDn);
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return users;
    }

    protected Collection<Group> findGroupsByDNsWithFilter(ILdapConnectionEx connection,
            Collection<String> memberDNs, String filter) throws Exception {
        ArrayList<Group> groups = new ArrayList<Group>();

        final String ATTR_NAME_CN = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
        final String ATTR_OBJECT_SID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_OBJECT_SID };

        if (memberDNs != null && !memberDNs.isEmpty()) {
            filter = _ldapSchemaMapping.getDNFilter(filter, memberDNs);

            ILdapMessage message = null;
            try {
                message = connection.search(ServerUtils.getDomainDN(this.getDomain()), LdapScope.SCOPE_SUBTREE,
                        filter, attrNames, false);
                if (message.getEntries() != null && message.getEntries().length > 0) {
                    ILdapEntry[] groupEntries = message.getEntries();

                    if (groupEntries != null) {
                        for (ILdapEntry groupEntry : groupEntries) {
                            Group group = this.buildGroup(groupEntry, null);
                            if (group != null) {
                                groups.add(group);
                            }
                        }
                    }
                }

            } finally {
                if (message != null) {
                    message.close();
                }
            }
        }

        return groups;
    }

    private String buildUserOrGroupFilterByAccountName(PrincipalId principal) {
        ValidateUtil.validateNotNull(principal, "principal");

        String escapedsAMAccountName = LdapFilterString.encode(principal.getName());
        return String.format(this._ldapSchemaMapping.getUserOrGroupQueryByAccountName(), escapedsAMAccountName);
    }

    private String buildUserQueryByUpn(PrincipalId principal) {
        ValidateUtil.validateNotNull(principal, "principal");

        String escapedPrincipalName = LdapFilterString.encode(GetUPN(principal));
        return String.format(this._ldapSchemaMapping.getUserQueryByUpn(), escapedPrincipalName);
    }

    private String buildUserQueryByAccountName(PrincipalId principal) {
        ValidateUtil.validateNotNull(principal, "principal");

        String escapedsAMAccountName = LdapFilterString.encode(principal.getName());
        return String.format(this._ldapSchemaMapping.getUserQueryByAccountName(), escapedsAMAccountName);
    }

    private boolean useDirectGroupsOnly() {
        return (this.getStoreDataEx().getFlags() & LdapWithAdMappingsProvider.FLAG_DIRECT_GROUPS_ONLY) != 0;
    }

    private boolean useMatchingRuleInChain() {
        return (((this.getStoreDataEx().getFlags()
                & LdapWithAdMappingsProvider.FLAG_AD_MATCHING_RULE_IN_CHAIN) != 0)
                && (this.useDirectGroupsOnly() == false));
    }

    private boolean useGroupBaseDnForNestedGroups() {
        return (this.getStoreDataEx().getFlags()
                & LdapWithAdMappingsProvider.FLAG_DO_NOT_USE_BASE_DN_FOR_NESTED_GROUPS) == 0;
    }

    private boolean enableSiteAffinity() {
        return (this.getStoreDataEx().getFlags() & LdapWithAdMappingsProvider.FLAG_ENABLE_SITE_AFFINITY) != 0;
    }

    @Override
    protected ILdapConnectionEx getConnection() throws Exception {
        if (enableSiteAffinity()) {
            return getConnectionWithSiteAffinity();
        } else {
            return super.getConnection();
        }
    }

    private ILdapConnectionEx getConnectionWithdomainStr(String domainNameStr, boolean bSsl) throws Exception {
        ILdapConnectionEx connection = null;
        ArrayList<String> connStrs = new ArrayList<String>();

        try {
            // Get GC connection first
            String connStr = String.format("%s://%s:%d", bSsl ? "ldaps" : "ldap", domainNameStr,
                    bSsl ? LdapConstants.LDAP_SSL_GC_PORT : LdapConstants.LDAP_GC_PORT);
            connStrs.add(connStr);
            logger.trace(String.format("The affinitized connection is %s", connStr));
            connection = super.getConnection(connStrs, false);
            _connInfoCache.addConnInfo(this.getDomain(), connStrs);

            return connection;
        } catch (Exception gc_ex) {
            String errMsg = String.format("Failed to get GC connection to domain %s due to %s", domainNameStr,
                    gc_ex.getMessage());
            logger.error(errMsg, gc_ex);
        }

        // Continue attempt to get non-GC connection
        try {
            String connStr = String.format("%s://%s:%d", bSsl ? "ldaps" : "ldap", domainNameStr,
                    bSsl ? LdapConstants.LDAP_SSL_PORT : LdapConstants.LDAP_PORT);
            connStrs.add(connStr);
            logger.trace(String.format("The affinitized connection is %s", connStr));
            connection = super.getConnection(connStrs, false);
            _connInfoCache.addConnInfo(this.getDomain(), connStrs);

            return connection;
        } catch (Exception ex) {
            String errMsg = String.format("Failed to get a connection to domain %s due to %s", domainNameStr,
                    ex.getMessage());
            logger.error(errMsg, ex);

            throw ex;
        }
    }

    private ILdapConnectionEx getConnectionWithSiteAffinity() throws Exception {
        boolean bForceRediscover = false;
        String domainName = this.getDomain();
        ArrayList<String> connStrs = _connInfoCache.findConnInfo(domainName);
        if (connStrs != null && !connStrs.isEmpty()) {
            try {
                return super.getConnection(connStrs, false);
            } catch (Exception ex) {
                logger.error(
                        String.format("Failed to get ldap connection with cached connection [%s] " + "due to %s",
                                connStrs.get(0), ex.getMessage()),
                        ex);
                _connInfoCache.deleteConnInfo(domainName);
                bForceRediscover = true;
            }
        }

        // In case connInfo is null or
        // not able to establish connection using the connection information
        // Do the complete site affinity logic
        DomainControllerInfo siteInfo = null;
        IDomainAdapter domainAdapter = DomainAdapterFactory.getInstance().getDomainAdapter();

        try {
            if (bForceRediscover) {
                siteInfo = domainAdapter.getDcInfoWithRediscover(domainName);
            } else {
                siteInfo = domainAdapter.getDcInfo(domainName);
            }
        } catch (DomainManagerNativeException | DomainManagerException exDcInfo) {
            logger.error(
                    String.format("Get domain controller information (site info) for domain [%s] failed due to %s",
                            domainName, exDcInfo.getMessage()),
                    exDcInfo);
        }

        if (siteInfo == null) {
            throw new IDMException(String.format("Failed to do site affinity for domain %s", domainName));
        }

        // Use affinitized site information
        String domainNameStr = domainName;
        if (!ServerUtils.isNullOrEmpty(siteInfo.domainFQDN)) {
            domainNameStr = siteInfo.domainFQDN;
        } else if (!ServerUtils.isNullOrEmpty(siteInfo.domainIpAddress)) {
            domainNameStr = siteInfo.domainIpAddress;
        }

        // Determine whether we do ldaps or ldap based on customer provided ldap connection strings
        Collection<URI> customerURIs = ServerUtils.toURIObjects(this.getStoreDataEx().getConnectionStrings());
        boolean isLdaps = false;

        if (customerURIs == null || customerURIs.isEmpty()) {
            isLdaps = true;
        } else {
            for (URI uri : customerURIs) {
                isLdaps = (uri == null) ? true
                        : DirectoryStoreProtocol.LDAPS.getName().equalsIgnoreCase(uri.getScheme());
                if (uri != null && isLdaps)
                    break;
            }
        }

        try {
            return getConnectionWithdomainStr(domainNameStr, isLdaps);
        } catch (Exception ex) {
            String errMsg = String.format("Cannot get a %s connection to domain %s with site affinity due to %s",
                    isLdaps ? "ldaps" : "ldap", domainNameStr, ex.getMessage());
            logger.error(errMsg, ex);
            throw ex;
        }
    }

    @Override
    public Collection<SecurityDomain> getDomains() {
        Collection<SecurityDomain> domains = new HashSet<SecurityDomain>();
        domains.add(new SecurityDomain(super.getDomain(), super.getAlias()));
        return domains;
    }

    @Override
    public PrincipalId findActiveUser(String attributeName, String attributeValue) throws Exception {
        Validate.notEmpty(attributeName, "attributeName");
        Validate.notEmpty(attributeValue, "attributeValue");
        ILdapConnectionEx connection;
        AccountLdapEntryInfo ldapEntryInfo = null;

        IIdmAuthStatRecorder idmAuthStatRecorder = this.createIdmAuthStatRecorderInstance(
                DiagnosticsContextFactory.getCurrentDiagnosticsContext().getTenantName(),
                ActivityKind.GETATTRIBUTES, EventLevel.INFO, attributeValue);
        idmAuthStatRecorder.start();

        try {
            try {
                connection = this.getConnection();
            } catch (Exception ex) {
                throw new IDMException("Failed to establish server connection", ex);
            }

            try {
                String[] attrNames = { ATTR_NAME_SAM_ACCOUNT, ATTR_USER_PRINCIPAL_NAME, ATTR_NAME_USER_ACCT_CTRL };

                String searchBaseDn = this.getStoreDataEx().getUserBaseDn();

                final String filter_by_attr = this.buildUserQueryByAttribute(attributeName, attributeValue);

                ldapEntryInfo = findAccountLdapEntry(connection, filter_by_attr, searchBaseDn, attrNames, false,
                        attributeValue, idmAuthStatRecorder);

                String accountName = getStringValue(
                        ldapEntryInfo.accountLdapEntry.getAttributeValues(ATTR_NAME_SAM_ACCOUNT));

                String userPrincipalName = getOptionalStringValue(
                        ldapEntryInfo.accountLdapEntry.getAttributeValues(ATTR_USER_PRINCIPAL_NAME));

                int currentFlag = getOptionalIntegerValue(
                        ldapEntryInfo.accountLdapEntry.getAttributeValues(ATTR_NAME_USER_ACCT_CTRL), 0);

                if (!AccountControlFlag.FLAG_DISABLED_ACCOUNT.isSet(currentFlag)) {
                    return ServerUtils.getPrincipalId(userPrincipalName, accountName, this.getDomain());
                } else {
                    throw new InvalidPrincipalException(
                            String.format("User account '%s@%s' is not active. ", accountName, this.getDomain()),
                            String.format("%s@%s", accountName, getDomain()));
                }

            } finally {
                if (ldapEntryInfo != null) {
                    ldapEntryInfo.close_messages();
                }
                if (connection != null) {
                    connection.close();
                }
            }

        } finally {
            idmAuthStatRecorder.end();
        }

    }

    private String buildUserQueryByAttribute(String attrName, String attrValue) {
        ValidateUtil.validateNotNull(attrName, "attrName");
        ValidateUtil.validateNotNull(attrValue, "attrValue");

        String escapedsAttrName = LdapFilterString.encode(attrName);
        String escapedsAttrVal = LdapFilterString.encode(attrValue);

        return String.format(this._ldapSchemaMapping.getUserQueryByAttribute(), escapedsAttrName, escapedsAttrVal);
    }

    @Override
    public String getStoreUPNAttributeName() {
        return ATTR_USER_PRINCIPAL_NAME;
    }
}