|
@@ -20,7 +20,7 @@
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
*/
|
|
|
|
|
|
-package password.pwm.ldap;
|
|
|
|
|
|
+package password.pwm.ldap.search;
|
|
|
|
|
|
import com.novell.ldapchai.ChaiFactory;
|
|
import com.novell.ldapchai.ChaiFactory;
|
|
import com.novell.ldapchai.ChaiUser;
|
|
import com.novell.ldapchai.ChaiUser;
|
|
@@ -28,11 +28,14 @@ import com.novell.ldapchai.exception.ChaiOperationException;
|
|
import com.novell.ldapchai.exception.ChaiUnavailableException;
|
|
import com.novell.ldapchai.exception.ChaiUnavailableException;
|
|
import com.novell.ldapchai.provider.ChaiProvider;
|
|
import com.novell.ldapchai.provider.ChaiProvider;
|
|
import com.novell.ldapchai.util.SearchHelper;
|
|
import com.novell.ldapchai.util.SearchHelper;
|
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
|
+import lombok.Getter;
|
|
import password.pwm.AppProperty;
|
|
import password.pwm.AppProperty;
|
|
import password.pwm.PwmApplication;
|
|
import password.pwm.PwmApplication;
|
|
import password.pwm.PwmConstants;
|
|
import password.pwm.PwmConstants;
|
|
import password.pwm.bean.SessionLabel;
|
|
import password.pwm.bean.SessionLabel;
|
|
import password.pwm.bean.UserIdentity;
|
|
import password.pwm.bean.UserIdentity;
|
|
|
|
+import password.pwm.config.Configuration;
|
|
import password.pwm.config.FormConfiguration;
|
|
import password.pwm.config.FormConfiguration;
|
|
import password.pwm.config.PwmSetting;
|
|
import password.pwm.config.PwmSetting;
|
|
import password.pwm.config.option.DuplicateMode;
|
|
import password.pwm.config.option.DuplicateMode;
|
|
@@ -42,68 +45,97 @@ import password.pwm.error.PwmError;
|
|
import password.pwm.error.PwmException;
|
|
import password.pwm.error.PwmException;
|
|
import password.pwm.error.PwmOperationalException;
|
|
import password.pwm.error.PwmOperationalException;
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
-import password.pwm.http.PwmRequest;
|
|
|
|
|
|
+import password.pwm.health.HealthRecord;
|
|
import password.pwm.svc.PwmService;
|
|
import password.pwm.svc.PwmService;
|
|
import password.pwm.svc.stats.Statistic;
|
|
import password.pwm.svc.stats.Statistic;
|
|
|
|
+import password.pwm.util.java.ConditionalTaskExecutor;
|
|
import password.pwm.util.java.JavaHelper;
|
|
import password.pwm.util.java.JavaHelper;
|
|
import password.pwm.util.java.JsonUtil;
|
|
import password.pwm.util.java.JsonUtil;
|
|
import password.pwm.util.java.StringUtil;
|
|
import password.pwm.util.java.StringUtil;
|
|
import password.pwm.util.java.TimeDuration;
|
|
import password.pwm.util.java.TimeDuration;
|
|
|
|
+import password.pwm.util.logging.PwmLogLevel;
|
|
import password.pwm.util.logging.PwmLogger;
|
|
import password.pwm.util.logging.PwmLogger;
|
|
|
|
|
|
-import java.io.Serializable;
|
|
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
-import java.util.Comparator;
|
|
|
|
|
|
+import java.util.HashSet;
|
|
|
|
+import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
-
|
|
|
|
-public class UserSearchEngine {
|
|
|
|
|
|
+import java.util.Set;
|
|
|
|
+import java.util.TreeMap;
|
|
|
|
+import java.util.concurrent.ArrayBlockingQueue;
|
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
|
+import java.util.concurrent.FutureTask;
|
|
|
|
+import java.util.concurrent.RejectedExecutionException;
|
|
|
|
+import java.util.concurrent.ThreadFactory;
|
|
|
|
+import java.util.concurrent.ThreadPoolExecutor;
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
+import java.util.concurrent.TimeoutException;
|
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
+
|
|
|
|
+public class UserSearchEngine implements PwmService {
|
|
|
|
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass(UserSearchEngine.class);
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass(UserSearchEngine.class);
|
|
|
|
|
|
- private static int searchCounter = 0;
|
|
|
|
|
|
+ private final AtomicInteger searchCounter = new AtomicInteger(0);
|
|
|
|
+ private final AtomicInteger foregroundJobCounter = new AtomicInteger(0);
|
|
|
|
+ private final AtomicInteger backgroundJobCounter = new AtomicInteger(0);
|
|
|
|
+ private final AtomicInteger rejectionJobCounter = new AtomicInteger(0);
|
|
|
|
+ private final AtomicInteger canceledJobCounter = new AtomicInteger(0);
|
|
|
|
+ private final AtomicInteger jobTimeoutCounter = new AtomicInteger(0);
|
|
|
|
|
|
private PwmApplication pwmApplication;
|
|
private PwmApplication pwmApplication;
|
|
- private SessionLabel sessionLabel;
|
|
|
|
|
|
|
|
- public UserSearchEngine(final PwmApplication pwmApplication, final SessionLabel sessionLabel) {
|
|
|
|
- this.pwmApplication = pwmApplication;
|
|
|
|
- this.sessionLabel = sessionLabel;
|
|
|
|
|
|
+ private ThreadPoolExecutor executor;
|
|
|
|
+
|
|
|
|
+ private final ConditionalTaskExecutor debugOutputTask = new ConditionalTaskExecutor(
|
|
|
|
+ () -> periodicDebugOutput(),
|
|
|
|
+ new ConditionalTaskExecutor.TimeDurationPredicate(1, TimeUnit.MINUTES)
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ public UserSearchEngine() {
|
|
}
|
|
}
|
|
|
|
|
|
- public UserSearchEngine(final PwmRequest pwmRequest) {
|
|
|
|
- this(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public STATUS status() {
|
|
|
|
+ return STATUS.OPEN;
|
|
}
|
|
}
|
|
|
|
|
|
- private static String figureSearchFilterForParams(
|
|
|
|
- final Map<FormConfiguration, String> formValues,
|
|
|
|
- final String searchFilter,
|
|
|
|
- final boolean enableValueEscaping
|
|
|
|
- )
|
|
|
|
- {
|
|
|
|
- String newSearchFilter = searchFilter;
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void init(final PwmApplication pwmApplication) throws PwmException {
|
|
|
|
+ this.pwmApplication = pwmApplication;
|
|
|
|
+ this.executor = createExecutor(pwmApplication);
|
|
|
|
+ this.periodicDebugOutput();
|
|
|
|
+ }
|
|
|
|
|
|
- for (final FormConfiguration formItem : formValues.keySet()) {
|
|
|
|
- final String attrName = "%" + formItem.getName() + "%";
|
|
|
|
- String value = formValues.get(formItem);
|
|
|
|
- if (enableValueEscaping) {
|
|
|
|
- value = StringUtil.escapeLdapFilter(value);
|
|
|
|
- }
|
|
|
|
- newSearchFilter = newSearchFilter.replace(attrName, value);
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void close() {
|
|
|
|
+ if (executor != null) {
|
|
|
|
+ executor.shutdown();
|
|
}
|
|
}
|
|
|
|
+ executor = null;
|
|
|
|
+ }
|
|
|
|
|
|
- return newSearchFilter;
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public List<HealthRecord> healthCheck() {
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public ServiceInfo serviceInfo() {
|
|
|
|
+ return new ServiceInfo(Collections.emptyList());
|
|
}
|
|
}
|
|
|
|
|
|
public UserIdentity resolveUsername(
|
|
public UserIdentity resolveUsername(
|
|
final String username,
|
|
final String username,
|
|
final String context,
|
|
final String context,
|
|
- final String profile
|
|
|
|
|
|
+ final String profile,
|
|
|
|
+ final SessionLabel sessionLabel
|
|
)
|
|
)
|
|
throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
|
|
throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
|
|
{
|
|
{
|
|
@@ -128,22 +160,21 @@ public class UserSearchEngine {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, sessionLabel);
|
|
|
|
-
|
|
|
|
try {
|
|
try {
|
|
//see if we need to do a contextless search.
|
|
//see if we need to do a contextless search.
|
|
- if (userSearchEngine.checkIfStringIsDN(username)) {
|
|
|
|
- return userSearchEngine.resolveUserDN(username);
|
|
|
|
|
|
+ if (checkIfStringIsDN(username, sessionLabel)) {
|
|
|
|
+ return resolveUserDN(username);
|
|
} else {
|
|
} else {
|
|
- final SearchConfiguration searchConfiguration = new SearchConfiguration();
|
|
|
|
- searchConfiguration.setUsername(username);
|
|
|
|
|
|
+ final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
|
|
|
|
+ builder.username(username);
|
|
if (context != null) {
|
|
if (context != null) {
|
|
- searchConfiguration.setContexts(Collections.singletonList(context));
|
|
|
|
|
|
+ builder.contexts(Collections.singletonList(context));
|
|
}
|
|
}
|
|
if (profile != null) {
|
|
if (profile != null) {
|
|
- searchConfiguration.setLdapProfile(profile);
|
|
|
|
|
|
+ builder.ldapProfile(profile);
|
|
}
|
|
}
|
|
- return userSearchEngine.performSingleUserSearch(searchConfiguration);
|
|
|
|
|
|
+ final SearchConfiguration searchConfiguration = builder.build();
|
|
|
|
+ return performSingleUserSearch(searchConfiguration, sessionLabel);
|
|
}
|
|
}
|
|
} catch (PwmOperationalException e) {
|
|
} catch (PwmOperationalException e) {
|
|
throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_CANT_MATCH_USER,e.getErrorInformation().getDetailedErrorMsg(),e.getErrorInformation().getFieldValues()));
|
|
throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_CANT_MATCH_USER,e.getErrorInformation().getDetailedErrorMsg(),e.getErrorInformation().getFieldValues()));
|
|
@@ -151,15 +182,16 @@ public class UserSearchEngine {
|
|
}
|
|
}
|
|
|
|
|
|
public UserIdentity performSingleUserSearch(
|
|
public UserIdentity performSingleUserSearch(
|
|
- final SearchConfiguration searchConfiguration
|
|
|
|
|
|
+ final SearchConfiguration searchConfiguration,
|
|
|
|
+ final SessionLabel sessionLabel
|
|
)
|
|
)
|
|
throws PwmUnrecoverableException, PwmOperationalException
|
|
throws PwmUnrecoverableException, PwmOperationalException
|
|
{
|
|
{
|
|
final long startTime = System.currentTimeMillis();
|
|
final long startTime = System.currentTimeMillis();
|
|
final DuplicateMode dupeMode = pwmApplication.getConfig().readSettingAsEnum(PwmSetting.LDAP_DUPLICATE_MODE, DuplicateMode.class);
|
|
final DuplicateMode dupeMode = pwmApplication.getConfig().readSettingAsEnum(PwmSetting.LDAP_DUPLICATE_MODE, DuplicateMode.class);
|
|
final int searchCount = (dupeMode == DuplicateMode.FIRST_ALL) ? 1 : 2;
|
|
final int searchCount = (dupeMode == DuplicateMode.FIRST_ALL) ? 1 : 2;
|
|
- final Map<UserIdentity,Map<String,String>> searchResults = performMultiUserSearch(searchConfiguration, searchCount, Collections.<String>emptyList());
|
|
|
|
- final List<UserIdentity> results = searchResults == null ? Collections.<UserIdentity>emptyList() : new ArrayList<>(searchResults.keySet());
|
|
|
|
|
|
+ final Map<UserIdentity,Map<String,String>> searchResults = performMultiUserSearch(searchConfiguration, searchCount, Collections.emptyList(), sessionLabel);
|
|
|
|
+ final List<UserIdentity> results = searchResults == null ? Collections.emptyList() : new ArrayList<>(searchResults.keySet());
|
|
if (results.isEmpty()) {
|
|
if (results.isEmpty()) {
|
|
final String errorMessage;
|
|
final String errorMessage;
|
|
if (searchConfiguration.getUsername() != null && searchConfiguration.getUsername().length() > 0) {
|
|
if (searchConfiguration.getUsername() != null && searchConfiguration.getUsername().length() > 0) {
|
|
@@ -192,14 +224,17 @@ public class UserSearchEngine {
|
|
final Locale locale,
|
|
final Locale locale,
|
|
final SearchConfiguration searchConfiguration,
|
|
final SearchConfiguration searchConfiguration,
|
|
final int maxResults,
|
|
final int maxResults,
|
|
- final List<FormConfiguration> formItem
|
|
|
|
|
|
+ final List<FormConfiguration> formItem,
|
|
|
|
+ final SessionLabel sessionLabel
|
|
)
|
|
)
|
|
- throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException {
|
|
|
|
|
|
+ throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException
|
|
|
|
+ {
|
|
final Map<String,String> attributeHeaderMap = UserSearchResults.fromFormConfiguration(formItem,locale);
|
|
final Map<String,String> attributeHeaderMap = UserSearchResults.fromFormConfiguration(formItem,locale);
|
|
final Map<UserIdentity,Map<String,String>> searchResults = performMultiUserSearch(
|
|
final Map<UserIdentity,Map<String,String>> searchResults = performMultiUserSearch(
|
|
searchConfiguration,
|
|
searchConfiguration,
|
|
maxResults + 1,
|
|
maxResults + 1,
|
|
- attributeHeaderMap.keySet()
|
|
|
|
|
|
+ attributeHeaderMap.keySet(),
|
|
|
|
+ sessionLabel
|
|
);
|
|
);
|
|
final boolean resultsExceeded = searchResults.size() > maxResults;
|
|
final boolean resultsExceeded = searchResults.size() > maxResults;
|
|
final Map<UserIdentity,Map<String,String>> returnData = new LinkedHashMap<>();
|
|
final Map<UserIdentity,Map<String,String>> returnData = new LinkedHashMap<>();
|
|
@@ -215,7 +250,8 @@ public class UserSearchEngine {
|
|
public Map<UserIdentity,Map<String,String>> performMultiUserSearch(
|
|
public Map<UserIdentity,Map<String,String>> performMultiUserSearch(
|
|
final SearchConfiguration searchConfiguration,
|
|
final SearchConfiguration searchConfiguration,
|
|
final int maxResults,
|
|
final int maxResults,
|
|
- final Collection<String> returnAttributes
|
|
|
|
|
|
+ final Collection<String> returnAttributes,
|
|
|
|
+ final SessionLabel sessionLabel
|
|
)
|
|
)
|
|
throws PwmUnrecoverableException, PwmOperationalException
|
|
throws PwmUnrecoverableException, PwmOperationalException
|
|
{
|
|
{
|
|
@@ -232,58 +268,59 @@ public class UserSearchEngine {
|
|
}
|
|
}
|
|
|
|
|
|
final boolean ignoreUnreachableProfiles = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.LDAP_IGNORE_UNREACHABLE_PROFILES);
|
|
final boolean ignoreUnreachableProfiles = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.LDAP_IGNORE_UNREACHABLE_PROFILES);
|
|
- final Map<UserIdentity,Map<String,String>> returnMap = new LinkedHashMap<>();
|
|
|
|
|
|
|
|
final List<String> errors = new ArrayList<>();
|
|
final List<String> errors = new ArrayList<>();
|
|
|
|
|
|
final long profileRetryDelayMS = Long.valueOf(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PROFILE_RETRY_DELAY));
|
|
final long profileRetryDelayMS = Long.valueOf(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PROFILE_RETRY_DELAY));
|
|
|
|
+
|
|
|
|
+ final List<UserSearchJob> searchJobs = new ArrayList<>();
|
|
for (final LdapProfile ldapProfile : ldapProfiles) {
|
|
for (final LdapProfile ldapProfile : ldapProfiles) {
|
|
- if (returnMap.size() < maxResults) {
|
|
|
|
- boolean skipProfile = false;
|
|
|
|
- final Instant lastLdapFailure = pwmApplication.getLdapConnectionService().getLastLdapFailureTime(ldapProfile);
|
|
|
|
- if (ldapProfiles.size() > 1 && lastLdapFailure != null && TimeDuration.fromCurrent(lastLdapFailure).isShorterThan(profileRetryDelayMS)) {
|
|
|
|
- LOGGER.info("skipping user search on ldap profile " + ldapProfile.getIdentifier() + " due to recent unreachable status (" + TimeDuration.fromCurrent(lastLdapFailure).asCompactString() + ")");
|
|
|
|
- skipProfile = true;
|
|
|
|
- }
|
|
|
|
- if (!skipProfile) {
|
|
|
|
- try {
|
|
|
|
- returnMap.putAll(performMultiUserSearchImpl(
|
|
|
|
- ldapProfile,
|
|
|
|
- searchConfiguration,
|
|
|
|
- maxResults - returnMap.size(),
|
|
|
|
- returnAttributes)
|
|
|
|
- );
|
|
|
|
- } catch (PwmUnrecoverableException e) {
|
|
|
|
- if (e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE) {
|
|
|
|
- pwmApplication.getLdapConnectionService().setLastLdapFailure(ldapProfile,e.getErrorInformation());
|
|
|
|
- if (ignoreUnreachableProfiles) {
|
|
|
|
- errors.add(e.getErrorInformation().getDetailedErrorMsg());
|
|
|
|
- if (errors.size() >= ldapProfiles.size()) {
|
|
|
|
- final String errorMsg = "all ldap profiles are unreachable; errors: " + JsonUtil.serializeCollection(errors);
|
|
|
|
- throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, errorMsg));
|
|
|
|
- }
|
|
|
|
|
|
+ boolean skipProfile = false;
|
|
|
|
+ final Instant lastLdapFailure = pwmApplication.getLdapConnectionService().getLastLdapFailureTime(ldapProfile);
|
|
|
|
+
|
|
|
|
+ if (ldapProfiles.size() > 1 && lastLdapFailure != null && TimeDuration.fromCurrent(lastLdapFailure).isShorterThan(profileRetryDelayMS)) {
|
|
|
|
+ LOGGER.info("skipping user search on ldap profile " + ldapProfile.getIdentifier() + " due to recent unreachable status (" + TimeDuration.fromCurrent(lastLdapFailure).asCompactString() + ")");
|
|
|
|
+ skipProfile = true;
|
|
|
|
+ }
|
|
|
|
+ if (!skipProfile) {
|
|
|
|
+ try {
|
|
|
|
+ searchJobs.addAll(this.makeSearchJobs(
|
|
|
|
+ ldapProfile,
|
|
|
|
+ searchConfiguration,
|
|
|
|
+ maxResults,
|
|
|
|
+ returnAttributes
|
|
|
|
+ ));
|
|
|
|
+ } catch (PwmUnrecoverableException e) {
|
|
|
|
+ if (e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE) {
|
|
|
|
+ pwmApplication.getLdapConnectionService().setLastLdapFailure(ldapProfile,e.getErrorInformation());
|
|
|
|
+ if (ignoreUnreachableProfiles) {
|
|
|
|
+ errors.add(e.getErrorInformation().getDetailedErrorMsg());
|
|
|
|
+ if (errors.size() >= ldapProfiles.size()) {
|
|
|
|
+ final String errorMsg = "all ldap profiles are unreachable; errors: " + JsonUtil.serializeCollection(errors);
|
|
|
|
+ throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, errorMsg));
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
- throw e;
|
|
|
|
}
|
|
}
|
|
|
|
+ } else {
|
|
|
|
+ throw e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return returnMap;
|
|
|
|
|
|
+
|
|
|
|
+ final Map<UserIdentity,Map<String,String>> resultsMap = new LinkedHashMap<>(executeSearchJobs(searchJobs, sessionLabel, searchCounter.getAndIncrement()));
|
|
|
|
+ final Map<UserIdentity,Map<String,String>> returnMap = trimOrderedMap(resultsMap, maxResults);
|
|
|
|
+ return Collections.unmodifiableMap(returnMap);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
- protected Map<UserIdentity,Map<String,String>> performMultiUserSearchImpl(
|
|
|
|
|
|
+ private Collection<UserSearchJob> makeSearchJobs(
|
|
final LdapProfile ldapProfile,
|
|
final LdapProfile ldapProfile,
|
|
final SearchConfiguration searchConfiguration,
|
|
final SearchConfiguration searchConfiguration,
|
|
final int maxResults,
|
|
final int maxResults,
|
|
final Collection<String> returnAttributes
|
|
final Collection<String> returnAttributes
|
|
)
|
|
)
|
|
- throws PwmUnrecoverableException, PwmOperationalException {
|
|
|
|
- final long startTime = System.currentTimeMillis();
|
|
|
|
- LOGGER.debug(sessionLabel, "beginning user search process");
|
|
|
|
-
|
|
|
|
|
|
+ throws PwmUnrecoverableException, PwmOperationalException
|
|
|
|
+ {
|
|
// check the search configuration data params
|
|
// check the search configuration data params
|
|
searchConfiguration.validate();
|
|
searchConfiguration.validate();
|
|
|
|
|
|
@@ -348,69 +385,53 @@ public class UserSearchEngine {
|
|
pwmApplication.getProxyChaiProvider(ldapProfile.getIdentifier()) :
|
|
pwmApplication.getProxyChaiProvider(ldapProfile.getIdentifier()) :
|
|
searchConfiguration.getChaiProvider();
|
|
searchConfiguration.getChaiProvider();
|
|
|
|
|
|
- final Map<UserIdentity,Map<String,String>> returnMap;
|
|
|
|
- returnMap = new LinkedHashMap<>();
|
|
|
|
|
|
+ final List<UserSearchJob> returnMap = new ArrayList<>();
|
|
for (final String loopContext : searchContexts) {
|
|
for (final String loopContext : searchContexts) {
|
|
- try {
|
|
|
|
- final Map<UserIdentity, Map<String, String>> singleContextResults = doSingleContextSearch(
|
|
|
|
- ldapProfile,
|
|
|
|
- searchFilter,
|
|
|
|
- loopContext,
|
|
|
|
- returnAttributes,
|
|
|
|
- maxResults - returnMap.size(),
|
|
|
|
- chaiProvider,
|
|
|
|
- timeLimitMS
|
|
|
|
- );
|
|
|
|
- returnMap.putAll(singleContextResults);
|
|
|
|
- } catch (Throwable t) {
|
|
|
|
- final ErrorInformation errorInformation;
|
|
|
|
- if (t instanceof PwmException) {
|
|
|
|
- errorInformation = new ErrorInformation(((PwmException) t).getError(), "unexpected error during ldap search ("
|
|
|
|
- + "profile=" + ldapProfile.getIdentifier() + ")"
|
|
|
|
- + ", error: " + t.getMessage());
|
|
|
|
- } else {
|
|
|
|
- errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, "unexpected error during ldap search ("
|
|
|
|
- + "profile=" + ldapProfile.getIdentifier() + ")"
|
|
|
|
- + ", error: " + JavaHelper.readHostileExceptionMessage(t));
|
|
|
|
- }
|
|
|
|
- LOGGER.error(sessionLabel, "error during user search: " + errorInformation.toDebugStr());
|
|
|
|
- throw new PwmUnrecoverableException(errorInformation);
|
|
|
|
- }
|
|
|
|
- if (returnMap.size() >= maxResults) {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ final UserSearchJob userSearchJob = UserSearchJob.builder()
|
|
|
|
+ .ldapProfile(ldapProfile)
|
|
|
|
+ .searchFilter(searchFilter)
|
|
|
|
+ .context(loopContext)
|
|
|
|
+ .returnAttributes(returnAttributes)
|
|
|
|
+ .maxResults(maxResults)
|
|
|
|
+ .chaiProvider(chaiProvider)
|
|
|
|
+ .timeoutMs(timeLimitMS)
|
|
|
|
+ .build();
|
|
|
|
+ returnMap.add(userSearchJob);
|
|
}
|
|
}
|
|
|
|
|
|
- LOGGER.debug(sessionLabel, "completed user search process in " + TimeDuration.fromCurrent(startTime).asCompactString() + ", resultSize=" + returnMap.size());
|
|
|
|
return returnMap;
|
|
return returnMap;
|
|
}
|
|
}
|
|
|
|
|
|
- private Map<UserIdentity,Map<String,String>> doSingleContextSearch(
|
|
|
|
- final LdapProfile ldapProfile,
|
|
|
|
- final String searchFilter,
|
|
|
|
- final String context,
|
|
|
|
- final Collection<String> returnAttributes,
|
|
|
|
- final int maxResults,
|
|
|
|
- final ChaiProvider chaiProvider,
|
|
|
|
- final long timeoutMs
|
|
|
|
|
|
+ private Map<UserIdentity,Map<String,String>> executeSearch(
|
|
|
|
+ final UserSearchJob userSearchJob,
|
|
|
|
+ final SessionLabel sessionLabel,
|
|
|
|
+ final int searchID,
|
|
|
|
+ final int jobID
|
|
)
|
|
)
|
|
throws PwmOperationalException, PwmUnrecoverableException
|
|
throws PwmOperationalException, PwmUnrecoverableException
|
|
{
|
|
{
|
|
|
|
+ debugOutputTask.conditionallyExecuteTask();
|
|
|
|
+
|
|
final SearchHelper searchHelper = new SearchHelper();
|
|
final SearchHelper searchHelper = new SearchHelper();
|
|
- searchHelper.setMaxResults(maxResults);
|
|
|
|
- searchHelper.setFilter(searchFilter);
|
|
|
|
- searchHelper.setAttributes(returnAttributes);
|
|
|
|
- searchHelper.setTimeLimit((int)timeoutMs);
|
|
|
|
- final int searchID = searchCounter++;
|
|
|
|
|
|
+ searchHelper.setMaxResults(userSearchJob.getMaxResults());
|
|
|
|
+ searchHelper.setFilter(userSearchJob.getSearchFilter());
|
|
|
|
+ searchHelper.setAttributes(userSearchJob.getReturnAttributes());
|
|
|
|
+ searchHelper.setTimeLimit((int)userSearchJob.getTimeoutMs());
|
|
|
|
|
|
- final String debugInfo = "searchID=" + searchID + " profile=" + ldapProfile.getIdentifier() + " base=" + context
|
|
|
|
- + " filter=" + searchHelper.toString() + " maxCount=" + searchHelper.getMaxResults();
|
|
|
|
- LOGGER.debug(sessionLabel, "performing ldap search for user; " + debugInfo);
|
|
|
|
|
|
+ final String debugInfo;
|
|
|
|
+ {
|
|
|
|
+ final Map<String,String> props = new LinkedHashMap<>();
|
|
|
|
+ props.put("profile", userSearchJob.getLdapProfile().getIdentifier());
|
|
|
|
+ props.put("base", userSearchJob.getContext());
|
|
|
|
+ props.put("maxCount", String.valueOf(searchHelper.getMaxResults()));
|
|
|
|
+ debugInfo = "[" + StringUtil.mapToString(props) + "]";
|
|
|
|
+ }
|
|
|
|
+ log(PwmLogLevel.TRACE, sessionLabel, searchID, jobID, "performing ldap search for user; " + debugInfo);
|
|
|
|
|
|
final Instant startTime = Instant.now();
|
|
final Instant startTime = Instant.now();
|
|
final Map<String, Map<String,String>> results;
|
|
final Map<String, Map<String,String>> results;
|
|
try {
|
|
try {
|
|
- results = chaiProvider.search(context, searchHelper);
|
|
|
|
|
|
+ results = userSearchJob.getChaiProvider().search(userSearchJob.getContext(), searchHelper);
|
|
} catch (ChaiUnavailableException e) {
|
|
} catch (ChaiUnavailableException e) {
|
|
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,e.getMessage()));
|
|
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,e.getMessage()));
|
|
} catch (ChaiOperationException e) {
|
|
} catch (ChaiOperationException e) {
|
|
@@ -424,15 +445,15 @@ public class UserSearchEngine {
|
|
}
|
|
}
|
|
|
|
|
|
if (results.isEmpty()) {
|
|
if (results.isEmpty()) {
|
|
- LOGGER.trace(sessionLabel, "no matches from search (" + searchDuration.asCompactString() +"); " + debugInfo);
|
|
|
|
|
|
+ log(PwmLogLevel.TRACE, sessionLabel, searchID, jobID, "no matches from search (" + searchDuration.asCompactString() +"); " + debugInfo);
|
|
return Collections.emptyMap();
|
|
return Collections.emptyMap();
|
|
}
|
|
}
|
|
|
|
|
|
- LOGGER.trace(sessionLabel, "found " + results.size() + " results in " + searchDuration.asCompactString() + "; " + debugInfo);
|
|
|
|
|
|
+ log(PwmLogLevel.TRACE, sessionLabel, searchID, jobID, "found " + results.size() + " results in " + searchDuration.asCompactString() + "; " + debugInfo);
|
|
|
|
|
|
final Map<UserIdentity,Map<String,String>> returnMap = new LinkedHashMap<>();
|
|
final Map<UserIdentity,Map<String,String>> returnMap = new LinkedHashMap<>();
|
|
for (final String userDN : results.keySet()) {
|
|
for (final String userDN : results.keySet()) {
|
|
- final UserIdentity userIdentity = new UserIdentity(userDN, ldapProfile.getIdentifier());
|
|
|
|
|
|
+ final UserIdentity userIdentity = new UserIdentity(userDN, userSearchJob.getLdapProfile().getIdentifier());
|
|
final Map<String,String> attributeMap = results.get(userDN);
|
|
final Map<String,String> attributeMap = results.get(userDN);
|
|
returnMap.put(userIdentity, attributeMap);
|
|
returnMap.put(userIdentity, attributeMap);
|
|
}
|
|
}
|
|
@@ -456,251 +477,274 @@ public class UserSearchEngine {
|
|
throw new PwmOperationalException(PwmError.ERROR_UNKNOWN,"context '" + context + "' is specified, but is not in configuration");
|
|
throw new PwmOperationalException(PwmError.ERROR_UNKNOWN,"context '" + context + "' is specified, but is not in configuration");
|
|
}
|
|
}
|
|
|
|
|
|
- public static class SearchConfiguration implements Serializable {
|
|
|
|
- private String ldapProfile;
|
|
|
|
- private String filter;
|
|
|
|
- private String username;
|
|
|
|
- private String groupDN;
|
|
|
|
- private List<String> contexts;
|
|
|
|
- private Map<FormConfiguration, String> formValues;
|
|
|
|
- private transient ChaiProvider chaiProvider;
|
|
|
|
- private long searchTimeout;
|
|
|
|
-
|
|
|
|
- private boolean enableValueEscaping = true;
|
|
|
|
- private boolean enableContextValidation = true;
|
|
|
|
- private boolean enableSplitWhitespace = false;
|
|
|
|
-
|
|
|
|
- public String getFilter() {
|
|
|
|
- return filter;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void setFilter(final String filter) {
|
|
|
|
- this.filter = filter;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public Map<FormConfiguration, String> getFormValues() {
|
|
|
|
- return formValues;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void setFormValues(final Map<FormConfiguration, String> formValues) {
|
|
|
|
- this.formValues = formValues;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public String getUsername() {
|
|
|
|
- return username;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void setUsername(final String username) {
|
|
|
|
- this.username = username;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public String getGroupDN() {
|
|
|
|
- return groupDN;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void setGroupDN(final String groupDN) {
|
|
|
|
- this.groupDN = groupDN;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public List<String> getContexts() {
|
|
|
|
- return contexts;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public void setContexts(final List<String> contexts) {
|
|
|
|
- this.contexts = contexts;
|
|
|
|
|
|
+ private boolean checkIfStringIsDN(
|
|
|
|
+ final String input,
|
|
|
|
+ final SessionLabel sessionLabel
|
|
|
|
+ )
|
|
|
|
+ {
|
|
|
|
+ if (input == null || input.length() < 1) {
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
- public ChaiProvider getChaiProvider() {
|
|
|
|
- return chaiProvider;
|
|
|
|
|
|
+ //if supplied user name starts with username attr assume its the full dn and skip the search
|
|
|
|
+ final Set<String> namingAttributes = new HashSet<>();
|
|
|
|
+ for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) {
|
|
|
|
+ final String usernameAttribute = ldapProfile.readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE);
|
|
|
|
+ if (input.toLowerCase().startsWith(usernameAttribute.toLowerCase() + "=")) {
|
|
|
|
+ LOGGER.trace(sessionLabel,
|
|
|
|
+ "username '" + input + "' appears to be a DN (starts with configured ldap naming attribute'" + usernameAttribute + "'), skipping username search");
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ namingAttributes.add(usernameAttribute);
|
|
}
|
|
}
|
|
|
|
|
|
- public void setChaiProvider(final ChaiProvider chaiProvider) {
|
|
|
|
- this.chaiProvider = chaiProvider;
|
|
|
|
- }
|
|
|
|
|
|
+ LOGGER.trace(sessionLabel, "username '" + input + "' does not appear to be a DN (does not start with any of the configured ldap naming attributes '"
|
|
|
|
+ + StringUtil.collectionToString(namingAttributes,",")
|
|
|
|
+ + "')");
|
|
|
|
|
|
- public boolean isEnableValueEscaping() {
|
|
|
|
- return enableValueEscaping;
|
|
|
|
- }
|
|
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
- public void setEnableValueEscaping(final boolean enableValueEscaping) {
|
|
|
|
- this.enableValueEscaping = enableValueEscaping;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- public boolean isEnableContextValidation() {
|
|
|
|
- return enableContextValidation;
|
|
|
|
|
|
+ private UserIdentity resolveUserDN(
|
|
|
|
+ final String userDN
|
|
|
|
+ )
|
|
|
|
+ throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException
|
|
|
|
+ {
|
|
|
|
+ final Collection<LdapProfile> ldapProfiles = pwmApplication.getConfig().getLdapProfiles().values();
|
|
|
|
+ for (final LdapProfile ldapProfile : ldapProfiles) {
|
|
|
|
+ final ChaiProvider provider = pwmApplication.getProxyChaiProvider(ldapProfile.getIdentifier());
|
|
|
|
+ final ChaiUser user = ChaiFactory.createChaiUser(userDN, provider);
|
|
|
|
+ if (user.isValid()) {
|
|
|
|
+ try {
|
|
|
|
+ return new UserIdentity(user.readCanonicalDN(), ldapProfile.getIdentifier());
|
|
|
|
+ } catch (ChaiOperationException e) {
|
|
|
|
+ LOGGER.error("unexpected error reading canonical userDN for '" + userDN + "', error: " + e.getMessage());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_CANT_MATCH_USER));
|
|
|
|
+ }
|
|
|
|
|
|
- public void setEnableContextValidation(final boolean enableContextValidation) {
|
|
|
|
- this.enableContextValidation = enableContextValidation;
|
|
|
|
- }
|
|
|
|
|
|
+ private Map<UserIdentity,Map<String,String>> executeSearchJobs(
|
|
|
|
+ final Collection<UserSearchJob> userSearchJobs,
|
|
|
|
+ final SessionLabel sessionLabel,
|
|
|
|
+ final int searchID
|
|
|
|
+ )
|
|
|
|
+ throws PwmUnrecoverableException
|
|
|
|
+ {
|
|
|
|
+ // create jobs
|
|
|
|
+ final List<JobInfo> jobs = new ArrayList<>();
|
|
|
|
+ {
|
|
|
|
+ int jobID = 0;
|
|
|
|
+ for (UserSearchJob userSearchJob : userSearchJobs) {
|
|
|
|
+ final int loopJobID = jobID++;
|
|
|
|
|
|
- public boolean isEnableSplitWhitespace() {
|
|
|
|
- return enableSplitWhitespace;
|
|
|
|
- }
|
|
|
|
|
|
+ final FutureTask<Map<UserIdentity, Map<String, String>>> futureTask = new FutureTask<>(()
|
|
|
|
+ -> executeSearch(userSearchJob, sessionLabel, searchID, loopJobID));
|
|
|
|
|
|
- public void setEnableSplitWhitespace(final boolean enableSplitWhitespace) {
|
|
|
|
- this.enableSplitWhitespace = enableSplitWhitespace;
|
|
|
|
- }
|
|
|
|
|
|
+ final JobInfo jobInfo = new JobInfo(searchID, loopJobID, userSearchJob, futureTask);
|
|
|
|
|
|
- private void validate() {
|
|
|
|
- if (this.username != null && this.formValues != null) {
|
|
|
|
- throw new IllegalArgumentException("username OR formValues cannot both be supplied");
|
|
|
|
|
|
+ jobs.add(jobInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public String getLdapProfile()
|
|
|
|
|
|
+ final Instant startTime = Instant.now();
|
|
{
|
|
{
|
|
- return ldapProfile;
|
|
|
|
|
|
+ final String filterText = jobs.isEmpty() ? "" : ", filter: " + jobs.iterator().next().getUserSearchJob().getSearchFilter();
|
|
|
|
+ log(PwmLogLevel.DEBUG, sessionLabel, searchID, -1, "beginning user search process with " + jobs.size() + " search jobs" + filterText);
|
|
}
|
|
}
|
|
|
|
|
|
- public void setLdapProfile(final String ldapProfile)
|
|
|
|
- {
|
|
|
|
- this.ldapProfile = ldapProfile;
|
|
|
|
- }
|
|
|
|
|
|
+ // execute jobs
|
|
|
|
+ for (Iterator<JobInfo> iterator = jobs.iterator(); iterator.hasNext(); ) {
|
|
|
|
+ final JobInfo jobInfo = iterator.next();
|
|
|
|
|
|
- public long getSearchTimeout()
|
|
|
|
- {
|
|
|
|
- return searchTimeout;
|
|
|
|
- }
|
|
|
|
|
|
+ boolean submittedToExecutor = false;
|
|
|
|
|
|
- public void setSearchTimeout(final long searchTimeout)
|
|
|
|
- {
|
|
|
|
- this.searchTimeout = searchTimeout;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ // use current thread to execute one (the last in the loop) task.
|
|
|
|
+ if (executor != null && iterator.hasNext()) {
|
|
|
|
+ try {
|
|
|
|
+ executor.submit(jobInfo.getFutureTask());
|
|
|
|
+ submittedToExecutor = true;
|
|
|
|
+ backgroundJobCounter.incrementAndGet();
|
|
|
|
+ } catch (RejectedExecutionException e) {
|
|
|
|
+ // executor is full, so revert to running locally
|
|
|
|
+ rejectionJobCounter.incrementAndGet();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- public boolean checkIfStringIsDN(final String input) {
|
|
|
|
- if (input == null || input.length() < 1) {
|
|
|
|
- return false;
|
|
|
|
|
|
+ if (!submittedToExecutor) {
|
|
|
|
+ try {
|
|
|
|
+ jobInfo.getFutureTask().run();
|
|
|
|
+ foregroundJobCounter.incrementAndGet();
|
|
|
|
+ } catch (Throwable t) {
|
|
|
|
+ log(PwmLogLevel.ERROR, sessionLabel, searchID, jobInfo.getJobID(), "unexpected error running job in local thread: " + t.getMessage());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- //if supplied user name starts with username attr assume its the full dn and skip the search
|
|
|
|
- for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) {
|
|
|
|
- final String usernameAttribute = ldapProfile.readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE);
|
|
|
|
- if (input.toLowerCase().startsWith(usernameAttribute.toLowerCase() + "=")) {
|
|
|
|
- LOGGER.trace(sessionLabel,
|
|
|
|
- "username '" + input + "' appears to be a DN (starts with configured ldap naming attribute'" + usernameAttribute + "'), skipping username search");
|
|
|
|
- return true;
|
|
|
|
|
|
+ // aggregate results
|
|
|
|
+ final Map<UserIdentity,Map<String,String>> results = new LinkedHashMap<>();
|
|
|
|
+ for (final JobInfo jobInfo : jobs) {
|
|
|
|
+ if (results.size() > jobInfo.getUserSearchJob().getMaxResults()) {
|
|
|
|
+ final FutureTask futureTask = jobInfo.getFutureTask();
|
|
|
|
+ if (!futureTask.isDone()) {
|
|
|
|
+ canceledJobCounter.incrementAndGet();
|
|
|
|
+ }
|
|
|
|
+ jobInfo.getFutureTask().cancel(false);
|
|
} else {
|
|
} else {
|
|
- LOGGER.trace(sessionLabel,
|
|
|
|
- "username '" + input + "' does not appear to be a DN (does not start with configured ldap naming attribute '" + usernameAttribute + "')");
|
|
|
|
|
|
+ final long maxWaitTime = jobInfo.getUserSearchJob().getTimeoutMs() * 3;
|
|
|
|
+ try {
|
|
|
|
+ results.putAll(jobInfo.getFutureTask().get(maxWaitTime, TimeUnit.MILLISECONDS));
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
+ final String errorMsg = "unexpected interruption during search job execution: " + e.getMessage();
|
|
|
|
+ log(PwmLogLevel.WARN, sessionLabel, searchID, jobInfo.getJobID(), errorMsg);
|
|
|
|
+ LOGGER.error(sessionLabel, errorMsg, e);
|
|
|
|
+ throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg));
|
|
|
|
+ } catch (ExecutionException e) {
|
|
|
|
+ final Throwable t = e.getCause();
|
|
|
|
+ final ErrorInformation errorInformation;
|
|
|
|
+ final String errorMsg = "unexpected error during ldap search ("
|
|
|
|
+ + "profile=" + jobInfo.getUserSearchJob().getLdapProfile() + ")"
|
|
|
|
+ + ", error: " + (t instanceof PwmException ? t.getMessage() : JavaHelper.readHostileExceptionMessage(t));
|
|
|
|
+ if (t instanceof PwmException) {
|
|
|
|
+ errorInformation = new ErrorInformation(((PwmException) t).getError(), errorMsg);
|
|
|
|
+ } else {
|
|
|
|
+ errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
|
|
|
|
+ }
|
|
|
|
+ log(PwmLogLevel.WARN, sessionLabel, searchID, jobInfo.getJobID(), "error during user search: " + errorInformation.toDebugStr());
|
|
|
|
+ throw new PwmUnrecoverableException(errorInformation);
|
|
|
|
+ } catch (TimeoutException e) {
|
|
|
|
+ final String errorMsg = "background search job timeout after " + jobInfo.getUserSearchJob().getTimeoutMs()
|
|
|
|
+ + "ms, to ldapProfile '"
|
|
|
|
+ + jobInfo.getUserSearchJob().getLdapProfile() + "'";
|
|
|
|
+ log(PwmLogLevel.WARN, sessionLabel, searchID, jobInfo.getJobID(), "error during user search: " + errorMsg);
|
|
|
|
+ jobTimeoutCounter.incrementAndGet();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- return false;
|
|
|
|
|
|
+ log(PwmLogLevel.DEBUG, sessionLabel, searchID, -1, "completed user search process in "
|
|
|
|
+ + TimeDuration.fromCurrent(startTime).asCompactString()
|
|
|
|
+ + ", intermediate result size=" + results.size());
|
|
|
|
+ return Collections.unmodifiableMap(results);
|
|
}
|
|
}
|
|
|
|
|
|
- public static class UserSearchResults implements Serializable {
|
|
|
|
- private final Map<String,String> headerAttributeMap;
|
|
|
|
- private final Map<UserIdentity,Map<String,String>> results;
|
|
|
|
- private boolean sizeExceeded;
|
|
|
|
-
|
|
|
|
- public UserSearchResults(final Map<String, String> headerAttributeMap, final Map<UserIdentity, Map<String, String>> results, final boolean sizeExceeded) {
|
|
|
|
- this.headerAttributeMap = headerAttributeMap;
|
|
|
|
- this.results = Collections.unmodifiableMap(defaultSort(results, headerAttributeMap));
|
|
|
|
- this.sizeExceeded = sizeExceeded;
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static Map<UserIdentity, Map<String, String>> defaultSort(
|
|
|
|
- final Map<UserIdentity, Map<String, String>> results,
|
|
|
|
- final Map<String,String> headerAttributeMap
|
|
|
|
- )
|
|
|
|
- {
|
|
|
|
- if (headerAttributeMap == null || headerAttributeMap.isEmpty() || results == null) {
|
|
|
|
- return results;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- final String sortAttribute = headerAttributeMap.keySet().iterator().next();
|
|
|
|
- final Comparator<UserIdentity> comparator = new Comparator<UserIdentity>() {
|
|
|
|
- @Override
|
|
|
|
- public int compare(final UserIdentity o1, final UserIdentity o2) {
|
|
|
|
- final String s1 = getSortValueByIdentity(o1);
|
|
|
|
- final String s2 = getSortValueByIdentity(o2);
|
|
|
|
- return s1.compareTo(s2);
|
|
|
|
- }
|
|
|
|
|
|
+ @Getter
|
|
|
|
+ @AllArgsConstructor
|
|
|
|
+ private static class JobInfo {
|
|
|
|
+ private final int searchID;
|
|
|
|
+ private final int jobID;
|
|
|
|
+ private final UserSearchJob userSearchJob;
|
|
|
|
+ private final FutureTask<Map<UserIdentity,Map<String,String>>> futureTask;
|
|
|
|
+ }
|
|
|
|
|
|
- private String getSortValueByIdentity(final UserIdentity userIdentity) {
|
|
|
|
- final Map<String,String> valueMap = results.get(userIdentity);
|
|
|
|
- if (valueMap != null) {
|
|
|
|
- final String sortValue = valueMap.get(sortAttribute);
|
|
|
|
- if (sortValue != null) {
|
|
|
|
- return sortValue;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return "";
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
|
|
+ private Map<String,String> debugProperties() {
|
|
|
|
+ final Map<String,String> properties = new TreeMap<>();
|
|
|
|
+ properties.put("searchCount", this.searchCounter.toString());
|
|
|
|
+ properties.put("backgroundJobCounter", Integer.toString(this.backgroundJobCounter.get()));
|
|
|
|
+ properties.put("foregroundJobCounter", Integer.toString(this.foregroundJobCounter.get()));
|
|
|
|
+ properties.put("jvmThreadCount", Integer.toString(Thread.activeCount()));
|
|
|
|
+ if (executor == null) {
|
|
|
|
+ properties.put("background-enabled","false");
|
|
|
|
+ } else {
|
|
|
|
+ properties.put("background-enabled","true");
|
|
|
|
+ properties.put("background-maxPoolSize", Integer.toString(executor.getMaximumPoolSize()));
|
|
|
|
+ properties.put("background-activeCount", Integer.toString(executor.getActiveCount()));
|
|
|
|
+ properties.put("background-largestPoolSize", Integer.toString(executor.getLargestPoolSize()));
|
|
|
|
+ properties.put("background-poolSize", Integer.toString(executor.getPoolSize()));
|
|
|
|
+ properties.put("background-queue-size", Integer.toString(executor.getQueue().size()));
|
|
|
|
+ properties.put("background-rejectionJobCounter", Integer.toString(rejectionJobCounter.get()));
|
|
|
|
+ properties.put("background-canceledJobCounter", Integer.toString(canceledJobCounter.get()));
|
|
|
|
+ properties.put("background-jobTimeoutCounter", Integer.toString(jobTimeoutCounter.get()));
|
|
|
|
+ }
|
|
|
|
+ return Collections.unmodifiableMap(properties);
|
|
|
|
+ }
|
|
|
|
|
|
- final List<UserIdentity> identitySortMap = new ArrayList<>();
|
|
|
|
- identitySortMap.addAll(results.keySet());
|
|
|
|
- Collections.sort(identitySortMap,comparator);
|
|
|
|
|
|
+ private void periodicDebugOutput() {
|
|
|
|
+ LOGGER.debug("periodic debug status: " + StringUtil.mapToString(debugProperties()));
|
|
|
|
+ }
|
|
|
|
|
|
- final Map<UserIdentity, Map<String, String>> sortedResults = new LinkedHashMap<>();
|
|
|
|
- for (final UserIdentity userIdentity : identitySortMap) {
|
|
|
|
- sortedResults.put(userIdentity, results.get(userIdentity));
|
|
|
|
- }
|
|
|
|
- return sortedResults;
|
|
|
|
- }
|
|
|
|
|
|
+ private void log(final PwmLogLevel level, final SessionLabel sessionLabel, final int searchID, final int jobID, final String message) {
|
|
|
|
+ final String idMsg = logIdString(searchID, jobID);
|
|
|
|
+ LOGGER.log(level, sessionLabel, idMsg + " " + message);
|
|
|
|
+ }
|
|
|
|
|
|
- public Map<String, String> getHeaderAttributeMap() {
|
|
|
|
- return headerAttributeMap;
|
|
|
|
|
|
+ private static String logIdString(final int searchID, final int jobID) {
|
|
|
|
+ String idMsg = "searchID=" + searchID;
|
|
|
|
+ if (jobID >= 0) {
|
|
|
|
+ idMsg += "-" + jobID;
|
|
}
|
|
}
|
|
|
|
+ return idMsg;
|
|
|
|
+ }
|
|
|
|
|
|
- public Map<UserIdentity, Map<String, String>> getResults() {
|
|
|
|
- return results;
|
|
|
|
- }
|
|
|
|
|
|
+ private static ThreadPoolExecutor createExecutor(final PwmApplication pwmApplication) {
|
|
|
|
+ final Configuration configuration = pwmApplication.getConfig();
|
|
|
|
|
|
- public boolean isSizeExceeded() {
|
|
|
|
- return sizeExceeded;
|
|
|
|
|
|
+ final boolean enabled = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.LDAP_SEARCH_PARALLEL_ENABLE));
|
|
|
|
+ if (!enabled) {
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
- public List<Map<String,Object>> resultsAsJsonOutput(final PwmApplication pwmApplication, final UserIdentity ignoreUser)
|
|
|
|
- throws PwmUnrecoverableException
|
|
|
|
|
|
+ final int endPoints;
|
|
{
|
|
{
|
|
- final List<Map<String,Object>> outputList = new ArrayList<>();
|
|
|
|
- int idCounter = 0;
|
|
|
|
- for (final UserIdentity userIdentity : this.getResults().keySet()) {
|
|
|
|
- if (ignoreUser == null || !ignoreUser.equals(userIdentity)) {
|
|
|
|
- final Map<String, Object> rowMap = new LinkedHashMap<>();
|
|
|
|
- for (final String attribute : this.getHeaderAttributeMap().keySet()) {
|
|
|
|
- rowMap.put(attribute, this.getResults().get(userIdentity).get(attribute));
|
|
|
|
- }
|
|
|
|
- rowMap.put("userKey", userIdentity.toObfuscatedKey(pwmApplication));
|
|
|
|
- rowMap.put("id", idCounter);
|
|
|
|
- outputList.add(rowMap);
|
|
|
|
- idCounter++;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return outputList;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public static Map<String,String> fromFormConfiguration(final List<FormConfiguration> formItems, final Locale locale) {
|
|
|
|
- final Map<String,String> results = new LinkedHashMap<>();
|
|
|
|
- for (final FormConfiguration formItem : formItems) {
|
|
|
|
- results.put(formItem.getName(), formItem.getLabel(locale));
|
|
|
|
|
|
+ int counter = 0;
|
|
|
|
+ for (final LdapProfile ldapProfile : configuration.getLdapProfiles().values()) {
|
|
|
|
+ final List<String> rootContexts = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_CONTEXTLESS_ROOT);
|
|
|
|
+ counter += rootContexts.size();
|
|
}
|
|
}
|
|
- return results;
|
|
|
|
- }
|
|
|
|
|
|
+ endPoints = counter;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (endPoints > 1) {
|
|
|
|
+ final int factor = Integer.parseInt(configuration.readAppProperty(AppProperty.LDAP_SEARCH_PARALLEL_FACTOR));
|
|
|
|
+ final int maxThreads = Integer.parseInt(configuration.readAppProperty(AppProperty.LDAP_SEARCH_PARALLEL_THREAD_MAX));
|
|
|
|
+ final int threads = Math.min(maxThreads, (endPoints) * factor);
|
|
|
|
+ final ThreadFactory threadFactory = JavaHelper.makePwmThreadFactory(JavaHelper.makeThreadName(pwmApplication, UserSearchEngine.class), true);
|
|
|
|
+ return new ThreadPoolExecutor(
|
|
|
|
+ threads,
|
|
|
|
+ threads,
|
|
|
|
+ 1,
|
|
|
|
+ TimeUnit.MINUTES,
|
|
|
|
+ new ArrayBlockingQueue<>(threads),
|
|
|
|
+ threadFactory
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
- public UserIdentity resolveUserDN(final String userDN) throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException {
|
|
|
|
- final Collection<LdapProfile> ldapProfiles = pwmApplication.getConfig().getLdapProfiles().values();
|
|
|
|
- final boolean ignoreUnreachableProfiles = pwmApplication.getConfig().readSettingAsBoolean(
|
|
|
|
- PwmSetting.LDAP_IGNORE_UNREACHABLE_PROFILES);
|
|
|
|
- for (final LdapProfile ldapProfile : ldapProfiles) {
|
|
|
|
- final ChaiProvider provider = pwmApplication.getProxyChaiProvider(ldapProfile.getIdentifier());
|
|
|
|
- final ChaiUser user = ChaiFactory.createChaiUser(userDN, provider);
|
|
|
|
- if (user.isValid()) {
|
|
|
|
- try {
|
|
|
|
- return new UserIdentity(user.readCanonicalDN(), ldapProfile.getIdentifier());
|
|
|
|
- } catch (ChaiOperationException e) {
|
|
|
|
- LOGGER.error("unexpected error reading canonical userDN for '" + userDN + "', error: " + e.getMessage());
|
|
|
|
|
|
+ private static <K,V> Map<K,V> trimOrderedMap(final Map<K,V> inputMap, final int maxEntries) {
|
|
|
|
+ final Map<K,V> returnMap = new LinkedHashMap<>(inputMap);
|
|
|
|
+ if (returnMap.size() > maxEntries) {
|
|
|
|
+ int counter = 0;
|
|
|
|
+ for (final Iterator<K> iterator = returnMap.keySet().iterator() ; iterator.hasNext(); ) {
|
|
|
|
+ iterator.next();
|
|
|
|
+ counter++;
|
|
|
|
+ if (counter > maxEntries) {
|
|
|
|
+ iterator.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_CANT_MATCH_USER));
|
|
|
|
|
|
+ return Collections.unmodifiableMap(returnMap);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private static String figureSearchFilterForParams(
|
|
|
|
+ final Map<FormConfiguration, String> formValues,
|
|
|
|
+ final String searchFilter,
|
|
|
|
+ final boolean enableValueEscaping
|
|
|
|
+ )
|
|
|
|
+ {
|
|
|
|
+ String newSearchFilter = searchFilter;
|
|
|
|
+
|
|
|
|
+ for (final FormConfiguration formItem : formValues.keySet()) {
|
|
|
|
+ final String attrName = "%" + formItem.getName() + "%";
|
|
|
|
+ String value = formValues.get(formItem);
|
|
|
|
+ if (enableValueEscaping) {
|
|
|
|
+ value = StringUtil.escapeLdapFilter(value);
|
|
|
|
+ }
|
|
|
|
+ newSearchFilter = newSearchFilter.replace(attrName, value);
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ return newSearchFilter;
|
|
|
|
+ }
|
|
}
|
|
}
|