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

Java tutorial

Introduction

Here is the source code for com.vmware.identity.idm.server.provider.ldap.LdapProvider.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.
 *
 */

/**
 * VMware Identity Service
 *
 * LDAP Provider
 *
 * @author:  Sriram Nambakam <snambakam@vmware.com>
 *
 * @version: 1.0
 * @since:   2011-12-7
 *
 */

package com.vmware.identity.idm.server.provider.ldap;

import java.security.InvalidParameterException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

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.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.NoSuchUserException;
import com.vmware.identity.idm.server.provider.PrincipalGroupLookupInfo;
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.ILdapMessageEx;
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.interop.ldap.SizeLimitExceededLdapException;
import com.vmware.identity.interop.ldap.UnavailableCritExtensionLdapException;
import com.vmware.identity.performanceSupport.IIdmAuthStat.ActivityKind;
import com.vmware.identity.performanceSupport.IIdmAuthStat.EventLevel;

public class LdapProvider extends BaseLdapProvider {
    private static final IDiagnosticsLogger log = DiagnosticsLoggerFactory.getLogger(LdapProvider.class);

    private static enum AccountControlFlag {
        FLAG_DISABLED_ACCOUNT(0x0002), FLAG_LOCKED_ACCOUNT(0x0010), FLAG_PASSWORD_EXPIRED(0x00080000);

        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 String SPECIAL_ATTR_SUBJECT_TYPE = "subjectType";
    private static final String SPECIAL_ATTR_USER_PRINCIPAL_NAME = "userPrincipalName";
    private static final String SPECIAL_ATTR_MEMBER_OF = "memberOf";

    private static final int DEFAULT_PAGE_SIZE = 1000;
    // 10000 seems reasonable limit for unlimited non-paged results...
    private static int NON_PAGED_SEARCH_MAX_RESULT_RETURN = DEFAULT_PAGE_SIZE * 10;

    private final Set<String> _specialAttributes;
    private final ILdapSchemaMapping _ldapSchemaMapping;
    private static final int INVALID_VALUE_PWD_LOCKOUT_DURATION = -1;
    private static final int UNINITIALIZED_VALUE_PWD_LOCKOUT_DURATION = -2;
    private int _pwdPolicyPwdLockoutDuration = UNINITIALIZED_VALUE_PWD_LOCKOUT_DURATION;

    private final AtomicInteger _pagedResultSupportedFlag;
    private static final int PAGED_RESULT_SUPPORTED_UNKNOWN = 0;
    private static final int PAGED_RESULT_SUPPORTED_YES = 1;
    private static final int PAGED_RESULT_SUPPORTED_NO = 2;
    private static final int LDAP_SIZELIMIT_EXCEEDED = 0x04;

    private final boolean _userGroupMembersListLinkExists;
    private final boolean _userGroupMembersListLinkIsDn;
    private final boolean _groupGroupMembersListLinkExists;
    private final boolean _groupGroupMembersListLinkIsDn;

    private final String USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE;
    private final String GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE;

    public LdapProvider(IIdentityStoreData store) {
        this(store, null);
    }

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

        _specialAttributes = new HashSet<String>();
        _specialAttributes.add(SPECIAL_ATTR_SUBJECT_TYPE.toLowerCase());
        _specialAttributes.add(SPECIAL_ATTR_USER_PRINCIPAL_NAME.toLowerCase());
        _specialAttributes.add(SPECIAL_ATTR_MEMBER_OF.toLowerCase());
        _ldapSchemaMapping = new OpenLdapSchemaMapping(this.getStoreDataEx().getIdentityStoreSchemaMapping());
        _pagedResultSupportedFlag = new AtomicInteger(PAGED_RESULT_SUPPORTED_UNKNOWN);

        USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeGroupMembersListLink);
        GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeGroupMembersListLink);
        _userGroupMembersListLinkExists = _ldapSchemaMapping.doesLinkExist(USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE);
        _userGroupMembersListLinkIsDn = _ldapSchemaMapping.isDnAttribute(USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE);
        _groupGroupMembersListLinkExists = _ldapSchemaMapping
                .doesLinkExist(GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE);
        _groupGroupMembersListLinkIsDn = _ldapSchemaMapping.isDnAttribute(GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE);
    }

    @Override
    public Set<String> getRegisteredUpnSuffixes() {
        // open ldap does not support userPrincipalName and such cannot have upn suffixes
        return null;
    }

    @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) {
            log.error("Failed authentication.", 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 {
        ValidateUtil.validateNotNull(principalId, "principalId");

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

        List<AttributeValuePair> result = new ArrayList<AttributeValuePair>();

        assert (attributes != null);

        List<String> attrNames = new ArrayList<String>();
        List<Attribute> regularAttrs = new ArrayList<Attribute>();
        Map<String, Attribute> specialAttrs = new HashMap<String, Attribute>();
        String userName = null;
        String ATTR_NAME_CN = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
        String ATTR_ENTRY_UUID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);

        Map<String, String> attrMap = this.getStoreDataEx().getAttributeMap();
        if (attrMap != null) {
            for (Attribute attr : attributes) {
                String mappedAttr = attrMap.get(attr.getName());
                if (mappedAttr == null) {
                    throw new IllegalArgumentException(
                            String.format("No attribute mapping found for [%s]", attr.getName()));
                }
                if (_specialAttributes.contains(mappedAttr.toLowerCase())) {
                    specialAttrs.put(mappedAttr, attr);
                } else {
                    regularAttrs.add(attr);
                    attrNames.add(mappedAttr);
                }
            }
        }

        // 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_CN);
        attrNames.add(ATTR_ENTRY_UUID);
        if ((this._userGroupMembersListLinkIsDn == false) && (this._userGroupMembersListLinkExists)) {
            attrNames.add(USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE);
        }

        ILdapConnectionEx connection = null;

        ILdapMessage message = null;
        try {
            connection = getConnection();
            try {
                String baseDN = this.getStoreDataEx().getUserBaseDn();

                final String filter = this.buildUserQueryByPrincipalId(principalId);

                message = connection.search(baseDN, LdapScope.SCOPE_SUBTREE, filter,
                        attrNames.toArray(new String[attrNames.size()]), false);

                ILdapEntry[] entries = message.getEntries();

                if (entries != null) {
                    if (entries.length > 1) {
                        String msg = String.format("multiple entries are found: %s", principalId.getUPN());
                        log.error(msg);
                        throw new InvalidPrincipalException(msg, principalId.getUPN());
                    }

                    int iAttr = 0;
                    ILdapEntry theEntry = entries[0];

                    userName = getUserAccountName(entries[0], ATTR_NAME_CN);

                    String userObjectId = getOptionalFirstStringValue(
                            entries[0].getAttributeValues(ATTR_ENTRY_UUID));

                    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 : regularAttrs) {
                        AttributeValuePair pair = new AttributeValuePair();

                        pair.setAttrDefinition(attr);

                        String attrName = attrNames.get(iAttr);

                        LdapValue[] values = null;

                        values = theEntry.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);

                        iAttr++;
                    }

                    Iterator<String> iter = specialAttrs.keySet().iterator();
                    while (iter.hasNext()) {
                        String key = iter.next();

                        if (key.equalsIgnoreCase(SPECIAL_ATTR_SUBJECT_TYPE)) {
                            AttributeValuePair avPair = new AttributeValuePair();

                            avPair.setAttrDefinition(specialAttrs.get(key));
                            // no solution users in OpenLdap.
                            String subjectTypeValue = "false";
                            avPair.getValues().add(subjectTypeValue);
                            result.add(avPair);
                        } else if (key.equalsIgnoreCase(SPECIAL_ATTR_USER_PRINCIPAL_NAME)) {
                            // open ldap does not have upn
                            String upn = String.format("%s@%s", userName, this.getDomain());
                            AttributeValuePair avPair = new AttributeValuePair();

                            avPair.setAttrDefinition(specialAttrs.get(key));
                            avPair.getValues().add(upn);
                            result.add(avPair);
                        } else if (key.equalsIgnoreCase(SPECIAL_ATTR_MEMBER_OF)) {
                            //The following implementation does not use objectSID
                            String userMembershipId = null;
                            if (this._userGroupMembersListLinkIsDn) {
                                userMembershipId = theEntry.getDN();
                            } else if (this._userGroupMembersListLinkExists) {
                                userMembershipId = getOptionalFirstStringValue(
                                        theEntry.getAttributeValues(USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE));
                            }
                            Set<Group> groups = Collections.emptySet();
                            if (ServerUtils.isNullOrEmpty(userMembershipId) == false) {
                                groups = getNestedGroups(connection, userMembershipId, true);
                            }
                            AttributeValuePair avPair = new AttributeValuePair();

                            avPair.setAttrDefinition(specialAttrs.get(key));

                            for (Group group : groups) {
                                avPair.getValues().add(group.getNetbios());
                                pairGroupSids.getValues().add(group.getObjectId());
                            }
                            result.add(avPair);
                        }
                    } // while
                    result.add(pairGroupSids);
                } else {
                    String msg = String.format("object not found -- baseDN: [%s], scope: [%s], filter: [%s]",
                            baseDN, LdapScope.SCOPE_SUBTREE, filter);
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, principalId.getUPN());
                }
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, principalId.getUPN());
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        idmAuthStatRecorder.end();

        return result;
    }

    @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());
            log.error(msg);
            throw new InvalidPrincipalException(msg, id.getUPN());
        }

        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_ENTRY_UUID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
            final String ATTR_PWD_ACCOUNT_LOCKED_TIME = getMappedAttrPwdAccountLockedTime();

            String[] attrNames = { ATTR_NAME_CN, ATTR_FIRST_NAME, ATTR_LAST_NAME, ATTR_EMAIL_ADDRESS,
                    ATTR_DESCRIPTION, ATTR_ACCOUNT_FLAGS, ATTR_PWD_ACCOUNT_LOCKED_TIME, ATTR_ENTRY_UUID };

            final String filter = this.buildUserQueryByPrincipalId(id);

            // 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
                    String msg = String.format("user %s doesn't exist or multiple users with same name",
                            id.getName());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, id.getUPN());
                }

                int currentFlag = getAccountFlags(entries[0]);

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

        return user;
    }

    private int getAccountFlags(ILdapEntry entry) {
        final String ATTR_ACCOUNT_FLAGS = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
        final String ATTR_PWD_ACCOUNT_LOCKED_TIME = getMappedAttrPwdAccountLockedTime();
        int currentFlag = 0;
        if (entry.getAttributeValues(ATTR_ACCOUNT_FLAGS) != null) {
            currentFlag = getOptionalIntegerValue(entry.getAttributeValues(ATTR_ACCOUNT_FLAGS), 0);
        } else if (entry.getAttributeValues(ATTR_PWD_ACCOUNT_LOCKED_TIME) != null) {
            String generalizedTime = getOptionalStringValue(entry.getAttributeValues(ATTR_PWD_ACCOUNT_LOCKED_TIME));
            if (StringUtils.isNotEmpty(generalizedTime)) {
                // handle special case when account is permanently locked
                // http://tools.ietf.org/html/draft-behera-ldap-password-policy-10#section-5.3
                if (generalizedTime.equals("000001010000Z")) {
                    log.info("User account is permanently locked!");
                    return AccountControlFlag.FLAG_LOCKED_ACCOUNT.getValue();
                }
                SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddhhmmss");
                formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
                boolean accountLocked = false;
                try {
                    Date lockedTs = formatter.parse(generalizedTime);
                    long lockoutDur = getLockoutDurationSettings();
                    if (lockoutDur == INVALID_VALUE_PWD_LOCKOUT_DURATION) {//invalid setting -- cannot load due to multiple entries or no entries
                        return currentFlag;
                    } else {
                        accountLocked = !(lockedTs
                                .getTime() < (System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(lockoutDur)));
                    }
                } catch (ParseException e) {
                    throw new IllegalArgumentException(e.getMessage());
                }
                if (accountLocked) {
                    currentFlag = AccountControlFlag.FLAG_LOCKED_ACCOUNT.getValue();
                }
            }
        }
        return currentFlag;
    }

    private int getLockoutDurationSettings() {
        if (this._pwdPolicyPwdLockoutDuration == UNINITIALIZED_VALUE_PWD_LOCKOUT_DURATION) {
            synchronized (this) {
                if (this._pwdPolicyPwdLockoutDuration == UNINITIALIZED_VALUE_PWD_LOCKOUT_DURATION) {
                    this._pwdPolicyPwdLockoutDuration = loadPwdPolicyPwdLockoutDuration();
                }
            }
        }
        return this._pwdPolicyPwdLockoutDuration;
    }

    private String getMappedAttrPwdAccountLockedTime() {
        return _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeLockoutTime);
    }

    private int loadPwdPolicyPwdLockoutDuration() {
        int value;
        ILdapConnectionEx connection = null;

        try {
            connection = getConnection();

            final String ATTR_NAME_CN = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
            final String ATTR_PWD_POLICY_ACCOUNT_LOCKOUT_DURATION = _ldapSchemaMapping.getPwdObjectAttribute(
                    IdentityStoreAttributeMapping.AttributeIds.PwdPolicyAttributePwdLockoutDuration);

            String[] attrNames = { ATTR_NAME_CN, ATTR_PWD_POLICY_ACCOUNT_LOCKOUT_DURATION };

            String filter = _ldapSchemaMapping.getPasswordSettingsQuery();

            // 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) { // Password Policy not configured or there are duplicated ones
                    return INVALID_VALUE_PWD_LOCKOUT_DURATION;
                }

                value = getOptionalIntegerValue(
                        entries[0].getAttributeValues(ATTR_PWD_POLICY_ACCOUNT_LOCKOUT_DURATION),
                        INVALID_VALUE_PWD_LOCKOUT_DURATION);
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } catch (Exception e) {// return invalid value when exception during loading
            value = INVALID_VALUE_PWD_LOCKOUT_DURATION;
        } finally {
            connection.close();
        }

        return value;
    }

    @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_ENTRY_UUID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
            final String ATTR_PWD_ACCOUNT_LOCKED_TIME = getMappedAttrPwdAccountLockedTime();

            String[] attrNames = { ATTR_NAME_CN, ATTR_FIRST_NAME, ATTR_LAST_NAME, ATTR_EMAIL_ADDRESS,
                    ATTR_DESCRIPTION, ATTR_ACCOUNT_FLAGS, ATTR_PWD_ACCOUNT_LOCKED_TIME, ATTR_ENTRY_UUID };

            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
                    String msg = String.format(
                            "user with entruUUID %s doesn't exist or multiple users with same name", userEntryUuid);
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, userEntryUuid);
                }

                int currentFlag = getAccountFlags(entries[0]);

                user = this.buildPersonUser(entries[0], currentFlag, true);
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, userEntryUuid);
            } 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>();

        // Short circuit since we're being asked for an empty result anyway
        if (limit == 0) {
            return users;
        }

        ILdapConnectionEx connection = getConnection();

        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_ACCOUNT_FLAGS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_ENTRY_UUID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
            final String ATTR_PWD_ACCOUNT_LOCKED_TIME = getMappedAttrPwdAccountLockedTime();

            String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_FIRST_NAME, ATTR_LAST_NAME,
                    ATTR_EMAIL_ADDRESS, ATTR_ACCOUNT_FLAGS, ATTR_PWD_ACCOUNT_LOCKED_TIME, ATTR_ENTRY_UUID };

            String searchBaseDn = getStoreDataEx().getUserBaseDn();

            try {
                Collection<ILdapMessage> messages = ldap_search(connection, 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 = getAccountFlags(entry);

                                    users.add(buildPersonUser(entry, flag));
                                }
                            }
                        } finally {
                            if (message != null) {
                                message.close();
                            }
                        }
                    }
                }
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, filter);
            }
        } 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 userFilter, 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();

            final String ATTR_MEMBER = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeMembersList);

            String[] attrNames = { ATTR_MEMBER };

            String groupFilter = String.format(_ldapSchemaMapping.getGroupQueryByAccountName(),
                    LdapFilterString.encode(groupId.getName()));

            ILdapMessage message = null;
            try {
                message = connection.search(getStoreDataEx().getGroupBaseDn(), LdapScope.SCOPE_SUBTREE, groupFilter,
                        attrNames, false);

                ILdapEntry[] entries = message.getEntries();

                if (entries == null || entries.length == 0) {
                    String msg = String.format("group %s doesn't exist", groupId.getName());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, groupId.getUPN());
                } else if (entries.length > 1) {
                    String msg = String.format("multiple groups same name %s", groupId.getUPN());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, groupId.getUPN());
                }

                Collection<String> dNs = ServerUtils
                        .getMultiStringValueAsCollection(entries[0].getAttributeValues(ATTR_MEMBER));

                if (dNs != null) {
                    int numUsersToRet = 0;
                    for (String dN : dNs) {
                        //non-user dn will be filtered out when the user oc is applied in
                        // the following search. Therefore, no users in nested group is returned
                        PersonUser user = findUserByDNByFilter(connection, dN, userFilter);
                        if (user != null && (limit <= 0 || numUsersToRet < limit)) {
                            users.add(user);
                            numUsersToRet++;
                        }
                    }
                }
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, groupFilter);
            }

            finally {
                if (message != null) {
                    message.close();
                }
            }
        } 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>();
        PrincipalInfo principalInfo = null;
        ILdapConnectionEx 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_ENTRY_UUID = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

            String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_ENTRY_UUID };
            principalInfo = getPrincipalGroupMembershipId(connection, principalId);
            if ((principalInfo != null)
                    && (ServerUtils.isNullOrEmpty(principalInfo.getGroupMembershipId()) == false)) {
                String filter = String.format(_ldapSchemaMapping.getDirectParentGroupsQuery(),
                        LdapFilterString.encode(principalInfo.getGroupMembershipId()));

                try {
                    Collection<ILdapMessage> messages = ldap_search(connection, 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) {
                    String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                    log.error(msg, e);
                    throw new InvalidPrincipalException(msg, principalId.getUPN());
                }
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

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

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

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

        try {
            principalInfo = getPrincipalGroupMembershipId(connection, userId);

            if ((principalInfo != null)
                    && (ServerUtils.isNullOrEmpty(principalInfo.getGroupMembershipId()) == false)) {
                // NestedParentGroups includes the direct parents, as well as the grandparents.
                groups = getNestedGroups(connection, principalInfo.getGroupMembershipId(), false);
            }
        } finally {
            connection.close();
        }

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

    @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_ENTRY_UUID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        String[] attrNames = { ATTR_NAME_GROUP_CN, ATTR_DESCRIPTION, ATTR_ENTRY_UUID };
        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) {
                    String msg = "duplicated groups with name " + groupId.getUPN();
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, groupId.getUPN());
                }

                return buildGroup(entries[0], null /* no default group dn */);
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, groupId.getUPN());
            } 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_ENTRY_UUID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

        String[] attrNames = { ATTR_NAME_GROUP_CN, ATTR_DESCRIPTION, ATTR_ENTRY_UUID };
        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
                    String msg = String.format(
                            "Group with entryUuid %s doesn't exist or multiple groups with same name",
                            groupEntryUuid);
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, groupEntryUuid);
                }

                return buildGroup(entries[0], null /* no default group dn */);
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, groupEntryUuid);
            } 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);
    }

    @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> findGroupsInternal(String filter, String domainName, int limit) throws Exception {
        Set<Group> groups = new HashSet<Group>();

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

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

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

            try {
                Collection<ILdapMessage> messages = ldap_search(connection, this.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) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, filter);
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return groups;
    }

    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 {
            final String ATTR_MEMBER = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeMembersList);

            connection = getConnection();
            String[] attrNames = { ATTR_MEMBER };

            String groupFilter = String.format(_ldapSchemaMapping.getGroupQueryByAccountName(),
                    LdapFilterString.encode(groupId.getName()));

            ILdapMessage message = null;
            try {
                message = connection.search(getStoreDataEx().getGroupBaseDn(), LdapScope.SCOPE_SUBTREE, groupFilter,
                        attrNames, false);

                ILdapEntry[] entries = message.getEntries();

                if (entries == null || entries.length == 0) {
                    String msg = String.format("invalid principal: %s", groupId.getName());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, groupId.getUPN());
                } else if (entries.length > 1) {
                    String msg = String.format("found duplicated group entries with id: %s", groupId.getUPN());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, groupId.getUPN());
                } else {
                    String[] values = ServerUtils.getMultiStringValue(entries[0].getAttributeValues(ATTR_MEMBER));
                    if (values != null) {
                        int numGroupsToRet = 0;
                        for (String memberDN : values) {
                            Group group = findGroupByDNWithFilter(connection, memberDN, filter);

                            if (null != group && (limit <= 0 || numGroupsToRet < limit)) {
                                groups.add(group);
                                numGroupsToRet++;
                            }
                        }
                    }

                }
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, groupId.getUPN());
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } 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;

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

        try {
            final String filter = this.buildUserQueryByPrincipalId(id);

            final String ATTR_ACCOUNT_FLAGS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_PWD_ACCOUNT_LOCKED_TIME = getMappedAttrPwdAccountLockedTime();

            String attributes[] = { ATTR_ACCOUNT_FLAGS, ATTR_PWD_ACCOUNT_LOCKED_TIME };

            ILdapMessage message = null;
            try {
                message = connection.search(this.getStoreDataEx().getUserBaseDn(), LdapScope.SCOPE_SUBTREE, filter,
                        attributes, false);

                ILdapEntry[] entries = message.getEntries();

                if (entries == null || entries.length == 0) {
                    String msg = String.format("No such principal %s was found in %s", id.getName(),
                            id.getDomain());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, id.getUPN());
                }

                if (entries.length > 1) {
                    String msg = String.format("Duplicate entries were found", id.getUPN());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, id.getUPN());
                }

                accountFlags = getAccountFlags(entries[0]);
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, id.getUPN());
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } finally {
            connection.close();
        }
        return accountFlags;
    }

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

            String[] attrNames = { null };

            final String filter = this.buildUserQueryByPrincipalId(id);

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

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

                ILdapEntry[] entries = message.getEntries();

                if (entries == null || entries.length == 0) {
                    String msg = String.format("User '%s' was not found.", id.getName());
                    log.error(msg);
                    throw new NoSuchUserException(msg);
                } else if (entries.length != 1) {
                    String msg = String.format("Duplicate entries were found: %s", id.getUPN());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, id.getUPN());
                }

                return entries[0].getDN();
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, id.getUPN());
            } finally {
                if (message != null) {
                    message.close();
                }
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

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

        String groupName = null;
        try {
            groupName = getFirstStringValue(entry.getAttributeValues(ATTR_NAME_GROUP_CN));
        } catch (InvalidParameterException ex) {
            String msg = String.format("empty value for [%s] found and is not allowed: %s", ATTR_NAME_GROUP_CN,
                    groupDN);
            log.error(msg);
            throw new InvalidPrincipalException(msg, groupDN);
        }

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

        String groupEntryUuid = null;
        String description = null;
        try {
            groupEntryUuid = getOptionalFirstStringValue(entry.getAttributeValues(ATTR_ENTRY_UUID));

            description = getOptionalLastStringValue(entry.getAttributeValues(ATTR_DESCRIPTION));
        } catch (IllegalStateException e) {
            String msg = String.format("multiple values for uuid and descritption found and is not allowed: %s",
                    groupDN);
            log.error(msg, e);
            throw new InvalidPrincipalException(msg, groupDN);
        }

        PrincipalId gid = ServerUtils.getPrincipalId(null, groupName, this.getDomain());

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

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

    Set<Group> getNestedGroups(ILdapConnectionEx connection, String membershipId, boolean groupNameOnly)
            throws NoSuchGroupException, InvalidPrincipalException {
        Set<Group> groups = new HashSet<Group>();
        if (ServerUtils.isNullOrEmpty(membershipId) == false) {
            final String ATTR_NAME_GROUP_CN = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
            final String ATTR_DESCRIPTION = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
            final String ATTR_ENTRY_UUID = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);
            ArrayList<String> attributeNames = getAttributesList(ATTR_NAME_GROUP_CN, ATTR_ENTRY_UUID,
                    ATTR_DESCRIPTION, !groupNameOnly);

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

            while (groupsToProcess.isEmpty() == false) {
                String currentMembershipId = groupsToProcess.pop();
                if (groupsProcessed.contains(currentMembershipId) == false) {
                    String filter = String.format(_ldapSchemaMapping.getDirectParentGroupsQuery(),
                            LdapFilterString.encode(currentMembershipId));

                    Collection<ILdapMessage> messages = null;
                    try {
                        messages = ldap_search(connection, getStoreDataEx().getGroupBaseDn(),
                                LdapScope.SCOPE_SUBTREE, filter, attributeNames, DEFAULT_PAGE_SIZE, -1);

                        String groupMembershipId = null;

                        if (messages != null && messages.size() > 0) {
                            for (ILdapMessage message : messages) {
                                ILdapEntry[] entries = message.getEntries();
                                if ((entries != null) && (entries.length > 0)) {
                                    for (ILdapEntry entry : entries) {
                                        Group g = buildGroupObject(entry, ATTR_NAME_GROUP_CN, ATTR_ENTRY_UUID,
                                                ATTR_DESCRIPTION, !groupNameOnly);

                                        if (this._groupGroupMembersListLinkIsDn) {
                                            groupMembershipId = entry.getDN();
                                        } else if (this._groupGroupMembersListLinkExists) {
                                            groupMembershipId = getOptionalFirstStringValue(entry
                                                    .getAttributeValues(GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE));
                                        }

                                        groups.add(g);

                                        if (ServerUtils.isNullOrEmpty(groupMembershipId) == false) {
                                            groupsToProcess.push(groupMembershipId);
                                        }
                                    }
                                }
                            }
                        }
                    } catch (NoSuchObjectLdapException e) {
                        log.error(
                                String.format("Failed to search for grup membership for [%s]", currentMembershipId),
                                e);
                        throw e;
                    } finally {
                        ServerUtils.disposeLdapMessages(messages);
                    } // try

                    groupsProcessed.add(currentMembershipId);
                }
            }
        }
        return groups;
    }

    private ArrayList<String> getAttributesList(String ATTR_NAME_GROUP_CN, String ATTR_ENTRY_UUID,
            String ATTR_DESCRIPTION, boolean getDescription) {
        ArrayList<String> attributeNames = new ArrayList<String>(4);
        attributeNames.add(ATTR_NAME_GROUP_CN);
        attributeNames.add(ATTR_ENTRY_UUID);
        if (getDescription == true) {
            attributeNames.add(ATTR_DESCRIPTION);
        }
        if ((this._groupGroupMembersListLinkIsDn == false) && (this._groupGroupMembersListLinkExists == true)) {
            attributeNames.add(GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE);
        }

        return attributeNames;
    }

    private Group buildGroupObject(ILdapEntry entry, String ATTR_NAME_GROUP_CN, String ATTR_ENTRY_UUID,
            String ATTR_DESCRIPTION, boolean getDescription) {
        String groupName = null;
        String groupDescription = null;
        String groupEntryUuid = null;
        groupName = getFirstStringValue(entry.getAttributeValues(ATTR_NAME_GROUP_CN));

        if (getDescription == true) {
            groupDescription = getOptionalLastStringValue(entry.getAttributeValues(ATTR_DESCRIPTION));
        }

        groupEntryUuid = getOptionalFirstStringValue(entry.getAttributeValues(ATTR_ENTRY_UUID));

        PrincipalId groupId = new PrincipalId(groupName, this.getStoreData().getName());
        PrincipalId groupAlias = null;
        GroupDetail groupDetail = null;

        if (getDescription == true) {
            groupAlias = ServerUtils.getPrincipalAliasId(groupName, this.getStoreDataEx().getAlias());

            groupDetail = new GroupDetail((groupDescription == null) ? "" : groupDescription);
        }
        Group g = new Group(groupId, groupAlias, groupEntryUuid, groupDetail);
        return g;
    }

    PersonUser findUserByDNByFilter(ILdapConnectionEx connection, String userDN, String filter)
            throws InvalidPrincipalException {
        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_ACCOUNT_FLAGS = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
        final String ATTR_ENTRY_UUID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
        final String ATTR_PWD_ACCOUNT_LOCKED_TIME = getMappedAttrPwdAccountLockedTime();

        PersonUser user = null;
        String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_FIRST_NAME, ATTR_LAST_NAME, ATTR_EMAIL_ADDRESS,
                ATTR_ACCOUNT_FLAGS, ATTR_PWD_ACCOUNT_LOCKED_TIME, ATTR_ENTRY_UUID };

        ILdapMessage message = connection.search(userDN, LdapScope.SCOPE_BASE, filter, attrNames, false);

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

            if (entries != null && entries.length == 1) {
                int flag = getAccountFlags(entries[0]);

                user = buildPersonUser(entries[0], flag);
            }
        } finally {
            if (message != null) {
                message.close();
            }
        }
        return user;
    }

    PersonUser buildPersonUser(ILdapEntry entry, int accountFlags) throws InvalidPrincipalException {
        return this.buildPersonUser(entry, accountFlags, false);
    }

    PersonUser buildPersonUser(ILdapEntry entry, int accountFlags, boolean provideExtendedAccountInfo)
            throws InvalidPrincipalException {
        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_ENTRY_UUID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);

        String accountName = null;
        String description = null;
        String firstName = null;
        String lastName = null;
        String email = null;
        try {
            accountName = getUserAccountName(entry, ATTR_NAME_CN);

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

            firstName = getOptionalLastStringValue(entry.getAttributeValues(ATTR_FIRST_NAME));

            lastName = getOptionalLastStringValue(entry.getAttributeValues(ATTR_LAST_NAME));

            email = getOptionalLastStringValue(entry.getAttributeValues(ATTR_EMAIL_ADDRESS));

        } catch (IllegalStateException e) {
            String cn = getStringValues(entry.getAttributeValues(ATTR_NAME_CN)).iterator().next();
            String principalId = String.format("%s@%s", cn, getDomain());
            String message = String.format("multiple values for attributes for %s are not allowed: %s ",
                    ATTR_NAME_CN, principalId);
            log.error(message);
            throw new InvalidPrincipalException(message, principalId);
        }

        String resultEntryUUID = null;

        PrincipalId id = ServerUtils.getPrincipalId(null, accountName, this.getDomain());

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

        if (provideExtendedAccountInfo) {
            resultEntryUUID = getOptionalFirstStringValue(entry.getAttributeValues(ATTR_ENTRY_UUID));
        }
        PersonDetail detail = new PersonDetail.Builder().firstName(firstName).lastName(lastName)
                .userPrincipalName(null).emailAddress(email).description(description).build();

        boolean disabled = AccountControlFlag.FLAG_DISABLED_ACCOUNT.isSet(accountFlags);

        boolean locked = AccountControlFlag.FLAG_LOCKED_ACCOUNT.isSet(accountFlags);

        return new PersonUser(id, alias, resultEntryUUID, detail, disabled, locked);
    }

    private class PrincipalInfo {
        private String _groupMembershipId;
        private String _objectId;

        public PrincipalInfo(String groupMembershipId, String objectId) {
            this._groupMembershipId = groupMembershipId;
            this._objectId = objectId;
        }

        public String getGroupMembershipId() {
            return this._groupMembershipId;
        }

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

    PrincipalInfo getPrincipalGroupMembershipId(ILdapConnectionEx connection, PrincipalId principalId)
            throws Exception {
        final String ATTR_USER_OBJECTID = _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);

        String principalMembershipId = null;
        String principalObjectId = null;
        String[] attrNames = ((this._userGroupMembersListLinkIsDn)
                || (this._userGroupMembersListLinkExists == false)) ? new String[] { ATTR_USER_OBJECTID, null }
                        : new String[] { USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE, ATTR_USER_OBJECTID, null };
        final String userFilter = this.buildUserQueryByPrincipalId(principalId);

        try (ILdapMessage userMessage = connection.search(this.getStoreDataEx().getUserBaseDn(),
                LdapScope.SCOPE_SUBTREE, userFilter, attrNames, false)) {
            ILdapEntry[] entries = userMessage.getEntries();

            if ((entries != null) && (entries.length == 1)) {
                if (this._userGroupMembersListLinkIsDn) {
                    principalMembershipId = entries[0].getDN();
                } else if (this._userGroupMembersListLinkExists) {
                    principalMembershipId = getOptionalFirstStringValue(
                            entries[0].getAttributeValues(USER_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE));
                }
                principalObjectId = getOptionalFirstStringValue(entries[0].getAttributeValues(ATTR_USER_OBJECTID));
            } else if ((entries != null) && (entries.length > 1)) {
                String msg = String.format("Multiple values found for %s", principalId.getUPN());
                log.error(msg);
                throw new InvalidPrincipalException(msg, principalId.getUPN());
            }
        } catch (NoSuchObjectLdapException e) {
            String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
            log.error(msg, e);
            throw new InvalidPrincipalException(msg, principalId.getUPN());
        }

        // try groups
        if (ServerUtils.isNullOrEmpty(principalMembershipId)) {
            final String ATTR_GROUP_OBJECTID = _ldapSchemaMapping
                    .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

            attrNames = ((this._groupGroupMembersListLinkIsDn) || (this._groupGroupMembersListLinkExists == false))
                    ? new String[] { ATTR_GROUP_OBJECTID, null }
                    : new String[] { GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE, ATTR_GROUP_OBJECTID, null };
            final String groupFilter = String.format(this._ldapSchemaMapping.getGroupQueryByAccountName(),
                    LdapFilterString.encode(principalId.getName()));

            try (ILdapMessage groupMessage = connection.search(this.getStoreDataEx().getGroupBaseDn(),
                    LdapScope.SCOPE_SUBTREE, groupFilter, attrNames, false)) {
                ILdapEntry[] entries = groupMessage.getEntries();

                if ((entries == null) || (entries.length == 0)) {
                    String msg = String.format("PrincipalId not found: %s", principalId);
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, principalId.getUPN());
                } else if (entries.length > 1) {
                    String msg = String.format("Multiple values found for %s", principalId.getUPN());
                    log.error(msg);
                    throw new InvalidPrincipalException(msg, principalId.getUPN());
                } else {
                    if (this._groupGroupMembersListLinkIsDn) {
                        principalMembershipId = entries[0].getDN();
                    } else if (this._groupGroupMembersListLinkExists) {
                        principalMembershipId = getOptionalFirstStringValue(
                                entries[0].getAttributeValues(GROUP_GROUP_MEMBERS_LIST_LINK_ATTRIBUTE));
                    }
                    principalObjectId = getOptionalFirstStringValue(
                            entries[0].getAttributeValues(ATTR_GROUP_OBJECTID));
                }
            } catch (NoSuchObjectLdapException e) {
                String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
                log.error(msg, e);
                throw new InvalidPrincipalException(msg, principalId.getUPN());
            }
        }

        return new PrincipalInfo(principalMembershipId, principalObjectId);
    }

    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_ACCOUNT_FLAGS = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);
            final String ATTR_ENTRY_UUID = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeObjectId);
            final String ATTR_PWD_ACCOUNT_LOCKED_TIME = getMappedAttrPwdAccountLockedTime();

            connection = getConnection();
            String[] attrNames = { ATTR_NAME_CN, ATTR_DESCRIPTION, ATTR_FIRST_NAME, ATTR_LAST_NAME,
                    ATTR_EMAIL_ADDRESS, ATTR_ACCOUNT_FLAGS, ATTR_PWD_ACCOUNT_LOCKED_TIME, ATTR_ENTRY_UUID };

            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 = ldap_search(connection, 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 = getAccountFlags(entry);

                                    boolean isBitSet = controlBit.isSet(flags);

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

        return users;
    }

    Group findGroupByDNWithFilter(ILdapConnectionEx connection, String entryDn, String filter)
            throws InvalidPrincipalException {
        final String ATTR_NAME_CN = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeAccountName);
        final String ATTR_DESCRIPTION = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeDescription);
        final String ATTR_ENTRY_UUID = _ldapSchemaMapping
                .getGroupAttribute(IdentityStoreAttributeMapping.AttributeIds.GroupAttributeObjectId);

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

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

            ILdapEntry[] entries = message.getEntries();
            if (entries == null || entries.length == 0) {
                return null; // return null when the entry is not a group object
            }

            return buildGroup(entries[0], entryDn);
        } catch (NoSuchObjectLdapException e) {
            String msg = String.format("errorCode; %d; %s", e.getErrorCode(), e.getMessage());
            log.error(msg, e);
            throw new InvalidPrincipalException(msg, entryDn);
        } finally {
            if (message != null) {
                message.close();
            }
        }
    }

    private String buildUserQueryByPrincipalId(PrincipalId principal) throws InvalidPrincipalException {
        ValidateUtil.validateNotNull(principal, "principal");

        if (this.isSameDomainUpn(principal)) {
            String escapedsAMAccountName = LdapFilterString.encode(principal.getName());
            return String.format(this._ldapSchemaMapping.getUserQueryByAccountName(), escapedsAMAccountName);
        } else {
            throw new InvalidPrincipalException("Unrecognized domain name in user Upn.", principal.getUPN());
        }
    }

    private String getUserAccountName(ILdapEntry entry, String attributeName) throws InvalidPrincipalException {
        try {
            return getFirstStringValue(entry.getAttributeValues(attributeName));
        } catch (InvalidParameterException ex) {
            String msg = String.format("Required user identity attribute [%s] is missing for user dn=[%s]",
                    attributeName, entry.getDN());
            log.error(msg, ex);
            throw new InvalidPrincipalException(msg, entry.getDN());
        }
    }

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

    private Collection<ILdapMessage> ldap_search(ILdapConnectionEx connection, String searchBaseDn, LdapScope scope,
            String filter, Collection<String> attributes, int pageSize, int limit) {
        Collection<ILdapMessage> result = null;
        int pagingSupported = _pagedResultSupportedFlag.get();
        if (pagingSupported == LdapProvider.PAGED_RESULT_SUPPORTED_YES) {
            result = connection.paged_search(searchBaseDn, scope, filter, attributes, pageSize, limit);
        } else if (pagingSupported == LdapProvider.PAGED_RESULT_SUPPORTED_NO) {
            result = ldap_search_no_paging(connection, searchBaseDn, scope, filter, attributes, limit);
        } else if (pagingSupported == LdapProvider.PAGED_RESULT_SUPPORTED_UNKNOWN) {
            try {
                result = connection.paged_search(searchBaseDn, scope, filter, attributes, pageSize, limit);
                // if limit is set to 0, connection.paged_search returns without running any query,
                // so we don't really know if paging is supported or not.
                if (limit != 0) {
                    _pagedResultSupportedFlag.compareAndSet(LdapProvider.PAGED_RESULT_SUPPORTED_UNKNOWN,
                            LdapProvider.PAGED_RESULT_SUPPORTED_YES);
                }
            } catch (UnavailableCritExtensionLdapException ex) {
                log.warn(String.format(
                        "Paged search is unavailable for identity source={name=[%s], url=[%s]} falling back to regular search.",
                        this.getName(),
                        ((this.getStoreDataEx() != null) ? this.getStoreDataEx().getConnectionStrings() : "")));
                _pagedResultSupportedFlag.compareAndSet(LdapProvider.PAGED_RESULT_SUPPORTED_UNKNOWN,
                        LdapProvider.PAGED_RESULT_SUPPORTED_NO);
                result = ldap_search_no_paging(connection, searchBaseDn, scope, filter, attributes, limit);
            }
        }

        return result;
    }

    private Collection<ILdapMessage> ldap_search_no_paging(ILdapConnectionEx connection, String searchBaseDn,
            LdapScope scope, String filter, Collection<String> attributes, int limit) {
        Collection<ILdapMessage> result = null;

        // refer to LdapConnection.paged_search which
        // short circuits for limit = 0;
        if (limit != 0) {
            // refer to LdapConnection.paged_search (for unlimited results)
            ILdapMessage m = connection.search_ext(searchBaseDn, scope, filter, attributes, false, null, null, null,
                    ((limit != -1) ? limit : (NON_PAGED_SEARCH_MAX_RESULT_RETURN + 1)));
            if ((limit == -1) && (m instanceof ILdapMessageEx)) {
                int numberEntries = ((ILdapMessageEx) m).getEntriesCount();
                if (numberEntries > (NON_PAGED_SEARCH_MAX_RESULT_RETURN)) {
                    log.error(String.format(
                            "too many search results for query: searchBaseDn=[%s], scope=[%s], filter=[%s]; number of entries=[%s]",
                            searchBaseDn, scope, filter, numberEntries));
                    throw new SizeLimitExceededLdapException(LDAP_SIZELIMIT_EXCEEDED, String.format(
                            "search has more than %d results to return", NON_PAGED_SEARCH_MAX_RESULT_RETURN));
                }
            }
            result = new ArrayList<ILdapMessage>(1);
            result.add(m);
        }
        return result;
    }

    @Override
    public PrincipalId findActiveUser(String ldapAttrName, String attributeValue) throws Exception {
        Validate.notEmpty(ldapAttrName, "ldapAttrName");
        Validate.notEmpty(attributeValue, "attributeValue");

        ILdapConnectionEx connection;
        AccountLdapEntryInfo ldapEntryInfo = null;

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

        try {
            final String ATTR_NAME_SAM_ACCOUNT = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAccountName);
            final String ATTR_NAME_USER_ACCT_CTRL = _ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributeAcountControl);

            String[] attrNames = { ATTR_NAME_SAM_ACCOUNT, ATTR_NAME_USER_ACCT_CTRL };

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

            if (ldapAttrName.equalsIgnoreCase(_ldapSchemaMapping
                    .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName))) {
                //if upn, use sam account to search
                idsAttrNameForSearch = ATTR_NAME_SAM_ACCOUNT;
                attrValueForSearch = attributeValue.split("@")[0];
            } else {
                idsAttrNameForSearch = ldapAttrName;
                attrValueForSearch = attributeValue;
            }
            ValidateUtil.validateNotNull(idsAttrNameForSearch, "idsAttrNameForSearch");

            final String filter_by_attr = this.buildUserQueryByAttribute(idsAttrNameForSearch, attrValueForSearch);

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

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

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

            if (!AccountControlFlag.FLAG_DISABLED_ACCOUNT.isSet(currentFlag)) {
                return new PrincipalId(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();
            }
        }

    }

    private String buildUserQueryByAttribute(String vmdirAttrName, String attributeValue) {
        ValidateUtil.validateNotNull(vmdirAttrName, "vmdirAttrName");
        ValidateUtil.validateNotNull(attributeValue, "attributeValue");

        String escapedsAttrName = LdapFilterString.encode(vmdirAttrName);
        String escapedsAttrValue = LdapFilterString.encode(attributeValue);
        return String.format(this._ldapSchemaMapping.getUserQueryByAttribute(), escapedsAttrName,
                escapedsAttrValue);
    }

    @Override
    public String getStoreUPNAttributeName() {
        return _ldapSchemaMapping
                .getUserAttribute(IdentityStoreAttributeMapping.AttributeIds.UserAttributePrincipalName);
    }
}