Преглед на файлове

parallel user searching for multiple contexts/ldap profiles

Jason Rivard преди 8 години
родител
ревизия
6153c7c569
променени са 53 файла, в които са добавени 1005 реда и са изтрити 4197 реда
  1. 2 0
      import-control.xml
  2. 8 0
      pom.xml
  3. 3 0
      src/main/java/password/pwm/AppProperty.java
  4. 5 0
      src/main/java/password/pwm/PwmApplication.java
  5. 1 0
      src/main/java/password/pwm/PwmConstants.java
  6. 12 5
      src/main/java/password/pwm/config/FormUtility.java
  7. 14 0
      src/main/java/password/pwm/config/PwmSettingCategory.java
  8. 2 1
      src/main/java/password/pwm/config/function/UserMatchViewerFunction.java
  9. 16 7
      src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  10. 1 0
      src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  11. 1 1
      src/main/java/password/pwm/config/value/FileValue.java
  12. 3 3
      src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  13. 15 12
      src/main/java/password/pwm/http/servlet/ActivateUserServlet.java
  14. 10 8
      src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  15. 14 8
      src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  16. 13 10
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  17. 25 15
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  18. 9 6
      src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  19. 8 3
      src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  20. 25 20
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  21. 8 4
      src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  22. 20 12
      src/main/java/password/pwm/ldap/LdapPermissionTester.java
  23. 5 5
      src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java
  24. 56 0
      src/main/java/password/pwm/ldap/search/SearchConfiguration.java
  25. 372 328
      src/main/java/password/pwm/ldap/search/UserSearchEngine.java
  26. 43 0
      src/main/java/password/pwm/ldap/search/UserSearchJob.java
  27. 131 0
      src/main/java/password/pwm/ldap/search/UserSearchResults.java
  28. 2 0
      src/main/java/password/pwm/svc/PwmServiceManager.java
  29. 6 3
      src/main/java/password/pwm/svc/intruder/RecordManagerImpl.java
  30. 25 10
      src/main/java/password/pwm/svc/report/ReportService.java
  31. 12 8
      src/main/java/password/pwm/svc/report/UserCacheService.java
  32. 7 7
      src/main/java/password/pwm/svc/stats/StatisticsBundle.java
  33. 2 2
      src/main/java/password/pwm/svc/token/CryptoTokenMachine.java
  34. 5 5
      src/main/java/password/pwm/svc/token/DBTokenMachine.java
  35. 13 10
      src/main/java/password/pwm/svc/token/LdapTokenMachine.java
  36. 5 5
      src/main/java/password/pwm/svc/token/LocalDBTokenMachine.java
  37. 2 2
      src/main/java/password/pwm/svc/token/TokenMachine.java
  38. 6 6
      src/main/java/password/pwm/svc/token/TokenPayload.java
  39. 21 20
      src/main/java/password/pwm/svc/token/TokenService.java
  40. 13 6
      src/main/java/password/pwm/util/cli/commands/ExportResponsesCommand.java
  41. 14 11
      src/main/java/password/pwm/util/cli/commands/ResponseStatsCommand.java
  42. 2 1
      src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java
  43. 35 12
      src/main/java/password/pwm/util/operations/PasswordUtility.java
  44. 0 10
      src/main/java/password/pwm/util/secure/SecureEngine.java
  45. 3 3
      src/main/java/password/pwm/ws/server/RestServerHelper.java
  46. 3 3
      src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java
  47. 3 0
      src/main/resources/password/pwm/AppProperty.properties
  48. 1 1
      src/main/webapp/WEB-INF/jsp/admin-tokenlookup.jsp
  49. 3 3
      src/main/webapp/public/resources/js/newuser.js
  50. 0 2875
      supplemental/PWMAdministrationGuide.pdf
  51. 0 649
      supplemental/history.txt
  52. 0 8
      supplemental/readme.txt
  53. 0 89
      supplemental/script/missing_translation.sh

+ 2 - 0
import-control.xml

@@ -77,6 +77,8 @@
     <allow pkg="org.xeustechnologies"/>
     <allow pkg="org.xeustechnologies"/>
     <allow pkg="net.glxn"/>
     <allow pkg="net.glxn"/>
     <allow pkg="org.webjars"/>
     <allow pkg="org.webjars"/>
+    <allow pkg="lombok"/>
+
 
 
     <!--servlet -->
     <!--servlet -->
     <subpackage name="http.servlet">
     <subpackage name="http.servlet">

+ 8 - 0
pom.xml

@@ -515,6 +515,14 @@
     </reporting>
     </reporting>
 
 
     <dependencies>
     <dependencies>
+        <!-- dev tool -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.16.14</version>
+            <scope>provided</scope>
+        </dependency>
+
         <!-- Test dependencies -->
         <!-- Test dependencies -->
         <dependency>
         <dependency>
             <groupId>junit</groupId>
             <groupId>junit</groupId>

+ 3 - 0
src/main/java/password/pwm/AppProperty.java

@@ -171,6 +171,9 @@ public enum     AppProperty {
     LDAP_BROWSER_MAX_ENTRIES                        ("ldap.browser.maxEntries"),
     LDAP_BROWSER_MAX_ENTRIES                        ("ldap.browser.maxEntries"),
     LDAP_SEARCH_PAGING_ENABLE                       ("ldap.search.paging.enable"),
     LDAP_SEARCH_PAGING_ENABLE                       ("ldap.search.paging.enable"),
     LDAP_SEARCH_PAGING_SIZE                         ("ldap.search.paging.size"),
     LDAP_SEARCH_PAGING_SIZE                         ("ldap.search.paging.size"),
+    LDAP_SEARCH_PARALLEL_ENABLE                     ("ldap.search.parallel.enable"),
+    LDAP_SEARCH_PARALLEL_FACTOR                     ("ldap.search.parallel.factor"),
+    LDAP_SEARCH_PARALLEL_THREAD_MAX                 ("ldap.search.parallel.threadMax"),
     LOGGING_PATTERN                                 ("logging.pattern"),
     LOGGING_PATTERN                                 ("logging.pattern"),
     LOGGING_FILE_MAX_SIZE                           ("logging.file.maxSize"),
     LOGGING_FILE_MAX_SIZE                           ("logging.file.maxSize"),
     LOGGING_FILE_MAX_ROLLOVER                       ("logging.file.maxRollover"),
     LOGGING_FILE_MAX_ROLLOVER                       ("logging.file.maxRollover"),

+ 5 - 0
src/main/java/password/pwm/PwmApplication.java

@@ -38,6 +38,7 @@ import password.pwm.health.HealthMonitor;
 import password.pwm.http.servlet.resource.ResourceServletService;
 import password.pwm.http.servlet.resource.ResourceServletService;
 import password.pwm.http.state.SessionStateService;
 import password.pwm.http.state.SessionStateService;
 import password.pwm.ldap.LdapConnectionService;
 import password.pwm.ldap.LdapConnectionService;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmServiceManager;
 import password.pwm.svc.PwmServiceManager;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.cache.CacheService;
@@ -493,6 +494,10 @@ public class PwmApplication {
         return (UrlShortenerService)pwmServiceManager.getService(UrlShortenerService.class);
         return (UrlShortenerService)pwmServiceManager.getService(UrlShortenerService.class);
     }
     }
 
 
+    public UserSearchEngine getUserSearchEngine() {
+        return (UserSearchEngine)pwmServiceManager.getService(UserSearchEngine.class);
+    }
+
     public VersionChecker getVersionChecker() {
     public VersionChecker getVersionChecker() {
         return (VersionChecker)pwmServiceManager.getService(VersionChecker.class);
         return (VersionChecker)pwmServiceManager.getService(VersionChecker.class);
     }
     }

+ 1 - 0
src/main/java/password/pwm/PwmConstants.java

@@ -113,6 +113,7 @@ public abstract class PwmConstants {
     public static final SessionLabel REPORTING_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"reporting",null,null);
     public static final SessionLabel REPORTING_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"reporting",null,null);
     public static final SessionLabel HEALTH_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"health",null,null);
     public static final SessionLabel HEALTH_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"health",null,null);
     public static final SessionLabel CLI_SESSION_LABEL= new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"cli",null,null);
     public static final SessionLabel CLI_SESSION_LABEL= new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"cli",null,null);
+    public static final SessionLabel TOKEN_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"token",null,null);
 
 
     public static final int DATABASE_ACCESSOR_KEY_LENGTH = Integer.parseInt(readPwmConstantsBundle("databaseAccessor.keyLength"));
     public static final int DATABASE_ACCESSOR_KEY_LENGTH = Integer.parseInt(readPwmConstantsBundle("databaseAccessor.keyLength"));
 
 

+ 12 - 5
src/main/java/password/pwm/config/FormUtility.java

@@ -37,8 +37,9 @@ import password.pwm.error.PwmError;
 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.http.PwmRequest;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.cache.CacheService;
@@ -234,16 +235,22 @@ public class FormUtility {
         final SearchHelper searchHelper = new SearchHelper();
         final SearchHelper searchHelper = new SearchHelper();
         searchHelper.setFilterAnd(filterClauses);
         searchHelper.setFilterAnd(filterClauses);
 
 
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setFilter(filter.toString());
+        final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                .filter(filter.toString())
+                .build();
 
 
         final int resultSearchSizeLimit = 1 + (excludeDN == null ? 0 : excludeDN.size());
         final int resultSearchSizeLimit = 1 + (excludeDN == null ? 0 : excludeDN.size());
         final long cacheLifetimeMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CACHE_FORM_UNIQUE_VALUE_LIFETIME_MS));
         final long cacheLifetimeMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CACHE_FORM_UNIQUE_VALUE_LIFETIME_MS));
         final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS(cacheLifetimeMS);
         final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS(cacheLifetimeMS);
 
 
         try {
         try {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, SessionLabel.SYSTEM_LABEL);
-            final Map<UserIdentity,Map<String,String>> results = new LinkedHashMap<>(userSearchEngine.performMultiUserSearch(searchConfiguration,resultSearchSizeLimit,Collections.<String>emptyList()));
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            final Map<UserIdentity,Map<String,String>> results = new LinkedHashMap<>(userSearchEngine.performMultiUserSearch(
+                    searchConfiguration,
+                    resultSearchSizeLimit,
+                    Collections.emptyList(),
+                    SessionLabel.SYSTEM_LABEL
+                    ));
 
 
             if (excludeDN != null && !excludeDN.isEmpty()) {
             if (excludeDN != null && !excludeDN.isEmpty()) {
                 for (final UserIdentity loopIgnoreIdentity : excludeDN) {
                 for (final UserIdentity loopIgnoreIdentity : excludeDN) {

+ 14 - 0
src/main/java/password/pwm/config/PwmSettingCategory.java

@@ -380,4 +380,18 @@ public enum PwmSettingCategory {
         }
         }
         return Collections.unmodifiableList(values);
         return Collections.unmodifiableList(values);
     }
     }
+
+    public static Collection<PwmSettingCategory> associatedProfileCategories(final PwmSettingCategory inputCategory) {
+        final Collection<PwmSettingCategory> returnValues = new ArrayList<>();
+        if (inputCategory != null && inputCategory.hasProfiles()) {
+            PwmSettingCategory topLevelCategory = inputCategory;
+            while (!topLevelCategory.isTopLevelProfile()) {
+                topLevelCategory = topLevelCategory.getParent();
+            }
+            returnValues.add(topLevelCategory);
+            returnValues.addAll(topLevelCategory.getChildCategories());
+        }
+
+        return Collections.unmodifiableCollection(returnValues);
+    }
 }
 }

+ 2 - 1
src/main/java/password/pwm/config/function/UserMatchViewerFunction.java

@@ -28,6 +28,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 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.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
@@ -108,7 +109,7 @@ public class UserMatchViewerFunction implements SettingUIFunction {
             }
             }
         }
         }
 
 
-        return LdapPermissionTester.discoverMatchingUsers(tempApplication, maxResultSize, permissions).keySet();
+        return LdapPermissionTester.discoverMatchingUsers(tempApplication, maxResultSize, permissions, SessionLabel.SYSTEM_LABEL).keySet();
     }
     }
 
 
 
 

+ 16 - 7
src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -52,15 +52,15 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.i18n.PwmLocaleBundle;
-import password.pwm.util.secure.BCrypt;
-import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.JsonUtil;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
+import password.pwm.util.java.JavaHelper;
+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.java.XmlUtil;
 import password.pwm.util.java.XmlUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.BCrypt;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.SecureEngine;
 import password.pwm.util.secure.SecureEngine;
@@ -71,6 +71,7 @@ import java.io.OutputStream;
 import java.io.Serializable;
 import java.io.Serializable;
 import java.text.ParseException;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashMap;
@@ -733,6 +734,7 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
     public void copyProfileID(final PwmSettingCategory category, final String sourceID, final String destinationID, final UserIdentity userIdentity)
     public void copyProfileID(final PwmSettingCategory category, final String sourceID, final String destinationID, final UserIdentity userIdentity)
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
+
         if (!category.hasProfiles()) {
         if (!category.hasProfiles()) {
             throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category " + category + ", category does not have profiles");
             throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category " + category + ", category does not have profiles");
         }
         }
@@ -743,12 +745,19 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
         if (existingProfiles.contains(destinationID)) {
         if (existingProfiles.contains(destinationID)) {
             throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, destination profileID '" + destinationID+ "' already exists");
             throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, destination profileID '" + destinationID+ "' already exists");
         }
         }
-        for (final PwmSetting pwmSetting : category.getSettings()) {
-            if (!isDefaultValue(pwmSetting, sourceID)) {
-                final StoredValue value = readSetting(pwmSetting, sourceID);
-                writeSetting(pwmSetting, destinationID, value, userIdentity);
+
+        {
+            final Collection<PwmSettingCategory> interestedCategories = PwmSettingCategory.associatedProfileCategories(category);
+            for (final PwmSettingCategory interestedCategory : interestedCategories) {
+                for (final PwmSetting pwmSetting : interestedCategory.getSettings()) {
+                    if (!isDefaultValue(pwmSetting, sourceID)) {
+                        final StoredValue value = readSetting(pwmSetting, sourceID);
+                        writeSetting(pwmSetting, destinationID, value, userIdentity);
+                    }
+                }
             }
             }
         }
         }
+
         final List<String> newProfileIDList = new ArrayList<>();
         final List<String> newProfileIDList = new ArrayList<>();
         newProfileIDList.addAll(existingProfiles);
         newProfileIDList.addAll(existingProfiles);
         newProfileIDList.add(destinationID);
         newProfileIDList.add(destinationID);

+ 1 - 0
src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java

@@ -148,4 +148,5 @@ public abstract class StoredConfigurationUtil {
         }
         }
         return outputObject;
         return outputObject;
     }
     }
+
 }
 }

+ 1 - 1
src/main/java/password/pwm/config/value/FileValue.java

@@ -103,7 +103,7 @@ public class FileValue extends AbstractValue implements StoredValue {
         public String md5sum()
         public String md5sum()
                 throws PwmUnrecoverableException
                 throws PwmUnrecoverableException
         {
         {
-            return SecureEngine.md5sum(new ByteArrayInputStream(contents));
+            return SecureEngine.hash(new ByteArrayInputStream(contents), PwmHashAlgorithm.MD5);
         }
         }
 
 
         public String sha1sum()
         public String sha1sum()

+ 3 - 3
src/main/java/password/pwm/http/filter/AuthenticationFilter.java

@@ -50,7 +50,7 @@ import password.pwm.http.servlet.oauth.OAuthMachine;
 import password.pwm.http.servlet.oauth.OAuthSettings;
 import password.pwm.http.servlet.oauth.OAuthSettings;
 import password.pwm.i18n.Display;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.PasswordChangeProgressChecker;
 import password.pwm.ldap.PasswordChangeProgressChecker;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.auth.SessionAuthenticator;
@@ -476,8 +476,8 @@ public class AuthenticationFilter extends AbstractPwmFilter {
                         pwmSession,
                         pwmSession,
                         PwmAuthenticationSource.BASIC_AUTH
                         PwmAuthenticationSource.BASIC_AUTH
                 );
                 );
