瀏覽代碼

Merge remote-tracking branch 'remotes/origin/ldapchai-07-update'

Jason Rivard 7 年之前
父節點
當前提交
b97c78234c
共有 42 個文件被更改,包括 405 次插入253 次删除
  1. 2 2
      server/pom.xml
  2. 1 2
      server/src/main/java/password/pwm/PwmApplication.java
  3. 2 2
      server/src/main/java/password/pwm/bean/TelemetryPublishBean.java
  4. 9 5
      server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java
  5. 2 2
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  6. 3 2
      server/src/main/java/password/pwm/config/profile/NewUserProfile.java
  7. 29 24
      server/src/main/java/password/pwm/health/LDAPStatusChecker.java
  8. 4 4
      server/src/main/java/password/pwm/http/SessionManager.java
  9. 3 2
      server/src/main/java/password/pwm/http/servlet/ActivateUserServlet.java
  10. 9 21
      server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  11. 1 2
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  12. 4 1
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  13. 6 3
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  14. 14 5
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java
  15. 1 0
      server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java
  16. 13 13
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  17. 19 10
      server/src/main/java/password/pwm/ldap/LdapBrowser.java
  18. 49 18
      server/src/main/java/password/pwm/ldap/LdapConnectionService.java
  19. 10 9
      server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java
  20. 79 38
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  21. 8 4
      server/src/main/java/password/pwm/ldap/LdapPermissionTester.java
  22. 1 2
      server/src/main/java/password/pwm/ldap/UserInfo.java
  23. 2 3
      server/src/main/java/password/pwm/ldap/UserInfoBean.java
  24. 6 13
      server/src/main/java/password/pwm/ldap/UserInfoReader.java
  25. 14 12
      server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java
  26. 1 0
      server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java
  27. 2 3
      server/src/main/java/password/pwm/ldap/schema/EdirSchemaExtender.java
  28. 6 5
      server/src/main/java/password/pwm/ldap/schema/SchemaManager.java
  29. 3 4
      server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java
  30. 1 0
      server/src/main/java/password/pwm/svc/PwmServiceEnum.java
  31. 0 22
      server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java
  32. 4 2
      server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java
  33. 1 1
      server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java
  34. 2 1
      server/src/main/java/password/pwm/util/cli/commands/LdapSchemaExtendCommand.java
  35. 56 0
      server/src/main/java/password/pwm/util/localdb/LocalDBService.java
  36. 1 0
      server/src/main/java/password/pwm/util/localdb/Xodus_LocalDB.java
  37. 1 1
      server/src/main/java/password/pwm/util/logging/PwmLogEvent.java
  38. 2 2
      server/src/main/java/password/pwm/util/operations/CrService.java
  39. 13 9
      server/src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java
  40. 1 2
      server/src/main/java/password/pwm/ws/server/RestServlet.java
  41. 18 1
      server/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp
  42. 2 1
      server/src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp

+ 2 - 2
server/pom.xml

@@ -652,7 +652,7 @@
         <dependency>
             <groupId>com.github.ldapchai</groupId>
             <artifactId>ldapchai</artifactId>
-            <version>0.6.11</version>
+            <version>0.7.0</version>
         </dependency>
         <dependency>
             <groupId>commons-net</groupId>
@@ -881,4 +881,4 @@
             </snapshots>
         </pluginRepository>
     </pluginRepositories>
-</project>
+</project>

+ 1 - 2
server/src/main/java/password/pwm/PwmApplication.java

@@ -22,7 +22,6 @@
 
 package password.pwm;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
@@ -445,7 +444,7 @@ public class PwmApplication {
     {
         try {
             final ChaiProvider proxiedProvider = getProxyChaiProvider(userIdentity.getLdapProfileID());
-            return ChaiFactory.createChaiUser(userIdentity.getUserDN(), proxiedProvider);
+            return proxiedProvider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
         } catch (ChaiUnavailableException e) {
             throw PwmUnrecoverableException.fromChaiException(e);
         }

+ 2 - 2
server/src/main/java/password/pwm/bean/TelemetryPublishBean.java

@@ -22,7 +22,7 @@
 
 package password.pwm.bean;
 
-import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import lombok.Builder;
 import lombok.Getter;
 
@@ -39,7 +39,7 @@ public class TelemetryPublishBean implements Serializable {
     private final String instanceHash;
     private final String siteDescription;
     private final Instant installTime;
-    private final List<ChaiProvider.DIRECTORY_VENDOR> ldapVendor;
+    private final List<DirectoryVendor> ldapVendor;
     private final Map<String,String> statistics;
     private final List<String> configuredSettings;
     private final String versionBuild;

+ 9 - 5
server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java

@@ -23,7 +23,7 @@
 package password.pwm.config.function;
 
 import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -127,13 +127,17 @@ public class UserMatchViewerFunction implements SettingUIFunction {
             ChaiEntry chaiEntry = null;
             try {
                 final ChaiProvider proxiedProvider = pwmApplication.getProxyChaiProvider(loopID);
-                chaiEntry = ChaiFactory.createChaiEntry(baseDN, proxiedProvider);
+                chaiEntry = proxiedProvider.getEntryFactory().newChaiEntry(baseDN);
             } catch (Exception e) {
                 LOGGER.error("error while testing entry DN for profile '" + profileID + "', error:" + profileID);
             }
-            if (chaiEntry != null && !chaiEntry.isValid()) {
-                final String errorMsg = "entry DN '" + baseDN + "' is not valid for profile " + loopID;
-                throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_LDAP_DATA_ERROR, errorMsg));
+            try {
+                if (chaiEntry != null && !chaiEntry.exists()) {
+                    final String errorMsg = "entry DN '" + baseDN + "' is not valid for profile " + loopID;
+                    throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_LDAP_DATA_ERROR, errorMsg));
+                }
+            } catch (ChaiUnavailableException e) {
+                throw PwmUnrecoverableException.fromChaiException(e);
             }
         }
     }

+ 2 - 2
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -23,7 +23,7 @@
 package password.pwm.config.profile;
 
 import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
