/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2017 The PWM Project
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.ldap;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.novell.ldapchai.ChaiFactory;
import com.novell.ldapchai.ChaiUser;
import com.novell.ldapchai.exception.ChaiOperationException;
import com.novell.ldapchai.exception.ChaiUnavailableException;
import com.novell.ldapchai.provider.ChaiProvider;
import password.pwm.PwmApplication;
import password.pwm.bean.UserIdentity;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.http.PwmSession;
import password.pwm.util.java.JavaHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class LdapUserDataReader implements Serializable, UserDataReader {
private static final Boolean NULL_CACHE_VALUE = Boolean.FALSE;
private final Cache<String,Object> cacheMap = Caffeine.newBuilder()
.maximumSize(100) // safety limit
.build();
private final ChaiUser user;
private final UserIdentity userIdentity;
private enum PrivateFlag {
MultiValueRead
}
public LdapUserDataReader(
final UserIdentity userIdentity,
final ChaiUser user
) {
this.userIdentity = userIdentity;
this.user = user;
}
public static UserDataReader appProxiedReader(
final PwmApplication pwmApplication,
final UserIdentity userIdentity
)
throws PwmUnrecoverableException
{
final ChaiUser user;
user = pwmApplication.getProxiedChaiUser(userIdentity);
return new LdapUserDataReader(userIdentity, user);
}
public static UserDataReader selfProxiedReader(
final PwmApplication pwmApplication,
final PwmSession pwmSession,
final UserIdentity userIdentity
)
throws PwmUnrecoverableException, ChaiUnavailableException
{
if (!userIdentity.getLdapProfileID().equals(pwmSession.getUserInfoBean().getUserIdentity().getLdapProfileID())) {
throw new PwmUnrecoverableException(PwmError.ERROR_NO_LDAP_CONNECTION);
}
final ChaiProvider chaiProvider = pwmSession.getSessionManager().getChaiProvider();
final ChaiUser chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(),chaiProvider);
return new LdapUserDataReader(userIdentity,chaiUser);
}
@Override
public String getUserDN() {
return this.user.getEntryDN();
}
@Override
public String readStringAttribute(
final String attribute,
final Flag... flags
)
throws ChaiUnavailableException, ChaiOperationException
{
final Map<String,String> results = readStringAttributes(Collections.singletonList(attribute), flags);
if (results == null || results.isEmpty()) {
return null;
}
return results.values().iterator().next();
}
@Override
public Date readDateAttribute(final String attribute)
throws ChaiUnavailableException, ChaiOperationException
{
return user.readDateAttribute(attribute);
}
public Map<String,String> readStringAttributes(
final Collection<String> attributes,
final Flag... flags
)
throws ChaiUnavailableException, ChaiOperationException
{
final Map<String,List<String>> valueMap = readMultiStringAttributesImpl(attributes, Collections.<PrivateFlag>emptyList(), flags);
final Map<String,String> returnValue = new LinkedHashMap<>();
for (final String key : valueMap.keySet()) {
final List<String> values = valueMap.get(key);
if (values != null && !values.isEmpty()) {
returnValue.put(key, values.iterator().next());
}
}
return returnValue;
}
public Map<String,List<String>> readMultiStringAttributes(
final Collection<String> attributes,
final Flag... flags
)
throws ChaiUnavailableException, ChaiOperationException
{
return readMultiStringAttributesImpl(attributes, Collections.singletonList(PrivateFlag.MultiValueRead), flags);
}
private Map<String,List<String>> readMultiStringAttributesImpl(
final Collection<String> attributes,
final Collection<PrivateFlag> privateFlags,
final Flag... flags
)
throws ChaiUnavailableException, ChaiOperationException
{
final boolean ignoreCache = JavaHelper.enumArrayContainsValue(flags, Flag.ignoreCache);
if (user == null || attributes == null || attributes.isEmpty()) {
return Collections.emptyMap();
}
if (ignoreCache) {
cacheMap.invalidateAll();
}
// figure out uncached attributes.
final List<String> uncachedAttributes = new ArrayList<>(attributes);
uncachedAttributes.removeAll(cacheMap.asMap().keySet());
// read uncached attributes into cache
if (!uncachedAttributes.isEmpty()) {
for (final String attribute : attributes) {
if (privateFlags.contains(PrivateFlag.MultiValueRead)) {
final Set<String> readData = user.readMultiStringAttribute(attribute);
final List<String> stringList = readData == null ? null : new ArrayList<>(readData);
cacheMap.put(attribute, stringList != null && !stringList.isEmpty() ? stringList : NULL_CACHE_VALUE);
} else {
final String readData = user.readStringAttribute(attribute);
cacheMap.put(attribute, readData != null ? Collections.singletonList(readData) : NULL_CACHE_VALUE);
}
}
}
// build result data from cache
final Map<String,List<String>> returnMap = new HashMap<>();
for (final String attribute : attributes) {
final Object cachedValue = cacheMap.getIfPresent(attribute);
if (cachedValue != null && !NULL_CACHE_VALUE.equals(cachedValue)) {
returnMap.put(attribute,(List<String>)cachedValue);
}
}
return Collections.unmodifiableMap(returnMap);
}
@Override
public List<String> readMultiStringAttribute(final String attribute, final Flag... flags) throws ChaiUnavailableException, ChaiOperationException {
return readMultiStringAttributesImpl(Collections.singletonList(attribute), Collections.singletonList(PrivateFlag.MultiValueRead), flags).get(attribute);
}
}