org.jahia.services.usermanager.ldap.LDAPUserGroupProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.services.usermanager.ldap.LDAPUserGroupProvider.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *     Copyright (C) 2002-2015 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ======================================================================================
 *
 *     IF YOU DECIDE TO CHOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     "This program is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License
 *     as published by the Free Software Foundation; either version 2
 *     of the License, or (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *     As a special exception to the terms and conditions of version 2.0 of
 *     the GPL (or any later version), you may redistribute this Program in connection
 *     with Free/Libre and Open Source Software ("FLOSS") applications as described
 *     in Jahia's FLOSS exception. You should have received a copy of the text
 *     describing the FLOSS exception, also available here:
 *     http://www.jahia.com/license"
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ======================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 *
 *
 * ==========================================================================================
 * =                                   ABOUT JAHIA                                          =
 * ==========================================================================================
 *
 *     Rooted in Open Source CMS, Jahia's Digital Industrialization paradigm is about
 *     streamlining Enterprise digital projects across channels to truly control
 *     time-to-market and TCO, project after project.
 *     Putting an end to "the Tunnel effect", the Jahia Studio enables IT and
 *     marketing teams to collaboratively and iteratively build cutting-edge
 *     online business solutions.
 *     These, in turn, are securely and easily deployed as modules and apps,
 *     reusable across any digital projects, thanks to the Jahia Private App Store Software.
 *     Each solution provided by Jahia stems from this overarching vision:
 *     Digital Factory, Workspace Factory, Portal Factory and eCommerce Factory.
 *     Founded in 2002 and headquartered in Geneva, Switzerland,
 *     Jahia Solutions Group has its North American headquarters in Washington DC,
 *     with offices in Chicago, Toronto and throughout Europe.
 *     Jahia counts hundreds of global brands and governmental organizations
 *     among its loyal customers, in more than 20 countries across the globe.
 *
 *     For more information, please visit http://www.jahia.com
 */
package org.jahia.services.usermanager.ldap;

import com.google.common.collect.Lists;
import com.sun.jndi.ldap.LdapURL;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.jahia.modules.external.users.*;
import org.jahia.services.usermanager.*;
import org.jahia.services.usermanager.ldap.cache.LDAPAbstractCacheEntry;
import org.jahia.services.usermanager.ldap.cache.LDAPCacheManager;
import org.jahia.services.usermanager.ldap.cache.LDAPGroupCacheEntry;
import org.jahia.services.usermanager.ldap.cache.LDAPUserCacheEntry;
import org.jahia.services.usermanager.ldap.communication.BaseLdapActionCallback;
import org.jahia.services.usermanager.ldap.communication.LdapTemplateWrapper;
import org.jahia.services.usermanager.ldap.config.AbstractConfig;
import org.jahia.services.usermanager.ldap.config.GroupConfig;
import org.jahia.services.usermanager.ldap.config.UserConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.core.*;
import org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.query.ConditionCriteria;
import org.springframework.ldap.query.ContainerCriteria;
import org.springframework.ldap.query.SearchScope;
import org.springframework.ldap.support.LdapUtils;

import javax.jcr.RepositoryException;
import javax.naming.InvalidNameException;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

import java.util.*;

import static org.springframework.ldap.query.LdapQueryBuilder.query;

/**
 * Implementation of UserGroupProvider for Spring LDAP
 *
 * @author david
 */
public class LDAPUserGroupProvider implements UserGroupProvider {
    protected static final String OBJECTCLASS_ATTRIBUTE = "objectclass";
    private static Logger logger = LoggerFactory.getLogger(LDAPUserGroupProvider.class);

    private ExternalUserGroupService externalUserGroupService;
    private LdapContextSource contextSource;
    private LdapTemplateWrapper ldapTemplateWrapper;
    private String key;

    // Configs
    private UserConfig userConfig;
    private GroupConfig groupConfig;
    // if user and group are different
    private boolean distinctBase = false;

    // Cache
    private LDAPCacheManager ldapCacheManager;

    private ContainerCriteria searchGroupCriteria;

    private ContainerCriteria searchGroupDynamicCriteria;

    @Override
    public JahiaUser getUser(String name) throws UserNotFoundException {
        LDAPUserCacheEntry userCacheEntry = getUserCacheEntry(name, true);
        if (!userCacheEntry.getExist()) {
            throw new UserNotFoundException("unable to find user " + name + " on provider " + key);
        } else {
            return userCacheEntry.getUser();
        }
    }

    @Override
    public JahiaGroup getGroup(String name) throws GroupNotFoundException {
        LDAPGroupCacheEntry groupCacheEntry = getGroupCacheEntry(name, true);
        if (!groupCacheEntry.getExist()) {
            throw new GroupNotFoundException("unable to find group " + name + " on provider " + key);
        } else {
            return groupCacheEntry.getGroup();
        }
    }

    @Override
    public List<Member> getGroupMembers(String groupName) {
        LDAPGroupCacheEntry groupCacheEntry = getGroupCacheEntry(groupName, false);
        if (!groupCacheEntry.getExist()) {
            return Collections.emptyList();
        }
        if (groupCacheEntry.getMembers() != null) {
            return groupCacheEntry.getMembers();
        }

        List<Member> members = null;
        if (groupCacheEntry.isDynamic() && StringUtils.isNotEmpty(groupCacheEntry.getDynamicMembersURL())) {
            List<Member> dynMembers = loadMembersFromUrl(groupCacheEntry.getDynamicMembersURL());
            if (CollectionUtils.isNotEmpty(dynMembers)) {
                members = Lists.newArrayList(dynMembers);
            }
        } else {
            members = loadMembersFromDN(groupCacheEntry.getDn());
        }

        if (CollectionUtils.isNotEmpty(members)) {
            groupCacheEntry.setMembers(members);
            ldapCacheManager.cacheGroup(getKey(), groupCacheEntry);
            return groupCacheEntry.getMembers();
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    public List<String> getMembership(final Member member) {
        boolean isGroup = member.getType().equals(Member.MemberType.GROUP);
        if (isGroup && !userConfig.isCanGroupContainSubGroups()) {
            return Collections.emptyList();
        }
        LDAPAbstractCacheEntry cacheEntry = isGroup ? getGroupCacheEntry(member.getName(), false)
                : getUserCacheEntry(member.getName(), false);
        if (cacheEntry.getMemberships() != null) {
            return cacheEntry.getMemberships();
        }
        if (cacheEntry.getExist()) {
            final String dn = cacheEntry.getDn();
            long l = System.currentTimeMillis();
            List<String> memberships = ldapTemplateWrapper
                    .execute(new BaseLdapActionCallback<List<String>>(externalUserGroupService, key) {
                        @Override
                        public List<String> doInLdap(LdapTemplate ldapTemplate) {
                            return ldapTemplate.search(
                                    query().base(groupConfig.getSearchName())
                                            .attributes(groupConfig.getSearchAttribute())
                                            .where(OBJECTCLASS_ATTRIBUTE).is(groupConfig.getSearchObjectclass())
                                            .and(groupConfig.getMembersAttribute()).like(dn),
                                    new AttributesMapper<String>() {
                                        public String mapFromAttributes(Attributes attrs) throws NamingException {
                                            return attrs.get(groupConfig.getSearchAttribute()).get().toString();
                                        }
                                    });
                        }
                    });
            if (logger.isDebugEnabled()) {
                logger.debug("Query getMembership for {} / {} dn={} in {} ms", new Object[] { member.getName(),
                        groupConfig.getSearchAttribute(), dn, System.currentTimeMillis() - l });
            }

            // in case of communication error, the result may be null
            if (memberships == null) {
                memberships = Collections.emptyList();
            }

            if (groupConfig.isDynamicEnabled()) {
                Properties searchCriteria = new Properties();
                searchCriteria.put("*", "*");
                List<String> dynGroups = searchGroups(searchCriteria, true);
                for (String dynGroup : dynGroups) {
                    List<Member> members = getGroupMembers(dynGroup);
                    if (members.contains(member)) {
                        memberships.add(dynGroup);
                    }
                }
            }

            cacheEntry.setMemberships(memberships);
            if (isGroup) {
                ldapCacheManager.cacheGroup(getKey(), (LDAPGroupCacheEntry) cacheEntry);
            } else {
                ldapCacheManager.cacheUser(getKey(), (LDAPUserCacheEntry) cacheEntry);
            }
        }
        return cacheEntry.getMemberships();
    }

    @Override
    public List<String> searchUsers(final Properties searchCriteria, long offset, long limit) {
        if (searchCriteria.containsKey("username") && searchCriteria.size() == 1
                && !searchCriteria.getProperty("username").contains("*")) {
            try {
                JahiaUser user = getUser((String) searchCriteria.get("username"));
                return Arrays.asList(user.getUsername());
            } catch (UserNotFoundException e) {
                return Collections.emptyList();
            }
        }
        final ContainerCriteria query = buildUserQuery(searchCriteria);
        if (query == null) {
            return Collections.emptyList();
        }
        final UsersNameClassPairCallbackHandler searchNameClassPairCallbackHandler = new UsersNameClassPairCallbackHandler();
        long currentTimeMillis = System.currentTimeMillis();
        ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(externalUserGroupService, key) {
            @Override
            public Object doInLdap(LdapTemplate ldapTemplate) {
                ldapTemplate.search(query, searchNameClassPairCallbackHandler);
                return null;
            }
        });
        logger.debug("Search users for {} in {} ms", searchCriteria,
                System.currentTimeMillis() - currentTimeMillis);

        ArrayList<String> l = new ArrayList<String>(searchNameClassPairCallbackHandler.getNames());
        return l.subList(Math.min((int) offset, l.size()),
                limit < 0 ? l.size() : Math.min((int) (offset + limit), l.size()));
    }

    @Override
    public List<String> searchGroups(Properties searchCriteria, long offset, long limit) {
        if (searchCriteria.containsKey("groupname") && searchCriteria.size() == 1
                && !searchCriteria.getProperty("groupname").contains("*")) {
            try {
                JahiaGroup group = getGroup((String) searchCriteria.get("groupname"));
                return Arrays.asList(group.getGroupname());
            } catch (GroupNotFoundException e) {
                return Collections.emptyList();
            }
        }

        List<String> groups = new LinkedList<String>(searchGroups(searchCriteria, false));

        // handle dynamics
        if (groupConfig.isDynamicEnabled()) {
            groups.addAll(searchGroups(searchCriteria, true));
        }

        return groups.subList(Math.min((int) offset, groups.size()),
                limit < 0 ? groups.size() : Math.min((int) (offset + limit), groups.size()));
    }

    @Override
    public boolean verifyPassword(String userName, String userPassword) {
        DirContext ctx = null;
        try {
            LDAPUserCacheEntry userCacheEntry = getUserCacheEntry(userName, true);
            if (userCacheEntry.getExist()) {
                long l = System.currentTimeMillis();
                ctx = contextSource.getContext(userCacheEntry.getDn(), userPassword);
                // Take care here - if a base was specified on the ContextSource
                // that needs to be removed from the user DN for the lookup to succeed.
                ctx.lookup(userCacheEntry.getDn());
                logger.debug("Verify password for {} in {} ms", userName, System.currentTimeMillis() - l);

                return true;
            }
        } catch (Exception e) {
            // Context creation failed - authentication did not succeed
            //logger.error("Login failed", e);
        } finally {
            // It is imperative that the created DirContext instance is always closed
            LdapUtils.closeContext(ctx);
        }
        return false;
    }

    @Override
    public boolean isAvailable() throws RepositoryException {
        // do a simple search on users to check the availability
        long l = System.currentTimeMillis();
        boolean available = ldapTemplateWrapper
                .execute(new BaseLdapActionCallback<Boolean>(externalUserGroupService, key) {
                    @Override
                    public Boolean doInLdap(LdapTemplate ldapTemplate) {
                        ldapTemplate.search(buildUserQuery(new Properties()), new NameClassPairCallbackHandler() {
                            @Override
                            public void handleNameClassPair(NameClassPair nameClassPair) throws NamingException {

                            }
                        });
                        return true;
                    }

                    @Override
                    public Boolean onError(Exception e) {
                        super.onError(e);
                        return false;
                    }
                });
        logger.debug("Is available in {} ms", System.currentTimeMillis() - l);

        if (!available) {
            // throw an exception instead of return false to display a custom message with the ldap server url.
            throw new RepositoryException("LDAP Server '" + userConfig.getUrl() + "' is not reachable");
        } else {
            return true;
        }
    }

    private List<String> searchGroups(final Properties searchCriteria, boolean isDynamics) {
        final ContainerCriteria query = getGroupQuery(searchCriteria, isDynamics);
        final GroupsNameClassPairCallbackHandler searchNameClassPairCallbackHandler = new GroupsNameClassPairCallbackHandler(
                isDynamics);
        long l = System.currentTimeMillis();
        ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(externalUserGroupService, key) {
            @Override
            public Object doInLdap(LdapTemplate ldapTemplate) {
                ldapTemplate.search(query, searchNameClassPairCallbackHandler);
                return null;
            }
        });
        logger.debug("Search groups for {} in {} ms", searchCriteria, System.currentTimeMillis() - l);
        return searchNameClassPairCallbackHandler.getNames();
    }

    /**
     * get the members from a ldap URL used for dynamic groups
     *
     * @param url
     * @return
     */
    private List<Member> loadMembersFromUrl(String url) {
        try {
            final LdapURL ldapURL = new LdapURL(url);
            final DynMembersNameClassPairCallbackHandler nameClassPairCallbackHandler = new DynMembersNameClassPairCallbackHandler();

            final Set<String> attrs = new HashSet<String>(getUserAttributes());
            attrs.addAll(getGroupAttributes(true));
            if (groupConfig.isDynamicEnabled()) {
                attrs.add(groupConfig.getDynamicSearchObjectclass());
            }
            attrs.add(OBJECTCLASS_ATTRIBUTE);

            final SearchScope searchScope;
            if ("one".equalsIgnoreCase(ldapURL.getScope())) {
                searchScope = SearchScope.ONELEVEL;
            } else if ("base".equalsIgnoreCase(ldapURL.getScope())) {
                searchScope = SearchScope.OBJECT;
            } else {
                searchScope = SearchScope.SUBTREE;
            }

            long l = System.currentTimeMillis();
            ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(externalUserGroupService, key) {
                @Override
                public Object doInLdap(LdapTemplate ldapTemplate) {
                    ldapTemplate.search(
                            query().base(ldapURL.getDN()).attributes(attrs.toArray(new String[attrs.size()]))
                                    .searchScope(searchScope).filter(ldapURL.getFilter()),
                            nameClassPairCallbackHandler);
                    return null;
                }
            });
            logger.debug("Load members from url {} in ms", url, System.currentTimeMillis() - l);

            return new ArrayList<Member>(nameClassPairCallbackHandler.getMembers());
        } catch (NamingException e) {
            logger.error("Error trying to get dynamic members from url: " + url);
        }
        return null;
    }

    /**
     * get the members from a group DN
     *
     * @param groupDN
     * @return
     */
    private List<Member> loadMembersFromDN(final String groupDN) {
        long l = System.currentTimeMillis();

        NamingEnumeration<?> members = ldapTemplateWrapper
                .execute(new BaseLdapActionCallback<NamingEnumeration<?>>(externalUserGroupService, key) {
                    @Override
                    public NamingEnumeration<?> doInLdap(LdapTemplate ldapTemplate) {
                        // use AD range search if a range is specify in the conf
                        if (groupConfig.getAdRangeStep() > 0) {
                            DefaultIncrementalAttributesMapper incrementalAttributesMapper = new DefaultIncrementalAttributesMapper(
                                    groupConfig.getAdRangeStep(), groupConfig.getMembersAttribute());

                            while (incrementalAttributesMapper.hasMore()) {
                                ldapTemplate.lookup(groupDN, incrementalAttributesMapper.getAttributesForLookup(),
                                        incrementalAttributesMapper);
                            }

                            Attributes attributes = incrementalAttributesMapper.getCollectedAttributes();
                            try {
                                return attributes.get(groupConfig.getMembersAttribute()).getAll();
                            } catch (NamingException e) {
                                logger.error("Error retrieving the LDAP members using range on group: " + groupDN,
                                        e);
                            }
                        } else {
                            return ldapTemplate.lookup(groupDN, new String[] { groupConfig.getMembersAttribute() },
                                    new AttributesMapper<NamingEnumeration<?>>() {
                                        @Override
                                        public NamingEnumeration<?> mapFromAttributes(Attributes attributes)
                                                throws NamingException {
                                            return attributes.get(groupConfig.getMembersAttribute()) != null
                                                    ? attributes.get(groupConfig.getMembersAttribute()).getAll()
                                                    : null;
                                        }
                                    });
                        }
                        return null;
                    }
                });
        logger.debug("Load group members {} in {} ms", groupDN, System.currentTimeMillis() - l);

        return loadMembers(members);
    }

    private List<Member> loadMembers(NamingEnumeration<?> members) {
        List<Member> memberList = new ArrayList<Member>();
        try {
            while (members != null && members.hasMore()) {
                final String memberNaming = (String) members.next();
                // try to know if we deal with a group or a user
                Boolean isUser = userConfig.isCanGroupContainSubGroups() ? guessUserOrGroupFromDN(memberNaming)
                        : true;

                // try to retrieve the object from the cache
                LDAPAbstractCacheEntry cacheEntry;
                if (isUser != null) {
                    if (isUser) {
                        cacheEntry = ldapCacheManager.getUserCacheEntryByDn(getKey(), memberNaming);
                    } else {
                        cacheEntry = ldapCacheManager.getGroupCacheEntryByDn(getKey(), memberNaming);
                    }
                } else {
                    // look in all cache
                    cacheEntry = ldapCacheManager.getUserCacheEntryByDn(getKey(), memberNaming);
                    if (cacheEntry == null) {
                        cacheEntry = ldapCacheManager.getGroupCacheEntryByDn(getKey(), memberNaming);
                        isUser = cacheEntry != null ? false : null;
                    } else {
                        isUser = true;
                    }
                }
                if (cacheEntry != null) {
                    if (isUser) {
                        memberList.add(new Member(cacheEntry.getName(), Member.MemberType.USER));
                    } else {
                        memberList.add(new Member(cacheEntry.getName(), Member.MemberType.GROUP));
                    }
                    continue;
                }

                // try to retrieve
                if (isUser != null && userConfig.isSearchAttributeInDn()) {
                    String name = getNameFromDn(memberNaming, isUser);
                    if (StringUtils.isNotEmpty(name)) {
                        memberList.add(isUser ? new Member(name, Member.MemberType.USER)
                                : new Member(name, Member.MemberType.GROUP));
                        continue;
                    }
                }

                // do queries
                // and cache the result
                Member member = null;
                LDAPUserCacheEntry userCacheEntry = getUserCacheEntryByDN(memberNaming, true);
                if (userCacheEntry == null) {
                    // look in groups
                    LDAPGroupCacheEntry groupCacheEntry = getGroupCacheEntryByDN(memberNaming, true, false);
                    if (groupCacheEntry == null) {
                        if (groupConfig.isDynamicEnabled()) {
                            // look in dynamic groups
                            groupCacheEntry = getGroupCacheEntryByDN(memberNaming, true, true);
                            if (groupCacheEntry != null) {
                                member = new Member(groupCacheEntry.getName(), Member.MemberType.GROUP);
                            }
                        }
                    } else {
                        member = new Member(groupCacheEntry.getName(), Member.MemberType.GROUP);
                    }
                } else {
                    member = new Member(userCacheEntry.getName(), Member.MemberType.USER);
                }

                if (member != null) {
                    memberList.add(member);
                }
            }
        } catch (NamingException e) {
            logger.error("Error retrieving LDAP group members for group", e);
        }

        return memberList;
    }

    /**
     * Retrieve the cache entry for a given username, if not found create a new one, and cache it if the param "cache" set to true
     *
     * @param userName
     * @param cache
     * @return
     */
    private LDAPUserCacheEntry getUserCacheEntry(final String userName, boolean cache) {
        LDAPUserCacheEntry userCacheEntry = ldapCacheManager.getUserCacheEntryByName(getKey(), userName);
        if (userCacheEntry != null) {
            if (userCacheEntry.getExist() != null && userCacheEntry.getExist()
                    && userCacheEntry.getUser() != null) {
                return userCacheEntry;
            } else if (userCacheEntry.getExist() != null && !userCacheEntry.getExist()) {
                return userCacheEntry;
            }
        }

        final List<String> userAttrs = getUserAttributes();
        final UserNameClassPairCallbackHandler nameClassPairCallbackHandler = new UserNameClassPairCallbackHandler(
                userCacheEntry);
        long l = System.currentTimeMillis();
        ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(externalUserGroupService, key) {
            @Override
            public Object doInLdap(LdapTemplate ldapTemplate) {
                ldapTemplate.search(query().base(userConfig.getUidSearchName())
                        .attributes(userAttrs.toArray(new String[userAttrs.size()])).where(OBJECTCLASS_ATTRIBUTE)
                        .is(userConfig.getSearchObjectclass()).and(userConfig.getUidSearchAttribute()).is(userName),
                        nameClassPairCallbackHandler);
                return null;
            }
        });
        logger.debug("Get user {} in {} ms", userName, System.currentTimeMillis() - l);

        if (nameClassPairCallbackHandler.getCacheEntry() != null) {
            userCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
            userCacheEntry.setExist(true);
        } else {
            userCacheEntry = new LDAPUserCacheEntry(userName);
            userCacheEntry.setExist(false);
        }

        if (cache) {
            ldapCacheManager.cacheUser(getKey(), userCacheEntry);
        }

        return userCacheEntry;
    }

    /**
     * Retrieve the cache entry for a given groupname, if not found create a new one, and cache it if the param "cache" set to true
     *
     * @param groupName
     * @param cache
     * @return
     */
    private LDAPGroupCacheEntry getGroupCacheEntry(String groupName, boolean cache) {
        LDAPGroupCacheEntry groupCacheEntry = ldapCacheManager.getGroupCacheEntryName(getKey(), groupName);
        if (groupCacheEntry != null) {
            if (groupCacheEntry.getExist() != null && groupCacheEntry.getExist()
                    && groupCacheEntry.getGroup() != null) {
                return groupCacheEntry;
            } else if (groupCacheEntry.getExist() != null && !groupCacheEntry.getExist()) {
                return groupCacheEntry;
            }
        }

        groupCacheEntry = getGroupCacheEntryByName(groupName, false, false);
        if (groupCacheEntry == null) {
            if (groupConfig.isDynamicEnabled()) {
                groupCacheEntry = getGroupCacheEntryByName(groupName, false, true);
            } else {
                groupCacheEntry = new LDAPGroupCacheEntry(groupName);
                groupCacheEntry.setExist(false);
            }
        }

        if (cache) {
            ldapCacheManager.cacheGroup(getKey(), groupCacheEntry);
        }

        return groupCacheEntry;
    }

    /**
     * Retrieve the cache entry for a given groupname, if not found create a new one, and cache it if the param "cache" set to true
     *
     * @param name
     * @param cache
     * @param isDynamic
     * @return
     */
    private LDAPGroupCacheEntry getGroupCacheEntryByName(final String name, boolean cache,
            final boolean isDynamic) {
        final List<String> groupAttrs = getGroupAttributes(isDynamic);
        final GroupNameClassPairCallbackHandler nameClassPairCallbackHandler = new GroupNameClassPairCallbackHandler(
                null, isDynamic);
        long l = System.currentTimeMillis();
        ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(externalUserGroupService, key) {
            @Override
            public Object doInLdap(LdapTemplate ldapTemplate) {
                ldapTemplate.search(query().base(groupConfig.getSearchName())
                        .attributes(groupAttrs.toArray(new String[groupAttrs.size()])).where(OBJECTCLASS_ATTRIBUTE)
                        .is(isDynamic ? groupConfig.getDynamicSearchObjectclass()
                                : groupConfig.getSearchObjectclass())
                        .and(groupConfig.getSearchAttribute()).is(name), nameClassPairCallbackHandler);
                return null;
            }
        });
        logger.debug("Get group {} in {} ms", name, System.currentTimeMillis() - l);

        if (nameClassPairCallbackHandler.getCacheEntry() != null) {
            LDAPGroupCacheEntry ldapGroupCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
            if (cache) {
                ldapCacheManager.cacheGroup(getKey(), ldapGroupCacheEntry);
            }
            return ldapGroupCacheEntry;
        }
        return null;
    }

    /**
     * Retrieve the cache entry for a given dn, if not found create a new one, and cache it if the param "cache" set to true
     *
     * @param dn
     * @param cache
     * @param isDynamic
     * @return
     */
    private LDAPGroupCacheEntry getGroupCacheEntryByDN(final String dn, boolean cache, final boolean isDynamic) {
        final List<String> groupAttrs = getGroupAttributes(isDynamic);
        final GroupNameClassPairCallbackHandler nameClassPairCallbackHandler = new GroupNameClassPairCallbackHandler(
                null, isDynamic);
        long l = System.currentTimeMillis();
        ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(externalUserGroupService, key) {
            @Override
            public Object doInLdap(LdapTemplate ldapTemplate) {
                ldapTemplate.search(query().base(dn).attributes(groupAttrs.toArray(new String[groupAttrs.size()]))
                        .searchScope(SearchScope.OBJECT).where(OBJECTCLASS_ATTRIBUTE)
                        .is(isDynamic ? groupConfig.getDynamicSearchObjectclass()
                                : groupConfig.getSearchObjectclass()),
                        nameClassPairCallbackHandler);
                return null;
            }
        });
        logger.debug("Get group from dn {} in {} ms", dn, System.currentTimeMillis() - l);

        if (nameClassPairCallbackHandler.getCacheEntry() != null) {
            LDAPGroupCacheEntry ldapGroupCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
            if (cache) {
                ldapCacheManager.cacheGroup(getKey(), ldapGroupCacheEntry);
            }
            return ldapGroupCacheEntry;
        }
        return null;
    }

    /**
     * Retrieve the cache entry for a given dn, if not found create a new one, and cache it if the param "cache" set to true
     *
     * @param dn
     * @param cache
     * @return
     */
    private LDAPUserCacheEntry getUserCacheEntryByDN(final String dn, boolean cache) {
        final List<String> userAttrs = getUserAttributes();
        final UserNameClassPairCallbackHandler nameClassPairCallbackHandler = new UserNameClassPairCallbackHandler(
                null);
        long l = System.currentTimeMillis();
        ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(externalUserGroupService, key) {
            @Override
            public Object doInLdap(LdapTemplate ldapTemplate) {
                ldapTemplate.search(query().base(dn).attributes(userAttrs.toArray(new String[userAttrs.size()]))
                        .searchScope(SearchScope.OBJECT).where(OBJECTCLASS_ATTRIBUTE)
                        .is(userConfig.getSearchObjectclass()), nameClassPairCallbackHandler);
                return null;
            }
        });
        logger.debug("Get user from dn {} in {} ms", dn, System.currentTimeMillis() - l);

        if (nameClassPairCallbackHandler.getCacheEntry() != null) {
            LDAPUserCacheEntry ldapUserCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
            if (cache) {
                ldapCacheManager.cacheUser(getKey(), ldapUserCacheEntry);
            }
            return ldapUserCacheEntry;
        }
        return null;
    }

    /**
     * Retrieve the search attribute from a dn. If the dn does'nt contains the search attribute null is returned
     *
     * @param dn
     * @param isUser
     * @return
     */
    private String getNameFromDn(String dn, boolean isUser) {
        LdapName ln = LdapUtils.newLdapName(dn);
        for (Rdn rdn : ln.getRdns()) {
            if (rdn.getType().equalsIgnoreCase(
                    isUser ? userConfig.getUidSearchAttribute() : groupConfig.getSearchAttribute())) {
                return rdn.getValue().toString();
            }
        }

        return null;
    }

    /**
     * Callback handler for a single user, create the corresponding cache entry
     */
    private class UserNameClassPairCallbackHandler implements NameClassPairCallbackHandler {
        private LDAPUserCacheEntry cacheEntry;

        public LDAPUserCacheEntry getCacheEntry() {
            return cacheEntry;
        }

        private UserNameClassPairCallbackHandler(LDAPUserCacheEntry cacheEntry) {
            this.cacheEntry = cacheEntry;
        }

        @Override
        public void handleNameClassPair(NameClassPair nameClassPair) throws NamingException {
            if (nameClassPair instanceof SearchResult) {
                SearchResult searchResult = (SearchResult) nameClassPair;
                cacheEntry = attributesToUserCacheEntry(searchResult.getAttributes(), cacheEntry);
                cacheEntry.setDn(searchResult.getNameInNamespace());
            } else {
                logger.error("Unexpected NameClassPair " + nameClassPair + " in " + getClass().getName());
            }
        }
    }

    /**
     * Callback handler for a single group, create the corresponding cache entry
     */
    private class GroupNameClassPairCallbackHandler implements NameClassPairCallbackHandler {
        private LDAPGroupCacheEntry cacheEntry;
        private boolean isDynamic;

        public LDAPGroupCacheEntry getCacheEntry() {
            return cacheEntry;
        }

        private GroupNameClassPairCallbackHandler(LDAPGroupCacheEntry cacheEntry, boolean isDynamic) {
            this.cacheEntry = cacheEntry;
            this.isDynamic = isDynamic;
        }

        @Override
        public void handleNameClassPair(NameClassPair nameClassPair) throws NamingException {
            if (nameClassPair instanceof SearchResult) {
                SearchResult searchResult = (SearchResult) nameClassPair;
                cacheEntry = attributesToGroupCacheEntry(searchResult.getAttributes(), cacheEntry);
                cacheEntry.setDynamic(isDynamic);
                if (isDynamic
                        && searchResult.getAttributes().get(groupConfig.getDynamicMembersAttribute()) != null) {
                    cacheEntry.setDynamicMembersURL(searchResult.getAttributes()
                            .get(groupConfig.getDynamicMembersAttribute()).get().toString());
                }
                cacheEntry.setDn(searchResult.getNameInNamespace());
            } else {
                logger.error("Unexpected NameClassPair " + nameClassPair + " in " + getClass().getName());
            }
        }
    }

    /**
     * Callback handler for users, retrieve the list of usernames
     */
    private class UsersNameClassPairCallbackHandler implements NameClassPairCallbackHandler {
        private List<String> names = new ArrayList<String>();

        public List<String> getNames() {
            return names;
        }

        @Override
        public void handleNameClassPair(NameClassPair nameClassPair) throws NamingException {
            if (nameClassPair instanceof SearchResult) {
                SearchResult searchResult = (SearchResult) nameClassPair;
                LDAPUserCacheEntry cacheEntry = ldapCacheManager.getUserCacheEntryByDn(getKey(),
                        searchResult.getNameInNamespace());
                UserNameClassPairCallbackHandler nameClassPairCallbackHandler = new UserNameClassPairCallbackHandler(
                        cacheEntry);
                nameClassPairCallbackHandler.handleNameClassPair(nameClassPair);
                LDAPUserCacheEntry ldapUserCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
                ldapCacheManager.cacheUser(getKey(), ldapUserCacheEntry);
                names.add(ldapUserCacheEntry.getName());
            } else {
                logger.error("Unexpected NameClassPair " + nameClassPair + " in " + getClass().getName());
            }
        }
    }

    /**
     * Callback handler for groups, retrieve the list of groupnames
     */
    private class GroupsNameClassPairCallbackHandler implements NameClassPairCallbackHandler {
        private List<String> names = new LinkedList<>();
        private boolean isDynamic;

        public List<String> getNames() {
            return names;
        }

        private GroupsNameClassPairCallbackHandler(boolean isDynamic) {
            this.isDynamic = isDynamic;
        }

        @Override
        public void handleNameClassPair(NameClassPair nameClassPair) throws NamingException {
            if (nameClassPair instanceof SearchResult) {
                SearchResult searchResult = (SearchResult) nameClassPair;
                LDAPGroupCacheEntry cacheEntry = ldapCacheManager.getGroupCacheEntryByDn(getKey(),
                        searchResult.getNameInNamespace());
                GroupNameClassPairCallbackHandler nameClassPairCallbackHandler = new GroupNameClassPairCallbackHandler(
                        cacheEntry, isDynamic);
                nameClassPairCallbackHandler.handleNameClassPair(nameClassPair);
                LDAPGroupCacheEntry ldapGroupCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
                ldapCacheManager.cacheGroup(getKey(), ldapGroupCacheEntry);
                names.add(ldapGroupCacheEntry.getName());
            } else {
                logger.error("Unexpected NameClassPair " + nameClassPair + " in " + getClass().getName());
            }
        }
    }

    /**
     * Calback handler for dynamic members, retrieve the list of members
     */
    private class DynMembersNameClassPairCallbackHandler implements NameClassPairCallbackHandler {
        List<Member> members = Lists.newArrayList();

        public List<Member> getMembers() {
            return members;
        }

        @Override
        public void handleNameClassPair(NameClassPair nameClassPair) throws NamingException {
            if (nameClassPair instanceof SearchResult) {
                SearchResult searchResult = (SearchResult) nameClassPair;

                // try to know if we deal with a group or a user
                Boolean isUser = guessUserOrGroupFromDN(searchResult.getNameInNamespace());

                // try to retrieve the object from the cache
                LDAPAbstractCacheEntry cacheEntry;
                if (isUser != null) {
                    if (isUser) {
                        cacheEntry = ldapCacheManager.getUserCacheEntryByDn(getKey(),
                                searchResult.getNameInNamespace());
                    } else {
                        cacheEntry = ldapCacheManager.getGroupCacheEntryByDn(getKey(),
                                searchResult.getNameInNamespace());
                    }
                } else {
                    // look in all cache
                    cacheEntry = ldapCacheManager.getUserCacheEntryByDn(getKey(),
                            searchResult.getNameInNamespace());
                    if (cacheEntry == null) {
                        cacheEntry = ldapCacheManager.getGroupCacheEntryByDn(getKey(),
                                searchResult.getNameInNamespace());
                        isUser = cacheEntry != null ? false : null;
                    } else {
                        isUser = true;
                    }
                }
                if (cacheEntry != null) {
                    if (isUser) {
                        logger.debug("Dynamic member {} retrieved from cache and resolved as a user",
                                searchResult.getNameInNamespace());
                        members.add(new Member(cacheEntry.getName(), Member.MemberType.USER));
                    } else {
                        logger.debug("Dynamic member {} retrieved from cache and resolved as a group",
                                searchResult.getNameInNamespace());
                        members.add(new Member(cacheEntry.getName(), Member.MemberType.GROUP));
                    }
                }

                // try the objectclass
                Boolean isDynamic = false;
                searchResult.getAttributes().get(OBJECTCLASS_ATTRIBUTE).getAll();
                List<String> objectclasses = new ArrayList<String>();
                LdapUtils.collectAttributeValues(searchResult.getAttributes(), OBJECTCLASS_ATTRIBUTE, objectclasses,
                        String.class);
                if (objectclasses.contains(userConfig.getSearchObjectclass())) {
                    isUser = true;
                } else if (objectclasses.contains(groupConfig.getSearchObjectclass())) {
                    isUser = false;
                } else if (groupConfig.isDynamicEnabled()
                        && objectclasses.contains(groupConfig.getDynamicSearchObjectclass())) {
                    isUser = false;
                    isDynamic = true;
                }
                if (isUser != null) {
                    if (isUser) {
                        handleUserNameClassPair(nameClassPair, searchResult);
                    } else {
                        handleGroupNameClassPair(nameClassPair, searchResult, isDynamic);
                    }
                    return;
                }

                // try to guess the type on attributes present in the searchresult
                List<String> searchResultsAttr = new ArrayList<String>();
                NamingEnumeration<String> attrs = searchResult.getAttributes().getIDs();
                while (attrs.hasMore()) {
                    searchResultsAttr.add(attrs.next());
                }
                List<String> commonUserAttrs = getCommonAttributesSize(searchResultsAttr, getUserAttributes());
                List<String> commonGroupAttrs = getCommonAttributesSize(searchResultsAttr,
                        getGroupAttributes(isDynamic));
                if (commonUserAttrs.size() > 0 && commonUserAttrs.contains(userConfig.getUidSearchAttribute())
                        && commonUserAttrs.size() > commonGroupAttrs.size()) {
                    handleUserNameClassPair(nameClassPair, searchResult);
                    return;
                } else if (commonGroupAttrs.size() > 0
                        && commonGroupAttrs.contains(groupConfig.getSearchAttribute())) {
                    handleGroupNameClassPair(nameClassPair, searchResult, false);
                    return;
                }

                // type not resolved
                logger.warn("Dynamic member: " + searchResult.getNameInNamespace()
                        + " not resolved as a user or a group");
            } else {
                logger.error("Unexpected NameClassPair " + nameClassPair + " in " + getClass().getName());
            }
        }

        private void handleGroupNameClassPair(NameClassPair nameClassPair, SearchResult searchResult,
                Boolean isDynamic) throws NamingException {
            GroupNameClassPairCallbackHandler groupNameClassPairCallbackHandler = new GroupNameClassPairCallbackHandler(
                    null, isDynamic);
            groupNameClassPairCallbackHandler.handleNameClassPair(nameClassPair);
            LDAPGroupCacheEntry groupCacheEntry = groupNameClassPairCallbackHandler.getCacheEntry();
            ldapCacheManager.cacheGroup(getKey(), groupCacheEntry);
            members.add(new Member(groupCacheEntry.getName(), Member.MemberType.GROUP));
            if (logger.isDebugEnabled()) {
                logger.debug("Dynamic member {} resolved as a {}", searchResult.getNameInNamespace(),
                        isDynamic ? " dynamic group" : " group");
            }
        }

        private void handleUserNameClassPair(NameClassPair nameClassPair, SearchResult searchResult)
                throws NamingException {
            UserNameClassPairCallbackHandler userNameClassPairCallbackHandler = new UserNameClassPairCallbackHandler(
                    null);
            userNameClassPairCallbackHandler.handleNameClassPair(nameClassPair);
            LDAPUserCacheEntry userCacheEntry = userNameClassPairCallbackHandler.getCacheEntry();
            ldapCacheManager.cacheUser(getKey(), userCacheEntry);
            members.add(new Member(userCacheEntry.getName(), Member.MemberType.USER));
            logger.debug("Dynamic member {} resolved as a user", searchResult.getNameInNamespace());
        }
    }

    /**
     * Populate the given cache entry or create new one if the given is null with the LDAP attributes
     *
     * @param attrs
     * @param userCacheEntry
     * @return
     * @throws NamingException
     */
    private LDAPUserCacheEntry attributesToUserCacheEntry(Attributes attrs, LDAPUserCacheEntry userCacheEntry)
            throws NamingException {
        String userId = (String) attrs.get(userConfig.getUidSearchAttribute()).get();
        JahiaUser jahiaUser = new JahiaUserImpl(userId, null, attributesToJahiaProperties(attrs, true), false, key,
                null);
        if (userCacheEntry == null) {
            userCacheEntry = new LDAPUserCacheEntry(userId);
        }
        userCacheEntry.setExist(true);
        userCacheEntry.setUser(jahiaUser);

        return userCacheEntry;
    }

    /**
     * Populate the given cache entry or create new one if the given is null with the LDAP attributes
     *
     * @param attrs
     * @param groupCacheEntry
     * @return
     * @throws NamingException
     */
    private LDAPGroupCacheEntry attributesToGroupCacheEntry(Attributes attrs, LDAPGroupCacheEntry groupCacheEntry)
            throws NamingException {
        String groupId = (String) attrs.get(groupConfig.getSearchAttribute()).get();
        JahiaGroup jahiaGroup = new JahiaGroupImpl(groupId, null, null, attributesToJahiaProperties(attrs, false));

        if (groupCacheEntry == null) {
            groupCacheEntry = new LDAPGroupCacheEntry(jahiaGroup.getName());
        }
        groupCacheEntry.setExist(true);
        groupCacheEntry.setGroup(jahiaGroup);

        return groupCacheEntry;
    }

    /**
     * Map ldap attributes to jahia properties
     *
     * @param attributes
     * @param isUser
     * @return
     */
    private Properties attributesToJahiaProperties(Attributes attributes, boolean isUser) {
        Properties props = new Properties();
        Map<String, String> attributesMapper = isUser ? userConfig.getAttributesMapper()
                : groupConfig.getAttributesMapper();
        for (String propertyKey : attributesMapper.keySet()) {
            Attribute ldapAttribute = attributes.get(attributesMapper.get(propertyKey));
            try {
                if (ldapAttribute != null && ldapAttribute.get() instanceof String) {
                    props.put(propertyKey, ldapAttribute.get());
                }
            } catch (NamingException e) {
                logger.error("Error reading LDAP attribute:" + ldapAttribute.toString());
            }

        }
        return props;
    }

    /**
     * build a user query, that use the searchCriteria from jahia forms
     *
     * @param searchCriteria
     * @return
     */
    private ContainerCriteria buildUserQuery(Properties searchCriteria) {
        List<String> attributesToRetrieve = getUserAttributes();

        ContainerCriteria query = query().base(userConfig.getUidSearchName())
                .attributes(attributesToRetrieve.toArray(new String[attributesToRetrieve.size()]))
                .countLimit((int) userConfig.getSearchCountlimit()).where(OBJECTCLASS_ATTRIBUTE)
                .is(StringUtils.defaultString(userConfig.getSearchObjectclass(), "*"));

        // transform jnt:user props to ldap props
        Properties ldapfilters = mapJahiaPropertiesToLDAP(searchCriteria, userConfig.getAttributesMapper());

        if (ldapfilters == null) {
            return null;
        }

        // define and / or operator
        boolean orOp = isOrOperator(ldapfilters, searchCriteria);

        // process the user specific filters
        ContainerCriteria filterQuery = getQueryFilters(ldapfilters, userConfig, orOp);

        if (filterQuery != null) {
            query.and(filterQuery);
        }

        return query;
    }

    private ContainerCriteria getGroupQuery(Properties searchCriteria, boolean isDynamic) {
        ContainerCriteria query = null;
        if (searchCriteria.isEmpty()) {
            // we once build and reuse the queries in case of empty search criteria
            if (isDynamic) {
                if (searchGroupDynamicCriteria == null) {
                    searchGroupDynamicCriteria = buildGroupQuery(searchCriteria, isDynamic);
                }
                query = searchGroupDynamicCriteria;
            } else {
                if (searchGroupCriteria == null) {
                    searchGroupCriteria = buildGroupQuery(searchCriteria, isDynamic);
                }
                query = searchGroupCriteria;
            }
        } else {
            query = buildGroupQuery(searchCriteria, isDynamic);
        }

        return query;
    }

    /**
     * Build a group query based on search criteria
     *
     * @param searchCriteria
     * @param isDynamic
     * @return
     */
    private ContainerCriteria buildGroupQuery(Properties searchCriteria, boolean isDynamic) {
        List<String> attributesToRetrieve = getGroupAttributes(isDynamic);
        if (isDynamic) {
            attributesToRetrieve.add(groupConfig.getDynamicMembersAttribute());
        }

        ContainerCriteria query = query().base(groupConfig.getSearchName())
                .attributes(attributesToRetrieve.toArray(new String[attributesToRetrieve.size()]))
                .countLimit((int) groupConfig.getSearchCountlimit()).where(OBJECTCLASS_ATTRIBUTE)
                .is(isDynamic ? groupConfig.getDynamicSearchObjectclass() : groupConfig.getSearchObjectclass());

        // transform jnt:user props to ldap props
        Properties ldapfilters = mapJahiaPropertiesToLDAP(searchCriteria, groupConfig.getAttributesMapper());

        // define and / or operator
        boolean orOp = isOrOperator(ldapfilters, searchCriteria);

        // process the user specific filters
        ContainerCriteria filterQuery = getQueryFilters(ldapfilters, groupConfig, orOp);

        if (filterQuery != null) {
            query.and(filterQuery);
        }

        return query;
    }

    private boolean isOrOperator(Properties ldapfilters, Properties searchCriteria) {
        if (ldapfilters.size() > 1) {
            if (searchCriteria.containsKey(JahiaUserManagerService.MULTI_CRITERIA_SEARCH_OPERATION)) {
                if (((String) searchCriteria.get(JahiaUserManagerService.MULTI_CRITERIA_SEARCH_OPERATION)).trim()
                        .toLowerCase().equals("and")) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Construct the filters for queries
     *
     * @param ldapfilters
     * @param config
     * @param isOrOperator
     * @return
     */
    private ContainerCriteria getQueryFilters(Properties ldapfilters, AbstractConfig config, boolean isOrOperator) {
        ContainerCriteria filterQuery = null;
        if (ldapfilters.containsKey("*")) {
            // Search on all wildcards attributes
            String filterValue = ldapfilters.getProperty("*");
            if (CollectionUtils.isNotEmpty(config.getSearchWildcardsAttributes())) {
                for (String wildcardAttribute : config.getSearchWildcardsAttributes()) {
                    if (filterQuery == null) {
                        filterQuery = query().where(wildcardAttribute).like(filterValue);
                    } else {
                        addCriteriaToQuery(filterQuery, true, wildcardAttribute).like(filterValue);
                    }
                }
            }
        } else {
            // consider the attributes
            Iterator<?> filterKeys = ldapfilters.keySet().iterator();
            while (filterKeys.hasNext()) {
                String filterName = (String) filterKeys.next();
                String filterValue = ldapfilters.getProperty(filterName);

                if (filterQuery == null) {
                    filterQuery = query().where(filterName).like(filterValue);
                } else {
                    addCriteriaToQuery(filterQuery, isOrOperator, filterName).like(filterValue);
                }
            }
        }
        return filterQuery;
    }

    private ConditionCriteria addCriteriaToQuery(ContainerCriteria query, boolean isOr, String attribute) {
        if (isOr) {
            return query.or(attribute);
        } else {
            return query.and(attribute);
        }
    }

    /**
     * Map jahia properties to ldap properties
     *
     * @param searchCriteria
     * @param configProperties
     * @return
     */
    private Properties mapJahiaPropertiesToLDAP(Properties searchCriteria, Map<String, String> configProperties) {
        if (searchCriteria.size() == 0) {
            return searchCriteria;
        }
        Properties p = new Properties();
        if (searchCriteria.containsKey("*")) {
            p.setProperty("*", searchCriteria.getProperty("*"));
            if (searchCriteria.size() == 1) {
                return p;
            }
        }

        for (Map.Entry<Object, Object> entry : searchCriteria.entrySet()) {
            if (configProperties.containsKey(entry.getKey())) {
                p.setProperty(configProperties.get(entry.getKey()), (String) entry.getValue());
            } else if (!entry.getKey().equals("*")
                    && !entry.getKey().equals(JahiaUserManagerService.MULTI_CRITERIA_SEARCH_OPERATION)) {
                return null;
            }
        }

        return p;
    }

    /**
     * Try to guess if the given dn is a user or a group
     *
     * @param dn
     * @return
     * @throws InvalidNameException
     */
    private Boolean guessUserOrGroupFromDN(String dn) throws InvalidNameException {
        Boolean isUser = null;
        final LdapName memberLdapName = LdapUtils.newLdapName(dn);
        if (memberLdapName.startsWith(new LdapName(userConfig.getUidSearchName()))) {
            // it's a user
            isUser = distinctBase ? true : null;
        } else if (memberLdapName.startsWith(new LdapName(groupConfig.getSearchName()))) {
            // it's a group
            isUser = distinctBase ? false : null;
        }
        return isUser;
    }

    /**
     * get user ldap attributes that need to be return from the ldap
     *
     * @return
     */
    private List<String> getUserAttributes() {
        List<String> attrs = new ArrayList<String>(userConfig.getAttributesMapper().values());
        attrs.add(userConfig.getUidSearchAttribute());
        return attrs;
    }

    /**
     * get group ldap attributes that need to be return from the ldap
     *
     * @return
     */
    private List<String> getGroupAttributes(boolean isDynamic) {
        List<String> attrs = new ArrayList<String>(groupConfig.getAttributesMapper().values());
        attrs.add(groupConfig.getSearchAttribute());
        if (isDynamic) {
            attrs.add(groupConfig.getDynamicMembersAttribute());
        }
        return attrs;
    }

    /**
     * Construct a list that contain only the elements also contains from the other list
     *
     * @param first
     * @param second
     * @return
     */
    private List<String> getCommonAttributesSize(List<String> first, List<String> second) {
        List<String> commons = new ArrayList<String>(first);
        commons.retainAll(second);
        return commons;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getKey() {
        return key;
    }

    public void setExternalUserGroupService(ExternalUserGroupService externalUserGroupService) {
        this.externalUserGroupService = externalUserGroupService;
    }

    public void setLdapTemplateWrapper(LdapTemplateWrapper ldapTemplateWrapper) {
        this.ldapTemplateWrapper = ldapTemplateWrapper;
    }

    public void setContextSource(LdapContextSource contextSource) {
        this.contextSource = contextSource;
    }

    /**
     * Registers the provider.
     */
    public void register() {
        externalUserGroupService.register(key, userConfig.getTargetSite(), this);
    }

    /**
     * unregister the provider
     */
    public void unregister() {
        externalUserGroupService.unregister(key);
    }

    public void setLdapCacheManager(LDAPCacheManager ldapCacheManager) {
        this.ldapCacheManager = ldapCacheManager;
    }

    public void setUserConfig(UserConfig userConfig) {
        this.userConfig = userConfig;
    }

    public void setGroupConfig(GroupConfig groupConfig) {
        this.groupConfig = groupConfig;
    }

    public void setDistinctBase(boolean distinctBase) {
        this.distinctBase = distinctBase;
    }

    public ExternalUserGroupService getExternalUserGroupService() {
        return externalUserGroupService;
    }

    @Override
    public boolean supportsGroups() {
        return groupConfig.isMinimalSettingsOk();
    }

    @Override
    public String toString() {
        return "LDAPUserGroupProvider{" + "key='" + key + '\'' + '}';
    }
}