-                final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, pwmSession.getLabel());
-                final UserIdentity userIdentity = userSearchEngine.resolveUsername(basicAuthInfo.getUsername(), null, null);
+                final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+                final UserIdentity userIdentity = userSearchEngine.resolveUsername(basicAuthInfo.getUsername(), null, null, pwmSession.getLabel());
                 sessionAuthenticator.authenticateUser(userIdentity, basicAuthInfo.getPassword());
                 sessionAuthenticator.authenticateUser(userIdentity, basicAuthInfo.getPassword());
                 pwmSession.getLoginInfoBean().setBasicAuth(basicAuthInfo);
                 pwmSession.getLoginInfoBean().setBasicAuth(basicAuthInfo);
 
 

+ 15 - 12
src/main/java/password/pwm/http/servlet/ActivateUserServlet.java

@@ -55,8 +55,9 @@ import password.pwm.http.bean.ActivateUserBean;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.auth.SessionAuthenticator;
@@ -144,7 +145,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
     protected void processAction(final PwmRequest pwmRequest)
     protected void processAction(final PwmRequest pwmRequest)
             throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
             throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
     {
     {
-            //Fetch the session state bean.
+        //Fetch the session state bean.
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
 
 
@@ -244,13 +245,15 @@ public class ActivateUserServlet extends AbstractPwmServlet {
             // read an ldap user object based on the params
             // read an ldap user object based on the params
             final UserIdentity userIdentity;
             final UserIdentity userIdentity;
             {
             {
-                final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, pwmSession.getLabel());
-                final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-                searchConfiguration.setContexts(Collections.singletonList(contextParam));
-                searchConfiguration.setFilter(searchFilter);
-                searchConfiguration.setFormValues(formValues);
-                searchConfiguration.setLdapProfile(ldapProfile);
-                userIdentity = userSearchEngine.performSingleUserSearch(searchConfiguration);
+                final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+                final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                        .contexts(Collections.singletonList(contextParam))
+                        .filter(searchFilter)
+                        .formValues(formValues)
+                        .ldapProfile(ldapProfile)
+                        .build();
+
+                userIdentity = userSearchEngine.performSingleUserSearch(searchConfiguration, pwmRequest.getSessionLabel());
             }
             }
 
 
             validateParamsAgainstLDAP(pwmRequest, formValues, userIdentity);
             validateParamsAgainstLDAP(pwmRequest, formValues, userIdentity);
@@ -758,9 +761,9 @@ public class ActivateUserServlet extends AbstractPwmServlet {
         }
         }
         return searchFilter;
         return searchFilter;
     }
     }
