/**
* ==========================================================================================
* = JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION =
* ==========================================================================================
*
* http://www.jahia.com
*
* Copyright (C) 2002-2017 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 CHOOSE 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 3 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, see <http://www.gnu.org/licenses/>.
*
*
* 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.
*/
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.apache.commons.lang.reflect.FieldUtils;
import org.apache.jackrabbit.util.Text;
import org.jahia.modules.external.users.*;
import org.jahia.services.content.decorator.JCRMountPointNode;
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.LdapTemplateCallback;
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.CommunicationException;
import org.springframework.ldap.InsufficientResourcesException;
import org.springframework.ldap.ServiceUnavailableException;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.NameClassPairCallbackHandler;
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.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
/**
* Implementation of UserGroupProvider for Spring LDAP
*
* @author david
*/
public class LDAPUserGroupProvider extends BaseUserGroupProvider {
public static final int CONNECTION_ERROR_CACHE_TTL = 5;
protected static final String OBJECTCLASS_ATTRIBUTE = "objectclass";
private static Logger logger = LoggerFactory.getLogger(LDAPUserGroupProvider.class);
private LdapContextSource contextSource;
private LdapTemplateWrapper ldapTemplateWrapper;
// Configs
private UserConfig userConfig;
private GroupConfig groupConfig;
private boolean distinctBase = false; // if user and group are different
// Cache
private LDAPCacheManager ldapCacheManager;
private ContainerCriteria searchGroupCriteria;
private ContainerCriteria searchGroupDynamicCriteria;
private AtomicInteger timeoutCount = new AtomicInteger(0);
private int maxLdapTimeoutCountBeforeDisconnect = 3;
private ContainerCriteria groupSearchFilterCriteria;
private ContainerCriteria userSearchFilterCriteria;
/**
* If a pre-defined group search filter was configured, apply it on the provided query.
*
* @param query the search query to apply the group filter on
* @return the adjusted query
*/
private ContainerCriteria applyPredefinedGroupFilter(ContainerCriteria query) {
ContainerCriteria userFilter = getGroupSearchFilterCriteria();
if (userFilter != null) {
query.and(userFilter);
}
return query;
}
/**
* If a pre-defined user search filter was configured, apply it on the provided query.
*
* @param query the search query to apply the user filter on
* @return the adjusted query
*/
private ContainerCriteria applyPredefinedUserFilter(ContainerCriteria query, boolean isSingleUserLookup) {
ContainerCriteria userFilter = getUserSearchFilterCriteria();
if (userFilter != null) {
query.and(userFilter);
}
return query;
}
@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 " + getKey());
} 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 " + getKey());
} 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 new ArrayList<Member>(groupCacheEntry.getMembers());
}
List<Member> members = null;
if (groupCacheEntry.isDynamic() && StringUtils.isNotEmpty(groupCacheEntry.getDynamicMembersURL())) {
members = loadMembersFromUrl(groupCacheEntry.getDynamicMembersURL());
} else {
members = loadMembersFromDN(groupCacheEntry.getDn());
}
if (CollectionUtils.isNotEmpty(members)) {
groupCacheEntry.setMembers(members);
ldapCacheManager.cacheGroup(getKey(), groupCacheEntry);
return new ArrayList<Member>(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 new ArrayList<String>(cacheEntry.getMemberships());
}
if (!cacheEntry.getExist()) {
return null;
}
final String dn = cacheEntry.getDn();
long startTime = System.currentTimeMillis();
List<String> memberships = ldapTemplateWrapper.execute(new BaseLdapActionCallback<List<String>>(getExternalUserGroupService(), getKey()) {
@Override
public List<String> doInLdap(LdapTemplate ldapTemplate) {
return ldapTemplate.search(
applyPredefinedGroupFilter(query().base(groupConfig.getSearchName())
.attributes(groupConfig.getSearchAttribute())
.where(OBJECTCLASS_ATTRIBUTE)
.is(groupConfig.getSearchObjectclass())
.and(groupConfig.getMembersAttribute())
.like(dn)),
new AttributesMapper<String>() {
@Override
public String mapFromAttributes(Attributes attrs) throws NamingException {
return encode(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() - startTime
});
}
// in case of communication error, the result may be null
if (memberships == null) {
memberships = new ArrayList<String>();
}
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 new ArrayList<String>(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 Collections.singletonList(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 startTime = System.currentTimeMillis();
ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(getExternalUserGroupService(), getKey()) {
@Override
public Object doInLdap(LdapTemplate ldapTemplate) {
ldapTemplate.search(query, searchNameClassPairCallbackHandler);
return null;
}
});
List<String> names = searchNameClassPairCallbackHandler.getNames();
if (logger.isDebugEnabled()) {
logger.debug("Search users for criteria {} using filter {} done in {} ms. Found {} entries.", new Object[] {
searchCriteria, query.filter(), System.currentTimeMillis() - startTime, names.size() });
}
return names.subList(Math.min((int) offset, names.size()), limit < 0 ? names.size() : Math.min((int) (offset + limit), names.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 = 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) {
logger.debug("Verifying password for {}...", userName);
DirContext ctx = null;
try {
LDAPUserCacheEntry userCacheEntry = getUserCacheEntry(userName, true);
if (userCacheEntry.getExist()) {
long startTime = 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(LdapUtils.newLdapName(userCacheEntry.getDn()));
logger.debug("Password verified for {} in {} ms", userName, System.currentTimeMillis() - startTime);
return true;
}
} catch (NamingException | org.springframework.ldap.NamingException e) {
// Context creation failed - authentication did not succeed
logger.warn("Login failed for user " + userName + ": " + e.getMessage() + " (enable debug for full stacktrace)");
logger.debug(e.getMessage(), e);
} finally {
LdapUtils.closeContext(ctx);
}
return false;
}
@Override
public boolean isAvailable() throws RepositoryException {
// do a simple search on users to check the availability
long startTime = System.currentTimeMillis();
final Exception[] exception = new Exception[1];
boolean available = ldapTemplateWrapper.execute(new BaseLdapActionCallback<Boolean>(getExternalUserGroupService(), getKey()) {
@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);
exception[0] = e;
return timeoutCount.get() < maxLdapTimeoutCountBeforeDisconnect;
}
});
logger.debug("Is available in {} ms", System.currentTimeMillis() - startTime);
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", exception[0]);
} 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 startTime = System.currentTimeMillis();
ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(getExternalUserGroupService(), getKey()) {
@Override
public Object doInLdap(LdapTemplate ldapTemplate) {
ldapTemplate.search(query, searchNameClassPairCallbackHandler);
return null;
}
});
List<String> names = searchNameClassPairCallbackHandler.getNames();
if (logger.isDebugEnabled()) {
logger.debug("Search groups for criteria {} using filter {} done in {} ms. Found {} entries.", new Object[] {
searchCriteria, query.filter(), System.currentTimeMillis() - startTime, names.size() });
}
return names;
}
/**
* 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 startTime = System.currentTimeMillis();
ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(getExternalUserGroupService(), getKey()) {
@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() - startTime);
return 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 startTime = System.currentTimeMillis();
final LdapName groupName = LdapUtils.newLdapName(groupDN);
NamingEnumeration<?> members = ldapTemplateWrapper.execute(new BaseLdapActionCallback<NamingEnumeration<?>>(getExternalUserGroupService(), getKey()) {
@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(groupName, 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(groupName, 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() - startTime);
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 = null;
if (userConfig.isCanGroupContainSubGroups()) {
isUser = guessUserOrGroupFromDN(memberNaming);
} else {
isUser = 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 startTime = System.currentTimeMillis();
boolean validLdapCall = ldapTemplateWrapper.execute(new BaseLdapActionCallback<Boolean>(getExternalUserGroupService(), getKey()) {
@Override
public Boolean doInLdap(LdapTemplate ldapTemplate) {
ldapTemplate.search(applyPredefinedUserFilter(query().base(userConfig.getUidSearchName())
.attributes(userAttrs.toArray(new String[userAttrs.size()]))
.where(OBJECTCLASS_ATTRIBUTE).is(userConfig.getSearchObjectclass())
.and(userConfig.getUidSearchAttribute()).is(decode(userName)), true),
nameClassPairCallbackHandler);
return true;
}
@Override
public Boolean onError(Exception e) {
super.onError(e);
return false;
}
});
if (logger.isDebugEnabled()) {
logger.debug("Get user {} in {} ms", userName, System.currentTimeMillis() - startTime);
}
if (nameClassPairCallbackHandler.getCacheEntry() != null) {
userCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
userCacheEntry.setExist(true);
} else {
userCacheEntry = new LDAPUserCacheEntry(userName);
userCacheEntry.setExist(false);
}
if (cache && validLdapCall) {
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;
}
}
try {
groupCacheEntry = getGroupCacheEntryByName(groupName, false, false);
if (groupCacheEntry == null) {
if (groupConfig.isDynamicEnabled()) {
groupCacheEntry = getGroupCacheEntryByName(groupName, false, true);
} else {
groupCacheEntry = new LDAPGroupCacheEntry(groupName);
groupCacheEntry.setExist(false);
}
}
} catch (Exception e) {
// Exception already logged, skip cache and return null
return null;
}
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) throws Exception {
final List<String> groupAttrs = getGroupAttributes(isDynamic);
final GroupNameClassPairCallbackHandler nameClassPairCallbackHandler = new GroupNameClassPairCallbackHandler(null, isDynamic);
long startTime = System.currentTimeMillis();
final Exception[] exceptions = new Exception[1];
boolean validLdapCall = ldapTemplateWrapper.execute(new BaseLdapActionCallback<Boolean>(getExternalUserGroupService(), getKey()) {
@Override
public Boolean doInLdap(LdapTemplate ldapTemplate) {
ldapTemplate.search(applyPredefinedGroupFilter(query().base(groupConfig.getSearchName())
.attributes(groupAttrs.toArray(new String[groupAttrs.size()]))
.where(OBJECTCLASS_ATTRIBUTE).is(isDynamic ? groupConfig.getDynamicSearchObjectclass() : groupConfig.getSearchObjectclass())
.and(groupConfig.getSearchAttribute()).is(decode(name))),
nameClassPairCallbackHandler);
return true;
}
@Override
public Boolean onError(Exception e) {
exceptions[0] = e;
super.onError(e);
return false;
}
});
if (!validLdapCall) {
throw exceptions[0];
}
if (logger.isDebugEnabled()) {
logger.debug("Get group {} in {} ms", name, System.currentTimeMillis() - startTime);
}
return getAndCacheGroupEntry(nameClassPairCallbackHandler, cache);
}
/**
* 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 startTime = System.currentTimeMillis();
ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(getExternalUserGroupService(), getKey()) {
@Override
public Object doInLdap(LdapTemplate ldapTemplate) {
ldapTemplate.search(applyPredefinedGroupFilter(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;
}
});
if (logger.isDebugEnabled()) {
logger.debug("Get group from dn {} in {} ms", dn, System.currentTimeMillis() - startTime);
}
return getAndCacheGroupEntry(nameClassPairCallbackHandler, cache);
}
private LDAPGroupCacheEntry getAndCacheGroupEntry(GroupNameClassPairCallbackHandler nameClassPairCallbackHandler, boolean cache) {
LDAPGroupCacheEntry ldapGroupCacheEntry = nameClassPairCallbackHandler.getCacheEntry();
if (ldapGroupCacheEntry != null) {
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 startTime = System.currentTimeMillis();
ldapTemplateWrapper.execute(new BaseLdapActionCallback<Object>(getExternalUserGroupService(), getKey()) {
@Override
public Object doInLdap(LdapTemplate ldapTemplate) {
ldapTemplate.search(applyPredefinedUserFilter(query().base(dn)
.attributes(userAttrs.toArray(new String[userAttrs.size()]))
.searchScope(SearchScope.OBJECT)
.where(OBJECTCLASS_ATTRIBUTE).is(userConfig.getSearchObjectclass()), true),
nameClassPairCallbackHandler);
return null;
}
});
if (logger.isDebugEnabled()) {
logger.debug("Get user from dn {} in {} ms", dn, System.currentTimeMillis() - startTime);
}
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);
if (cacheEntry != null) {
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());
if (cacheEntry == null || cacheEntry.getExist() == null || !cacheEntry.getExist()) {
UserNameClassPairCallbackHandler nameClassPairCallbackHandler = new UserNameClassPairCallbackHandler(cacheEntry);
nameClassPairCallbackHandler.handleNameClassPair(nameClassPair);
cacheEntry = nameClassPairCallbackHandler.getCacheEntry();
if (cacheEntry != null) {
ldapCacheManager.cacheUser(getKey(), cacheEntry);
}
}
if (cacheEntry != null) {
names.add(cacheEntry.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());
if (cacheEntry == null || cacheEntry.getExist() == null || !cacheEntry.getExist().booleanValue()) {
GroupNameClassPairCallbackHandler nameClassPairCallbackHandler = new GroupNameClassPairCallbackHandler(cacheEntry, isDynamic);
nameClassPairCallbackHandler.handleNameClassPair(nameClassPair);
cacheEntry = nameClassPairCallbackHandler.getCacheEntry();
if (cacheEntry != null) {
ldapCacheManager.cacheGroup(getKey(), cacheEntry);
}
}
if (cacheEntry != null) {
names.add(cacheEntry.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 {
private 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) {
if (logger.isDebugEnabled()) {
logger.debug("Dynamic member {} retrieved from cache and resolved as a user", searchResult.getNameInNamespace());
}
members.add(new Member(cacheEntry.getName(), Member.MemberType.USER));
} else {
if (logger.isDebugEnabled()) {
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 = getCommonAttributes(searchResultsAttr, getUserAttributes());
List<String> commonGroupAttrs = getCommonAttributes(searchResultsAttr, getGroupAttributes(isDynamic));
if (commonUserAttrs.contains(userConfig.getUidSearchAttribute()) && commonUserAttrs.size() > commonGroupAttrs.size()) {
handleUserNameClassPair(nameClassPair, searchResult);
return;
} else if (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();
if (userCacheEntry != null) {
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 {
Attribute uidAttr = attrs.get(userConfig.getUidSearchAttribute());
if (uidAttr == null) {
logger.warn("LDAP user entry is missing the required {} attribute. Skipping user. Available attributes: {}",
userConfig.getUidSearchAttribute(), attrs);
return null;
}
String userId = (String) uidAttr.get();
JahiaUser jahiaUser = new JahiaUserImpl(encode(userId), null, attributesToJahiaProperties(attrs, true), getKey(), 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(encode(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;
}
applyPredefinedUserFilter(query, false);
// 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 createContainerCriteria(String filter) {
try {
return (ContainerCriteria) FieldUtils.readField(query().filter(filter), "rootContainer", true);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private ContainerCriteria getGroupSearchFilterCriteria() {
if (groupSearchFilterCriteria == null) {
String filter = groupConfig.getSearchFilter();
if (filter != null) {
groupSearchFilterCriteria = createContainerCriteria(filter);
logger.info("Using pre-defined filter for group search: {}", filter);
}
}
return groupSearchFilterCriteria;
}
private ContainerCriteria getUserSearchFilterCriteria() {
if (userSearchFilterCriteria == null) {
String filter = userConfig.getSearchFilter();
if (filter != null) {
userSearchFilterCriteria = createContainerCriteria(filter);
logger.info("Using pre-defined filter for user search: {}", filter);
}
}
return userSearchFilterCriteria;
}
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) {
// First do non-synchronized check to avoid locking any threads that invoke the method simultaneously.
if (searchGroupDynamicCriteria == null) {
// Then check-again-and-initialize-if-needed within the synchronized block to ensure check-and-initialization consistency.
synchronized(this) {
if (searchGroupDynamicCriteria == null) {
searchGroupDynamicCriteria = buildGroupQuery(searchCriteria, isDynamic);
}
}
}
query = searchGroupDynamicCriteria;
} else {
// First do non-synchronized check to avoid locking any threads that invoke the method simultaneously.
if (searchGroupCriteria == null) {
// Then check-again-and-initialize-if-needed within the synchronized block to ensure check-and-initialization consistency.
synchronized(this) {
if (searchGroupCriteria == null) {
searchGroupCriteria = buildGroupQuery(searchCriteria, isDynamic);
}
}
}
query = searchGroupCriteria;
}
} else {
query = buildGroupQuery(searchCriteria, isDynamic);
}
return query;
}
private void flushGroupQuery() {
searchGroupCriteria = null;
searchGroupDynamicCriteria = null;
groupSearchFilterCriteria = null;
}
/**
* 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());
applyPredefinedGroupFilter(query);
// 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 static 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.isEmpty()) {
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> getCommonAttributes(List<String> first, List<String> second) {
List<String> commons = new ArrayList<String>(first);
commons.retainAll(second);
return commons;
}
public void setLdapTemplateWrapper(LdapTemplateWrapper ldapTemplateWrapper) {
this.ldapTemplateWrapper = ldapTemplateWrapper;
}
public void setContextSource(LdapContextSource contextSource) {
this.contextSource = contextSource;
}
@Override
protected String getSiteKey() {
return userConfig.getTargetSite();
}
public void setLdapCacheManager(LDAPCacheManager ldapCacheManager) {
this.ldapCacheManager = ldapCacheManager;
}
public void setUserConfig(UserConfig userConfig) {
this.userConfig = userConfig;
userSearchFilterCriteria = null;
}
// This only should be invoked during a period of provider inactivity, so that flushing group queries does not interfere with ongoing requests serving different threads simultaneously.
public void setGroupConfig(GroupConfig groupConfig) {
this.groupConfig = groupConfig;
flushGroupQuery();
}
public void setDistinctBase(boolean distinctBase) {
this.distinctBase = distinctBase;
}
public void setMaxLdapTimeoutCountBeforeDisconnect(int maxLdapTimeoutCountBeforeDisconnect) {
this.maxLdapTimeoutCountBeforeDisconnect = maxLdapTimeoutCountBeforeDisconnect;
}
@Override
public boolean supportsGroups() {
return groupConfig.isMinimalSettingsOk();
}
@Override
public String toString() {
return "LDAPUserGroupProvider{" + "getKey()='" + getKey() + '\'' + '}';
}
/**
* Base LDAP template action callback that unmounts the LDAP provider in case of communication issue with the LDAP server
* using the onError method.
* Feel free to use it, implementing at least the doInLdap to wrap the call to the ldapTemplate object.
*
* @author kevan
*/
public abstract class BaseLdapActionCallback<T> implements LdapTemplateCallback<T> {
private final ExternalUserGroupService externalUserGroupService;
private final String key;
protected BaseLdapActionCallback(ExternalUserGroupService externalUserGroupService, String key) {
this.externalUserGroupService = externalUserGroupService;
this.key = key;
}
@Override
public void onSuccess() {
timeoutCount.set(0);
}
@Override
public T onError(Exception e) {
final Throwable cause = e.getCause();
logger.error("An error occurred while communicating with the LDAP server " + key, e);
if (cause instanceof javax.naming.CommunicationException || cause instanceof javax.naming.NamingException || cause instanceof CommunicationException || cause instanceof ServiceUnavailableException || cause instanceof InsufficientResourcesException) {
if (timeoutCount.incrementAndGet() >= maxLdapTimeoutCountBeforeDisconnect) {
externalUserGroupService.setMountStatus(key, JCRMountPointNode.MountStatus.waiting, cause.getMessage());
}
} else {
externalUserGroupService.setMountStatus(key, JCRMountPointNode.MountStatus.error, e.getMessage());
}
return null;
}
}
private String decode(String name) {
return Text.unescapeIllegalJcrChars(name);
}
private String encode(String value) throws NamingException {
return Text.escapeIllegalJcrChars(value);
}
}