+
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
@@ -146,7 +146,7 @@ public class LdapProfile extends AbstractProfile implements Profile {
         if (canonicalValue == null) {
             try {
                 final ChaiProvider chaiProvider = this.getProxyChaiProvider(pwmApplication);
-                final ChaiEntry chaiEntry = ChaiFactory.createChaiEntry(dnValue, chaiProvider);
+                final ChaiEntry chaiEntry = chaiProvider.getEntryFactory().newChaiEntry(dnValue);
                 canonicalValue = chaiEntry.readCanonicalDN();
 
                 if (enableCanonicalCache) {

+ 3 - 2
server/src/main/java/password/pwm/config/profile/NewUserProfile.java

@@ -22,9 +22,9 @@
 
 package password.pwm.config.profile;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
+import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -115,7 +115,8 @@ public class NewUserProfile extends AbstractProfile {
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,"user ldap dn in setting " + PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE) + " can not be resolved"));
             } else {
                 try {
-                    final ChaiUser chaiUser = ChaiFactory.createChaiUser(lookupDN, pwmApplication.getProxyChaiProvider(defaultLdapProfile.getIdentifier()));
+                    final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(defaultLdapProfile.getIdentifier());
+                    final ChaiUser chaiUser = chaiProvider.getEntryFactory().newChaiUser(lookupDN);
                     final UserIdentity userIdentity = new UserIdentity(lookupDN, defaultLdapProfile.getIdentifier());
                     thePolicy = PasswordUtility.readPasswordPolicyForUser(pwmApplication, null, userIdentity, chaiUser, userLocale);
                 } catch (ChaiUnavailableException e) {

+ 29 - 24
server/src/main/java/password/pwm/health/LDAPStatusChecker.java

@@ -23,7 +23,6 @@
 package password.pwm.health;
 
 import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiError;
 import com.novell.ldapchai.exception.ChaiErrors;
@@ -31,8 +30,8 @@ import com.novell.ldapchai.exception.ChaiException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiConfiguration;
 import com.novell.ldapchai.provider.ChaiProvider;
-import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiSetting;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import com.novell.ldapchai.util.ChaiUtility;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -100,7 +99,7 @@ public class LDAPStatusChecker implements HealthChecker {
                     checkBasicLdapConnectivity(pwmApplication, config, entry.getValue(), true));
 
             if (profileRecords.isEmpty()) {
-                profileRecords.addAll(checkLdapServerUrls(config, ldapProfiles.get(profileID)));
+                profileRecords.addAll(checkLdapServerUrls(pwmApplication, config, ldapProfiles.get(profileID)));
             }
 
             if (profileRecords.isEmpty()) {
@@ -191,6 +190,7 @@ public class LDAPStatusChecker implements HealthChecker {
             try {
 
                 chaiProvider = LdapOperationsHelper.createChaiProvider(
+                        pwmApplication,
                         SessionLabel.HEALTH_SESSION_LABEL,
                         ldapProfile,
                         config,
@@ -198,7 +198,7 @@ public class LDAPStatusChecker implements HealthChecker {
                         proxyUserPW
                 );
 
-                theUser = ChaiFactory.createChaiUser(testUserDN, chaiProvider);
+                theUser = chaiProvider.getEntryFactory().newChaiUser(testUserDN);
 
             } catch (ChaiUnavailableException e) {
                 returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserUnavailable,
@@ -230,7 +230,7 @@ public class LDAPStatusChecker implements HealthChecker {
             LOGGER.trace(SessionLabel.HEALTH_SESSION_LABEL, "beginning process to check ldap test user password read/write operations for profile " + ldapProfile.getIdentifier());
             try {
                 final boolean readPwdEnabled = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.EDIRECTORY_READ_USER_PWD)
-                        && theUser.getChaiProvider().getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY;
+                        && theUser.getChaiProvider().getDirectoryVendor() == DirectoryVendor.EDIRECTORY;
 
                 if (readPwdEnabled) {
                     try {
@@ -352,7 +352,11 @@ public class LDAPStatusChecker implements HealthChecker {
     }
 
 
-    public List<HealthRecord> checkLdapServerUrls(final Configuration config, final LdapProfile ldapProfile)
+    public List<HealthRecord> checkLdapServerUrls(
+            final PwmApplication pwmApplication,
+            final Configuration config,
+            final LdapProfile ldapProfile
+    )
     {
         final List<HealthRecord> returnRecords = new ArrayList<>();
         final List<String> serverURLs = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS);
@@ -361,6 +365,7 @@ public class LDAPStatusChecker implements HealthChecker {
             ChaiProvider chaiProvider = null;
             try {
                 chaiProvider = LdapOperationsHelper.createChaiProvider(
+                        pwmApplication,
                         SessionLabel.HEALTH_SESSION_LABEL,
                         config,
                         ldapProfile,
@@ -368,8 +373,8 @@ public class LDAPStatusChecker implements HealthChecker {
                         proxyDN,
                         ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD)
                 );
-                final ChaiUser proxyUser = ChaiFactory.createChaiUser(proxyDN, chaiProvider);
-                proxyUser.isValid();
+                final ChaiUser proxyUser = chaiProvider.getEntryFactory().newChaiUser(proxyDN);
+                proxyUser.exists();
             } catch (Exception e) {
                 final String errorString = "error connecting to ldap server '" + loopURL + "': " + e.getMessage();
                 returnRecords.add(new HealthRecord(
@@ -395,7 +400,7 @@ public class LDAPStatusChecker implements HealthChecker {
         final List<HealthRecord> returnRecords = new ArrayList<>();
         ChaiProvider chaiProvider = null;
         try{
-            ChaiProvider.DIRECTORY_VENDOR directoryVendor = null;
+            final DirectoryVendor directoryVendor;
             try {
                 final String proxyDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN);
                 final PasswordData proxyPW = ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD);
@@ -405,9 +410,9 @@ public class LDAPStatusChecker implements HealthChecker {
                 if (proxyPW == null) {
                     return Collections.singletonList(new HealthRecord(HealthStatus.WARN,HealthTopic.LDAP,"Missing Proxy User Password"));
                 }
-                chaiProvider = LdapOperationsHelper.createChaiProvider(SessionLabel.HEALTH_SESSION_LABEL,ldapProfile,config,proxyDN,proxyPW);
-                final ChaiEntry adminEntry = ChaiFactory.createChaiEntry(proxyDN,chaiProvider);
-                adminEntry.isValid();
+                chaiProvider = LdapOperationsHelper.createChaiProvider(pwmApplication, SessionLabel.HEALTH_SESSION_LABEL,ldapProfile,config,proxyDN,proxyPW);
+                final ChaiEntry adminEntry = chaiProvider.getEntryFactory().newChaiEntry(proxyDN);
+                adminEntry.exists();
                 directoryVendor = chaiProvider.getDirectoryVendor();
             } catch (ChaiException e) {
                 final ChaiError chaiError = ChaiErrors.getErrorForMessage(e.getMessage());
@@ -439,14 +444,14 @@ public class LDAPStatusChecker implements HealthChecker {
                 return returnRecords;
             }
 
-            if (directoryVendor != null && directoryVendor == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
+            if (directoryVendor != null && directoryVendor == DirectoryVendor.ACTIVE_DIRECTORY) {
                 returnRecords.addAll(checkAd(pwmApplication, config, ldapProfile));
             }
 
             if (testContextlessRoot) {
                 for (final String loopContext : ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_CONTEXTLESS_ROOT)) {
                     try {
-                        final ChaiEntry contextEntry = ChaiFactory.createChaiEntry(loopContext,chaiProvider);
+                        final ChaiEntry contextEntry = chaiProvider.getEntryFactory().newChaiEntry(loopContext);
                         final Set<String> objectClasses = contextEntry.readObjectClass();
 
                         if (objectClasses == null || objectClasses.isEmpty()) {
@@ -538,7 +543,7 @@ public class LDAPStatusChecker implements HealthChecker {
 
         LOGGER.trace(SessionLabel.HEALTH_SESSION_LABEL,"beginning check for replica vendor sameness");
         boolean errorReachingServer = false;
-        final Map<String,ChaiProvider.DIRECTORY_VENDOR> replicaVendorMap = new HashMap<>();
+        final Map<String, DirectoryVendor> replicaVendorMap = new HashMap<>();
 
         try {
             for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) {
@@ -548,8 +553,8 @@ public class LDAPStatusChecker implements HealthChecker {
                 );
                 final Collection<ChaiConfiguration> replicaConfigs = ChaiUtility.splitConfigurationPerReplica(profileChaiConfiguration, Collections.<ChaiSetting,String>emptyMap());
                 for (final ChaiConfiguration chaiConfiguration : replicaConfigs) {
-                    final ChaiProvider loopProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
-                    replicaVendorMap.put(chaiConfiguration.getSetting(ChaiSetting.BIND_URLS),loopProvider.getDirectoryVendor());
+                    final ChaiProvider loopProvider = pwmApplication.getLdapConnectionService().getChaiProviderFactory().newProvider(chaiConfiguration);
+                    replicaVendorMap.put(chaiConfiguration.getSetting(ChaiSetting.BIND_URLS), loopProvider.getDirectoryVendor());
                 }
             }
         } catch (Exception e) {
@@ -558,12 +563,12 @@ public class LDAPStatusChecker implements HealthChecker {
         }
 
         final ArrayList<HealthRecord> healthRecords = new ArrayList<>();
-        final Set<ChaiProvider.DIRECTORY_VENDOR> discoveredVendors = new HashSet<>(replicaVendorMap.values());
+        final Set<DirectoryVendor> discoveredVendors = new HashSet<>(replicaVendorMap.values());
 
         if (discoveredVendors.size() >= 2) {
             final StringBuilder vendorMsg = new StringBuilder();
-            for (final Iterator<Map.Entry<String,ChaiProvider.DIRECTORY_VENDOR>> iterator = replicaVendorMap.entrySet().iterator(); iterator.hasNext(); ) {
-                final Map.Entry<String,ChaiProvider.DIRECTORY_VENDOR> entry = iterator.next();
+            for (final Iterator<Map.Entry<String, DirectoryVendor>> iterator = replicaVendorMap.entrySet().iterator(); iterator.hasNext(); ) {
+                final Map.Entry<String, DirectoryVendor> entry = iterator.next();
                 final String key = entry.getKey();
                 vendorMsg.append(key).append("=").append(entry.getValue().toString());
                 if (iterator.hasNext()) {
@@ -613,7 +618,7 @@ public class LDAPStatusChecker implements HealthChecker {
                 );
                 final Collection<ChaiConfiguration> replicaConfigs = ChaiUtility.splitConfigurationPerReplica(profileChaiConfiguration, Collections.<ChaiSetting,String>emptyMap());
                 for (final ChaiConfiguration chaiConfiguration : replicaConfigs) {
-                    final ChaiProvider loopProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
+                    final ChaiProvider loopProvider = pwmApplication.getLdapConnectionService().getChaiProviderFactory().newProvider(chaiConfiguration);
                     final ChaiEntry rootDSE = ChaiUtility.getRootDSE(loopProvider);
                     final Set<String> controls = rootDSE.readMultiStringAttribute("supportedControl");
                     final boolean asnSupported = controls.contains(PwmConstants.LDAP_AD_PASSWORD_POLICY_CONTROL_ASN);
@@ -765,8 +770,8 @@ public class LDAPStatusChecker implements HealthChecker {
         final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(ldapProfileID);
         try {
             if (!isExampleDN(dnValue)) {
-                final ChaiEntry baseDNEntry = ChaiFactory.createChaiEntry(dnValue, chaiProvider);
-                if (!baseDNEntry.isValid()) {
+                final ChaiEntry baseDNEntry = chaiProvider.getEntryFactory().newChaiEntry(dnValue);
+                if (!baseDNEntry.exists()) {
                     return "DN '" + dnValue + "' is invalid";
                 } else {
                     final String canonicalDN = baseDNEntry.readCanonicalDN();
@@ -819,7 +824,7 @@ public class LDAPStatusChecker implements HealthChecker {
         profileRecords.addAll(ldapStatusChecker.checkBasicLdapConnectivity(tempApplication, config, ldapProfile,
                 testContextless));
         if (fullTest) {
-            profileRecords.addAll(ldapStatusChecker.checkLdapServerUrls(config, ldapProfile));
+            profileRecords.addAll(ldapStatusChecker.checkLdapServerUrls(pwmApplication, config, ldapProfile));
         }
 
         if (profileRecords.isEmpty()) {

+ 4 - 4
server/src/main/java/password/pwm/http/SessionManager.java

@@ -22,7 +22,6 @@
 
 package password.pwm.http;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
@@ -88,6 +87,7 @@ public class SessionManager {
 
         try {
             this.chaiProvider = LdapOperationsHelper.createChaiProvider(
+                    pwmApplication,
                     pwmSession.getLabel(),
                     userIdentity.getLdapProfile(pwmApplication.getConfig()),
                     pwmApplication.getConfig(),
@@ -95,7 +95,7 @@ public class SessionManager {
                     userPassword
             );
             final String userDN = userIdentity.getUserDN();
-            ChaiFactory.createChaiEntry(userDN,chaiProvider).isValid();
+            chaiProvider.getEntryFactory().newChaiEntry(userDN).exists();
         } catch (ChaiUnavailableException e) {
             final ErrorInformation errorInformation = new ErrorInformation(
                     PwmError.ERROR_DIRECTORY_UNAVAILABLE,
@@ -130,7 +130,7 @@ public class SessionManager {
             throw new IllegalStateException("user not logged in");
         }
 
-        return ChaiFactory.createChaiUser(userDN.getUserDN(), this.getChaiProvider());
+        return this.getChaiProvider().getEntryFactory().newChaiUser(userDN.getUserDN());
     }
 
     public boolean hasActiveLdapConnection() {
@@ -149,7 +149,7 @@ public class SessionManager {
                 throw new PwmUnrecoverableException(PwmError.ERROR_NO_LDAP_CONNECTION);
             }
             final ChaiProvider provider = this.getChaiProvider();
-            return ChaiFactory.createChaiUser(userIdentity.getUserDN(), provider);
+            return provider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
         } catch (ChaiUnavailableException e) {
             throw PwmUnrecoverableException.fromChaiException(e);
         }

+ 3 - 2
server/src/main/java/password/pwm/http/servlet/ActivateUserServlet.java

@@ -22,11 +22,11 @@
 
 package password.pwm.http.servlet;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException;
+import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
@@ -476,7 +476,8 @@ public class ActivateUserServlet extends AbstractPwmServlet {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final String searchFilter = figureLdapSearchFilter(pwmRequest);
-        final ChaiUser chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()));
+        final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID());
+        final ChaiUser chaiUser = chaiProvider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
 
         for (final Map.Entry<FormConfiguration, String> entry : formValues.entrySet()) {
             final FormConfiguration formItem = entry.getKey();

+ 9 - 21
server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java

@@ -22,7 +22,6 @@
 
 package password.pwm.http.servlet;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
@@ -231,7 +230,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
                     Collections.singletonList(guestRegistrationBean.getUpdateUserIdentity())
             );
 
-            final Date expirationDate = readExpirationFromRequest(pwmRequest);
+            final Instant expirationDate = readExpirationFromRequest(pwmRequest);
 
             // Update user attributes
             LdapOperationsHelper.writeFormValuesToLdap(pwmApplication, pwmSession.getSessionManager().getMacroMachine(pwmApplication), theGuest, formValues, false);
@@ -344,9 +343,9 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
                 }
                 final String expirationAttribute = config.readSettingAsString(PwmSetting.GUEST_EXPIRATION_ATTRIBUTE);
                 if (expirationAttribute != null && expirationAttribute.length() > 0) {
-                    final Date expiration = guestUserInfo.readDateAttribute(expirationAttribute);
+                    final Instant expiration = guestUserInfo.readDateAttribute(expirationAttribute);
                     if (expiration != null) {
-                        guBean.setUpdateUserExpirationDate(expiration.toInstant());
+                        guBean.setUpdateUserExpirationDate(expiration);
                     }
                 }
 
@@ -395,7 +394,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
                     pwmRequest, guestUserForm, locale);
 
             //read the expiration date from the request.
-            final Date expirationDate = readExpirationFromRequest(pwmRequest);
+            final Instant expirationDate = readExpirationFromRequest(pwmRequest);
 
             // see if the values meet form requirements.
             FormUtility.validateFormValues(config, formValues, locale);
@@ -428,7 +427,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
             provider.createEntry(guestUserDN, createObjectClasses, createAttributes);
             LOGGER.info(pwmSession, "created user object: " + guestUserDN);
 
-            final ChaiUser theUser = ChaiFactory.createChaiUser(guestUserDN, provider);
+            final ChaiUser theUser = provider.getEntryFactory().newChaiUser(guestUserDN);
             final UserIdentity userIdentity = new UserIdentity(guestUserDN, pwmSession.getUserInfo().getUserIdentity().getLdapProfileID());
 
             // write the expiration date:
@@ -440,17 +439,6 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
             final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser(pwmApplication, pwmSession.getLabel(), userIdentity, theUser, locale);
             final PasswordData newPassword = RandomPasswordGenerator.createRandomPassword(pwmSession.getLabel(), passwordPolicy, pwmApplication);
             theUser.setPassword(newPassword.getStringValue());
-            /*
-            final UserInfoBean guestUserInfoBean = new UserInfoBean();
-            final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication);
-            userStatusReader.populateUserInfoBean(
-                    pwmSession.getLabel(),
-                    guestUserInfoBean,
-                    pwmSession.getSessionStateBean().getLocale(),
-                    userIdentity,
-                    theUser.getChaiProvider()
-            );
-            */
 
 
             {  // execute configured actions
@@ -487,7 +475,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
         }
     }
 
-    private static Date readExpirationFromRequest(
+    private static Instant readExpirationFromRequest(
             final PwmRequest pwmRequest
     )
             throws PwmOperationalException, ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException
@@ -518,15 +506,15 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
 
         final long durationValueMs = durationValueDays * 24 * 60 * 60 * 1000;
         final long futureDateMs = System.currentTimeMillis() + durationValueMs;
-        final Date futureDate = new Date(futureDateMs);
+        final Instant futureDate = Instant.ofEpochMilli(futureDateMs);
 
-        if (expirationDate.after(futureDate)) {
+        if (expirationDate.after(Date.from(futureDate))) {
             final String errorMsg = "expiration date must be sooner than " + futureDate.toString();
             throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED,errorMsg));
         }
 
         LOGGER.trace(pwmRequest, "read expiration date as " + expirationDate.toString());
-        return expirationDate;
+        return expirationDate.toInstant();
     }
 
     private static String determineUserDN(final Map<FormConfiguration, String> formValues, final Configuration config)

+ 1 - 2
server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java

@@ -151,8 +151,7 @@ public class AppDashboardData implements Serializable {
     }
 
     private static int ldapConnectionCount(final PwmApplication pwmApplication) {
-        return pwmApplication.getSessionTrackService().ldapConnectionCount()
-                + pwmApplication.getLdapConnectionService().connectionCount();
+        return pwmApplication.getLdapConnectionService().connectionCount();
     }
 
     private static List<DisplayElement> makeAboutData(

+ 4 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -876,7 +876,10 @@ public class ConfigEditorServlet extends ControlledPwmServlet {
         final String profile = inputMap.get("profile");
         final String dn = inputMap.containsKey("dn") ? inputMap.get("dn") : "";
 
-        final LdapBrowser ldapBrowser = new LdapBrowser(configManagerBean.getStoredConfiguration());
+        final LdapBrowser ldapBrowser = new LdapBrowser(
+                pwmRequest.getPwmApplication().getLdapConnectionService().getChaiProviderFactory(),
+                configManagerBean.getStoredConfiguration()
+        );
 
         LdapBrowser.LdapBrowseResult result;
         try {

+ 6 - 3
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -330,7 +330,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet {
     private ProcessStatus restBrowseLdap(
             final PwmRequest pwmRequest
     )
-            throws IOException, ServletException, PwmUnrecoverableException
+            throws IOException, PwmUnrecoverableException
     {
         final ConfigGuideBean configGuideBean = getBean(pwmRequest);
 
@@ -345,7 +345,10 @@ public class ConfigGuideServlet extends ControlledPwmServlet {
         final String profile = inputMap.get("profile");
         final String dn = inputMap.getOrDefault("dn", "");
 
-        final LdapBrowser ldapBrowser = new LdapBrowser(storedConfiguration);
+        final LdapBrowser ldapBrowser = new LdapBrowser(
+                pwmRequest.getPwmApplication().getLdapConnectionService().getChaiProviderFactory(),
+                storedConfiguration
+        );
         final LdapBrowser.LdapBrowseResult result = ldapBrowser.doBrowse(profile, dn);
         ldapBrowser.close();
 
@@ -445,7 +448,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet {
         final ConfigGuideBean configGuideBean = getBean(pwmRequest);
 
         try {
-            final SchemaOperationResult schemaOperationResult = ConfigGuideUtils.extendSchema(configGuideBean, true);
+            final SchemaOperationResult schemaOperationResult = ConfigGuideUtils.extendSchema(pwmRequest.getPwmApplication(), configGuideBean, true);
             pwmRequest.outputJsonResult(RestResultBean.withData(schemaOperationResult.getOperationLog()));
         } catch (Exception e) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,e.getMessage());

+ 14 - 5
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java

@@ -2,7 +2,6 @@ package password.pwm.http.servlet.configguide;
 
 import com.novell.ldapchai.provider.ChaiConfiguration;
 import com.novell.ldapchai.provider.ChaiProvider;
-import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiSetting;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.PwmApplication;
@@ -84,14 +83,24 @@ public class ConfigGuideUtils {
         }
     }
 
-    public static SchemaOperationResult extendSchema(final ConfigGuideBean configGuideBean, final boolean doSchemaExtension) {
+    public static SchemaOperationResult extendSchema(
+            final PwmApplication pwmApplication,
+            final ConfigGuideBean configGuideBean,
+            final boolean doSchemaExtension
+    ) {
         final Map<ConfigGuideFormField,String> form = configGuideBean.getFormData();
         final boolean ldapServerSecure = "true".equalsIgnoreCase(form.get(ConfigGuideFormField.PARAM_LDAP_SECURE));
         final String ldapUrl = "ldap" + (ldapServerSecure ? "s" : "") + "://" + form.get(ConfigGuideFormField.PARAM_LDAP_HOST) + ":" + form.get(ConfigGuideFormField.PARAM_LDAP_PORT);
         try {
-            final ChaiConfiguration chaiConfiguration = new ChaiConfiguration(ldapUrl, form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_DN), form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_PW));
-            chaiConfiguration.setSetting(ChaiSetting.PROMISCUOUS_SSL,"true");
-            final ChaiProvider chaiProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
+            final ChaiConfiguration chaiConfiguration = ChaiConfiguration.builder(
+                    ldapUrl,
+                    form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_DN),
+                    form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_PW)
+            )
+                    .setSetting(ChaiSetting.PROMISCUOUS_SSL,"true")
+                    .build();
+
+            final ChaiProvider chaiProvider = pwmApplication.getLdapConnectionService().getChaiProviderFactory().newProvider(chaiConfiguration);
             if (doSchemaExtension) {
                 return SchemaManager.extendSchema(chaiProvider);
             } else {

+ 1 - 0
server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java

@@ -361,6 +361,7 @@ public class DebugItemGenerator {
         @Override
         public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception {
             final List<LdapDebugDataGenerator.LdapDebugInfo> ldapDebugInfos = LdapDebugDataGenerator.makeLdapDebugInfos(
+                    pwmApplication,
                     pwmRequest.getSessionLabel(),
                     pwmApplication.getConfig(),
                     pwmRequest.getLocale()

+ 13 - 13
server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -22,14 +22,13 @@
 
 package password.pwm.http.servlet.newuser;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiConfiguration;
 import com.novell.ldapchai.provider.ChaiProvider;
-import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiSetting;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.bean.EmailItemBean;
@@ -37,13 +36,13 @@ import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.TokenVerificationProgress;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.TokenStorageMethod;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
@@ -168,13 +167,13 @@ class NewUserUtils {
             throw new PwmOperationalException(errorInformation);
         }
 
-        final ChaiUser theUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
+        final ChaiUser theUser = chaiProvider.getEntryFactory().newChaiUser(newUserDN);
 
         final boolean useTempPw;
         {
             final String settingValue = pwmApplication.getConfig().readAppProperty(AppProperty.NEWUSER_LDAP_USE_TEMP_PW);
             if ("auto".equalsIgnoreCase(settingValue)) {
-                useTempPw = chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY;
+                useTempPw = chaiProvider.getDirectoryVendor() == DirectoryVendor.ACTIVE_DIRECTORY;
             } else {
                 useTempPw = Boolean.parseBoolean(settingValue);
             }
@@ -189,7 +188,7 @@ class NewUserUtils {
                         .build();
                 temporaryPassword = RandomPasswordGenerator.createRandomPassword(pwmSession.getLabel(), randomGeneratorConfig, pwmApplication);
             }
-            final ChaiUser proxiedUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
+            final ChaiUser proxiedUser = chaiProvider.getEntryFactory().newChaiUser(newUserDN);
             try { //set password as admin
                 proxiedUser.setPassword(temporaryPassword.getStringValue());
                 NewUserUtils.LOGGER.debug(pwmSession, "set temporary password for new user entry: " + newUserDN);
@@ -201,7 +200,7 @@ class NewUserUtils {
             }
 
             // add AD-specific attributes
-            if (ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
+            if (DirectoryVendor.ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
                 try {
                     NewUserUtils.LOGGER.debug(pwmSession,
                             "setting userAccountControl attribute to enable account " + theUser.getEntryDN());
@@ -217,11 +216,12 @@ class NewUserUtils {
             try { // bind as user
                 NewUserUtils.LOGGER.debug(pwmSession,
                         "attempting bind as user to then allow changing to requested password for new user entry: " + newUserDN);
-                final ChaiConfiguration chaiConfiguration = new ChaiConfiguration(chaiProvider.getChaiConfiguration());
-                chaiConfiguration.setSetting(ChaiSetting.BIND_DN, newUserDN);
-                chaiConfiguration.setSetting(ChaiSetting.BIND_PASSWORD, temporaryPassword.getStringValue());
-                final ChaiProvider bindAsProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
-                final ChaiUser bindAsUser = ChaiFactory.createChaiUser(newUserDN, bindAsProvider);
+                final ChaiConfiguration chaiConfiguration = ChaiConfiguration.builder(chaiProvider.getChaiConfiguration())
+                        .setSetting(ChaiSetting.BIND_DN, newUserDN)
+                        .setSetting(ChaiSetting.BIND_PASSWORD, temporaryPassword.getStringValue())
+                        .build();
+                final ChaiProvider bindAsProvider = pwmApplication.getLdapConnectionService().getChaiProviderFactory().newProvider(chaiConfiguration);
+                final ChaiUser bindAsUser = bindAsProvider.getEntryFactory().newChaiUser(newUserDN);
                 bindAsUser.changePassword(temporaryPassword.getStringValue(), userPassword.getStringValue());
                 NewUserUtils.LOGGER.debug(pwmSession, "changed to user requested password for new user entry: " + newUserDN);
                 bindAsProvider.close();
@@ -243,7 +243,7 @@ class NewUserUtils {
             }
 
             // add AD-specific attributes
-            if (ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
+            if (DirectoryVendor.ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
                 try {
                     theUser.writeStringAttribute("userAccountControl", "512");
                 } catch (ChaiOperationException e) {

+ 19 - 10
server/src/main/java/password/pwm/ldap/LdapBrowser.java

@@ -23,10 +23,12 @@
 package password.pwm.ldap;
 
 import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.ChaiProviderFactory;
+import com.novell.ldapchai.provider.DirectoryVendor;
+import com.novell.ldapchai.provider.SearchScope;
 import com.novell.ldapchai.util.ChaiUtility;
 import com.novell.ldapchai.util.SearchHelper;
 import password.pwm.AppProperty;
@@ -50,11 +52,18 @@ import java.util.TreeMap;
 
 public class LdapBrowser {
     private static final PwmLogger LOGGER = PwmLogger.forClass(LdapBrowser.class);
-    final StoredConfigurationImpl storedConfiguration;
+    private final StoredConfigurationImpl storedConfiguration;
 
-    private Map<String,ChaiProvider> providerCache = new HashMap<>();
+    private final ChaiProviderFactory chaiProviderFactory ;
+    private final Map<String,ChaiProvider> providerCache = new HashMap<>();
 
-    public LdapBrowser(final StoredConfigurationImpl storedConfiguration) throws PwmUnrecoverableException {
+    public LdapBrowser(
+            final ChaiProviderFactory chaiProviderFactory,
+            final StoredConfigurationImpl storedConfiguration
+            )
+            throws PwmUnrecoverableException
+    {
+        this.chaiProviderFactory = chaiProviderFactory;
         this.storedConfiguration = storedConfiguration;
     }
 
@@ -106,7 +115,7 @@ public class LdapBrowser {
         if (adRootDNList(profileID).contains(dn)) {
             result.setParentDN("");
         } else if (dn != null && !dn.isEmpty()) {
-            final ChaiEntry dnEntry = ChaiFactory.createChaiEntry(dn, getChaiProvider(profileID));
+            final ChaiEntry dnEntry = getChaiProvider(profileID).getEntryFactory().newChaiEntry(dn);
             final ChaiEntry parentEntry = dnEntry.getParentEntry();
             if (parentEntry == null) {
                 result.setParentDN("");
@@ -122,7 +131,7 @@ public class LdapBrowser {
         if (!providerCache.containsKey(profile)) {
             final Configuration configuration = new Configuration(storedConfiguration);
             final LdapProfile ldapProfile = LdapProfile.makeFromStoredConfiguration(storedConfiguration, profile);
-            final ChaiProvider chaiProvider = LdapOperationsHelper.openProxyChaiProvider(null,ldapProfile,configuration,null);
+            final ChaiProvider chaiProvider = LdapOperationsHelper.openProxyChaiProvider(chaiProviderFactory, null,ldapProfile,configuration,null);
             providerCache.put(profile,chaiProvider);
         }
         return providerCache.get(profile);
@@ -150,7 +159,7 @@ public class LdapBrowser {
 
         final HashMap<String, Boolean> returnMap = new HashMap<>();
         final ChaiProvider chaiProvider = getChaiProvider(profile);
-        if ((dn == null || dn.isEmpty()) && chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
+        if ((dn == null || dn.isEmpty()) && chaiProvider.getDirectoryVendor() == DirectoryVendor.ACTIVE_DIRECTORY) {
             final Set<String> adRootDNList = adRootDNList(profile);
             for (final String rootDN : adRootDNList) {
                 returnMap.put(rootDN, true);
@@ -163,7 +172,7 @@ public class LdapBrowser {
                 searchHelper.setFilter("(objectclass=*)");
                 searchHelper.setMaxResults(getMaxSizeLimit());
                 searchHelper.setAttributes("subordinateCount");
-                searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.ONE);
+                searchHelper.setSearchScope(SearchScope.ONE);
                 results = chaiProvider.searchMultiValues(dn, searchHelper);
 
             }
@@ -180,7 +189,7 @@ public class LdapBrowser {
                     searchHelper.setFilter("(objectclass=*)");
                     searchHelper.setMaxResults(1);
                     searchHelper.setAttributes(Collections.emptyList());
-                    searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.ONE);
+                    searchHelper.setSearchScope(SearchScope.ONE);
                     try {
                         final Map<String, Map<String, String>> subSearchResults = chaiProvider.search(resultDN, searchHelper);
                         hasSubs = !subSearchResults.isEmpty();
@@ -197,7 +206,7 @@ public class LdapBrowser {
     private Set<String> adRootDNList(final String profile) throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException {
         final ChaiProvider chaiProvider = getChaiProvider(profile);
         final Set<String> adRootValues = new HashSet<>();
-        if (chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
+        if (chaiProvider.getDirectoryVendor() == DirectoryVendor.ACTIVE_DIRECTORY) {
             final ChaiEntry chaiEntry = ChaiUtility.getRootDSE(chaiProvider);
             adRootValues.addAll(chaiEntry.readMultiStringAttribute("namingContexts"));
         }

+ 49 - 18
server/src/main/java/password/pwm/ldap/LdapConnectionService.java

@@ -24,6 +24,7 @@ package password.pwm.ldap;
 
 import com.google.gson.reflect.TypeToken;
 import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.ChaiProviderFactory;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.config.option.DataStorageMethod;
@@ -39,10 +40,9 @@ import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -56,6 +56,7 @@ public class LdapConnectionService implements PwmService {
     private STATUS status = STATUS.NEW;
     private AtomicLoopIntIncrementer slotIncrementer;
     private final ThreadLocal<Map<LdapProfile,ChaiProvider>> threadLocalProvider = new ThreadLocal<>();
+    private ChaiProviderFactory chaiProviderFactory;
 
     public STATUS status()
     {
@@ -67,6 +68,8 @@ public class LdapConnectionService implements PwmService {
     {
         this.pwmApplication = pwmApplication;
 
+        chaiProviderFactory = ChaiProviderFactory.newProviderFactory();
+
         // read the lastLoginTime
         this.lastLdapErrors.putAll(readLastLdapFailure(pwmApplication));
 
@@ -85,9 +88,9 @@ public class LdapConnectionService implements PwmService {
     {
         status = STATUS.CLOSED;
         LOGGER.trace("closing ldap proxy connections");
-        for (final ChaiProvider existingProvider : getAllProviders()) {
+        if (chaiProviderFactory != null) {
             try {
-                existingProvider.close();
+                chaiProviderFactory.close();
             } catch (Exception e) {
                 LOGGER.error("error closing ldap proxy connection: " + e.getMessage(), e);
             }
@@ -102,7 +105,13 @@ public class LdapConnectionService implements PwmService {
 
     public ServiceInfoBean serviceInfo()
     {
-        return new ServiceInfoBean(Collections.singletonList(DataStorageMethod.LDAP));
+        final Map<String,String> debugProperties = new LinkedHashMap<>();
+        debugProperties.putAll(chaiProviderFactory.getGlobalStatistics());
+        debugProperties.putAll(connectionDebugInfo());
+        return new ServiceInfoBean(
+                Collections.singletonList(DataStorageMethod.LDAP),
+                Collections.unmodifiableMap(debugProperties)
+        );
     }
 
 
@@ -151,6 +160,7 @@ public class LdapConnectionService implements PwmService {
 
         try {
             final ChaiProvider newProvider = LdapOperationsHelper.openProxyChaiProvider(
+                    pwmApplication,
                     null,
                     ldapProfile,
                     pwmApplication.getConfig(),
@@ -230,25 +240,46 @@ public class LdapConnectionService implements PwmService {
         return perProfile;
     }
 
-    private Collection<ChaiProvider> getAllProviders() {
-        final List<ChaiProvider> returnList = new ArrayList<>();
-        for (final Map<Integer,ChaiProvider> loopProfileMap : proxyChaiProviders.values()) {
-            for (final ChaiProvider chaiProvider : loopProfileMap.values()) {
-                if (chaiProvider != null) {
-                    returnList.add(chaiProvider);
+    public int connectionCount() {
+        int count = 0;
+        if (chaiProviderFactory != null) {
+            for (final ChaiProvider chaiProvider : chaiProviderFactory.activeProviders()) {
+                if (chaiProvider.isConnected()) {
+                    count++;
                 }
             }
         }
-        return Collections.unmodifiableList(returnList);
+        return count;
     }
 
-    public int connectionCount() {
-        int count = 0;
-        for (final ChaiProvider chaiProvider : getAllProviders()) {
-            if (chaiProvider.isConnected()) {
-                count++;
+    public ChaiProviderFactory getChaiProviderFactory() {
+        return chaiProviderFactory;
+    }
+
+    private enum DebugKey {
+        ALLOCATED_CONNECTIONS,
+        ACTIVE_CONNECTIONS,
+        IDLE_CONNECTIONS,
+    }
+
+    private Map<String,String> connectionDebugInfo() {
+        int allocatedConnections = 0;
+        int activeConnections = 0;
+        int idleConnections = 0;
+        if (chaiProviderFactory != null) {
+            for (final ChaiProvider chaiProvider : chaiProviderFactory.activeProviders()) {
+                allocatedConnections++;
+                if (chaiProvider.isConnected()) {
+                    activeConnections++;
+                } else {
+                    idleConnections++;
+                }
             }
         }
-        return count;
+        final Map<String,String> debugInfo = new HashMap<>();
+        debugInfo.put(DebugKey.ALLOCATED_CONNECTIONS.name(), String.valueOf(allocatedConnections));
+        debugInfo.put(DebugKey.ACTIVE_CONNECTIONS.name(), String.valueOf(activeConnections));
+        debugInfo.put(DebugKey.IDLE_CONNECTIONS.name(), String.valueOf(idleConnections));
+        return Collections.unmodifiableMap(debugInfo);
     }
 }

+ 10 - 9
server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java

@@ -23,14 +23,13 @@
 package password.pwm.ldap;
 
 import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiConfiguration;
 import com.novell.ldapchai.provider.ChaiProvider;
-import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.util.ChaiUtility;
+import password.pwm.PwmApplication;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
@@ -50,6 +49,7 @@ public class LdapDebugDataGenerator {
     private static final PwmLogger LOGGER = PwmLogger.forClass(LdapDebugDataGenerator.class);
 
     public static List<LdapDebugInfo> makeLdapDebugInfos(
+            final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
             final Configuration configuration,
             final Locale locale
@@ -63,6 +63,7 @@ public class LdapDebugDataGenerator {
             ldapDebugInfo.setDisplayName(ldapProfile.getDisplayName(locale));
             try {
                 final ChaiProvider chaiProvider = LdapOperationsHelper.createChaiProvider(
+                        pwmApplication,
                         null,
                         ldapProfile,
                         configuration,
@@ -74,7 +75,7 @@ public class LdapDebugDataGenerator {
                 for (final ChaiConfiguration chaiConfiguration : chaiConfigurations) {
                     final LdapDebugServerInfo ldapDebugServerInfo = new LdapDebugServerInfo();
                     ldapDebugServerInfo.setLdapServerlUrl(chaiConfiguration.getSetting(ChaiSetting.BIND_URLS));
-                    final ChaiProvider loopProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
+                    final ChaiProvider loopProvider = chaiProvider.getProviderFactory().newProvider(chaiConfiguration);
 
                     {
                         final ChaiEntry rootDSEentry = ChaiUtility.getRootDSE(loopProvider);
@@ -86,8 +87,8 @@ public class LdapDebugDataGenerator {
                         final String proxyUserDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN);
                         if (proxyUserDN != null) {
                             ldapDebugServerInfo.setProxyDN(proxyUserDN);
-                            final ChaiEntry proxyUserEntry = ChaiFactory.createChaiEntry(proxyUserDN, chaiProvider);
-                            if (proxyUserEntry.isValid()) {
+                            final ChaiEntry proxyUserEntry = chaiProvider.getEntryFactory().newChaiEntry(proxyUserDN);
+                            if (proxyUserEntry.exists()) {
                                 final Map<String, List<String>> proxyUserData = LdapOperationsHelper.readAllEntryAttributeValues(proxyUserEntry);
                                 ldapDebugServerInfo.setProxyUserAttributes(proxyUserData);
                             }
@@ -99,8 +100,8 @@ public class LdapDebugDataGenerator {
                         final String testUserDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_TEST_USER_DN);
                         if (testUserDN != null) {
                             ldapDebugServerInfo.setTestUserDN(testUserDN);
-                            final ChaiEntry testUserEntry = ChaiFactory.createChaiEntry(testUserDN, chaiProvider);
-                            if (testUserEntry.isValid()) {
+                            final ChaiEntry testUserEntry = chaiProvider.getEntryFactory().newChaiEntry(testUserDN);
+                            if (testUserEntry.exists()) {
                                 final Map<String, List<String>> testUserdata = LdapOperationsHelper.readAllEntryAttributeValues(testUserEntry);
                                 ldapDebugServerInfo.setTestUserAttributes(testUserdata);
                             }
@@ -122,8 +123,8 @@ public class LdapDebugDataGenerator {
     private Map<String,List<String>> readUserAttributeData(final ChaiProvider chaiProvider, final String userDN)
             throws ChaiUnavailableException, ChaiOperationException
     {
-        final ChaiEntry testUserEntry = ChaiFactory.createChaiEntry(userDN, chaiProvider);
-        if (testUserEntry.isValid()) {
+        final ChaiEntry testUserEntry = chaiProvider.getEntryFactory().newChaiEntry(userDN);
+        if (testUserEntry.exists()) {
             final Map<String,List<String>> returnData = new LinkedHashMap<>();
             final Map<String, List<String>> testUserdata = LdapOperationsHelper.readAllEntryAttributeValues(testUserEntry);
             testUserdata.put("dn",Collections.singletonList(userDN));

+ 79 - 38
server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java

@@ -24,7 +24,6 @@ package password.pwm.ldap;
 
 import com.novell.ldapchai.ChaiConstant;
 import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.cr.Answer;
 import com.novell.ldapchai.exception.ChaiOperationException;
@@ -33,6 +32,7 @@ import com.novell.ldapchai.provider.ChaiConfiguration;
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiSetting;
+import com.novell.ldapchai.provider.SearchScope;
 import com.novell.ldapchai.util.SearchHelper;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -65,7 +65,6 @@ import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -91,7 +90,7 @@ public class LdapOperationsHelper {
             return;
         }
         final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID());
-        final ChaiUser theUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider);
+        final ChaiUser theUser = chaiProvider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
         addUserObjectClass(sessionLabel, theUser, newObjClasses);
     }
 
@@ -125,6 +124,25 @@ public class LdapOperationsHelper {
     }
 
     public static ChaiProvider openProxyChaiProvider(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final LdapProfile ldapProfile,
+            final Configuration config,
+            final StatisticsManager statisticsManager
+    )
+            throws PwmUnrecoverableException
+    {
+        return openProxyChaiProvider(
+                pwmApplication.getLdapConnectionService().getChaiProviderFactory(),
+                sessionLabel,
+                ldapProfile,
+                config,
+                statisticsManager
+        );
+    }
+
+    static ChaiProvider openProxyChaiProvider(
+            final ChaiProviderFactory chaiProviderFactory,
             final SessionLabel sessionLabel,
             final LdapProfile ldapProfile,
             final Configuration config,
@@ -138,7 +156,7 @@ public class LdapOperationsHelper {
         final PasswordData proxyPW = ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD);
 
         try {
-            return createChaiProvider(sessionLabel, ldapProfile, config, proxyDN, proxyPW);
+            return createChaiProvider(chaiProviderFactory, sessionLabel, ldapProfile, config, proxyDN, proxyPW);
         } catch (ChaiUnavailableException e) {
             if (statisticsManager != null) {
                 statisticsManager.incrementValue(Statistic.LDAP_UNAVAILABLE_COUNT);
@@ -218,7 +236,6 @@ public class LdapOperationsHelper {
      * <p/>
      * Any ldap operation exceptions are not reported (but logged).
      *
-     * @param pwmSession       for looking up session info
      * @param theUser          User to write to
      * @param formValues       A map with {@link FormConfiguration} keys and String values.
      * @throws ChaiUnavailableException if the directory is unavailable
@@ -449,8 +466,28 @@ public class LdapOperationsHelper {
         }
     }
 
+    public static ChaiProvider createChaiProvider(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final LdapProfile ldapProfile,
+            final Configuration config,
+            final String userDN,
+            final PasswordData userPassword
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException
+    {
+        return createChaiProvider(
+                pwmApplication.getLdapConnectionService().getChaiProviderFactory(),
+                sessionLabel,
+                ldapProfile,
+                config,
+                userDN,
+                userPassword
+        );
+    }
 
     public static ChaiProvider createChaiProvider(
+            final ChaiProviderFactory chaiProviderFactory,
             final SessionLabel sessionLabel,
             final LdapProfile ldapProfile,
             final Configuration config,
@@ -462,10 +499,11 @@ public class LdapOperationsHelper {
         final List<String> ldapURLs = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS);
         final ChaiConfiguration chaiConfig = createChaiConfiguration(config, ldapProfile, ldapURLs, userDN, userPassword);
         LOGGER.trace(sessionLabel,"creating new ldap connection using config: " + chaiConfig.toString());
-        return ChaiProviderFactory.createProvider(chaiConfig);
+        return chaiProviderFactory.newProvider(chaiConfig);
     }
 
     public static ChaiProvider createChaiProvider(
+            final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
             final Configuration config,
             final LdapProfile ldapProfile,
@@ -477,7 +515,7 @@ public class LdapOperationsHelper {
     {
         final ChaiConfiguration chaiConfig = createChaiConfiguration( config, ldapProfile, ldapURLs, userDN, userPassword);
         LOGGER.trace(sessionLabel,"creating new ldap connection using config: " + chaiConfig.toString());
-        return ChaiProviderFactory.createProvider(chaiConfig);
+        return pwmApplication.getLdapConnectionService().getChaiProviderFactory().newProvider(chaiConfig);
     }
 
     public static ChaiConfiguration createChaiConfiguration(
@@ -501,32 +539,38 @@ public class LdapOperationsHelper {
     )
             throws PwmUnrecoverableException
     {
-        final ChaiConfiguration chaiConfig = new ChaiConfiguration(ldapURLs, userDN, userPassword == null ? null : userPassword.getStringValue());
+        final ChaiConfiguration.ChaiConfigurationBuilder configBuilder = ChaiConfiguration.builder(
+                ldapURLs,
+                userDN,
+                userPassword == null
+                        ? null
+                        : userPassword.getStringValue()
+        );
 
-        chaiConfig.setSetting(ChaiSetting.PROMISCUOUS_SSL, config.readAppProperty(AppProperty.LDAP_PROMISCUOUS_ENABLE));
+        configBuilder.setSetting(ChaiSetting.PROMISCUOUS_SSL, config.readAppProperty(AppProperty.LDAP_PROMISCUOUS_ENABLE));
         {
             final boolean enableNmasExtensions = Boolean.parseBoolean(config.readAppProperty(AppProperty.LDAP_EXTENSIONS_NMAS_ENABLE));
-            chaiConfig.setSetting(ChaiSetting.EDIRECTORY_ENABLE_NMAS, Boolean.toString(enableNmasExtensions));
+            configBuilder.setSetting(ChaiSetting.EDIRECTORY_ENABLE_NMAS, Boolean.toString(enableNmasExtensions));
         }
 
-        chaiConfig.setSetting(ChaiSetting.CR_CHAI_STORAGE_ATTRIBUTE, ldapProfile.readSettingAsString(PwmSetting.CHALLENGE_USER_ATTRIBUTE));
-        chaiConfig.setSetting(ChaiSetting.CR_ALLOW_DUPLICATE_RESPONSES, Boolean.toString(config.readSettingAsBoolean(PwmSetting.CHALLENGE_ALLOW_DUPLICATE_RESPONSES)));
-        chaiConfig.setSetting(ChaiSetting.CR_CASE_INSENSITIVE, Boolean.toString(config.readSettingAsBoolean(PwmSetting.CHALLENGE_CASE_INSENSITIVE)));
+        configBuilder.setSetting(ChaiSetting.CR_CHAI_STORAGE_ATTRIBUTE, ldapProfile.readSettingAsString(PwmSetting.CHALLENGE_USER_ATTRIBUTE));
+        configBuilder.setSetting(ChaiSetting.CR_ALLOW_DUPLICATE_RESPONSES, Boolean.toString(config.readSettingAsBoolean(PwmSetting.CHALLENGE_ALLOW_DUPLICATE_RESPONSES)));
+        configBuilder.setSetting(ChaiSetting.CR_CASE_INSENSITIVE, Boolean.toString(config.readSettingAsBoolean(PwmSetting.CHALLENGE_CASE_INSENSITIVE)));
         {
             final String setting = config.readAppProperty(AppProperty.SECURITY_RESPONSES_HASH_ITERATIONS);
             if (setting != null && setting.length() > 0) {
                 final int intValue = Integer.parseInt(setting);
-                chaiConfig.setSetting(ChaiSetting.CR_CHAI_SALT_COUNT, Integer.toString(intValue));
+                configBuilder.setSetting(ChaiSetting.CR_CHAI_SALT_COUNT, Integer.toString(intValue));
             }
         }
 
-        chaiConfig.setSetting(ChaiSetting.JNDI_ENABLE_POOL, "false"); // can cause issues with previous password authentication
+        configBuilder.setSetting(ChaiSetting.JNDI_ENABLE_POOL, "false"); // can cause issues with previous password authentication
 
-        chaiConfig.setSetting(ChaiSetting.CR_DEFAULT_FORMAT_TYPE, Answer.FormatType.SHA1_SALT.toString());
+        configBuilder.setSetting(ChaiSetting.CR_DEFAULT_FORMAT_TYPE, Answer.FormatType.SHA1_SALT.toString());
         final String storageMethodString = config.readSettingAsString(PwmSetting.CHALLENGE_STORAGE_HASHED);
         try {
             final Answer.FormatType formatType = Answer.FormatType.valueOf(storageMethodString);
-            chaiConfig.setSetting(ChaiSetting.CR_DEFAULT_FORMAT_TYPE, formatType.toString());
+            configBuilder.setSetting(ChaiSetting.CR_DEFAULT_FORMAT_TYPE, formatType.toString());
         } catch (Exception e) {
             LOGGER.warn("unknown CR storage format type '" + storageMethodString + "' ");
         }
@@ -534,27 +578,26 @@ public class LdapOperationsHelper {
         final List<X509Certificate> ldapServerCerts = ldapProfile.readSettingAsCertificate(PwmSetting.LDAP_SERVER_CERTS);
         if (ldapServerCerts != null && ldapServerCerts.size() > 0) {
             final X509TrustManager tm = new X509Utils.CertMatchingTrustManager(config, ldapServerCerts);
-            chaiConfig.setTrustManager(new X509TrustManager[]{tm});
+            configBuilder.setTrustManager(new X509TrustManager[]{tm});
         }
 
         final String idleTimeoutMsString = config.readAppProperty(AppProperty.LDAP_CONNECTION_TIMEOUT);
-        chaiConfig.setSetting(ChaiSetting.LDAP_CONNECT_TIMEOUT,idleTimeoutMsString);
+        configBuilder.setSetting(ChaiSetting.LDAP_CONNECT_TIMEOUT,idleTimeoutMsString);
 
         // set the watchdog idle timeout.
         final int idleTimeoutMs = (int)config.readSettingAsLong(PwmSetting.LDAP_IDLE_TIMEOUT) * 1000;
         if (idleTimeoutMs > 0) {
-            chaiConfig.setSetting(ChaiSetting.WATCHDOG_ENABLE, "true");
-            chaiConfig.setSetting(ChaiSetting.WATCHDOG_IDLE_TIMEOUT, idleTimeoutMsString);
-            chaiConfig.setSetting(ChaiSetting.WATCHDOG_CHECK_FREQUENCY, Long.toString(5 * 1000));
+            configBuilder.setSetting(ChaiSetting.WATCHDOG_ENABLE, "true");
+            configBuilder.setSetting(ChaiSetting.WATCHDOG_IDLE_TIMEOUT, idleTimeoutMsString);
         } else {
-            chaiConfig.setSetting(ChaiSetting.WATCHDOG_ENABLE, "false");
+            configBuilder.setSetting(ChaiSetting.WATCHDOG_ENABLE, "false");
         }
 
-        chaiConfig.setSetting(ChaiSetting.LDAP_SEARCH_PAGING_ENABLE, config.readAppProperty(AppProperty.LDAP_SEARCH_PAGING_ENABLE));
-        chaiConfig.setSetting(ChaiSetting.LDAP_SEARCH_PAGING_SIZE, config.readAppProperty(AppProperty.LDAP_SEARCH_PAGING_SIZE));
+        configBuilder.setSetting(ChaiSetting.LDAP_SEARCH_PAGING_ENABLE, config.readAppProperty(AppProperty.LDAP_SEARCH_PAGING_ENABLE));
+        configBuilder.setSetting(ChaiSetting.LDAP_SEARCH_PAGING_SIZE, config.readAppProperty(AppProperty.LDAP_SEARCH_PAGING_SIZE));
 
         if (config.readSettingAsBoolean(PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET)) {
-            chaiConfig.setSetting(ChaiSetting.AD_SET_POLICY_HINTS_ON_PW_SET,"true");
+            configBuilder.setSetting(ChaiSetting.AD_SET_POLICY_HINTS_ON_PW_SET,"true");
         }
 
         // write out any configured values;
@@ -568,20 +611,20 @@ public class LdapOperationsHelper {
                 if (theSetting == null) {
                     LOGGER.warn("ignoring unknown chai setting '" + key + "'");
                 } else {
-                    chaiConfig.setSetting(theSetting, entry.getValue());
+                    configBuilder.setSetting(theSetting, entry.getValue());
                 }
             }
         }
 
         // set ldap referrals
-        chaiConfig.setSetting(ChaiSetting.LDAP_FOLLOW_REFERRALS,String.valueOf(config.readSettingAsBoolean(PwmSetting.LDAP_FOLLOW_REFERRALS)));
+        configBuilder.setSetting(ChaiSetting.LDAP_FOLLOW_REFERRALS,String.valueOf(config.readSettingAsBoolean(PwmSetting.LDAP_FOLLOW_REFERRALS)));
 
         // enable wire trace;
         if (config.readSettingAsBoolean(PwmSetting.LDAP_ENABLE_WIRE_TRACE)) {
-            chaiConfig.setSetting(ChaiSetting.WIRETRACE_ENABLE, "true");
+            configBuilder.setSetting(ChaiSetting.WIRETRACE_ENABLE, "true");
         }
 
-        return chaiConfig;
+        return configBuilder.build();
     }
 
     /**
@@ -608,7 +651,7 @@ public class LdapOperationsHelper {
 
         if (updateAttribute != null && updateAttribute.length() > 0) {
             try {
-                theUser.writeDateAttribute(updateAttribute, new Date());
+                theUser.writeDateAttribute(updateAttribute, Instant.now());
                 LOGGER.debug(sessionLabel, "wrote pwdLastModified update attribute for " + theUser.getEntryDN());
                 success = true;
             } catch (ChaiOperationException e) {
@@ -623,7 +666,7 @@ public class LdapOperationsHelper {
             throws ChaiUnavailableException, ChaiOperationException
     {
         final SearchHelper searchHelper = new SearchHelper();
-        searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.BASE);
+        searchHelper.setSearchScope(SearchScope.BASE);
         searchHelper.setFilter("(objectClass=*)");
         final Map<String, Map<String, List<String>>> results = chaiEntry.getChaiProvider().searchMultiValues(chaiEntry.getEntryDN(), searchHelper);
         if (!results.isEmpty()) {
@@ -688,14 +731,12 @@ public class LdapOperationsHelper {
 
     public static Instant readPasswordExpirationTime(final ChaiUser theUser) {
         try {
-            Date ldapPasswordExpirationTime = theUser.readPasswordExpirationDate();
-            if (ldapPasswordExpirationTime != null && ldapPasswordExpirationTime.getTime() < 0) {
+            Instant ldapPasswordExpirationTime = theUser.readPasswordExpirationDate();
+            if (ldapPasswordExpirationTime != null && ldapPasswordExpirationTime.toEpochMilli() < 0) {
                 // If ldapPasswordExpirationTime is less than 0, this may indicate an extremely late date, past the epoch.
                 ldapPasswordExpirationTime = null;
             }
-            return ldapPasswordExpirationTime == null
-                    ? null
-                    : ldapPasswordExpirationTime.toInstant();
+            return ldapPasswordExpirationTime;
         } catch (Exception e) {
             LOGGER.warn("error reading password expiration time: " + e.getMessage());
         }
@@ -715,7 +756,7 @@ public class LdapOperationsHelper {
         }
 
         final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID());
-        final ChaiUser chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider);
+        final ChaiUser chaiUser = chaiProvider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
 
         // use chai (nmas) to retrieve user password
         if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.EDIRECTORY_READ_USER_PWD)) {

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

@@ -24,14 +24,14 @@ package password.pwm.ldap;
 
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiException;
-import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.SearchScope;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -137,7 +137,11 @@ public class LdapPermissionTester {
             try {
                 LOGGER.trace(pwmSession, "checking ldap to see if " + userIdentity + " matches group '" + groupDN + "' using filter '" + filterString + "'");
                 final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userIdentity);
-                final Map<String, Map<String, String>> results = theUser.getChaiProvider().search(theUser.getEntryDN(), filterString, Collections.<String>emptySet(), ChaiProvider.SEARCH_SCOPE.BASE);
+                final Map<String, Map<String, String>> results = theUser.getChaiProvider().search(
+                        theUser.getEntryDN(),
+                        filterString,
+                        Collections.<String>emptySet(), SearchScope.BASE
+                );
                 if (results.size() == 1 && results.keySet().contains(theUser.getEntryDN())) {
                     result = true;
                 }
@@ -182,7 +186,7 @@ public class LdapPermissionTester {
             try {
                 LOGGER.trace(pwmSession, "checking ldap to see if " + userIdentity + " matches '" + filterString + "'");
                 final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userIdentity);
-                final Map<String, Map<String, String>> results = theUser.getChaiProvider().search(theUser.getEntryDN(), filterString, Collections.<String>emptySet(), ChaiProvider.SEARCH_SCOPE.BASE);
+                final Map<String, Map<String, String>> results = theUser.getChaiProvider().search(theUser.getEntryDN(), filterString, Collections.emptySet(), SearchScope.BASE);
                 if (results.size() == 1 && results.keySet().contains(theUser.getEntryDN())) {
                     result = true;
                 }

+ 1 - 2
server/src/main/java/password/pwm/ldap/UserInfo.java

@@ -33,7 +33,6 @@ import password.pwm.util.operations.otp.OTPUserRecord;
 
 import java.time.Instant;
 import java.util.Collection;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -90,7 +89,7 @@ public interface UserInfo {
 
     String readStringAttribute(String attribute) throws PwmUnrecoverableException;
 
-    Date readDateAttribute(String attribute) throws PwmUnrecoverableException;
+    Instant readDateAttribute(String attribute) throws PwmUnrecoverableException;
 
     List<String> readMultiStringAttribute(String attribute) throws PwmUnrecoverableException;
 

+ 2 - 3
server/src/main/java/password/pwm/ldap/UserInfoBean.java

@@ -37,7 +37,6 @@ import password.pwm.util.operations.otp.OTPUserRecord;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -100,10 +99,10 @@ public class UserInfoBean implements UserInfo {
     }
 
     @Override
-    public Date readDateAttribute(final String attribute) throws PwmUnrecoverableException
+    public Instant readDateAttribute(final String attribute) throws PwmUnrecoverableException
     {
         if (attributes.containsKey(attribute)) {
-            return EdirEntries.convertZuluToDate(attributes.get(attribute));
+            return EdirEntries.convertZuluToInstant(attributes.get(attribute));
         }
         return null;
     }

+ 6 - 13
server/src/main/java/password/pwm/ldap/UserInfoReader.java

@@ -22,12 +22,12 @@
 
 package password.pwm.ldap;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiException;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.SearchScope;
 import password.pwm.PwmApplication;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.bean.ResponseInfoBean;
@@ -66,7 +66,6 @@ import password.pwm.util.operations.otp.OTPUserRecord;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -108,7 +107,7 @@ public class UserInfoReader implements UserInfo {
         this.sessionLabel = sessionLabel;
 
         final ChaiProvider cachingProvider = CachingProxyWrapper.create(ChaiProvider.class, chaiProvider);
-        this.chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), cachingProvider);
+        this.chaiUser = cachingProvider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
     }
 
     static UserInfo create(
@@ -153,10 +152,7 @@ public class UserInfoReader implements UserInfo {
     public Instant getLastLdapLoginTime() throws PwmUnrecoverableException
     {
         try {
-            final Date lastLoginTime = chaiUser.readLastLoginTime();
-            return lastLoginTime == null
-                    ? null
-                    : lastLoginTime.toInstant();
+            return chaiUser.readLastLoginTime();
         } catch (ChaiOperationException e) {
             LOGGER.warn(sessionLabel, "error reading user's last ldap login time: " + e.getMessage());
         } catch (ChaiUnavailableException e) {
@@ -518,10 +514,7 @@ public class UserInfoReader implements UserInfo {
     public Instant getAccountExpirationTime() throws PwmUnrecoverableException
     {
         try {
-            final Date accountExpireDate = chaiUser.readAccountExpirationDate();
-            return accountExpireDate == null
-                    ? null
-                    : accountExpireDate.toInstant();
+            return chaiUser.readAccountExpirationDate();
         } catch (ChaiOperationException e) {
             LOGGER.warn(sessionLabel, "error reading user's account expiration time: " + e.getMessage());
         } catch (ChaiUnavailableException e) {
@@ -581,7 +574,7 @@ public class UserInfoReader implements UserInfo {
     }
 
     @Override
-    public Date readDateAttribute(final String attribute)
+    public Instant readDateAttribute(final String attribute)
             throws PwmUnrecoverableException
     {
         try {
@@ -637,7 +630,7 @@ public class UserInfoReader implements UserInfo {
                         chaiUser.getEntryDN(),
                         "(objectclass=*)",
                         uncachedAttributes,
-                        ChaiProvider.SEARCH_SCOPE.BASE
+                        SearchScope.BASE
                 );
             } catch (ChaiOperationException e) {
                 final String msg = "ldap operational error while reading user data" + e.getMessage();

+ 14 - 12
server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java

@@ -23,7 +23,6 @@
 package password.pwm.ldap.auth;
 
 import com.novell.ldapchai.ChaiConstant;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiError;
 import com.novell.ldapchai.exception.ChaiException;
@@ -33,6 +32,7 @@ import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException;
 import com.novell.ldapchai.impl.oracleds.entry.OracleDSEntries;
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiSetting;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -64,6 +64,7 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.PasswordUtility;
 
+import java.time.Instant;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
@@ -194,17 +195,17 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
                 testCredentials(userIdentity, password);
             } catch (PwmOperationalException e) {
                 boolean permitAuthDespiteError = false;
-                final ChaiProvider.DIRECTORY_VENDOR vendor = pwmApplication.getProxyChaiProvider(
+                final DirectoryVendor vendor = pwmApplication.getProxyChaiProvider(
                         userIdentity.getLdapProfileID()).getDirectoryVendor();
                 if (PwmError.PASSWORD_NEW_PASSWORD_REQUIRED == e.getError()) {
-                    if (vendor == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
+                    if (vendor == DirectoryVendor.ACTIVE_DIRECTORY) {
                         if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_ALLOW_AUTH_REQUIRE_NEW_PWD)) {
                             log(PwmLogLevel.INFO,
                                     "auth bind failed, but will allow login due to 'must change password on next login AD error', error: " + e.getErrorInformation().toDebugStr());
                             allowBindAsUser = false;
                             permitAuthDespiteError = true;
                         }
-                    } else if (vendor == ChaiProvider.DIRECTORY_VENDOR.ORACLE_DS) {
+                    } else if (vendor == DirectoryVendor.ORACLE_DS) {
                         if (pwmApplication.getConfig().readSettingAsBoolean(
                                 PwmSetting.ORACLE_DS_ALLOW_AUTH_REQUIRE_NEW_PWD)) {
                             log(PwmLogLevel.INFO,
@@ -214,7 +215,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
                         }
                     }
                 } else if (PwmError.PASSWORD_EXPIRED == e.getError()) { // handle ad case where password is expired
-                    if (vendor == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
+                    if (vendor == DirectoryVendor.ACTIVE_DIRECTORY) {
                         if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_ALLOW_AUTH_REQUIRE_NEW_PWD)) {
                             if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_ALLOW_AUTH_EXPIRED)) {
                                 throw e;
@@ -316,6 +317,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
         try {
             //read a provider using the user's DN and password.
             userProvider = LdapOperationsHelper.createChaiProvider(
+                    pwmApplication,
                     sessionLabel,
                     userIdentity.getLdapProfile(pwmApplication.getConfig()),
                     pwmApplication.getConfig(),
@@ -370,7 +372,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
         final boolean configAlwaysUseProxy = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_USE_PROXY_FOR_FORGOTTEN);
 
         final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID());
-        final ChaiUser chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider);
+        final ChaiUser chaiUser = chaiProvider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
 
         // try setting a random password on the account to authenticate.
         if (!configAlwaysUseProxy && requestedAuthType == AuthenticationType.AUTH_FROM_PUBLIC_MODULE) {
@@ -423,7 +425,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
             return null;
         }
 
-        if (ChaiProvider.DIRECTORY_VENDOR.ORACLE_DS != chaiUser.getChaiProvider().getDirectoryVendor()) {
+        if (DirectoryVendor.ORACLE_DS != chaiUser.getChaiProvider().getDirectoryVendor()) {
             return null;
         }
 
@@ -435,11 +437,11 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
 
 
         if (oracleDS_PrePasswordAllowChangeTime != null && !oracleDS_PrePasswordAllowChangeTime.isEmpty()) {
-            final Date date = OracleDSEntries.convertZuluToDate(oracleDS_PrePasswordAllowChangeTime);
+            final Instant date = OracleDSEntries.convertZuluToDate(oracleDS_PrePasswordAllowChangeTime);
 
             final boolean enforceFromForgotten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME);
             if (enforceFromForgotten) {
-                if (new Date().before(date)) {
+                if (Instant.now().isBefore(date)) {
                     final String errorMsg = "change not permitted until " + JavaHelper.toIsoDate(
                             date);
                     throw new PwmUnrecoverableException(
@@ -463,7 +465,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
         }
 
         // oracle DS special case: passwordAllowChangeTime handler
-        if (ChaiProvider.DIRECTORY_VENDOR.ORACLE_DS != chaiUser.getChaiProvider().getDirectoryVendor()) {
+        if (DirectoryVendor.ORACLE_DS != chaiUser.getChaiProvider().getDirectoryVendor()) {
             return;
         }
 
@@ -483,7 +485,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
                 final boolean PostTempUseCurrentTime = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_ORACLE_POST_TEMPPW_USE_CURRENT_TIME));
                 if (PostTempUseCurrentTime) {
                     log(PwmLogLevel.TRACE, "a new value for passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN() + " has appeared, will replace with current time value");
-                    final String newTimeValue = OracleDSEntries.convertDateToZulu(new Date());
+                    final String newTimeValue = OracleDSEntries.convertDateToZulu(Instant.now());
                     final Set<String> values = new HashSet<>(Collections.singletonList(newTimeValue));
                     chaiProvider.writeStringAttribute(chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME, values, true);
                     log(PwmLogLevel.TRACE, "wrote attribute value '" + newTimeValue + "' for passwordAllowChangeTime attribute on user " + chaiUser.getEntryDN());
@@ -521,7 +523,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
         final LdapProfile profile = pwmApplication.getConfig().getLdapProfiles().get(userIdentity.getLdapProfileID());
         final String proxyDN = profile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN);
         final PasswordData proxyPassword = profile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD);
-        return LdapOperationsHelper.createChaiProvider(sessionLabel, profile, pwmApplication.getConfig(), proxyDN, proxyPassword);
+        return LdapOperationsHelper.createChaiProvider(pwmApplication, sessionLabel, profile, pwmApplication.getConfig(), proxyDN, proxyPassword);
     }
 
     private void log(final PwmLogLevel level, final CharSequence message) {

+ 1 - 0
server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java

@@ -248,6 +248,7 @@ public class SessionAuthenticator {
             //read a provider using the user's DN and password.
 
             provider = LdapOperationsHelper.createChaiProvider(
+                    pwmApplication,
                     sessionLabel,
                     userIdentity.getLdapProfile(pwmApplication.getConfig()),
                     pwmApplication.getConfig(),

+ 2 - 3
server/src/main/java/password/pwm/ldap/schema/EdirSchemaExtender.java

@@ -24,7 +24,6 @@ package password.pwm.ldap.schema;
 
 import com.novell.ldap.client.SchemaParser;
 import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
@@ -52,11 +51,11 @@ public class EdirSchemaExtender implements SchemaExtender {
     private ChaiEntry schemaEntry;
 
     private final StringBuilder activityLog = new StringBuilder();
-    private final Map<String,SchemaDefinition.State> stateMap = new HashMap();
+    private final Map<String,SchemaDefinition.State> stateMap = new HashMap<>();
 
     public void init(final ChaiProvider chaiProvider) throws PwmUnrecoverableException {
         try {
-            schemaEntry = ChaiFactory.createChaiEntry(LDAP_SCHEMA_DN, chaiProvider);
+            schemaEntry = chaiProvider.getEntryFactory().newChaiEntry(LDAP_SCHEMA_DN);
         } catch (ChaiUnavailableException e) {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage()));
         }

+ 6 - 5
server/src/main/java/password/pwm/ldap/schema/SchemaManager.java

@@ -24,6 +24,7 @@ package password.pwm.ldap.schema;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -37,11 +38,11 @@ import java.util.Map;
 public class SchemaManager {
     private static final PwmLogger LOGGER = PwmLogger.forClass(SchemaManager.class);
 
-    private static final Map<ChaiProvider.DIRECTORY_VENDOR, Class<? extends SchemaExtender>> IMPLEMENTATIONS;
+    private static final Map<DirectoryVendor, Class<? extends SchemaExtender>> IMPLEMENTATIONS;
 
     static {
-        final Map<ChaiProvider.DIRECTORY_VENDOR, Class<? extends SchemaExtender>> implMap = new HashMap<>();
-        implMap.put(ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY, EdirSchemaExtender.class);
+        final Map<DirectoryVendor, Class<? extends SchemaExtender>> implMap = new HashMap<>();
+        implMap.put(DirectoryVendor.EDIRECTORY, EdirSchemaExtender.class);
         IMPLEMENTATIONS = Collections.unmodifiableMap(implMap);
     }
 
@@ -50,7 +51,7 @@ public class SchemaManager {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, "provider is not connected"));
         }
         try {
-            if (chaiProvider.getDirectoryVendor() != ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY) {
+            if (chaiProvider.getDirectoryVendor() != DirectoryVendor.EDIRECTORY) {
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, "directory vendor is not supported"));
             }
             final List<String> urls = chaiProvider.getChaiConfiguration().bindURLsAsList();
@@ -58,7 +59,7 @@ public class SchemaManager {
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, "provider used for schema extension must have only a single ldap url defined"));
             }
 
-            final ChaiProvider.DIRECTORY_VENDOR vendor = chaiProvider.getDirectoryVendor();
+            final DirectoryVendor vendor = chaiProvider.getDirectoryVendor();
             final Class<? extends SchemaExtender> implClass = IMPLEMENTATIONS.get(vendor);
             final SchemaExtender schemaExtenderImpl = implClass.newInstance();
             schemaExtenderImpl.init(chaiProvider);

+ 3 - 4
server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java

@@ -22,7 +22,6 @@
 
 package password.pwm.ldap.search;
 
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
@@ -149,7 +148,7 @@ public class UserSearchEngine implements PwmService {
             if (inputIdentity != null) {
                 try {
                     final ChaiUser theUser = pwmApplication.getProxiedChaiUser(inputIdentity);
-                    if (theUser.isValid()) {
+                    if (theUser.exists()) {
                         final String canonicalDN;
                         canonicalDN = theUser.readCanonicalDN();
                         return new UserIdentity(canonicalDN, inputIdentity.getLdapProfileID());
@@ -525,8 +524,8 @@ public class UserSearchEngine implements PwmService {
         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()) {
+            final ChaiUser user = provider.getEntryFactory().newChaiUser(userDN);
+            if (user.exists()) {
                 try {
                     return new UserIdentity(user.readCanonicalDN(), ldapProfile.getIdentifier());
                 } catch (ChaiOperationException e) {

+ 1 - 0
server/src/main/java/password/pwm/svc/PwmServiceEnum.java

@@ -29,6 +29,7 @@ import java.util.Collections;
 import java.util.List;
 
 public enum PwmServiceEnum {
+    LocalDBService(         password.pwm.util.localdb.LocalDBService.class,         Flag.StartDuringRuntimeInstance),
     SecureService(          password.pwm.util.secure.SecureService.class,           Flag.StartDuringRuntimeInstance),
     LdapConnectionService(  password.pwm.ldap.LdapConnectionService.class,          Flag.StartDuringRuntimeInstance),
     DatabaseService(        password.pwm.util.db.DatabaseService.class,             Flag.StartDuringRuntimeInstance),

+ 0 - 22
server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java

@@ -130,28 +130,6 @@ public class SessionTrackService implements PwmService {
         return Collections.emptyMap();
     }
 
-    public int ldapConnectionCount() {
-        int counter = 0;
-        try {
-            for (final String identifer : pwmApplication.getConfig().getLdapProfiles().keySet()) {
-                if (pwmApplication.getProxyChaiProvider(identifer).isConnected()) {
-                    counter++;
-                }
-            }
-
-            for (final PwmSession loopSession : currentValidSessionSet()) {
-                if (loopSession != null) {
-                    if (loopSession.getSessionManager().hasActiveLdapConnection()) {
-                        counter++;
-                    }
-                }
-            }
-        } catch (Exception e) {
-            LOGGER.error("unexpected error counting ldap connections: " + e.getMessage());
-        }
-        return counter;
-    }
-
     private Set<PwmSession> currentValidSessionSet() {
         final Set<PwmSession> returnSet = new HashSet<>();
         for (final PwmSession pwmSession : copyOfSessionSet()) {

+ 4 - 2
server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java

@@ -22,7 +22,7 @@
 
 package password.pwm.svc.telemetry;
 
-import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import lombok.Builder;
 import lombok.Getter;
 import password.pwm.AppProperty;
@@ -140,6 +140,8 @@ public class TelemetryService implements PwmService {
         executorService = JavaHelper.makeSingleThreadExecutorService(pwmApplication, TelemetryService.class);
 
         scheduleNextJob();
+
+        status = STATUS.OPEN;
     }
 
     private void initSender() throws PwmUnrecoverableException
@@ -256,7 +258,7 @@ public class TelemetryService implements PwmService {
             }
         }
 
-        final Set<ChaiProvider.DIRECTORY_VENDOR> ldapVendors = new LinkedHashSet<>();
+        final Set<DirectoryVendor> ldapVendors = new LinkedHashSet<>();
         for (final LdapProfile ldapProfile : config.getLdapProfiles().values()) {
             try {
                 ldapVendors.add(ldapProfile.getProxyChaiProvider(pwmApplication).getDirectoryVendor());

+ 1 - 1
server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java

@@ -63,7 +63,7 @@ public class ImportResponsesCommand extends AbstractCliCommand {
 
                 final UserIdentity userIdentity = UserIdentity.fromDelimitedKey(inputData.username);
                 final ChaiUser user = pwmApplication.getProxiedChaiUser(userIdentity);
-                if (user.isValid()) {
+                if (user.exists()) {
                     out("writing responses to user '" + user.getEntryDN() + "'");
                     try {
                         final ChallengeProfile challengeProfile = pwmApplication.getCrService().readUserChallengeProfile(

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

@@ -51,7 +51,8 @@ public class LdapSchemaExtendCommand extends AbstractCliCommand {
             console.writer().flush();
             bindPW = new String(console.readPassword());
         }
-        final ChaiProvider chaiProvider = ChaiProviderFactory.createProvider(ldapUrl, bindDN, bindPW);
+        final ChaiProviderFactory chaiProviderFactory = cliEnvironment.getPwmApplication().getLdapConnectionService().getChaiProviderFactory();
+        final ChaiProvider chaiProvider = chaiProviderFactory.newProvider(ldapUrl, bindDN, bindPW);
         final SchemaOperationResult operationResult = SchemaManager.extendSchema(chaiProvider);
         final boolean checkOk = operationResult.isSuccess();
         if (checkOk) {

+ 56 - 0
server/src/main/java/password/pwm/util/localdb/LocalDBService.java

@@ -0,0 +1,56 @@
+package password.pwm.util.localdb;
+
+import password.pwm.PwmApplication;
+import password.pwm.config.option.DataStorageMethod;
+import password.pwm.error.PwmException;
+import password.pwm.health.HealthRecord;
+import password.pwm.svc.PwmService;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LocalDBService implements PwmService {
+    private PwmApplication pwmApplication;
+
+    @Override
+    public STATUS status() {
+        if ( pwmApplication != null
+                && pwmApplication.getLocalDB() != null
+                && pwmApplication.getLocalDB().status() == LocalDB.Status.OPEN)
+        {
+            return STATUS.OPEN;
+        }
+
+        return STATUS.CLOSED;
+    }
+
+    @Override
+    public void init(final PwmApplication pwmApplication) throws PwmException {
+        this.pwmApplication = pwmApplication;
+    }
+
+    @Override
+    public void close() {
+        //no-op
+    }
+
+    @Override
+    public List<HealthRecord> healthCheck() {
+        return null;
+    }
+
+    @Override
+    public ServiceInfoBean serviceInfo() {
+        final Map<String,String> returnInfo = new LinkedHashMap<>();
+        if (status() == STATUS.OPEN) {
+            final Map<String,Serializable> localDbInfo = pwmApplication.getLocalDB().debugInfo();
+            for (final Map.Entry<String,Serializable> entry : localDbInfo.entrySet()) {
+                returnInfo.put( entry.getKey(), String.valueOf(entry.getValue()) );
+            }
+        }
+        return new ServiceInfoBean(Collections.singleton(DataStorageMethod.LOCALDB), Collections.unmodifiableMap(returnInfo));
+    }
+}

+ 1 - 0
server/src/main/java/password/pwm/util/localdb/Xodus_LocalDB.java

@@ -159,6 +159,7 @@ public class Xodus_LocalDB implements LocalDBProvider {
         final EnvironmentConfig environmentConfig = new EnvironmentConfig();
         environmentConfig.setEnvCloseForcedly(true);
         environmentConfig.setMemoryUsage(50 * 1024 * 1024);
+        environmentConfig.setEnvGatherStatistics(true);
 
         for (final Map.Entry<String,String> entry : initParameters.entrySet()) {
             final String key = entry.getKey();

+ 1 - 1
server/src/main/java/password/pwm/util/logging/PwmLogEvent.java

@@ -39,7 +39,7 @@ import java.time.Instant;
 @EqualsAndHashCode
 public class PwmLogEvent implements Serializable, Comparable {
 
-    private static final int MAX_MESSAGE_LENGTH = 100_000;
+    private static final int MAX_MESSAGE_LENGTH = 50_000;
 
     private final PwmLogLevel level;
 

+ 2 - 2
server/src/main/java/password/pwm/util/operations/CrService.java

@@ -32,7 +32,7 @@ import com.novell.ldapchai.exception.ChaiException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiValidationException;
 import com.novell.ldapchai.impl.edir.NmasCrFactory;
-import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.bean.ResponseInfoBean;
@@ -132,7 +132,7 @@ public class CrService implements PwmService {
 
         if (config.readSettingAsBoolean(PwmSetting.EDIRECTORY_READ_CHALLENGE_SET)) {
             try {
-                if (theUser.getChaiProvider().getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY) {
+                if (theUser.getChaiProvider().getDirectoryVendor() == DirectoryVendor.EDIRECTORY) {
                     if (policy != null && policy.getChaiPasswordPolicy() != null) {
                         returnSet = NmasCrFactory.readAssignedChallengeSet(theUser.getChaiProvider(), policy.getChaiPasswordPolicy(), locale);
                     }

+ 13 - 9
server/src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java

@@ -24,7 +24,6 @@ package password.pwm.util.operations.cr;
 
 import com.novell.ldap.LDAPConnection;
 import com.novell.ldap.LDAPException;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.cr.ChaiChallenge;
 import com.novell.ldapchai.cr.ChaiChallengeSet;
@@ -44,6 +43,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiProviderImplementor;
 import com.novell.ldapchai.provider.ChaiSetting;
+import com.novell.ldapchai.provider.DirectoryVendor;
 import com.novell.ldapchai.provider.JLDAPProviderImpl;
 import com.novell.security.nmas.client.NMASCallback;
 import com.novell.security.nmas.client.NMASCompletionCallback;
@@ -244,7 +244,7 @@ public class NMASCrOperator implements CrOperator {
         pwmApplication.getIntruderManager().convenience().checkUserIdentity(userIdentity);
 
         try {
-            if (theUser.getChaiProvider().getDirectoryVendor() != ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY) {
+            if (theUser.getChaiProvider().getDirectoryVendor() != DirectoryVendor.EDIRECTORY) {
                 return null;
             }
 
@@ -266,7 +266,7 @@ public class NMASCrOperator implements CrOperator {
             throws PwmUnrecoverableException
     {
         try {
-            if (theUser.getChaiProvider().getDirectoryVendor() != ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY) {
+            if (theUser.getChaiProvider().getDirectoryVendor() != DirectoryVendor.EDIRECTORY) {
                 LOGGER.debug("skipping request to read NMAS responses for " + userIdentity + ", directory type is not eDirectory");
                 return null;
             }
@@ -290,7 +290,7 @@ public class NMASCrOperator implements CrOperator {
             throws PwmUnrecoverableException
     {
         try {
-            if (theUser.getChaiProvider().getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY) {
+            if (theUser.getChaiProvider().getDirectoryVendor() == DirectoryVendor.EDIRECTORY) {
                 NmasCrFactory.clearResponseSet(theUser);
                 LOGGER.info("cleared responses for user " + theUser.getEntryDN() + " using NMAS method ");
             }
@@ -311,7 +311,7 @@ public class NMASCrOperator implements CrOperator {
             throws PwmUnrecoverableException
     {
         try {
-            if (theUser.getChaiProvider().getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY) {
+            if (theUser.getChaiProvider().getDirectoryVendor() == DirectoryVendor.EDIRECTORY) {
 
                 final NmasResponseSet nmasResponseSet = NmasCrFactory.newNmasResponseSet(
                         responseInfoBean.getCrMap(),
@@ -389,9 +389,12 @@ public class NMASCrOperator implements CrOperator {
             final List<String> ldapURLs = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS);
             final String proxyDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN);
             final PasswordData proxyPW = ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD);
-            chaiConfiguration = LdapOperationsHelper.createChaiConfiguration(config, ldapProfile, ldapURLs, proxyDN,
+            final ChaiConfiguration newChaiConfig = LdapOperationsHelper.createChaiConfiguration(config, ldapProfile, ldapURLs, proxyDN,
                     proxyPW);
-            chaiConfiguration.setSetting(ChaiSetting.PROVIDER_IMPLEMENTATION, JLDAPProviderImpl.class.getName());
+
+            chaiConfiguration = ChaiConfiguration.builder(newChaiConfig)
+                    .setSetting(ChaiSetting.PROVIDER_IMPLEMENTATION, JLDAPProviderImpl.class.getName())
+                    .build();
 
             cycle();
         }
@@ -408,8 +411,9 @@ public class NMASCrOperator implements CrOperator {
         }
 
         private LDAPConnection makeLdapConnection() throws Exception {
-            final ChaiProvider chaiProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
-            final ChaiUser theUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider);
+            final ChaiProviderFactory chaiProviderFactory = pwmApplication.getLdapConnectionService().getChaiProviderFactory();
+            final ChaiProvider chaiProvider = chaiProviderFactory.newProvider(chaiConfiguration);
+            final ChaiUser theUser = chaiProvider.getEntryFactory().newChaiUser(userIdentity.getUserDN());
             try {
                 if (theUser.isPasswordLocked()) {
                     LOGGER.trace("user " + theUser.getEntryDN() + " appears to be intruder locked, aborting nmas ResponseSet loading" );

+ 1 - 2
server/src/main/java/password/pwm/ws/server/RestServlet.java

@@ -1,7 +1,6 @@
 package password.pwm.ws.server;
 
 import com.google.gson.stream.MalformedJsonException;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
@@ -306,7 +305,7 @@ public abstract class RestServlet extends HttpServlet{
 
         public ChaiUser getChaiUser() throws PwmUnrecoverableException {
             try {
-                return ChaiFactory.createChaiUser(userIdentity.getUserDN(), getChaiProvider());
+                return getChaiProvider().getEntryFactory().newChaiUser(userIdentity.getUserDN());
             } catch (ChaiUnavailableException e) {
                 throw PwmUnrecoverableException.fromChaiException(e);
             }

+ 18 - 1
server/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp

@@ -238,9 +238,13 @@
                         </td>
                     </tr>
                     <% for (final AppDashboardData.ServiceData loopService : appDashboardData.getServices()) { %>
-                    <tr>
+                    <tr id="serviceName-<%=loopService.getName()%>">
                         <td>
                             <%= loopService.getName() %>
+                            <% if (!JavaHelper.isEmpty(loopService.getDebugData())) { %>
+                            &nbsp;
+                            <div class="btn-icon pwm-icon pwm-icon-list-alt"></div>
+                            <% } %>
                         </td>
                         <td>
                             <%= loopService.getStatus() %>
@@ -430,6 +434,19 @@
                     }})
                 });
             });
+            <% for (final AppDashboardData.ServiceData loopService : appDashboardData.getServices()) { %>
+            <% if (!JavaHelper.isEmpty(loopService.getDebugData())) { %>
+            PWM_MAIN.addEventHandler('serviceName-<%=loopService.getName()%>','click',function(){
+                var tableText = '<table>';
+                <% for (final Map.Entry<String,String> entry : loopService.getDebugData().entrySet()) { %>
+                tableText += '<tr><td><%=StringUtil.escapeJS(entry.getKey())%></td>'
+                + '<td><%=StringUtil.escapeJS(entry.getValue())%></td></tr>';
+                <% } %>
+                tableText += '</table>';
+                PWM_MAIN.showDialog({title:'Debug Properties',text:tableText});
+            });
+            <% } %>
+            <% } %>
         });
     </script>
 </pwm:script>

+ 2 - 1
server/src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp

@@ -41,7 +41,8 @@
     boolean existingSchemaGood = false;
     String schemaActivityLog = "";
     try {
-        final SchemaOperationResult schemaManager = ConfigGuideUtils.extendSchema(configGuideBean,false);
+        final PwmApplication pwmApplication = JspUtility.getPwmRequest(pageContext).getPwmApplication();
+        final SchemaOperationResult schemaManager = ConfigGuideUtils.extendSchema(pwmApplication, configGuideBean, false);
         existingSchemaGood = schemaManager.isSuccess();
         schemaActivityLog = schemaManager.getOperationLog();
     } catch (Exception e) {