-    
-    private static void forwardToActivateUserForm(final PwmRequest pwmRequest) 
-            throws ServletException, PwmUnrecoverableException, IOException 
+
+    private static void forwardToActivateUserForm(final PwmRequest pwmRequest)
+            throws ServletException, PwmUnrecoverableException, IOException
     {
     {
         pwmRequest.addFormInfoToRequestAttr(PwmSetting.ACTIVATE_USER_FORM,false,false);
         pwmRequest.addFormInfoToRequestAttr(PwmSetting.ACTIVATE_USER_FORM,false,false);
         pwmRequest.forwardToJsp(JspUrl.ACTIVATE_USER);
         pwmRequest.forwardToJsp(JspUrl.ACTIVATE_USER);

+ 10 - 8
src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -44,8 +44,9 @@ import password.pwm.http.JspUrl;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.util.CaptchaUtility;
 import password.pwm.util.CaptchaUtility;
@@ -168,13 +169,14 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet {
 
 
             final UserIdentity userIdentity;
             final UserIdentity userIdentity;
             {
             {
-                final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, pwmSession.getLabel());
-                final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-                searchConfiguration.setFilter(searchFilter);
-                searchConfiguration.setFormValues(formValues);
-                searchConfiguration.setLdapProfile(ldapProfile);
-                searchConfiguration.setContexts(Collections.singletonList(contextParam));
-                userIdentity = userSearchEngine.performSingleUserSearch(searchConfiguration);
+                final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+                final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                        .filter(searchFilter)
+                        .formValues(formValues)
+                        .ldapProfile(ldapProfile)
+                        .contexts(Collections.singletonList(contextParam))
+                        .build();
+                userIdentity = userSearchEngine.performSingleUserSearch(searchConfiguration, pwmSession.getLabel());
             }
             }
 
 
             if (userIdentity == null) {
             if (userIdentity == null) {

+ 14 - 8
src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java

@@ -52,8 +52,9 @@ import password.pwm.http.bean.GuestRegistrationBean;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.util.FormMap;
 import password.pwm.util.FormMap;
@@ -304,15 +305,20 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
         final String usernameParam = pwmRequest.readParameterAsString("username");
         final String usernameParam = pwmRequest.readParameterAsString("username");
         final GuestRegistrationBean guBean = pwmApplication.getSessionStateService().getBean(pwmRequest, GuestRegistrationBean.class);
         final GuestRegistrationBean guBean = pwmApplication.getSessionStateService().getBean(pwmRequest, GuestRegistrationBean.class);
 
 
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setChaiProvider(chaiProvider);
-        searchConfiguration.setContexts(Collections.singletonList(config.readSettingAsString(PwmSetting.GUEST_CONTEXT)));
-        searchConfiguration.setEnableContextValidation(false);
-        searchConfiguration.setUsername(usernameParam);
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, pwmSession.getLabel());
+
+        SearchConfiguration.builder();
+
+        final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                    .chaiProvider(chaiProvider)
+                    .contexts(Collections.singletonList(config.readSettingAsString(PwmSetting.GUEST_CONTEXT)))
+                    .enableContextValidation(false)
+                    .username(usernameParam)
+                    .build();
+
+        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
 
 
         try {
         try {
-            final UserIdentity theGuest = userSearchEngine.performSingleUserSearch(searchConfiguration);
+            final UserIdentity theGuest = userSearchEngine.performSingleUserSearch(searchConfiguration, pwmSession.getLabel());
             final FormMap formProps = guBean.getFormValues();
             final FormMap formProps = guBean.getFormValues();
             try {
             try {
                 final List<FormConfiguration> guestUpdateForm = config.readSettingAsForm(PwmSetting.GUEST_UPDATE_FORM);
                 final List<FormConfiguration> guestUpdateForm = config.readSettingAsForm(PwmSetting.GUEST_UPDATE_FORM);

+ 13 - 10
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -73,8 +73,9 @@ import password.pwm.http.servlet.oauth.OAuthSettings;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationUtility;
 import password.pwm.ldap.auth.AuthenticationUtility;
@@ -389,13 +390,15 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
             // convert the username field to an identity
             // convert the username field to an identity
             final UserIdentity userIdentity;
             final UserIdentity userIdentity;
             {
             {
-                final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
-                final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-                searchConfiguration.setFilter(searchFilter);
-                searchConfiguration.setFormValues(formValues);
-                searchConfiguration.setContexts(Collections.singletonList(contextParam));
-                searchConfiguration.setLdapProfile(ldapProfile);
-                userIdentity = userSearchEngine.performSingleUserSearch(searchConfiguration);
+                final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+                final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                        .filter(searchFilter)
+                        .formValues(formValues)
+                        .contexts(Collections.singletonList(contextParam))
+                        .ldapProfile(ldapProfile)
+                        .build();
+
+                userIdentity = userSearchEngine.performSingleUserSearch(searchConfiguration, pwmRequest.getSessionLabel());
             }
             }
 
 
             if (userIdentity == null) {
             if (userIdentity == null) {
@@ -581,9 +584,9 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
 
 
         final UserIdentity oauthUserIdentity;
         final UserIdentity oauthUserIdentity;
         {
         {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
+            final UserSearchEngine userSearchEngine =pwmRequest.getPwmApplication().getUserSearchEngine();
             try {
             try {
-                oauthUserIdentity = userSearchEngine.resolveUsername(userDNfromOAuth, null, null);
+                oauthUserIdentity = userSearchEngine.resolveUsername(userDNfromOAuth, null, null, pwmRequest.getSessionLabel());
             } catch (PwmOperationalException e) {
             } catch (PwmOperationalException e) {
                 final String errorMsg = "unexpected error searching for oauth supplied username in ldap; error: " + e.getMessage() ;
                 final String errorMsg = "unexpected error searching for oauth supplied username in ldap; error: " + e.getMessage() ;
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR, errorMsg);
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR, errorMsg);

+ 25 - 15
src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -61,8 +61,10 @@ import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchResults;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.HelpdeskAuditRecord;
 import password.pwm.svc.event.HelpdeskAuditRecord;
@@ -491,27 +493,35 @@ public class HelpdeskServlet extends ControlledPwmServlet {
             return ProcessStatus.Halt;
             return ProcessStatus.Halt;
         }
         }
 
 
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setContexts(helpdeskProfile.readSettingAsStringArray(PwmSetting.HELPDESK_SEARCH_BASE));
-        searchConfiguration.setEnableContextValidation(false);
-        searchConfiguration.setUsername(username);
-        searchConfiguration.setEnableValueEscaping(false);
-        searchConfiguration.setFilter(getSearchFilter(pwmRequest.getConfig(),helpdeskProfile));
-        searchConfiguration.setEnableSplitWhitespace(true);
+        final UserSearchEngine userSearchEngine = pwmRequest.getPwmApplication().getUserSearchEngine();
 
 
 
 
-        if (!useProxy) {
-            final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity();
-            searchConfiguration.setLdapProfile(loggedInUser.getLdapProfileID());
-            searchConfiguration.setChaiProvider(getChaiUser(pwmRequest, helpdeskProfile, loggedInUser).getChaiProvider());
+        final SearchConfiguration searchConfiguration;
+        {
+            final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
+            builder.contexts(helpdeskProfile.readSettingAsStringArray(PwmSetting.HELPDESK_SEARCH_BASE));
+            builder.enableContextValidation(false);
+            builder.username(username);
+            builder.enableValueEscaping(false);
+            builder.filter(getSearchFilter(pwmRequest.getConfig(), helpdeskProfile));
+            builder.enableSplitWhitespace(true);
+
+            if (!useProxy) {
+                final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity();
+                builder.ldapProfile(loggedInUser.getLdapProfileID());
+                builder.chaiProvider(getChaiUser(pwmRequest, helpdeskProfile, loggedInUser).getChaiProvider());
+            }
+
+            searchConfiguration = builder.build();
         }
         }
 
 
-        final UserSearchEngine.UserSearchResults results;
+
+
+        final UserSearchResults results;
         final boolean sizeExceeded;
         final boolean sizeExceeded;
         try {
         try {
             final Locale locale = pwmRequest.getLocale();
             final Locale locale = pwmRequest.getLocale();
-            results = userSearchEngine.performMultiUserSearchFromForm(locale, searchConfiguration, maxResults, searchForm);
+            results = userSearchEngine.performMultiUserSearchFromForm(locale, searchConfiguration, maxResults, searchForm, pwmRequest.getSessionLabel());
             sizeExceeded = results.isSizeExceeded();
             sizeExceeded = results.isSizeExceeded();
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             final ErrorInformation errorInformation = e.getErrorInformation();
             final ErrorInformation errorInformation = e.getErrorInformation();

+ 9 - 6
src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -51,8 +51,9 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.NewUserBean;
 import password.pwm.http.bean.NewUserBean;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditEvent;
@@ -81,7 +82,7 @@ import java.util.Set;
 
 
 public class NewUserUtils {
 public class NewUserUtils {
     private static PwmLogger LOGGER = password.pwm.util.logging.PwmLogger.forClass(NewUserUtils.class);
     private static PwmLogger LOGGER = password.pwm.util.logging.PwmLogger.forClass(NewUserUtils.class);
-    
+
     private NewUserUtils() {
     private NewUserUtils() {
     }
     }
 
 
@@ -361,12 +362,14 @@ public class NewUserUtils {
     )
     )
             throws PwmUnrecoverableException, ChaiUnavailableException
             throws PwmUnrecoverableException, ChaiUnavailableException
     {
     {
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setUsername(rdnValue);
+        final UserSearchEngine userSearchEngine = pwmRequest.getPwmApplication().getUserSearchEngine();
+        final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                .username(rdnValue)
+                .build();
+
         try {
         try {
             final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
             final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
-                    searchConfiguration, 2, Collections.<String>emptyList());
+                    searchConfiguration, 2, Collections.emptyList(), pwmRequest.getSessionLabel());
             return results != null && !results.isEmpty();
             return results != null && !results.isEmpty();
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             final String msg = "ldap error while searching for duplicate entry names: " + e.getMessage();
             final String msg = "ldap error while searching for duplicate entry names: " + e.getMessage();

+ 8 - 3
src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java

@@ -40,7 +40,7 @@ import password.pwm.http.PwmURL;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet;
 import password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.auth.SessionAuthenticator;
@@ -245,8 +245,13 @@ public class OAuthConsumerServlet extends AbstractPwmServlet {
 
 
         if (userIsAuthenticated) {
         if (userIsAuthenticated) {
             try {
             try {
-                final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
-                final UserIdentity resolvedIdentity = userSearchEngine.resolveUsername(oauthSuppliedUsername, null, null);
+                final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+                final UserIdentity resolvedIdentity = userSearchEngine.resolveUsername(
+                        oauthSuppliedUsername,
+                        null,
+                        null,
+                        pwmSession.getLabel()
+                );
                 if (resolvedIdentity != null && resolvedIdentity.canonicalEquals(pwmSession.getUserInfoBean().getUserIdentity(),pwmApplication)) {
                 if (resolvedIdentity != null && resolvedIdentity.canonicalEquals(pwmSession.getUserInfoBean().getUserIdentity(),pwmApplication)) {
                     LOGGER.debug(pwmSession, "verified incoming oauth code for already authenticated session does resolve to same as logged in user");
                     LOGGER.debug(pwmSession, "verified incoming oauth code for already authenticated session does resolve to same as logged in user");
                 } else {
                 } else {

+ 25 - 20
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java

@@ -44,9 +44,11 @@ import password.pwm.http.PwmRequest;
 import password.pwm.i18n.Display;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.UserStatusReader;
+import password.pwm.ldap.search.UserSearchResults;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
@@ -198,7 +200,7 @@ public class PeopleSearchDataReader {
             throw e;
             throw e;
         }
         }
 
 
-        final UserSearchEngine.UserSearchResults detailResults = doDetailLookup(userIdentity);
+        final UserSearchResults detailResults = doDetailLookup(userIdentity);
         final Map<String, String> searchResults = detailResults.getResults().get(userIdentity);
         final Map<String, String> searchResults = detailResults.getResults().get(userIdentity);
 
 
         final UserDetailBean userDetailBean = new UserDetailBean();
         final UserDetailBean userDetailBean = new UserDetailBean();
@@ -563,13 +565,13 @@ public class PeopleSearchDataReader {
         return useProxy || !pwmRequest.isAuthenticated() && publicAccessEnabled;
         return useProxy || !pwmRequest.isAuthenticated() && publicAccessEnabled;
     }
     }
 
 
-    private UserSearchEngine.UserSearchResults doDetailLookup(
+    private UserSearchResults doDetailLookup(
             final UserIdentity userIdentity
             final UserIdentity userIdentity
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final List<FormConfiguration> detailFormConfig = pwmRequest.getConfig().readSettingAsForm(PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
         final List<FormConfiguration> detailFormConfig = pwmRequest.getConfig().readSettingAsForm(PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
-        final Map<String, String> attributeHeaderMap = UserSearchEngine.UserSearchResults.fromFormConfiguration(
+        final Map<String, String> attributeHeaderMap = UserSearchResults.fromFormConfiguration(
                 detailFormConfig, pwmRequest.getLocale());
                 detailFormConfig, pwmRequest.getLocale());
 
 
         if (config.isOrgChartEnabled()) {
         if (config.isOrgChartEnabled()) {
@@ -586,7 +588,7 @@ public class PeopleSearchDataReader {
         try {
         try {
             final ChaiUser theUser = getChaiUser(userIdentity);
             final ChaiUser theUser = getChaiUser(userIdentity);
             final Map<String, String> values = theUser.readStringAttributes(attributeHeaderMap.keySet());
             final Map<String, String> values = theUser.readStringAttributes(attributeHeaderMap.keySet());
-            return new UserSearchEngine.UserSearchResults(
+            return new UserSearchResults(
                     attributeHeaderMap,
                     attributeHeaderMap,
                     Collections.singletonMap(userIdentity, values),
                     Collections.singletonMap(userIdentity, values),
                     false
                     false
@@ -637,23 +639,26 @@ public class PeopleSearchDataReader {
         }
         }
 
 
         final boolean useProxy = useProxy();
         final boolean useProxy = useProxy();
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setContexts(
-                pwmRequest.getConfig().readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_SEARCH_BASE));
-        searchConfiguration.setEnableContextValidation(false);
-        searchConfiguration.setUsername(username);
-        searchConfiguration.setEnableValueEscaping(false);
-        searchConfiguration.setFilter(getSearchFilter(pwmRequest.getConfig()));
-        searchConfiguration.setEnableSplitWhitespace(true);
+        final UserSearchEngine userSearchEngine = pwmRequest.getPwmApplication().getUserSearchEngine();
 
 
-
-        if (!useProxy) {
-            searchConfiguration.setLdapProfile(pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity().getLdapProfileID());
-            searchConfiguration.setChaiProvider(pwmRequest.getPwmSession().getSessionManager().getChaiProvider());
+        final SearchConfiguration searchConfiguration;
+        {
+            final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
+            builder.contexts(pwmRequest.getConfig().readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_SEARCH_BASE));
+            builder.enableContextValidation(false);
+            builder.username(username);
+            builder.enableValueEscaping(false);
+            builder.filter(getSearchFilter(pwmRequest.getConfig()));
+            builder.enableSplitWhitespace(true);
+
+            if (!useProxy) {
+                builder.ldapProfile(pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity().getLdapProfileID());
+                builder.chaiProvider(pwmRequest.getPwmSession().getSessionManager().getChaiProvider());
+            }
+            searchConfiguration = builder.build();
         }
         }
 
 
-        final UserSearchEngine.UserSearchResults results;
+        final UserSearchResults results;
         final boolean sizeExceeded;
         final boolean sizeExceeded;
         try {
         try {
             final List<FormConfiguration> searchForm = pwmRequest.getConfig().readSettingAsForm(
             final List<FormConfiguration> searchForm = pwmRequest.getConfig().readSettingAsForm(
@@ -661,7 +666,7 @@ public class PeopleSearchDataReader {
             final int maxResults = (int) pwmRequest.getConfig().readSettingAsLong(
             final int maxResults = (int) pwmRequest.getConfig().readSettingAsLong(
                     PwmSetting.PEOPLE_SEARCH_RESULT_LIMIT);
                     PwmSetting.PEOPLE_SEARCH_RESULT_LIMIT);
             final Locale locale = pwmRequest.getLocale();
             final Locale locale = pwmRequest.getLocale();
-            results = userSearchEngine.performMultiUserSearchFromForm(locale, searchConfiguration, maxResults, searchForm);
+            results = userSearchEngine.performMultiUserSearchFromForm(locale, searchConfiguration, maxResults, searchForm, pwmRequest.getSessionLabel());
             sizeExceeded = results.isSizeExceeded();
             sizeExceeded = results.isSizeExceeded();
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             final ErrorInformation errorInformation = e.getErrorInformation();
             final ErrorInformation errorInformation = e.getErrorInformation();

+ 8 - 4
src/main/java/password/pwm/ldap/LdapOperationsHelper.java

@@ -47,6 +47,8 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
@@ -337,10 +339,12 @@ public class LdapOperationsHelper {
                 if (!"DN".equalsIgnoreCase(guidAttributeName) && !"VENDORGUID".equalsIgnoreCase(guidAttributeName)) {
                 if (!"DN".equalsIgnoreCase(guidAttributeName) && !"VENDORGUID".equalsIgnoreCase(guidAttributeName)) {
                     try {
                     try {
                         // check if it is unique
                         // check if it is unique
-                        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-                        searchConfiguration.setFilter("(" + guidAttributeName + "=" + guidValue + ")");
-                        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, sessionLabel);
-                        final UserIdentity result = userSearchEngine.performSingleUserSearch(searchConfiguration);
+                        final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                                .filter("(" + guidAttributeName + "=" + guidValue + ")")
+                                .build();
+
+                        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+                        final UserIdentity result = userSearchEngine.performSingleUserSearch(searchConfiguration, sessionLabel);
                         exists = result != null;
                         exists = result != null;
                     } catch (PwmOperationalException e) {
                     } catch (PwmOperationalException e) {
                         if (e.getError() != PwmError.ERROR_CANT_MATCH_USER) {
                         if (e.getError() != PwmError.ERROR_CANT_MATCH_USER) {

+ 20 - 12
src/main/java/password/pwm/ldap/LdapPermissionTester.java

@@ -41,6 +41,8 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
@@ -196,28 +198,31 @@ public class LdapPermissionTester {
     public static Map<UserIdentity, Map<String, String>> discoverMatchingUsers(
     public static Map<UserIdentity, Map<String, String>> discoverMatchingUsers(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final int maxResultSize,
             final int maxResultSize,
-            final List<UserPermission> userPermissions
+            final List<UserPermission> userPermissions,
+            final SessionLabel sessionLabel
     )
     )
             throws Exception
             throws Exception
     {
     {
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, SessionLabel.SYSTEM_LABEL);
+        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
 
 
         final Map<UserIdentity, Map<String, String>> results = new TreeMap<>();
         final Map<UserIdentity, Map<String, String>> results = new TreeMap<>();
         for (final UserPermission userPermission : userPermissions) {
         for (final UserPermission userPermission : userPermissions) {
             if ((maxResultSize) - results.size() > 0) {
             if ((maxResultSize) - results.size() > 0) {
-                final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
+
+                final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
+
                 switch (userPermission.getType()) {
                 switch (userPermission.getType()) {
                     case ldapQuery: {
                     case ldapQuery: {
-                        searchConfiguration.setFilter(userPermission.getLdapQuery());
+                        builder.filter(userPermission.getLdapQuery());
                         if (userPermission.getLdapBase() != null && !userPermission.getLdapBase().isEmpty()) {
                         if (userPermission.getLdapBase() != null && !userPermission.getLdapBase().isEmpty()) {
-                            searchConfiguration.setEnableContextValidation(false);
-                            searchConfiguration.setContexts(Collections.singletonList(userPermission.getLdapBase()));
+                            builder.enableContextValidation(false);
+                            builder.contexts(Collections.singletonList(userPermission.getLdapBase()));
                         }
                         }
                     }
                     }
                     break;
                     break;
 
 
                     case ldapGroup: {
                     case ldapGroup: {
-                        searchConfiguration.setGroupDN(userPermission.getLdapBase());
+                        builder.groupDN(userPermission.getLdapBase());
                     }
                     }
                     break;
                     break;
 
 
@@ -226,15 +231,18 @@ public class LdapPermissionTester {
                 }
                 }
 
 
                 if (userPermission.getLdapProfileID() != null && !userPermission.getLdapProfileID().isEmpty() && !userPermission.getLdapProfileID().equals(PwmConstants.PROFILE_ID_ALL)) {
                 if (userPermission.getLdapProfileID() != null && !userPermission.getLdapProfileID().isEmpty() && !userPermission.getLdapProfileID().equals(PwmConstants.PROFILE_ID_ALL)) {
-                    searchConfiguration.setLdapProfile(userPermission.getLdapProfileID());
+                    builder.ldapProfile(userPermission.getLdapProfileID());
                 }
                 }
 
 
+                final SearchConfiguration searchConfiguration = builder.build();
+
                 try {
                 try {
                     results.putAll(userSearchEngine.performMultiUserSearch(
                     results.putAll(userSearchEngine.performMultiUserSearch(
-                                    searchConfiguration,
-                                    (maxResultSize) - results.size(),
-                                    Collections.<String>emptyList())
-                    );
+                            searchConfiguration,
+                            (maxResultSize) - results.size(),
+                            Collections.emptyList(),
+                            sessionLabel
+                    ));
                 } catch (PwmUnrecoverableException e) {
                 } catch (PwmUnrecoverableException e) {
                     LOGGER.error("error reading matching users: " + e.getMessage());
                     LOGGER.error("error reading matching users: " + e.getMessage());
                     throw new PwmOperationalException(e.getErrorInformation());
                     throw new PwmOperationalException(e.getErrorInformation());

+ 5 - 5
src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java

@@ -44,7 +44,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.intruder.RecordType;
@@ -91,8 +91,8 @@ public class SessionAuthenticator {
         pwmApplication.getIntruderManager().check(RecordType.USERNAME, username);
         pwmApplication.getIntruderManager().check(RecordType.USERNAME, username);
         UserIdentity userIdentity = null;
         UserIdentity userIdentity = null;
         try {
         try {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, sessionLabel);
-            userIdentity = userSearchEngine.resolveUsername(username, context, ldapProfile);
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            userIdentity = userSearchEngine.resolveUsername(username, context, ldapProfile, sessionLabel);
 
 
             final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest(
             final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest(
                     pwmApplication,
                     pwmApplication,
@@ -177,8 +177,8 @@ public class SessionAuthenticator {
 
 
         UserIdentity userIdentity = null;
         UserIdentity userIdentity = null;
         try {
         try {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, sessionLabel);
-            userIdentity = userSearchEngine.resolveUsername(username, null, null);
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            userIdentity = userSearchEngine.resolveUsername(username, null, null, sessionLabel);
 
 
             final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest(
             final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest(
                     pwmApplication,
                     pwmApplication,

+ 56 - 0
src/main/java/password/pwm/ldap/search/SearchConfiguration.java

@@ -0,0 +1,56 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 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.search;
+
+import com.novell.ldapchai.provider.ChaiProvider;
+import lombok.Builder;
+import lombok.Data;
+import password.pwm.config.FormConfiguration;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+@Builder
+@Data
+public class SearchConfiguration implements Serializable {
+
+    private String filter;
+    private String ldapProfile;
+    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;
+
+    void validate() {
+        if (this.username != null && this.formValues != null) {
+            throw new IllegalArgumentException("username OR formValues cannot both be supplied");
+        }
+    }
+
+}

+ 372 - 328
src/main/java/password/pwm/ldap/UserSearchEngine.java → src/main/java/password/pwm/ldap/search/UserSearchEngine.java

@@ -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;
+    }
 }
 }

+ 43 - 0
src/main/java/password/pwm/ldap/search/UserSearchJob.java

@@ -0,0 +1,43 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 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.search;
+
+import com.novell.ldapchai.provider.ChaiProvider;
+import lombok.Builder;
+import lombok.Getter;
+import password.pwm.config.profile.LdapProfile;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+@Getter
+@Builder
+public class UserSearchJob implements Serializable {
+    private final LdapProfile ldapProfile;
+    private final String searchFilter;
+    private final String context;
+    private final Collection<String> returnAttributes;
+    private final int maxResults;
+    private final ChaiProvider chaiProvider;
+    private final long timeoutMs;
+}

+ 131 - 0
src/main/java/password/pwm/ldap/search/UserSearchResults.java

@@ -0,0 +1,131 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 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.search;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.FormConfiguration;
+import password.pwm.error.PwmUnrecoverableException;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public 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);
+            }
+
+            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 "";
+            }
+        };
+
+        final List<UserIdentity> identitySortMap = new ArrayList<>();
+        identitySortMap.addAll(results.keySet());
+        identitySortMap.sort(comparator);
+
+        final Map<UserIdentity, Map<String, String>> sortedResults = new LinkedHashMap<>();
+        for (final UserIdentity userIdentity : identitySortMap) {
+            sortedResults.put(userIdentity, results.get(userIdentity));
+        }
+        return sortedResults;
+    }
+
+    public Map<String, String> getHeaderAttributeMap() {
+        return headerAttributeMap;
+    }
+
+    public Map<UserIdentity, Map<String, String>> getResults() {
+        return results;
+    }
+
+    public boolean isSizeExceeded() {
+        return sizeExceeded;
+    }
+
+    public List<Map<String,Object>> resultsAsJsonOutput(final PwmApplication pwmApplication, final UserIdentity ignoreUser)
+            throws PwmUnrecoverableException
+    {
+        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));
+        }
+        return results;
+    }
+}

+ 2 - 0
src/main/java/password/pwm/svc/PwmServiceManager.java

@@ -32,6 +32,7 @@ import password.pwm.health.HealthMonitor;
 import password.pwm.http.servlet.resource.ResourceServletService;
 import password.pwm.http.servlet.resource.ResourceServletService;
 import password.pwm.http.state.SessionStateService;
 import password.pwm.http.state.SessionStateService;
 import password.pwm.ldap.LdapConnectionService;
 import password.pwm.ldap.LdapConnectionService;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.event.AuditService;
 import password.pwm.svc.event.AuditService;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.intruder.IntruderManager;
@@ -92,6 +93,7 @@ public class PwmServiceManager {
         ResourceServletService( ResourceServletService.class,    false),
         ResourceServletService( ResourceServletService.class,    false),
         SessionTrackService(    SessionTrackService.class,       false),
         SessionTrackService(    SessionTrackService.class,       false),
         SessionStateSvc(        SessionStateService.class,       false),
         SessionStateSvc(        SessionStateService.class,       false),
+        UserSearchEngine(       UserSearchEngine.class,          true),
 
 
         ;
         ;
 
 

+ 6 - 3
src/main/java/password/pwm/svc/intruder/RecordManagerImpl.java

@@ -30,6 +30,7 @@ import password.pwm.util.java.ClosableIterator;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.SecureEngine;
 import password.pwm.util.secure.SecureEngine;
 
 
 class RecordManagerImpl implements RecordManager {
 class RecordManagerImpl implements RecordManager {
@@ -39,6 +40,8 @@ class RecordManagerImpl implements RecordManager {
     private final RecordStore recordStore;
     private final RecordStore recordStore;
     private final IntruderSettings settings;
     private final IntruderSettings settings;
 
 
+    private static final PwmHashAlgorithm KEY_HASH_ALG = PwmHashAlgorithm.SHA256;
+
     RecordManagerImpl(final RecordType recordType, final RecordStore recordStore, final IntruderSettings settings) {
     RecordManagerImpl(final RecordType recordType, final RecordStore recordStore, final IntruderSettings settings) {
         this.recordType = recordType;
         this.recordType = recordType;
         this.recordStore = recordStore;
         this.recordStore = recordStore;
@@ -133,13 +136,13 @@ class RecordManagerImpl implements RecordManager {
     }
     }
 
 
     private String makeKey(final String subject) throws PwmOperationalException {
     private String makeKey(final String subject) throws PwmOperationalException {
-        final String md5sum;
+        final String hash;
         try {
         try {
-            md5sum = SecureEngine.md5sum(subject);
+            hash = SecureEngine.hash(subject, KEY_HASH_ALG);
         } catch (PwmUnrecoverableException e) {
         } catch (PwmUnrecoverableException e) {
             throw new PwmOperationalException(PwmError.ERROR_UNKNOWN,"error generating md5sum for intruder record: " + e.getMessage());
             throw new PwmOperationalException(PwmError.ERROR_UNKNOWN,"error generating md5sum for intruder record: " + e.getMessage());
         }
         }
-        return md5sum + recordType.toString();
+        return hash + recordType.toString();
     }
     }
 
 
 
 

+ 25 - 10
src/main/java/password/pwm/svc/report/ReportService.java

@@ -39,7 +39,8 @@ 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.health.HealthRecord;
 import password.pwm.health.HealthRecord;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.stats.EventRateMeter;
 import password.pwm.svc.stats.EventRateMeter;
@@ -427,20 +428,34 @@ public class ReportService implements PwmService {
         )
         )
                 throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException
                 throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException
         {
         {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication,null);
-            final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-            searchConfiguration.setEnableValueEscaping(false);
-            searchConfiguration.setSearchTimeout(Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT)));
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
 
 
-            if (searchFilter == null) {
-                searchConfiguration.setUsername("*");
-            } else {
-                searchConfiguration.setFilter(searchFilter);
+            final SearchConfiguration searchConfiguration;
+            {
+                final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
+
+                builder.enableValueEscaping(false);
+                builder.searchTimeout(Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT)));
+
+                if (searchFilter == null) {
+                    builder.username("*");
+                } else {
+                    builder.filter(searchFilter);
+                }
+
+                searchConfiguration = builder.build();
             }
             }
 
 
+
             LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"beginning UserReportService user search using parameters: " + (JsonUtil.serialize(searchConfiguration)));
             LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"beginning UserReportService user search using parameters: " + (JsonUtil.serialize(searchConfiguration)));
 
 
-            final Map<UserIdentity,Map<String,String>> searchResults = userSearchEngine.performMultiUserSearch(searchConfiguration, maxResults, Collections.<String>emptyList());
+            final Map<UserIdentity,Map<String,String>> searchResults = userSearchEngine.performMultiUserSearch(
+                    searchConfiguration,
+                    maxResults,
+                    Collections.emptyList(),
+                    PwmConstants.REPORTING_SESSION_LABEL
+
+            );
             LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"user search found " + searchResults.size() + " users for reporting");
             LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"user search found " + searchResults.size() + " users for reporting");
             return new ArrayList<>(searchResults.keySet());
             return new ArrayList<>(searchResults.keySet());
         }
         }

+ 12 - 8
src/main/java/password/pwm/svc/report/UserCacheService.java

@@ -38,7 +38,7 @@ import password.pwm.util.java.JsonUtil;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.SecureEngine;
+import password.pwm.util.secure.SecureService;
 
 
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
@@ -50,6 +50,8 @@ public class UserCacheService implements PwmService {
     private CacheStoreWrapper cacheStore;
     private CacheStoreWrapper cacheStore;
     private STATUS status;
     private STATUS status;
 
 
+    private PwmApplication pwmApplication;
+
 
 
     public STATUS status() {
     public STATUS status() {
         return status;
         return status;
@@ -58,7 +60,7 @@ public class UserCacheService implements PwmService {
     public UserCacheRecord updateUserCache(final UserInfoBean userInfoBean)
     public UserCacheRecord updateUserCache(final UserInfoBean userInfoBean)
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final StorageKey storageKey = StorageKey.fromUserInfoBean(userInfoBean);
+        final StorageKey storageKey = StorageKey.fromUserInfoBean(userInfoBean, pwmApplication);
 
 
         boolean preExisting = false;
         boolean preExisting = false;
         try {
         try {
@@ -91,7 +93,7 @@ public class UserCacheService implements PwmService {
     public void store(final UserCacheRecord userCacheRecord)
     public void store(final UserCacheRecord userCacheRecord)
             throws LocalDBException, PwmUnrecoverableException
             throws LocalDBException, PwmUnrecoverableException
     {
     {
-        final StorageKey storageKey = StorageKey.fromUserGUID(userCacheRecord.getUserGUID());
+        final StorageKey storageKey = StorageKey.fromUserGUID(userCacheRecord.getUserGUID(), pwmApplication);
         cacheStore.write(storageKey, userCacheRecord);
         cacheStore.write(storageKey, userCacheRecord);
     }
     }
 
 
@@ -138,6 +140,7 @@ public class UserCacheService implements PwmService {
 
 
     public void init(final PwmApplication pwmApplication) throws PwmException {
     public void init(final PwmApplication pwmApplication) throws PwmException {
         status = STATUS.OPENING;
         status = STATUS.OPENING;
+        this.pwmApplication = pwmApplication;
         this.cacheStore = new CacheStoreWrapper(pwmApplication.getLocalDB());
         this.cacheStore = new CacheStoreWrapper(pwmApplication.getLocalDB());
         status = STATUS.OPEN;
         status = STATUS.OPEN;
     }
     }
@@ -176,24 +179,25 @@ public class UserCacheService implements PwmService {
             return key;
             return key;
         }
         }
 
 
-        public static StorageKey fromUserInfoBean(final UserInfoBean userInfoBean)
+        public static StorageKey fromUserInfoBean(final UserInfoBean userInfoBean, final PwmApplication pwmApplication)
                 throws PwmUnrecoverableException
                 throws PwmUnrecoverableException
         {
         {
             final String userGUID = userInfoBean.getUserGuid();
             final String userGUID = userInfoBean.getUserGuid();
-            return fromUserGUID(userGUID);
+            return fromUserGUID(userGUID, pwmApplication);
         }
         }
 
 
         public static StorageKey fromUserIdentity(final PwmApplication pwmApplication, final UserIdentity userIdentity)
         public static StorageKey fromUserIdentity(final PwmApplication pwmApplication, final UserIdentity userIdentity)
                 throws ChaiUnavailableException, PwmUnrecoverableException
                 throws ChaiUnavailableException, PwmUnrecoverableException
         {
         {
             final String userGUID = LdapOperationsHelper.readLdapGuidValue(pwmApplication, null, userIdentity, true);
             final String userGUID = LdapOperationsHelper.readLdapGuidValue(pwmApplication, null, userIdentity, true);
-            return fromUserGUID(userGUID);
+            return fromUserGUID(userGUID, pwmApplication);
         }
         }
 
 
-        private static StorageKey fromUserGUID(final String userGUID)
+        private static StorageKey fromUserGUID(final String userGUID, final PwmApplication pwmApplication)
                 throws PwmUnrecoverableException
                 throws PwmUnrecoverableException
         {
         {
-            return new StorageKey(SecureEngine.md5sum(userGUID));
+            final SecureService secureService = pwmApplication.getSecureService();
+            return new StorageKey(secureService.hash(userGUID));
         }
         }
     }
     }
 
 

+ 7 - 7
src/main/java/password/pwm/svc/stats/StatisticsBundle.java

@@ -55,14 +55,14 @@ public class StatisticsBundle {
 
 
     public static StatisticsBundle input(final String inputString) {
     public static StatisticsBundle input(final String inputString) {
         final Map<Statistic, String> srcMap = new HashMap<>();
         final Map<Statistic, String> srcMap = new HashMap<>();
-            final Map<String, String> loadedMap = JsonUtil.deserializeStringMap(inputString);
-            for (final String key : loadedMap.keySet()) {
-                try {
-                    srcMap.put(Statistic.valueOf(key),loadedMap.get(key));
-                } catch (IllegalArgumentException e) {
-                    LOGGER.error("error parsing statistic key '" + key + "', reason: " + e.getMessage());
-                }
+        final Map<String, String> loadedMap = JsonUtil.deserializeStringMap(inputString);
+        for (final String key : loadedMap.keySet()) {
+            try {
+                srcMap.put(Statistic.valueOf(key),loadedMap.get(key));
+            } catch (IllegalArgumentException e) {
+                LOGGER.error("error parsing statistic key '" + key + "', reason: " + e.getMessage());
             }
             }
+        }
         final StatisticsBundle bundle = new StatisticsBundle();
         final StatisticsBundle bundle = new StatisticsBundle();
 
 
         for (final Statistic loopStat : Statistic.values()) {
         for (final Statistic loopStat : Statistic.values()) {

+ 2 - 2
src/main/java/password/pwm/svc/token/CryptoTokenMachine.java

@@ -53,7 +53,7 @@ class CryptoTokenMachine implements TokenMachine {
         return returnString.toString();
         return returnString.toString();
     }
     }
 
 
-    public TokenPayload retrieveToken(final String tokenKey)
+    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         if (tokenKey == null || tokenKey.length() < 1) {
         if (tokenKey == null || tokenKey.length() < 1) {
@@ -65,7 +65,7 @@ class CryptoTokenMachine implements TokenMachine {
     public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
     public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
     }
     }
 
 
-    public void removeToken(final String tokenKey) throws PwmOperationalException, PwmUnrecoverableException {
+    public void removeToken(final String tokenKey, final SessionLabel sessionLabel) throws PwmOperationalException, PwmUnrecoverableException {
     }
     }
 
 
     public int size() throws PwmOperationalException, PwmUnrecoverableException {
     public int size() throws PwmOperationalException, PwmUnrecoverableException {

+ 5 - 5
src/main/java/password/pwm/svc/token/DBTokenMachine.java

@@ -48,10 +48,10 @@ class DBTokenMachine implements TokenMachine {
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
     }
     }
 
 
-    public TokenPayload retrieveToken(final String tokenKey)
+    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
         final String storedRawValue = databaseAccessor.get(DatabaseTable.TOKENS,md5sumToken);
         final String storedRawValue = databaseAccessor.get(DatabaseTable.TOKENS,md5sumToken);
 
 
         if (storedRawValue != null && storedRawValue.length() > 0 ) {
         if (storedRawValue != null && storedRawValue.length() > 0 ) {
@@ -63,12 +63,12 @@ class DBTokenMachine implements TokenMachine {
 
 
     public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
     public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
         final String rawValue = tokenService.toEncryptedString(tokenPayload);
         final String rawValue = tokenService.toEncryptedString(tokenPayload);
-        final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
         databaseAccessor.put(DatabaseTable.TOKENS, md5sumToken, rawValue);
         databaseAccessor.put(DatabaseTable.TOKENS, md5sumToken, rawValue);
     }
     }
 
 
-    public void removeToken(final String tokenKey) throws PwmOperationalException, PwmUnrecoverableException {
-        final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+    public void removeToken(final String tokenKey, final SessionLabel sessionLabel) throws PwmOperationalException, PwmUnrecoverableException {
+        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
         databaseAccessor.remove(DatabaseTable.TOKENS,tokenKey);
         databaseAccessor.remove(DatabaseTable.TOKENS,tokenKey);
         databaseAccessor.remove(DatabaseTable.TOKENS,md5sumToken);
         databaseAccessor.remove(DatabaseTable.TOKENS,md5sumToken);
     }
     }

+ 13 - 10
src/main/java/password/pwm/svc/token/LdapTokenMachine.java

@@ -34,8 +34,9 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 
 
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
@@ -65,12 +66,12 @@ class LdapTokenMachine implements TokenMachine {
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
     }
     }
 
 
-    public TokenPayload retrieveToken(final String tokenKey)
+    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         final String searchFilter;
         final String searchFilter;
         {
         {
-            final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+            final String md5sumToken = tokenService.makeTokenHash(tokenKey);
             final SearchHelper tempSearchHelper = new SearchHelper();
             final SearchHelper tempSearchHelper = new SearchHelper();
             final Map<String,String> filterAttributes = new HashMap<>();
             final Map<String,String> filterAttributes = new HashMap<>();
             for (final String loopStr : pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES)) {
             for (final String loopStr : pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES)) {
@@ -82,10 +83,12 @@ class LdapTokenMachine implements TokenMachine {
         }
         }
 
 
         try {
         try {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, null);
-            final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-            searchConfiguration.setFilter(searchFilter);
-            final UserIdentity user = userSearchEngine.performSingleUserSearch(searchConfiguration);
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                    .filter(searchFilter)
+                    .build();
+
+            final UserIdentity user = userSearchEngine.performSingleUserSearch(searchConfiguration, sessionLabel);
             if (user == null) {
             if (user == null) {
                 return null;
                 return null;
             }
             }
@@ -117,7 +120,7 @@ class LdapTokenMachine implements TokenMachine {
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         try {
         try {
-            final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+            final String md5sumToken = tokenService.makeTokenHash(tokenKey);
             final String encodedTokenPayload = tokenService.toEncryptedString(tokenPayload);
             final String encodedTokenPayload = tokenService.toEncryptedString(tokenPayload);
 
 
             final UserIdentity userIdentity = tokenPayload.getUserIdentity();
             final UserIdentity userIdentity = tokenPayload.getUserIdentity();
@@ -130,10 +133,10 @@ class LdapTokenMachine implements TokenMachine {
         }
         }
     }
     }
 
 
-    public void removeToken(final String tokenKey)
+    public void removeToken(final String tokenKey, final SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final TokenPayload payload = retrieveToken(tokenKey);
+        final TokenPayload payload = retrieveToken(tokenKey, sessionLabel);
         if (payload != null) {
         if (payload != null) {
             final UserIdentity userIdentity = payload.getUserIdentity();
             final UserIdentity userIdentity = payload.getUserIdentity();
             try {
             try {

+ 5 - 5
src/main/java/password/pwm/svc/token/LocalDBTokenMachine.java

@@ -48,10 +48,10 @@ class LocalDBTokenMachine implements TokenMachine {
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
     }
     }
 
 
-    public TokenPayload retrieveToken(final String tokenKey)
+    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
         final String storedRawValue = localDB.get(LocalDB.DB.TOKENS, md5sumToken);
         final String storedRawValue = localDB.get(LocalDB.DB.TOKENS, md5sumToken);
 
 
         if (storedRawValue != null && storedRawValue.length() > 0 ) {
         if (storedRawValue != null && storedRawValue.length() > 0 ) {
@@ -63,14 +63,14 @@ class LocalDBTokenMachine implements TokenMachine {
 
 
     public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
     public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
         final String rawValue = tokenService.toEncryptedString(tokenPayload);
         final String rawValue = tokenService.toEncryptedString(tokenPayload);
-        final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
         localDB.put(LocalDB.DB.TOKENS, md5sumToken, rawValue);
         localDB.put(LocalDB.DB.TOKENS, md5sumToken, rawValue);
     }
     }
 
 
-    public void removeToken(final String tokenKey)
+    public void removeToken(final String tokenKey, final SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final String md5sumToken = TokenService.makeTokenHash(tokenKey);
+        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
         localDB.remove(LocalDB.DB.TOKENS, tokenKey);
         localDB.remove(LocalDB.DB.TOKENS, tokenKey);
         localDB.remove(LocalDB.DB.TOKENS, md5sumToken);
         localDB.remove(LocalDB.DB.TOKENS, md5sumToken);
     }
     }

+ 2 - 2
src/main/java/password/pwm/svc/token/TokenMachine.java

@@ -32,13 +32,13 @@ interface TokenMachine {
     String generateToken( SessionLabel sessionLabel,  TokenPayload tokenPayload)
     String generateToken( SessionLabel sessionLabel,  TokenPayload tokenPayload)
             throws PwmUnrecoverableException, PwmOperationalException;
             throws PwmUnrecoverableException, PwmOperationalException;
 
 
-    TokenPayload retrieveToken( String tokenKey)
+    TokenPayload retrieveToken(String tokenKey, SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException;
             throws PwmOperationalException, PwmUnrecoverableException;
 
 
     void storeToken( String tokenKey,  TokenPayload tokenPayload)
     void storeToken( String tokenKey,  TokenPayload tokenPayload)
             throws PwmOperationalException, PwmUnrecoverableException;
             throws PwmOperationalException, PwmUnrecoverableException;
 
 
-    void removeToken( String tokenKey)
+    void removeToken(String tokenKey, SessionLabel sessionLabel)
             throws PwmOperationalException, PwmUnrecoverableException;
             throws PwmOperationalException, PwmUnrecoverableException;
 
 
     int size()
     int size()

+ 6 - 6
src/main/java/password/pwm/svc/token/TokenPayload.java

@@ -25,13 +25,13 @@ package password.pwm.svc.token;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.Date;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
 public class TokenPayload implements Serializable {
 public class TokenPayload implements Serializable {
-    private final java.util.Date date;
+    private final Instant date;
     private final String name;
     private final String name;
     private final Map<String,String> data;
     private final Map<String,String> data;
     private final UserIdentity user;
     private final UserIdentity user;
@@ -39,15 +39,15 @@ public class TokenPayload implements Serializable {
     private final String guid;
     private final String guid;
 
 
     TokenPayload(final String name, final Map<String, String> data, final UserIdentity user, final Set<String> dest, final String guid) {
     TokenPayload(final String name, final Map<String, String> data, final UserIdentity user, final Set<String> dest, final String guid) {
-        this.date = new Date();
-        this.data = data == null ? Collections.<String,String>emptyMap() : Collections.unmodifiableMap(data);
+        this.date = Instant.now();
+        this.data = data == null ? Collections.emptyMap() : Collections.unmodifiableMap(data);
         this.name = name;
         this.name = name;
         this.user = user;
         this.user = user;
-        this.dest = dest == null ? Collections.<String>emptySet() : Collections.unmodifiableSet(dest);
+        this.dest = dest == null ? Collections.emptySet() : Collections.unmodifiableSet(dest);
         this.guid = guid;
         this.guid = guid;
     }
     }
 
 
-    public Date getDate() {
+    public Instant getDate() {
         return date;
         return date;
     }
     }
 
 

+ 21 - 20
src/main/java/password/pwm/svc/token/TokenService.java

@@ -62,12 +62,11 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmRandom;
-import password.pwm.util.secure.SecureEngine;
+import password.pwm.util.secure.SecureService;
 
 
 import java.time.Instant;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.Date;
 import java.util.Iterator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
@@ -101,7 +100,7 @@ public class TokenService implements PwmService {
     private TokenMachine tokenMachine;
     private TokenMachine tokenMachine;
     private long counter;
     private long counter;
 
 
-    private ServiceInfo serviceInfo = new ServiceInfo(Collections.<DataStorageMethod>emptyList());
+    private ServiceInfo serviceInfo = new ServiceInfo(Collections.emptyList());
     private STATUS status = STATUS.NEW;
     private STATUS status = STATUS.NEW;
 
 
     private ErrorInformation errorInformation = null;
     private ErrorInformation errorInformation = null;
@@ -115,7 +114,8 @@ public class TokenService implements PwmService {
         final long count = counter++;
         final long count = counter++;
         final StringBuilder guid = new StringBuilder();
         final StringBuilder guid = new StringBuilder();
         try {
         try {
-            guid.append(SecureEngine.md5sum(pwmApplication.getInstanceID() + pwmApplication.getStartupTime().toString()));
+            final SecureService secureService = pwmApplication.getSecureService();
+            guid.append(secureService.hash(pwmApplication.getInstanceID() + pwmApplication.getStartupTime().toString()));
             guid.append("-");
             guid.append("-");
             guid.append(count);
             guid.append(count);
         } catch (Exception e) {
         } catch (Exception e) {
@@ -250,7 +250,7 @@ public class TokenService implements PwmService {
 
 
         final TokenPayload tokenPayload;
         final TokenPayload tokenPayload;
         try {
         try {
-            tokenPayload = retrieveTokenData(tokenKey);
+            tokenPayload = retrieveTokenData(tokenKey, pwmSession.getLabel());
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             return;
             return;
         }
         }
@@ -270,13 +270,13 @@ public class TokenService implements PwmService {
         StatisticsManager.incrementStat(pwmApplication, Statistic.TOKENS_PASSSED);
         StatisticsManager.incrementStat(pwmApplication, Statistic.TOKENS_PASSSED);
     }
     }
 
 
-    public TokenPayload retrieveTokenData(final String tokenKey)
+    public TokenPayload retrieveTokenData(final String tokenKey, final SessionLabel sessionLabel)
             throws PwmOperationalException
             throws PwmOperationalException
     {
     {
         checkStatus();
         checkStatus();
 
 
         try {
         try {
-            final TokenPayload storedToken = tokenMachine.retrieveToken(tokenKey);
+            final TokenPayload storedToken = tokenMachine.retrieveToken(tokenKey, sessionLabel);
             if (storedToken != null) {
             if (storedToken != null) {
 
 
                 if (testIfTokenIsExpired(storedToken)) {
                 if (testIfTokenIsExpired(storedToken)) {
@@ -284,7 +284,7 @@ public class TokenService implements PwmService {
                 }
                 }
 
 
                 if (testIfTokenIsPurgable(storedToken)) {
                 if (testIfTokenIsPurgable(storedToken)) {
-                    tokenMachine.removeToken(tokenKey);
+                    tokenMachine.removeToken(tokenKey, sessionLabel);
                 }
                 }
 
 
                 return storedToken;
                 return storedToken;
@@ -345,12 +345,12 @@ public class TokenService implements PwmService {
         if (theToken == null) {
         if (theToken == null) {
             return false;
             return false;
         }
         }
-        final Date issueDate = theToken.getDate();
+        final Instant issueDate = theToken.getDate();
         if (issueDate == null) {
         if (issueDate == null) {
             LOGGER.error("retrieved token has no issueDate, marking as expired: " + JsonUtil.serialize(theToken));
             LOGGER.error("retrieved token has no issueDate, marking as expired: " + JsonUtil.serialize(theToken));
             return true;
             return true;
         }
         }
-        final TimeDuration duration = new TimeDuration(issueDate,new Date());
+        final TimeDuration duration = TimeDuration.fromCurrent(issueDate);
         return duration.isLongerThan(maxTokenAgeMS);
         return duration.isLongerThan(maxTokenAgeMS);
     }
     }
 
 
@@ -358,12 +358,12 @@ public class TokenService implements PwmService {
         if (theToken == null) {
         if (theToken == null) {
             return false;
             return false;
         }
         }
-        final Date issueDate = theToken.getDate();
+        final Instant issueDate = theToken.getDate();
         if (issueDate == null) {
         if (issueDate == null) {
             LOGGER.error("retrieved token has no issueDate, marking as purgable: " + JsonUtil.serialize(theToken));
             LOGGER.error("retrieved token has no issueDate, marking as purgable: " + JsonUtil.serialize(theToken));
             return true;
             return true;
         }
         }
-        final TimeDuration duration = new TimeDuration(issueDate,new Date());
+        final TimeDuration duration = TimeDuration.fromCurrent(issueDate);
         return duration.isLongerThan(maxTokenPurgeAgeMS);
         return duration.isLongerThan(maxTokenPurgeAgeMS);
     }
     }
 
 
@@ -375,10 +375,10 @@ public class TokenService implements PwmService {
         int cleanedTokens = 0;
         int cleanedTokens = 0;
         final List<String> tempKeyList = new ArrayList<>();
         final List<String> tempKeyList = new ArrayList<>();
         final int purgeBatchSize = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.TOKEN_PURGE_BATCH_SIZE));
         final int purgeBatchSize = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.TOKEN_PURGE_BATCH_SIZE));
-        tempKeyList.addAll(discoverPurgeableTokenKeys(purgeBatchSize));
+        tempKeyList.addAll(discoverPurgeableTokenKeys(purgeBatchSize, PwmConstants.TOKEN_SESSION_LABEL));
         while (status() == STATUS.OPEN && !tempKeyList.isEmpty()) {
         while (status() == STATUS.OPEN && !tempKeyList.isEmpty()) {
             for (final String loopKey : tempKeyList) {
             for (final String loopKey : tempKeyList) {
-                tokenMachine.removeToken(loopKey);
+                tokenMachine.removeToken(loopKey, PwmConstants.HEALTH_SESSION_LABEL);
             }
             }
             cleanedTokens = cleanedTokens + tempKeyList.size();
             cleanedTokens = cleanedTokens + tempKeyList.size();
             tempKeyList.clear();
             tempKeyList.clear();
@@ -388,7 +388,7 @@ public class TokenService implements PwmService {
         }
         }
     }
     }
 
 
-    private List<String> discoverPurgeableTokenKeys(final int maxCount)
+    private List<String> discoverPurgeableTokenKeys(final int maxCount, final SessionLabel sessionLabel)
             throws PwmUnrecoverableException, PwmOperationalException
             throws PwmUnrecoverableException, PwmOperationalException
     {
     {
         final List<String> returnList = new ArrayList<>();
         final List<String> returnList = new ArrayList<>();
@@ -399,7 +399,7 @@ public class TokenService implements PwmService {
 
 
             while (status() == STATUS.OPEN && returnList.size() < maxCount && keyIterator.hasNext()) {
             while (status() == STATUS.OPEN && returnList.size() < maxCount && keyIterator.hasNext()) {
                 final String loopKey = keyIterator.next();
                 final String loopKey = keyIterator.next();
-                final TokenPayload loopInfo = tokenMachine.retrieveToken(loopKey);
+                final TokenPayload loopInfo = tokenMachine.retrieveToken(loopKey, sessionLabel);
                 if (loopInfo != null) {
                 if (loopInfo != null) {
                     if (testIfTokenIsPurgable(loopInfo)) {
                     if (testIfTokenIsPurgable(loopInfo)) {
                         returnList.add(loopKey);
                         returnList.add(loopKey);
@@ -469,7 +469,7 @@ public class TokenService implements PwmService {
         while (tokenKey == null && attempts < maxUniqueCreateAttempts) {
         while (tokenKey == null && attempts < maxUniqueCreateAttempts) {
             tokenKey = makeRandomCode(configuration);
             tokenKey = makeRandomCode(configuration);
             LOGGER.trace(sessionLabel, "generated new token random code, checking for uniqueness");
             LOGGER.trace(sessionLabel, "generated new token random code, checking for uniqueness");
-            if (machine.retrieveToken(tokenKey) != null) {
+            if (machine.retrieveToken(tokenKey, sessionLabel) != null) {
                 tokenKey = null;
                 tokenKey = null;
             }
             }
             attempts++;
             attempts++;
@@ -483,8 +483,9 @@ public class TokenService implements PwmService {
         return tokenKey;
         return tokenKey;
     }
     }
 
 
-    static String makeTokenHash(final String tokenKey) throws PwmUnrecoverableException {
-        return SecureEngine.md5sum(tokenKey) + "-hash";
+     String makeTokenHash(final String tokenKey) throws PwmUnrecoverableException {
+        final SecureService secureService = pwmApplication.getSecureService();
+        return secureService.hash(tokenKey) + "-hash";
     }
     }
 
 
     private static boolean tokensAreUsedInConfig(final Configuration configuration) {
     private static boolean tokensAreUsedInConfig(final Configuration configuration) {
@@ -594,7 +595,7 @@ public class TokenService implements PwmService {
     {
     {
         final TokenPayload tokenPayload;
         final TokenPayload tokenPayload;
         try {
         try {
-            tokenPayload = pwmApplication.getTokenService().retrieveTokenData(userEnteredCode);
+            tokenPayload = pwmApplication.getTokenService().retrieveTokenData(userEnteredCode, pwmSession.getLabel());
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             final String errorMsg = "unexpected error attempting to read token from storage: " + e.getErrorInformation().toDebugStr();
             final String errorMsg = "unexpected error attempting to read token from storage: " + e.getErrorInformation().toDebugStr();
             throw new PwmOperationalException(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
             throw new PwmOperationalException(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);

+ 13 - 6
src/main/java/password/pwm/util/cli/commands/ExportResponsesCommand.java

@@ -28,7 +28,8 @@ 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.ldap.UserSearchEngine;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.ldap.search.UserSearchEngine;
 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.TimeDuration;
 import password.pwm.util.java.TimeDuration;
@@ -54,14 +55,20 @@ public class ExportResponsesCommand extends AbstractCliCommand {
         JavaHelper.pause(2000);
         JavaHelper.pause(2000);
 
 
         final long startTime = System.currentTimeMillis();
         final long startTime = System.currentTimeMillis();
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, SessionLabel.SYSTEM_LABEL);
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setEnableValueEscaping(false);
-        searchConfiguration.setUsername("*");
+        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+        final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                .enableValueEscaping(false)
+                .username("*")
+                .build();
 
 
         final String systemRecordDelimiter = System.getProperty("line.separator");
         final String systemRecordDelimiter = System.getProperty("line.separator");
         final Writer writer = new BufferedWriter(new PrintWriter(outputFile, PwmConstants.DEFAULT_CHARSET.toString()));
         final Writer writer = new BufferedWriter(new PrintWriter(outputFile, PwmConstants.DEFAULT_CHARSET.toString()));
-        final Map<UserIdentity,Map<String,String>> results = userSearchEngine.performMultiUserSearch(searchConfiguration, Integer.MAX_VALUE, Collections.<String>emptyList());
+        final Map<UserIdentity,Map<String,String>> results = userSearchEngine.performMultiUserSearch(
+                searchConfiguration,
+                Integer.MAX_VALUE,
+                Collections.emptyList(),
+                SessionLabel.SYSTEM_LABEL
+        );
         out("searching " + results.size() + " users for stored responses to write to " + outputFile.getAbsolutePath() + "....");
         out("searching " + results.size() + " users for stored responses to write to " + outputFile.getAbsolutePath() + "....");
         int counter = 0;
         int counter = 0;
         for (final UserIdentity identity : results.keySet()) {
         for (final UserIdentity identity : results.keySet()) {

+ 14 - 11
src/main/java/password/pwm/util/cli/commands/ResponseStatsCommand.java

@@ -29,12 +29,14 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.bean.ResponseInfoBean;
 import password.pwm.bean.ResponseInfoBean;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
@@ -148,20 +150,21 @@ public class ResponseStatsCommand extends AbstractCliCommand {
         final List<UserIdentity> returnList = new ArrayList<>();
         final List<UserIdentity> returnList = new ArrayList<>();
 
 
         for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) {
         for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication,null);
-            final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-            searchConfiguration.setEnableValueEscaping(false);
-            searchConfiguration.setSearchTimeout(Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT)));
-
-            searchConfiguration.setUsername("*");
-            searchConfiguration.setEnableValueEscaping(false);
-            searchConfiguration.setFilter(ldapProfile.readSettingAsString(PwmSetting.LDAP_USERNAME_SEARCH_FILTER));
-            searchConfiguration.setLdapProfile(ldapProfile.getIdentifier());
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                    .enableValueEscaping(false)
+                    .searchTimeout(Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT)))
+                    .username("*")
+                    .enableValueEscaping(false)
+                    .filter(ldapProfile.readSettingAsString(PwmSetting.LDAP_USERNAME_SEARCH_FILTER))
+                    .ldapProfile(ldapProfile.getIdentifier())
+                    .build();
 
 
             final Map<UserIdentity,Map<String,String>> searchResults = userSearchEngine.performMultiUserSearch(
             final Map<UserIdentity,Map<String,String>> searchResults = userSearchEngine.performMultiUserSearch(
                     searchConfiguration,
                     searchConfiguration,
                     Integer.MAX_VALUE,
                     Integer.MAX_VALUE,
-                    Collections.<String>emptyList()
+                    Collections.emptyList(),
+                    SessionLabel.SYSTEM_LABEL
             );
             );
             returnList.addAll(searchResults.keySet());
             returnList.addAll(searchResults.keySet());
 
 

+ 2 - 1
src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java

@@ -23,6 +23,7 @@
 package password.pwm.util.cli.commands;
 package password.pwm.util.cli.commands;
 
 
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenService;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
@@ -43,7 +44,7 @@ public class TokenInfoCommand extends AbstractCliCommand {
         TokenPayload tokenPayload = null;
         TokenPayload tokenPayload = null;
         Exception lookupError = null;
         Exception lookupError = null;
         try {
         try {
-            tokenPayload = tokenService.retrieveTokenData(tokenKey);
+            tokenPayload = tokenService.retrieveTokenData(tokenKey, PwmConstants.TOKEN_SESSION_LABEL);
         } catch (Exception e) {
         } catch (Exception e) {
             lookupError = e;
             lookupError = e;
         }
         }

+ 35 - 12
src/main/java/password/pwm/util/operations/PasswordUtility.java

@@ -38,6 +38,7 @@ import com.novell.ldapchai.util.ChaiUtility;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.bean.PasswordStatus;
@@ -98,6 +99,7 @@ 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;
+import java.util.concurrent.TimeUnit;
 
 
 /**
 /**
  * @author Jason D. Rivard
  * @author Jason D. Rivard
@@ -1101,8 +1103,8 @@ public class PasswordUtility {
                 if (oracleDS_PrePasswordAllowChangeTime != null && !oracleDS_PrePasswordAllowChangeTime.isEmpty()) {
                 if (oracleDS_PrePasswordAllowChangeTime != null && !oracleDS_PrePasswordAllowChangeTime.isEmpty()) {
                     final Date date = OracleDSEntries.convertZuluToDate(oracleDS_PrePasswordAllowChangeTime);
                     final Date date = OracleDSEntries.convertZuluToDate(oracleDS_PrePasswordAllowChangeTime);
                     if (new Date().before(date)) {
                     if (new Date().before(date)) {
-                        LOGGER.debug("discovered oracleds allowed change time is set to: " + JavaHelper.toIsoDate(date) + ", won't permit password change");
-                        final String errorMsg = "change not permitted until " + JavaHelper.toIsoDate(date);
+                        LOGGER.debug("discovered oracleds allowed change time is set to: " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(date) + ", won't permit password change");
+                        final String errorMsg = "change not permitted until " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(date);
                         final ErrorInformation errorInformation = new ErrorInformation(PwmError.PASSWORD_TOO_SOON, errorMsg);
                         final ErrorInformation errorInformation = new ErrorInformation(PwmError.PASSWORD_TOO_SOON, errorMsg);
                         throw new PwmUnrecoverableException(errorInformation);
                         throw new PwmUnrecoverableException(errorInformation);
                     }
                     }
@@ -1113,20 +1115,36 @@ public class PasswordUtility {
             LOGGER.debug(sessionLabel, "unexpected error reading OracleDS password allow modification time: " + e.getMessage());
             LOGGER.debug(sessionLabel, "unexpected error reading OracleDS password allow modification time: " + e.getMessage());
         }
         }
 
 
+        final TimeDuration minimumLifetime;
+        {
+            final int minimumLifetimeSeconds = passwordPolicy.getRuleHelper().readIntValue(PwmPasswordRule.MinimumLifetime);
+            if (minimumLifetimeSeconds < 1) {
+                return;
+            }
 
 
-        final int minimumLifetime = passwordPolicy.getRuleHelper().readIntValue(PwmPasswordRule.MinimumLifetime);
-        if (minimumLifetime < 1) {
-            return;
+            if (lastModified == null) {
+                LOGGER.debug(sessionLabel, "skipping minimum lifetime check, password last set time is unknown");
+                return;
+            }
+
+            minimumLifetime = new TimeDuration(minimumLifetimeSeconds, TimeUnit.SECONDS);
         }
         }
 
 
-        if (lastModified == null || lastModified.isAfter(Instant.now())) {
-            LOGGER.debug(sessionLabel, "skipping minimum lifetime check, password last set time is unknown");
+        final TimeDuration passwordAge = TimeDuration.fromCurrent(lastModified);
+        LOGGER.trace(sessionLabel, "beginning check for minimum lifetime, lastModified="
+                + PwmConstants.DEFAULT_DATETIME_FORMAT.format(lastModified)
+                + ", minimumLifetimeSeconds=" + minimumLifetime.asCompactString()
+                + ", passwordAge=" + passwordAge.asCompactString());
+
+
+        if (lastModified.isAfter(Instant.now())) {
+            LOGGER.debug(sessionLabel, "skipping minimum lifetime check, password lastModified time is in the future");
             return;
             return;
         }
         }
 
 
-        final TimeDuration passwordAge = TimeDuration.fromCurrent(lastModified);
-        final boolean passwordTooSoon = passwordAge.getTotalSeconds() < minimumLifetime;
+        final boolean passwordTooSoon = passwordAge.isShorterThan(minimumLifetime);
         if (!passwordTooSoon) {
         if (!passwordTooSoon) {
+            LOGGER.trace(sessionLabel, "minimum lifetime check passed, password age ");
             return;
             return;
         }
         }
 
 
@@ -1135,10 +1153,15 @@ public class PasswordUtility {
             return;
             return;
         }
         }
 
 
-        final Date allowedChangeDate = new Date(System.currentTimeMillis() + (minimumLifetime * 1000));
-        final String errorMsg = "last password change is too recent, password cannot be changed until after " + JavaHelper.toIsoDate(allowedChangeDate);
+        final Instant allowedChangeDate = Instant.ofEpochMilli(lastModified.toEpochMilli() + minimumLifetime.getTotalMilliseconds());
+        final String errorMsg = "last password change was at "
+                + PwmConstants.DEFAULT_DATETIME_FORMAT.format(lastModified)
+                + " and is too recent (" + passwordAge.asCompactString()
+                + " ago), password cannot be changed within minimum lifetime of "
+                + minimumLifetime.asCompactString()
+                + ", next eligible time to change is after " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(allowedChangeDate);
+
         final ErrorInformation errorInformation = new ErrorInformation(PwmError.PASSWORD_TOO_SOON,errorMsg);
         final ErrorInformation errorInformation = new ErrorInformation(PwmError.PASSWORD_TOO_SOON,errorMsg);
         throw new PwmOperationalException(errorInformation);
         throw new PwmOperationalException(errorInformation);
     }
     }
-
 }
 }

+ 0 - 10
src/main/java/password/pwm/util/secure/SecureEngine.java

@@ -221,16 +221,6 @@ public class SecureEngine {
         }
         }
     }
     }
 
 
-    public static String md5sum(final String input)
-            throws PwmUnrecoverableException {
-        return hash(input, PwmHashAlgorithm.MD5);
-    }
-
-    public static String md5sum(final InputStream is)
-            throws PwmUnrecoverableException {
-        return hash(is, PwmHashAlgorithm.MD5);
-    }
-
     public static String hash(
     public static String hash(
             final byte[] input,
             final byte[] input,
             final PwmHashAlgorithm algorithm
             final PwmHashAlgorithm algorithm

+ 3 - 3
src/main/java/password/pwm/ws/server/RestServerHelper.java

@@ -38,7 +38,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.http.filter.AuthenticationFilter;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
@@ -195,8 +195,8 @@ public abstract class RestServerHelper {
 
 
 
 
         try {
         try {
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, pwmSession.getLabel());
-            return userSearchEngine.resolveUsername(username, null, null);
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            return userSearchEngine.resolveUsername(username, null, null, pwmSession.getLabel());
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             throw new PwmUnrecoverableException(e.getErrorInformation());
             throw new PwmUnrecoverableException(e.getErrorInformation());
         } catch (ChaiUnavailableException e) {
         } catch (ChaiUnavailableException e) {

+ 3 - 3
src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java

@@ -32,7 +32,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.util.operations.OtpService;
 import password.pwm.util.operations.OtpService;
 import password.pwm.util.operations.otp.OTPUserRecord;
 import password.pwm.util.operations.otp.OTPUserRecord;
 import password.pwm.ws.server.RestRequestBean;
 import password.pwm.ws.server.RestRequestBean;
@@ -83,11 +83,11 @@ public class RestVerifyOtpServer extends AbstractRestServer {
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"actor does not have required permission"));
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"actor does not have required permission"));
             }
             }
 
 
-            final UserSearchEngine userSearchEngine = new UserSearchEngine(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession().getLabel());
+            final UserSearchEngine userSearchEngine = restRequestBean.getPwmApplication().getUserSearchEngine();
             UserIdentity userIdentity = restRequestBean.getUserIdentity();
             UserIdentity userIdentity = restRequestBean.getUserIdentity();
             if (userIdentity == null) {
             if (userIdentity == null) {
                 final ChaiUser chaiUser = restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication());
                 final ChaiUser chaiUser = restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication());
-                userIdentity = userSearchEngine.resolveUsername(chaiUser.readUsername(), null, null);
+                userIdentity = userSearchEngine.resolveUsername(chaiUser.readUsername(), null, null, restRequestBean.getPwmSession().getLabel());
             }
             }
 
 
             final OtpService otpService = restRequestBean.getPwmApplication().getOtpService();
             final OtpService otpService = restRequestBean.getPwmApplication().getOtpService();

+ 3 - 0
src/main/resources/password/pwm/AppProperty.properties

@@ -151,6 +151,9 @@ ldap.guid.pattern=@UUID@
 ldap.browser.maxEntries=1000
 ldap.browser.maxEntries=1000
 ldap.search.paging.enable=auto
 ldap.search.paging.enable=auto
 ldap.search.paging.size=500
 ldap.search.paging.size=500
+ldap.search.parallel.enable=true
+ldap.search.parallel.factor=5
+ldap.search.parallel.threadMax=50
 localdb.aggressiveCompact.enabled=false
 localdb.aggressiveCompact.enabled=false
 localdb.implementation=password.pwm.util.localdb.Xodus_LocalDB
 localdb.implementation=password.pwm.util.localdb.Xodus_LocalDB
 localdb.initParameters=00
 localdb.initParameters=00

+ 1 - 1
src/main/webapp/WEB-INF/jsp/admin-tokenlookup.jsp

@@ -69,7 +69,7 @@
                 boolean tokenExpired = false;
                 boolean tokenExpired = false;
                 String lookupError = null;
                 String lookupError = null;
                 try {
                 try {
-                    tokenPayload = tokenlookup_pwmRequest.getPwmApplication().getTokenService().retrieveTokenData(tokenKey);
+                    tokenPayload = tokenlookup_pwmRequest.getPwmApplication().getTokenService().retrieveTokenData(tokenKey, tokenlookup_pwmRequest.getSessionLabel());
                 } catch (PwmOperationalException e) {
                 } catch (PwmOperationalException e) {
                     tokenExpired= e.getError() == PwmError.ERROR_TOKEN_EXPIRED;
                     tokenExpired= e.getError() == PwmError.ERROR_TOKEN_EXPIRED;
                     lookupError = e.getMessage();
                     lookupError = e.getMessage();

+ 3 - 3
src/main/webapp/public/resources/js/newuser.js

@@ -144,8 +144,8 @@ PWM_NEWUSER.markStrength=function(strength) { //strength meter
 
 
 PWM_NEWUSER.refreshCreateStatus=function(refreshInterval) {
 PWM_NEWUSER.refreshCreateStatus=function(refreshInterval) {
     require(["dojo","dijit/registry"],function(dojo,registry){
     require(["dojo","dijit/registry"],function(dojo,registry){
-        var checkStatusUrl = PWM_MAIN.addParamToUrl(window.location.href,"processAction","checkProgress");
-        var completedUrl = PWM_MAIN.addParamToUrl(window.location.href,"processAction","complete");
+        var checkStatusUrl = PWM_MAIN.addParamToUrl(window.location.pathname,"processAction","checkProgress");
+        var completedUrl = PWM_MAIN.addParamToUrl(window.location.pathname,"processAction","complete");
         var loadFunction = function(data) {
         var loadFunction = function(data) {
             var supportsProgress = (document.createElement('progress').max !== undefined);
             var supportsProgress = (document.createElement('progress').max !== undefined);
             if (supportsProgress) {
             if (supportsProgress) {
@@ -168,4 +168,4 @@ PWM_NEWUSER.refreshCreateStatus=function(refreshInterval) {
         };
         };
         PWM_MAIN.ajaxRequest(checkStatusUrl, loadFunction, {method:'GET'});
         PWM_MAIN.ajaxRequest(checkStatusUrl, loadFunction, {method:'GET'});
     });
     });
-}
+};

Файловите разлики са ограничени, защото са твърде много
+ 0 - 2875
supplemental/PWMAdministrationGuide.pdf


+ 0 - 649
supplemental/history.txt

@@ -1,649 +0,0 @@
-PWM History/Changelog
-http://code.google.com/p/pwm
-
-[------legend----------------]
-[ + Added feature            ]
-[ * Improved/changed feature ]
-[ - Bug fixed/refactoring    ]
-[ ! security bug fix         ]
-[ ~ partial implementation   ]
-[----------------------------]
-
-[this file is no longer maintained]
-
-v1.8.0
----
-(Changes since v1.7.0 build 1228)
-+ Functionality/option to store intruder table in Database
-+ Functionality/option to store user event history in Database
-+ Intruder tracking for form input values and token send destinations
-+ Show/Hide password fields button replaced with eye hide/close icon on all pages
-+ "Ajaxified" all admin section tables
-+ Added support for BCRYPT, SCRYPT, SHA-256, SHA-512, PBKDF2 and MD5 stored responses
-+ Option for helpdesk functionality to always use application proxy user
-+ Send email on intruder detection to user or admin
-+ Send email on system or user audit events
-+ Support for multiple ldap profiles across all functionality
-+ Added system audit module and events for startup, shutdown, configChange and fatal events
-+ Added user audit events for helpdesk actions, token issued and token consumed events
-+ Replaced most constant "hardcoded" values in application as configurable AppProperty values.
-+ Updated change password UI with icon-based password guide and random password options.
-+ Improved show/hide password option with per-password-field
-+ Added ability to set email TO address per email, using macros, allowing for flexible per-email email user attribute configuration
-+ Added setting for smtp port
-
-- Removed "ADDB" template, replaced with extra Guide option to select responses
-- Refactored and formalized rest security model
-- fixed issue with slash character in DNs (issue #416)
-- cancel button sometimes triggered with enter key on forms (issue #507)
-- Separated ConfigEditor from ConfigManager implementation and user interface
-- Improved several ldap error handling scenarios and error message reporting
-- Refactored configuration module to support profile categories
-- Enabled localDB compression to support lengthy emails and other localdb-size constrained features
-
-! fixed issue where SHA1 responses where hashed only once regardless of iteration count setting
-
-v1.7.1 (February 18, 2014) build 1231
----
-* Updated Hungarian language (issue #469) thanks to torlasz
-+ Added Greek language (issue #527) thanks to smaistros
-- Updated ldapChai library for NetIQ eDirectory 8.8.8
-- SMS queue issue due to incorrect setting type (issue #523, #529) thanks to nils.rekow
-* Updated seedlist.zip (word list), removed offensive words
-* Minor theme changes
-- eDirectory schema LDIF updated (issue #472), thanks to tomgreene
-
-v1.7.0 (September 8, 2013) build 1228
----
-+ Option to disable idle countdown timer added (issue #404)
-+ REST based web services (including documentation and examples) for
-   reading/writing/deleting challenge responses, setting password, reading user status, application health and other functions
-+ Added capability to call external rest web services after most operations.
-+ Improvements in mobile/tablet display support
-+ Challenges refreshed into chosen language when locale is changed on setup challenges page (issue #390)
-+ Norwegian language (Nynorsk) localization (issue #376) thanks hagazaz!
-+ Several fixes for macros to work in more settings (issue #370, #363, #352, others)
-+ Install Guide "wizzard" for new installation
-+ Internal LDAP certificate auto-import, management and validator.
-+ Support for "force change password on next login" with AD
-+ Support for authenticating expired users with AD
-+ Support for reading AD "fine-grained" password policies
-+ Http Header "SSO" authentication option
-+ Hungarian language (magyar) localization (issue #441) thanks torlasz!
-
-! protection against session fixation attacks (issue #391)
-
-
-v1.6.5 (not published)
----
-+ Forgotten password search form now has multiple fields available (issue #207)
-+ Multiple ldap root contexts are now possible for login, helpdesk and other functions (issue #227)
-+ HTTP BasicAuth header default decoding is now "UTF-8"; exposed decoding charset in PwmConstants.properties (issue #34)
-+ Added forgotten password action option to send new password via email (issue #143);
-+ Refactored and improved UI for managing "form" configuration settings.
-+ regex validation of form field values and custom error messages (issue #260)
-+ html5 placeholder form values
-+ most html5 type input form types (url, date, datetime, number, tel, time, week, month)
-+ custom javascript support per form field
-+ select type forms with configurable option values (issue #273)
-+ all pages and content now gzip compressed when supported by browser
-+ new user form password strength meter and validation check
-+ helpdesk user search results
-+ sortable/column select grid for tabled data and user search results throughout pwm
-+ search filter for helpdesk user lookup (issue #228)
-+ added "please select" message during setup response randoms (issue #306)
-+ added helpdesk viewable challenge/responses (issue #229)
-
-
-- Shortcut order is sometimes randomized (issue #277)
-- Null pointer (and other issues) when using ldap token storage method (issue #281)
-- Allow HTML in error messages(
-
-
-v1.6.4 (August 23, 2012) build 1185
----
-+ Norwegian language support; thanks Izaz! (issue #261)
-+ Configuration option to enable/disable eDirectory NMAS password reading
-
-- Workaround for Apache Tomcat v7.0.29 deployment bug (issue #244)
-
-! increased hash loop count for credentials stored in shared history
-
-
-v1.6.3 (August 6, 2012) build 1181
----
-+ Improvements in monitoring and gauge presentation on PWM Admin and health pages.
-+ Backup backup files.  Saving config now keeps 5 previous PwmConfiguration.xml files as backups.
-+ Added setting for show logout button.
-+ Added export of statistics via CSV from PWM Admin GUI and PwmCommand utility.
-
-* Changed packaging structure to place pwm binaries in pwm-servlet.jar instead of WEB-INF/classes
-* Refactored response save configuration settings to allow more flexibility and clarity
-
-- Change language on UpdateProfile no longer clears form values (issue #243)
-- Show/Hide Response button now toggles attribute values as well as responses (issue #246)
-- Forgotten Password not working when user attributes required but no responses are required (issue #245)
-- Password History not working (duplicate local variable error) (issue #240)
-
-! Intruder lockout now backed by PwmDB instead of memory to prevent OOM under DDOS attack.
-
-
-v1.6.2 (July 14, 2012) build 1175
----
-+ Added token (email and sms) support for user activation
-+ Added new user agreement support for user activation
-+ Added crypto and ldap options for tokens
-+ ConfigManager performance improvements for display value editing
-+ Helpdesk random passsword generation and clear responses options
-+ Helpdesk session timeout configuration option
-+ "Force Profile Update" option added to route users through the Profile Update module
-+ Added partial localizations for Thai, Korean, Japanese and Chinese (issue #184)
-+ Support for token expiration to prevent re-use of tokens
-+ Added option for forgotten password query match (issue #180)
-+ Option for confirmation of Profile Update values
-+ Added several rest based web services
-+ Security option to disable external clients from using web services; default is disabled
-+ Improved token security by hashing stored keys and encrypting payloads
-+ Disallow (configurable) challenge text from being used as response text
-+ Added password strength meter check to helpdesk change password
-+ Added language updates for most PWM localizations (issue #232)
-
-* Refactored all JSP pages to be HTML5 compliant (html tags and Doctypes)
-* Improved page load performance for most pages
-* Config menu re-ordering and text improvements
-* Locale list is now configurable (issue #181)
-* Changed chai ResponseSet save format to exclude carriage returns in the ldap value (issue #197)
-
-- Changed servlet version to v2.5, v2.5 container (tomcat 6) and java 6 is now minimum requirement
-- Resolved issue where newuser agreement screen could be bypassed (issue #165)
-- Fixed url path issue (/private url required for public pages) (issue #154)
-- Issue where activate user wasn't performing LDAP compare on correct form values (issue #153)
-- Update Query Match filter not being applied (issue #178)
-- Email address destination token not being inserted (issue #177)
-- Double nested form tags on ActiveUser data entry page (issue #175)
-- PwmMacro engine broken when multiple macros used in a single string (issue #200)
-- Incorrect email encoding for non-latin character sets (issue #190)
-- Novell UserApp responses rejected when using random responses (issue #188)
-- Fixed issue with double-URLDecoding for logoutURL and forwardURL (issue #216)
-- Attribute names with hyphen ("-") not working (issue #221)
-
-
-v1.6.1 (January 16, 2012) build 1123
----
-+ Added "application leave detection" to detect if user leaves PWM site.
-+ Added settings to manage custom CSS & JavaScript from PwmApplication.xml
-+ Added option to override Case Sensitivity detection (issue #127)
-+ Option to skip success messages
-+ Request Sequence detection (duplicate form submit detection) added with option to disable.
-+ Added feature/option to trip a "bad password" auth to ldap in case of forgotten password failure.
-+ Password rule text configuration option to override auto-generated rule text.
-+ Added finnish locale, thanks tami.rauhala!
-+ Introduced PWM Macros for certain configuration settings and display fields.
-+ Version checking and anonymous statistics publishing (configurable)
-
-* Improved helpdesk module and removed administrative "UserInformation" module due to redundancy
-* Idle authentication timeout now configurable, separate from http session timeout
-* Implemented Jersey framework for rest web services
-! properly escape search strings during ldap username searches
-
-- Issue where change password page refresh would not properly show error (issue #131)
-- PwmCommand line broken with "cannot allow mutator operations" (issue #134)
-- Added multi-line option as default for SMS multi-line response (issue #132)
-- Broken responseSet.meetsRequirements() for userapp responses with user-defined questions (issue #140)
-- NullPointerException on password requirements tag when a minimum-interval is set in policy (issue #137)
-- SMTP Password encrypted in configuration (issue #144) 
-    NOTE: when upgrading from 1.6.0, the SMTP Password needs to be re-entered
-
-
-v1.6.0 (October 17, 2011) build 1096
----
-+ Added turkish locale, thanks erdem.bayer! (issue #86)
-+ Added slovak locale, thanks svacko! (issue #87)
-+ Added hebrew locale, thanks dordorqwerty! (issue #92)
-+ Added helpdesk module for password resets (issue #99)
-+ Support for customizable CSS themes, and several default themes included (issue #103)
-+ Support for SMTP authentication (issue #104)
-+ Overhaul of the NewUser registration module,
-   + Password field is now on initial new user password field
-   + Form UI supports more fields in less space
-   + Randomized DN generation
-   + While-you-type form validation
-   + Configurable password policy template user
-   + Configurable minimum wait time on new user creation
-+ Added stored token database (to PwmDB or RDBMS) for forgotten password and new user password
-+ Updated look & feel for form tables throughout the application
-+ UserReport module in /pwm/private/admin and from PwmCommand command line
-
-* Moved public menu options (ForgottenPassword, NewUser, Activate) to login page
-* Moved private menu options to /pwm/private url, and made menu visibility based on permission
-* Continued improvements in configmanager process
-
-- Fixed bug where unsupported browser locale results in blank page/null pointer (issue #83)
-- Fixed bug where non-english server locale results in configuration manager issues (issue #84)
-- Double-byte characters not stored properly in PwmConfiguration.xml (issue #100)
-- Issue where SMS Servlet Gateway URL couldn't be configured with a port number (issue #97)
-- Config file size limit of 50k characters increased to 10mb.
-- Current password required in some cases on forgotten password reset page. (issue #119)
-
-
-v1.5.5 (July 7, 2011) build 1056
----
-- Update profile error removing attribute on blank value (issue #74)
-- Responses required when not configured to be (issue #80)
-
-
-v1.5.4 (July 5, 2011) build 1054
----
-+ Storage of responses in RDBM database (useful for AD, or otherwise un-extensible ldap directories)
-+ Added guest account registration and management
-+ Added "selectable" type configuration syntax, and applied as appropriate
-+ Added SMS token integration to provide for text messaging (sms) of tokens during forgotten password
-+ Added Jasig CAS authentication server integration (http://www.jasig.org/cas) (issue #54)
-+ Improved new installation configuration experience
-* Can now use configuration gui to set all user-displayable text strings
-+ Configurable form "reset" button (issue #71)
-+ More control over pushing users to update profile (issue #72)
-+ Localization updates
-
-- Update Profile now correctly deletes values when blanked by user (issue #59)
-- Renamed Update Attributes servlet to UpdateProfile and changed checkAttributes parameter to checkProfile
-- General refactoring to improve error messages, reporting and handling
-
-~ Basic "PeopleSearch" module added
-
-! Security issue (issue #73)
-
-v1.5.3 (April 2, 2011) build 1026
----
-+ Added locale selector menu to footer (issue #31)
-+ Changed forgotten password sequence so email token is after response questions (issue #26)
-+ Added option to disable reverse dns resolution (in advanced) (issue #35)
-+ Added functionality for token-only, user attribute-only, or response-only forgotten password operation, or any combination of the three
-+ Added support for locally stored (PwmDB) responses
-+ Added command line interface (PwmCommand.bat) to manage locally stored responses
-+ Added forgotten username module
-
-* Added menu title for main index page (issue #30)
-* Added i18n and Dutch localizations for index page (issue #27)
-* Updated Dutch localizations (issue #32, #42, #47)
-* Improved AD complexity password checking (issue #34)
-* Improved health monitor checking and display screens
-
-- Fixed broken login contexts option on activation page (issue #26)
-- Fixed issue with cookieless (URL) sessions not working since v1.5.2
-- Fixed issue wih CommandServlet?processAction=checkExpire not working
-- Improved ConfigManager UI performance and layout, with simple/advanced mode
-- changed from simpleJson java library to google json (gson) library for flexibility and correctness
-
-! Added url escaping to user input reflected html form values to prevent certain types of XSS attacks
-
-
-v1.5.2 (October 22, 2010) build 996
----
-+ Changed main menu to hide un-enabled functions
-+ Added logout button to header when logged in
-+ Added "Password Change Message" configuration option
-+ Added java interfaces (and config settings) for ExternalChangeMethod, ExternalJudgeMethod and ExternalRuleMethod
-+ Strength meter on password page now has tooltip explaining the strength meter
-+ Added config options to show/hide strength meter, random generator
-+ Implemented method to retrieve password expiration time from DirectoryServer389 (chai)
-+ Added change password policy settings for min/max non-alphabetic characters
-+ Added "HealthMonitor" functionality to periodically test system health
-+ PWM now ignores Novell UP nspmComplexityRules that are customized (changed to other than default MS-AD 3of4 policy)
-+ Replaced "challenge.forceAllRandoms" setting with "challenge.minRandomsSetup" to allow more flexibility.
-+ Added "Show Password Guide" configuration and feature
-+ Added "Display Show/Hide Password Fields" configuration and feature
-+ Added "Show Cancel Button" configuration and feature
-+ Added "eDirectory-UserApp Forgotten Password SOAP API" configuration and feature
-+ Updated setup responses to dynamically prevent duplicate questions in simple mode
-+ Added reset to default value for settings in configuration editor
-
-* Modified active user module to write ldap attributes after the first password change subsequent to a successful activate user event. (issue #17)
-* Refactored logging module to more efficiently store log events in the pwmDB
-* Updated setup response screen ajax code to be list 'jittery' like change password screen
-
-- Updated reCaptcha urls and html to new google api 
-- Refactored ExternalPasswordMethod to ExternalChangeMethod java interface
-- Fixed issue with LoginContexts setting not being parsed properly
-- Fixed issue where ldap urls could not contain a hyphen
-- Fixed issue where older/corrupt user history attribute would cause a NullPointerException
-- Removed "dojo.css" include to prevent IE display issues (small fonts in IE)
-- Fixed issue where random generator would sometimes generate really long passwords (especially for complex policies)
-- Resolved issue where SharedHistory would not be purged correctly due to ClassCastException in PwmDBAdaptor
-- Fixed issue where clear button doesn't work on some pages in some browsers
-- Resolved issue where form buttons were overly wide in ie6/7 
-
-v1.5.1 (August 16, 2010) build 975
----
-+ Added idle timeout warning dialog
-+ Added settings to control Admin Alerts for startup, shutdown, config changes, intruder detection, fatal events and daily stats.
-+ Added setting to enable/disable session validation
-+ Updated random password generator to show multiple randomly generated passwords at once on change password screen
-
-* Improved statistics reliability and performance.
-
-- Fixed bug where forgotten password recovery would fail when no required attributes were configured
-- Fixed bug where forgotten password recovery would fail when DirectoryServer389 server was used
-- Fixed a bug where the configuration setting for multiple ldap login contexts could not be read properly
-
-
-v1.5.0 (July 6, 2010) build 959
----
-! Corrected a problem where XSS field inputs were not being properly validated and scrubbed
-! Added form "nonce" validations to prevent certain types of XSS attacks
-! Passwords stored in configuration file are now obfuscated (though config files still need to be protected securely)
-
-+ Added graphical feedback on password confirmation field
-+ Added web based configuration file editor (/pwm/config/ConfigManager) (issue #4)
-+ Switched configuration file format to PwmConfiguration.xml from pwmServlet.properties
-+ Basic support for 389 Directory Server (http://directory.fedoraproject.org/)
-+ Ability to send html email
-+ Added send email option for user activation
-+ Added smtp advanced properties configuration setting
-+ Added ldap advanced properties configuration setting
-+ Added send email token functionality during forgotten password reset
-+ Added config option for "agreement" text to be shown prior to password change.
-+ Added config option for ldap proxy connection idle timeout
-+ Added option to require current password on change password screen
-
-- Replaced vertical strength meter with horizontal strength meter with better CSS degradation
-- Fixed issue with caps lock warning on IE browsers (issue #3)
-- Added/Updated some of the french localization strings (issue #5)
-- Fixed issue with User Information crashing if NMAS is not being used (issue #6)
-- Fixed issue with Log4j (issue #7)
-- Refactored ajax calls to use dojo api, and also to resolve issue with incorrect encoding of i18n characters (issue #9)
-- Removed "aggressiveUrlParsing" setting, values for logoutURL/forwardURL now read url encoded values properly
-- Refactored idle counter javascript to be more efficient
-- Changed default location of pwmDB from META-INF to WEB-INF
-- Removed tabindex html attributes to allow proper tab behavior
-
-
-v1.4.3 (1/10/2010) build 922
----
-+ Improved handling of servlet container clustering and session pausing
-+ Added caps-lock detector during password entry
-+ Enhanced SetupResponses to show select lists when challenge.randomStyle=SETUP and there are no user defined random challenges
-+ Added admin user information debugging page to aid in troubleshooting for admins
-+ Added user user information debugging page to aid in troubleshooting for end users
-+ Added daily stats viewer and chart history in administrator status screen
-
-* Refined password strength meter to allow full strength to be reached, and to be more permissive
-* Tuned ajax communications to decrease request traffic.
-* Added "eventLog.localDbMaxAge" setting to control age of pwmDB event log storage
-* Added "ldapPromiscuousSSL" setting instead of using "autocert" param in the ldap server url configuration
-* Refactored HTML/CSS to improve appearance, correctness of both normal and mobile viewing
-
-- Fixed bug where "previously used" error incorrectly results in unknown error when using non-nmas password method (defect #51)
-- Removed setting "passwordSetMethod", for edirectory, this is replaced by "ldap.edirectory.enableNmas"
-- Refactored ajax channels to use JSON encoding
-- Fixed bug where WARN log status inaccurately reported no discovered nmas challenge set policy (defect #50)
-- Fixed bug where forgotten password doesn't work when there are no required challenges configured (defect #52)
-- Renamed "password.readEdirectoryPasswordPolicy" setting to "ldap.edirectory.readPasswordPolicies"
-- Added "ldap.edirectory.storeNmasResponses" setting, and removed nmas options from "challenge.storageMethod"
-- Replaced "authUsingBind" setting with "ldap.edirectory.alwaysUseProxy" setting
-- Fixed bug where newer Client32 clients forced users to configure responses after PWM saves responses using nmas (defect #53)
-- Refactored ant build.xml script to accommodate new layout used in google project svn
-- Moved XForwardedFor setting to pwmServlet.properties from web.xml as "useXForwardedForHeader" setting.
-- Fixed issue where random password generation sometimes generated out-of-policy passwords
-- Added "ldap.edirectory.readChallengeSets" and removed "challenge.policyMethod" setting
-- Added "ldap.edirectory.storeNmasResponses" setting, removing NMAS options from "challenge.storageMethod" setting
-
-
-
-v1.4.2 (7/23/2009) build 842
-------
-+ Added "usernameSearchFilter" config property to allow non "cn" based usernames throught pwm
-+ Added "challenge.requiredAttributes" config property to require ldap attribute values during forgotten password recovery
-+ Added "forceBasicAuth" web.xml parameter
-+ Added captcha functionality using reCaptcha with "recaptcha.privateKey" / "recaptcha.publicKey" settings.
-+ Added "activateUser.searchFilter" setting, and changed "activateUser.attributes" functionality to allow
-    arbitrary searching for users without having to know the username
-+ Added "aggressiveUrlParsing" web.xml parameter
-+ Added "password.ADComplexity" config property
-+ PWM now honors universal password policy "AD Complexity" configurations
-+ Added "challenge.forceSetup" config property
-+ Added "challenge.allowDuplicateResponses" config property
-+ Added "challenge.forgottenStyle" parameter to allow for mixing unlock/or password reset options during forgotten password use.
-+ Preliminary, undocumented, support for using Active Directory as the ldap directory
-+ Updated logging model to use local PWM database for persistance.  Log4j interface is still available to support external log systems.
-
-* Moved misleading and infrequently changed config property 'userNameAttribute' to web.xml parameter 'ldapNamingAttribute' (defect #36)
-* ChangePassword doesn't work when the "password.WordlistFile" config property is blank (defect #29)
-* ChangePassword screen only no longer shows expiration warning on new user creation, activation or forgotten password.
-
-! Corrected a scenario where multiple forgotten password attempts could exceed pwm user intruder limits over different http sessions
-! Change to prevent response values from being written to debug log.
-
-v1.4.1 (unreleased)
-
-v1.4.0 (10/4/2008) build 776  
-------
-+ Added "cookie-less" functionality and "allowUrlSessions" web.xml setting.  Help for restricted
-     browsers which disallow cookies.
-+ Added "expireWarnTime" setting and feature to pre-warn users of a soon to be expired pw
-
-* HTML look overhaul, now CSS based with graceful degradation for low-capability devices
-
-- Fixed a problem where email alerts are sent with a non-configurable from address (defect #27)
-- Problem where "challenge.caseInsensitive" setting was not honored (defect #28)
-- "Bad Session Proxy" error during forgotten password recovery (defect #26)
-- Placed a commented out text string on the change password jsp to show grace logins remaining (defect #23)
-
-v1.3.1 (unpublished)
-------
-+ Added "Shortcuts" servlet and "shortcut.query.[x]" settings
-* Added Shared Password History functionality and "password.sharedHistory.age" setting
-
-* Updated BerkelyDB version to v3.6.74 to correct a DB corruption bug
-* Removed duplicates from published wordlist files to speed up imports
-* Added a "Apache Derby" implementation of PwmDB
-
-- Fixed password change screen so the clear button resets the strength (defect #13)
-- Fixed password change screen so the clear button moves focus to the new
-    password field (defect #14)
-- Minor log file output enhancements
-- Fixed password change screen to show "too soon for password change" error correctly (defect #18)
-- Fixed password change screen to show "too soon for password change" requirement correctly (defect #18)
-
-v1.3.0 (1/14/2008)
-------
-+ PWM Responses are now case-insensitive (defect#1)
-+ can now read challenge set policy from Universal Password policy
-+ added "challenge.randomStyle" setting to change random behavior
-+ can set c/r min/max length in pwmServlet.properties local and i18n files
-+ localized nmas challenge policies are now supported
-+ PWM Admin status is now determined by ldap query string instead of configured uid/pwd
-+ rebuilt password wordlist checker, supports massive wordlists and doesn't
-    require gobs of memory (uses pluggable DB type, default is embedded berkelyDB),
-    tested with 20 million word dictionary.
-+ added animated gif to pwd sync wait page
-+ support for user-selectable random questions at population time
-+ added password.allowChange.queryMatch setting, to control which user populations
-     can use pwm for changing password.
-+ added UserDebugServlet for checking user password debug info
-+ all user-related log events now use syntax of '{1} event text [ip]' where {1} is a session
-    number, and [ip] is the user's IP/dns address.
-+ added a unique "instanceID" value to all headers, to track which server a user
-    is accessing when multiple servers exsist.
-+ updated ldapChai API to v0.4.1, moved all PWM c/r code into Chai.  Thus, there
-    is now a stand alone API available for manipulating PWM style c/r values in ldap.
-+ upgraded JavaMail API to fix some i18n issues with email.
-+ most URLs have been modified so "Servlet" isn't part of the name.  For example,
-    "ChangePasswordServlet" has been changed to "ChangePassword"
-+ added "strength" meter to password change screen
-
-* random password generation is wordlist based instead of purely random characters, also
-    one passsword is generated at a time instead of displaying a bunch to the user at once.
-* enhanced ajax password verification to improve reliability with different types of browsers
-* enhanced ajax password verification to clear "confirm password" field when "new password field" is modified.
-* added back capability to read NMAS challenge/response policies configured in eDirectory.
-* added nmas check for password history during ajax password verification
-* "debugMode" setting removed from pwmServlet.properties, replaced with log4jconfig.xml "trace" level.
-* XForwadedFor configuration now in web.xml, moved from pwmServlet.properties
-
-! SHA1 Responses are now stored with a random salt to prevent dictionary attacks against
-    response values in ldap
-! disallow "<.*script.*>" and other junk in input feilds to combat against xss attacks,
-    configurable in web.xml.
-! refactored many operations to use the user's ldap bind instead of the proxy user.
-
-
-v1.2.2 (6/20/2006) build 628
-------
-- Updated ldap api to prevent memory leaks
-- Fixed NPE while changing password when used with older eDirectory versions (pre 8.6)
-
-
-v1.2.1 (1/21/2006)
-------
-
-+ Optional NMAS functionality:
-    + Added PWMSHA1+NMAS option for challenge/response storage in nmas to challenge.storageMethod configuration.
-    + Added nmasChange option to passwordSetMethod
-    + Added nmas pre-change policy checker
-+ Encoded language files properly with native2ascii
-- Fixed several encoding issues, UTF-8 is now used for all web traffic
-- Fixed idletimer (missing javascript) method in v1.2.0 distribution
-- Updated to LDAP Chai v0.1.1 (failover fixes to prevent intruder lockout when using authUsingBind option)
-+ Added more configuration options
-+ Added buildNumber information to build process
-
-
-v1.2.0 (12/8/2005)
-------
-+ Integrated LDAP Chai v0.1.0 (Novell Forge LDAP library) (includes several fail-over and pooler fixes)
-- Codebase updated for Java 1.5 language specification
-+ added checkAll command to CommandServlet.
-+ password.MaximumOldPasswordChars option
-
-
-v1.1.4 (unpublished)
-------
-+ All user-viewable screens are now internationalized
-+ Added localizations for spanish, portugese, polish, italian, french, czech
-+ Added support for enforcing universal password password policy option during login
-+ Enhanced idletimer
-+ All HTML pages now use CSS properly, and all have the same borders and tables.
-
-
-v1.1.3 (9/30/2005)
-------
-- fixed thread locked wordList issues under heavy load
-* general enhancements for performance
-    - better ldap connection handling
-    - "proper" session timeout handling
-    - removed synchronization blocks for password checking
-    * vastly improved performance of while-you-type javascript password checker
-+ added Threads admin servlet to see the list of currently running JVM threads
-+ added Configuration admin servlet to view (partially) the running PWM config
-- made PWM more freindly to container restarts (memory leak issues)
-* remove "wordListCache" option from pwmConfig, wordLists are now always cached
-+ added thread list in the admin stats
-+ more stats: users created, users activated
-- bugfix: account sessions stats goes negative after restarting pwm container
-- bugfix: logging timestamps using minutes in place of month field
-+ added "passwordSetMethod" option pwmConfig
-~ refactored all logging code to make replacing/improving log4j easier
-+ removed scriptlets from most user-facing pages (replaced with taglibs)
-+ html/jsp cleanup.  Most pages pass w3c validation now
-+ added idle timeout countdown timer in status bar
-- fixed problem where smtp email errors would backlog in the queue forever
-+ added logout.jsp page (just for default, logout page should still be configured to something else)
-+ idle ldap connections are now closed quickly
-
-
-v1.1.2 (8/30/2005)
-------
-+ random passwords on changepassword.jsp are now cached in the session (performance boost)
-+ ip address intruder detection
-+ userDN intruder detection (seperate from edirectory intruder detection)
-- newuserservlet and general intruder code refactoring
-+ added newUser.writeAttributes option to write admin defined attributes during new user creation
-+ added show/hide button on change password screen (not available in IE due to IE bug)
-- per-keystroke javascript refactoring to improve reliability
-+ added admin-only section and configurable PWM admin username/password
-+ added intruder-lockout status screen
-+ added session monitor
-+ added web-based log monitor
-+ increased scalability of ldap connector
-+ added update attributes functionality (companion checkAttributes function to be added in future)
-+ newuser, activate user and update attribute forms now automatically generated.
-- loginContexts settings now are orderd properly
-
-
-v1.1.1 (8/5/2005)
-------
-- bugfix: "hint to long" error during setuphints
-! security: recover password would allow reset password for users with no responses set in some cases
-
-
-v1.1.0 (8/3/2005)
-------
-+ added support for reading universal password challenge/response sets from ldap
-+ added javascript client side caching for changepassword page
-+ added wordlist (dictionary) server-side caching
-+ added support for multiple hints
-+ added support for user defined hints
-+ added support for random hints
-+ hint answers are now stored using SHA1 hashes
-+ changed default color scheme to more 'novellish' color
-+ added per/user xml history log and viewing jsp
-+ new "activateuser" servlet replaces "validation" functionality in  "newuser" servlet.
-+ support for location (context) selection list on login and password recovery servlets for ldap trees with duplicate userIDs
-- additional bugfixes
-
-
-v1.0.7 (07/11/05)
-------
-- bugfix: tld.fmt caused jsp exceptions on tomcat 4.x
-- bugfix: null pointer when user's 'passwordMinimumLength' attr missing
-- bugfix: ldapchai: ldaps not working with > 1.4.1 jdk, also added more debug info to log
-
-
-v1.0.6 (07/06/05)
-------
-+ bugfix: Passwords with any of these chars: "*()?/" (excluding quotes) would not be able to auth using normal (non-bind) auth
-+ bugfix: no longer confused by aliases.  eDirectory user aliases are now completely ignored.
-+ added auto-suggest passwords on change password page.
-+ added ldap timeout setting
-
-v1.0.5 (unreleased)
-------
-+ added real time check-while-typing functionality for password validations
-+ renamed "nonAlphaNumeric" password rules to "special" to be in line with universal password policies
-+ added edirectory password policy reader, now will read per-user password policies from eDirectory (including universal password policies))
-+ added a pile of password rules to conform with universal password
-+ added regular expression checks for password rules
-+ new scheme for handling password recovery through ldap, should be more reliable
-
-v1.0.4 (06/13/05)
-------
-+ added externalPasswordMethods options
-+ modified pwmSchema.ldif
-+ On a clear day, you can refactor forever.....
-+ added passwordSyncMaxWaitTime options, PWM now waits for the password to be synchronized accross all known servers
-+ fixed bug where iChain SSO was dependent on ichain profile being configured using lowercase.
-+ enhanced iChain auth intergration
-
-
-v1.0.3 (05/19/2005)
-------
-+ fixed a bug where expireCheckDuringAuth didn't always work
-+ updated ant script and zip distribution so war file is buildable
-
-
-v1.0.2 (05/11/2005)
-------
-+ changed war packaging to remove compression to better support certain platforms (ie, NetWare)
-+ added "authUsingBind" configuration option
-+ changed default log4jconfig.xml to default logging to stdout
-+ added footer.jsp
-+ added auto-text for password rules on change password pages
-+ added "expireCheckDuringAuth" configuration option
-- removed unused "expirePauseTime" configuration option
-~ expiremental code for pre-expire password email notifications
-
-
-v1.0.1 (04/06/2005)
-------
-first public release of PWM.

+ 0 - 8
supplemental/readme.txt

@@ -1,8 +0,0 @@
-PWM Readme
-----------
-
-PWM is an open source Password Self Service application.   Instructions, downloads, and project information
-can be found at the PWM Project website:
-
-http://code.google.com/p/pwm
-

+ 0 - 89
supplemental/script/missing_translation.sh

@@ -1,89 +0,0 @@
-#!/bin/bash
-#
-# Password Management Servlets (PWM)
-# http://code.google.com/p/pwm/
-#
-# Copyright (c) 2011 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
-#
-
-function usage() {
-  echo "This script can be used on a modern unix like environment in order to"
-  echo "determine the missing strings in localisation property files."
-  echo
-  echo "Usage:"
-  echo "  $0 <path to tomcat>"
-  echo 
-  echo "<path to tomcat> is the path to the root of your tomcat instance."
-  echo
-  echo "A file will be created in the current directory for each language"
-  echo "/property file combination containing a listing of the variables"
-  echo "with a missing translation."
-  echo
-  echo "Example:"
-  echo "  $0 /usr/share/tomcat6"
-}
-
-
-if [ "$1" = "" ]; then
-  usage
-  exit 1
-fi
-
-
-FILEPATH="${1}/webapps/pwm/WEB-INF/classes/password/pwm/config"
-
-shopt -s nullglob
-
-for i in Display Message PwmError
-
-do
-  echo $i
-  if [ $i == "PwmError" ]; then
-    FILEPATH="${1}/webapps/pwm/WEB-INF/classes/password/pwm/error"
-  fi
-
-  for f in $FILEPATH/$i\_* 
-  do
-    echo Processing: $f
-
-    OUTPUTFILE="Missing_`basename $f`"
-	if [ -f "${OUTPUTFILE}" ]; then rm "${OUTPUTFILE}" ; fi
-
-    TMP="Display`date +%s`"
-    TFBASE="/tmp/__${TMP}.properties"
-    TFLANG="/tmp/__${TMP}_`basename $f`"
-    TFMISSING="/tmp/__${TMP}_Missing_`basename $f`"
-
-    grep -v "^[[:space:]]*$" "${FILEPATH}/${i}.properties" | grep -v "^#" | grep "^[[:alpha:]]" | cut -d '=' -f 1 | sort -u > "${TFBASE}"
-    grep -v "^[[:space:]]*$" "${f}" | grep -v "^#" | grep "^[[:alpha:]]" | cut -d '=' -f 1 | sort -u > "${TFLANG}"
-    diff -w "${TFLANG}" "${TFBASE}" | grep "^>" | cut -d " " -f2- > $TFMISSING
-
-    while read line; do
-      grep "$line" "${FILEPATH}/${i}.properties" >> $OUTPUTFILE
-    done < "$TFMISSING"
-
-
-    if [ -f "${TFBASE}" ]; then rm "${TFBASE}" ; fi
-    if [ -f "${TFLANG}" ]; then rm "${TFLANG}" ; fi
-    if [ -f "${TFMISSING}" ]; then rm "${TFMISSING}" ; fi
-
-  done
-done
-
-shopt -u nullglob
-
-exit 0

Някои файлове не бяха показани, защото твърде много файлове са промени