浏览代码

bugfix omnibus

Jason Rivard 8 年之前
父节点
当前提交
43a358e2a6
共有 46 个文件被更改,包括 863 次插入615 次删除
  1. 2 2
      README.md
  2. 12 1
      pom.xml
  3. 7 2
      src/main/java/password/pwm/AppProperty.java
  4. 3 0
      src/main/java/password/pwm/PwmAboutProperty.java
  5. 3 10
      src/main/java/password/pwm/PwmConstants.java
  6. 17 15
      src/main/java/password/pwm/bean/pub/PublicUserInfoBean.java
  7. 24 1
      src/main/java/password/pwm/config/FormConfiguration.java
  8. 1 1
      src/main/java/password/pwm/config/PwmSettingXml.java
  9. 3 3
      src/main/java/password/pwm/config/stored/NGStorageEngineImpl.java
  10. 5 6
      src/main/java/password/pwm/config/stored/NGStoredConfiguration.java
  11. 5 6
      src/main/java/password/pwm/config/stored/NGStoredConfigurationFactory.java
  12. 2 2
      src/main/java/password/pwm/config/stored/StoredConfiguration.java
  13. 15 14
      src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  14. 4 4
      src/main/java/password/pwm/config/stored/ValueMetaData.java
  15. 7 1
      src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  16. 3 2
      src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  17. 2 2
      src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  18. 12 8
      src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java
  19. 9 0
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  20. 6 6
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  21. 0 2
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  22. 62 16
      src/main/java/password/pwm/ldap/LdapConnectionService.java
  23. 26 10
      src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java
  24. 2 2
      src/main/java/password/pwm/ldap/search/SearchConfiguration.java
  25. 44 11
      src/main/java/password/pwm/svc/token/CryptoTokenMachine.java
  26. 0 91
      src/main/java/password/pwm/svc/token/DBTokenMachine.java
  27. 149 0
      src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java
  28. 23 19
      src/main/java/password/pwm/svc/token/LdapTokenMachine.java
  29. 0 93
      src/main/java/password/pwm/svc/token/LocalDBTokenMachine.java
  30. 70 0
      src/main/java/password/pwm/svc/token/StoredTokenKey.java
  31. 27 0
      src/main/java/password/pwm/svc/token/TokenKey.java
  32. 8 9
      src/main/java/password/pwm/svc/token/TokenMachine.java
  33. 17 23
      src/main/java/password/pwm/svc/token/TokenPayload.java
  34. 83 121
      src/main/java/password/pwm/svc/token/TokenService.java
  35. 1 1
      src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java
  36. 94 1
      src/main/java/password/pwm/util/java/JavaHelper.java
  37. 60 82
      src/main/java/password/pwm/util/localdb/Xodus_LocalDB.java
  38. 11 1
      src/main/java/password/pwm/ws/server/RestServerHelper.java
  39. 1 1
      src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java
  40. 7 3
      src/main/resources/password/pwm/AppProperty.properties
  41. 23 31
      src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp
  42. 1 1
      src/main/webapp/WEB-INF/jsp/admin-tokenlookup.jsp
  43. 4 4
      src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp
  44. 4 4
      src/main/webapp/WEB-INF/jsp/fragment/token-form-field.jsp
  45. 4 2
      src/main/webapp/WEB-INF/jsp/updateprofile-confirm.jsp
  46. 0 1
      src/main/webapp/public/resources/js/main.js

+ 2 - 2
README.md

@@ -7,8 +7,8 @@ Official project page is at [https://github.com/pwm-project/pwm/](https://github
 # Links
 # Links
 * [PWM-General Google Group](https://groups.google.com/group/pwm-general) - please ask for assistance here first.
 * [PWM-General Google Group](https://groups.google.com/group/pwm-general) - please ask for assistance here first.
 * [PWM Documentation Wiki](https://github.com/pwm-project/pwm/wiki) - Home for PWM documentation
 * [PWM Documentation Wiki](https://github.com/pwm-project/pwm/wiki) - Home for PWM documentation
-* [Current Builds](http://www.pwm-project.org/artifacts/pwm/) - Current downloads built from recent github project commits
-* [PWM Reference](http://www.pwm-project.org/pwm/public/reference/) - Reference documentation built into PWM.
+* [Current Builds](https://www.pwm-project.org/artifacts/pwm/) - Current downloads built from recent github project commits
+* [PWM Reference](https://www.pwm-project.org/pwm/public/reference/) - Reference documentation built into PWM.
 
 
 Features
 Features
 * Web based configuration manager with over 400 configurable settings
 * Web based configuration manager with over 400 configurable settings

+ 12 - 1
pom.xml

@@ -22,7 +22,7 @@
     </licenses>
     </licenses>
 
 
     <organization>
     <organization>
-        <name>Pwm Project</name>
+        <name>PWM Project</name>
         <url>http://www.pwm-project.org</url>
         <url>http://www.pwm-project.org</url>
     </organization>
     </organization>
 
 
@@ -176,6 +176,17 @@
                 <configuration>
                 <configuration>
                     <archiveClasses>true</archiveClasses>
                     <archiveClasses>true</archiveClasses>
                     <packagingExcludes>WEB-INF/classes</packagingExcludes>
                     <packagingExcludes>WEB-INF/classes</packagingExcludes>
+                    <archive>
+                        <manifestEntries>
+                            <Implementation-Title>${project.name}</Implementation-Title>
+                            <Implementation-Version>${project.version}</Implementation-Version>
+                            <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+                            <Implementation-URL>${project.organization.url}</Implementation-URL>
+                            <Implementation-Build>${build.number}</Implementation-Build>
+                            <Implementation-Revision>${build.revision}</Implementation-Revision>
+                            <Implementation-Version-Display>v${project.version} b${build.number} r${build.revision}</Implementation-Version-Display>
+                        </manifestEntries>
+                    </archive>
                 </configuration>
                 </configuration>
             </plugin>
             </plugin>
             <plugin>
             <plugin>

+ 7 - 2
src/main/java/password/pwm/AppProperty.java

@@ -172,6 +172,8 @@ public enum     AppProperty {
     LDAP_CACHE_USER_GUID_ENABLE                     ("ldap.cache.userGuid.enable"),
     LDAP_CACHE_USER_GUID_ENABLE                     ("ldap.cache.userGuid.enable"),
     LDAP_CACHE_USER_GUID_SECONDS                    ("ldap.cache.userGuid.seconds"),
     LDAP_CACHE_USER_GUID_SECONDS                    ("ldap.cache.userGuid.seconds"),
     LDAP_CHAI_SETTINGS                              ("ldap.chaiSettings"),
     LDAP_CHAI_SETTINGS                              ("ldap.chaiSettings"),
+    LDAP_PROXY_CONNECTION_PER_PROFILE               ("ldap.proxy.connectionsPerProfile"),
+    LDAP_PROXY_MAX_CONNECTIONS                      ("ldap.proxy.maxConnections"),
     LDAP_EXTENSIONS_NMAS_ENABLE                     ("ldap.extensions.nmas.enable"),
     LDAP_EXTENSIONS_NMAS_ENABLE                     ("ldap.extensions.nmas.enable"),
     LDAP_CONNECTION_TIMEOUT                         ("ldap.connection.timeoutMS"),
     LDAP_CONNECTION_TIMEOUT                         ("ldap.connection.timeoutMS"),
     LDAP_PROFILE_RETRY_DELAY                        ("ldap.profile.retryDelayMS"),
     LDAP_PROFILE_RETRY_DELAY                        ("ldap.profile.retryDelayMS"),
@@ -187,6 +189,7 @@ public enum     AppProperty {
     LDAP_SEARCH_PARALLEL_ENABLE                     ("ldap.search.parallel.enable"),
     LDAP_SEARCH_PARALLEL_ENABLE                     ("ldap.search.parallel.enable"),
     LDAP_SEARCH_PARALLEL_FACTOR                     ("ldap.search.parallel.factor"),
     LDAP_SEARCH_PARALLEL_FACTOR                     ("ldap.search.parallel.factor"),
     LDAP_SEARCH_PARALLEL_THREAD_MAX                 ("ldap.search.parallel.threadMax"),
     LDAP_SEARCH_PARALLEL_THREAD_MAX                 ("ldap.search.parallel.threadMax"),
+    LDAP_ORACLE_POST_TEMPPW_USE_CURRENT_TIME        ("ldap.oracle.postTempPasswordUseCurrentTime"),
     LOGGING_PATTERN                                 ("logging.pattern"),
     LOGGING_PATTERN                                 ("logging.pattern"),
     LOGGING_FILE_MAX_SIZE                           ("logging.file.maxSize"),
     LOGGING_FILE_MAX_SIZE                           ("logging.file.maxSize"),
     LOGGING_FILE_MAX_ROLLOVER                       ("logging.file.maxRollover"),
     LOGGING_FILE_MAX_ROLLOVER                       ("logging.file.maxRollover"),
@@ -247,6 +250,7 @@ public enum     AppProperty {
     SECURITY_HTTP_STRIP_HEADER_REGEX                ("security.http.stripHeaderRegex"),
     SECURITY_HTTP_STRIP_HEADER_REGEX                ("security.http.stripHeaderRegex"),
     SECURITY_HTTP_PERFORM_CSRF_HEADER_CHECKS        ("security.http.performCsrfHeaderChecks"),
     SECURITY_HTTP_PERFORM_CSRF_HEADER_CHECKS        ("security.http.performCsrfHeaderChecks"),
     SECURITY_HTTP_PROMISCUOUS_ENABLE                ("security.http.promiscuousEnable"),
     SECURITY_HTTP_PROMISCUOUS_ENABLE                ("security.http.promiscuousEnable"),
+    SECURITY_HTTP_CONFIG_CSP_HEADER                 ("security.http.config.cspHeader"),
     SECURITY_HTTPSSERVER_SELF_FUTURESECONDS         ("security.httpsServer.selfCert.futureSeconds"),
     SECURITY_HTTPSSERVER_SELF_FUTURESECONDS         ("security.httpsServer.selfCert.futureSeconds"),
     SECURITY_HTTPSSERVER_SELF_ALG                   ("security.httpsServer.selfCert.alg"),
     SECURITY_HTTPSSERVER_SELF_ALG                   ("security.httpsServer.selfCert.alg"),
     SECURITY_HTTPSSERVER_SELF_KEY_SIZE              ("security.httpsServer.selfCert.keySize"),
     SECURITY_HTTPSSERVER_SELF_KEY_SIZE              ("security.httpsServer.selfCert.keySize"),
@@ -267,11 +271,12 @@ public enum     AppProperty {
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ("security.defaultEphemeralHashAlg"),
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ("security.defaultEphemeralHashAlg"),
     SEEDLIST_BUILTIN_PATH                           ("seedlist.builtin.path"),
     SEEDLIST_BUILTIN_PATH                           ("seedlist.builtin.path"),
     SMTP_SUBJECT_ENCODING_CHARSET                   ("smtp.subjectEncodingCharset"),
     SMTP_SUBJECT_ENCODING_CHARSET                   ("smtp.subjectEncodingCharset"),
-    TOKEN_REMOVAL_DELAY_MS                          ("token.removalDelayMS"),
-    TOKEN_PURGE_BATCH_SIZE                          ("token.purgeBatchSize"),
     TOKEN_MAX_UNIQUE_CREATE_ATTEMPTS                ("token.maxUniqueCreateAttempts"),
     TOKEN_MAX_UNIQUE_CREATE_ATTEMPTS                ("token.maxUniqueCreateAttempts"),
     TOKEN_RESEND_ENABLED                            ("token.resend.enabled"),
     TOKEN_RESEND_ENABLED                            ("token.resend.enabled"),
     TOKEN_RESEND_DELAY_MS                           ("token.resend.delayMS"),
     TOKEN_RESEND_DELAY_MS                           ("token.resend.delayMS"),
+    TOKEN_REMOVE_ON_CLAIM                           ("token.removeOnClaim"),
+    TOKEN_VERIFY_PW_MODIFY_TIME                     ("token.verifyPwModifyTime"),
+
 
 
     /** Regular expression to be used for matching URLs to be shortened by the URL Shortening Service Class. */
     /** Regular expression to be used for matching URLs to be shortened by the URL Shortening Service Class. */
     URL_SHORTNER_URL_REGEX                          ("urlshortener.url.regex"),
     URL_SHORTNER_URL_REGEX                          ("urlshortener.url.regex"),

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

@@ -31,6 +31,7 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmRandom;
 
 
+import java.nio.charset.Charset;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Date;
@@ -90,6 +91,7 @@ public enum PwmAboutProperty {
     java_osName,
     java_osName,
     java_osVersion,
     java_osVersion,
     java_randomAlgorithm,
     java_randomAlgorithm,
+    java_defaultCharset,
 
 
     database_driverName,
     database_driverName,
     database_driverVersion,
     database_driverVersion,
@@ -182,6 +184,7 @@ public enum PwmAboutProperty {
             aboutMap.put(java_osName,              System.getProperty("os.name"));
             aboutMap.put(java_osName,              System.getProperty("os.name"));
             aboutMap.put(java_osVersion,           System.getProperty("os.version"));
             aboutMap.put(java_osVersion,           System.getProperty("os.version"));
             aboutMap.put(java_randomAlgorithm,     PwmRandom.getInstance().getAlgorithm());
             aboutMap.put(java_randomAlgorithm,     PwmRandom.getInstance().getAlgorithm());
+            aboutMap.put(java_defaultCharset,      Charset.defaultCharset().name());
         }
         }
 
 
         { // build info
         { // build info

+ 3 - 10
src/main/java/password/pwm/PwmConstants.java

@@ -29,12 +29,10 @@ import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmHashAlgorithm;
 
 
 import java.nio.charset.Charset;
 import java.nio.charset.Charset;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.Date;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.ResourceBundle;
 import java.util.ResourceBundle;
@@ -49,7 +47,7 @@ public abstract class PwmConstants {
 // ------------------------------ FIELDS ------------------------------
 // ------------------------------ FIELDS ------------------------------
 
 
     // ------------------------- PUBLIC CONSTANTS -------------------------
     // ------------------------- PUBLIC CONSTANTS -------------------------
-    public static final String BUILD_TIME           = readBuildInfoBundle("build.time",SimpleDateFormat.getDateTimeInstance().format(new Date()));
+    public static final String BUILD_TIME           = readBuildInfoBundle("build.time", Instant.now().toString());
     public static final String BUILD_NUMBER         = readBuildInfoBundle("build.number","0");
     public static final String BUILD_NUMBER         = readBuildInfoBundle("build.number","0");
     public static final String BUILD_TYPE           = readBuildInfoBundle("build.type","");
     public static final String BUILD_TYPE           = readBuildInfoBundle("build.type","");
     public static final String BUILD_USER           = readBuildInfoBundle("build.user",System.getProperty("user.name"));
     public static final String BUILD_USER           = readBuildInfoBundle("build.user",System.getProperty("user.name"));
@@ -96,11 +94,6 @@ public abstract class PwmConstants {
 
 
     public static final String DEFAULT_DATETIME_FORMAT_STR = readPwmConstantsBundle("locale.defaultDateTimeFormat");
     public static final String DEFAULT_DATETIME_FORMAT_STR = readPwmConstantsBundle("locale.defaultDateTimeFormat");
     public static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone(readPwmConstantsBundle("locale.defaultTimeZone"));
     public static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone(readPwmConstantsBundle("locale.defaultTimeZone"));
-    public static final DateFormat DEFAULT_DATETIME_FORMAT = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT_STR, DEFAULT_LOCALE);
-
-    static {
-        DEFAULT_DATETIME_FORMAT.setTimeZone(DEFAULT_TIMEZONE);
-    }
 
 
     public static final String APPLICATION_PATH_INFO_FILE = readPwmConstantsBundle("applicationPathInfoFile");
     public static final String APPLICATION_PATH_INFO_FILE = readPwmConstantsBundle("applicationPathInfoFile");
 
 
@@ -109,7 +102,7 @@ public abstract class PwmConstants {
     public static final int TRIAL_MAX_AUTHENTICATIONS = 100;
     public static final int TRIAL_MAX_AUTHENTICATIONS = 100;
     public static final int TRIAL_MAX_TOTAL_AUTH = 10000;
     public static final int TRIAL_MAX_TOTAL_AUTH = 10000;
 
 
-    private static final String SESSION_LABEL_SESSION_ID = "#";
+    public static final String SESSION_LABEL_SESSION_ID = "#";
     public static final SessionLabel REPORTING_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"reporting",null,null);
     public static final SessionLabel REPORTING_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"reporting",null,null);
     public static final SessionLabel HEALTH_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"health",null,null);
     public static final SessionLabel HEALTH_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"health",null,null);
     public static final SessionLabel CLI_SESSION_LABEL= new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"cli",null,null);
     public static final SessionLabel CLI_SESSION_LABEL= new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"cli",null,null);

+ 17 - 15
src/main/java/password/pwm/bean/pub/PublicUserInfoBean.java

@@ -22,6 +22,7 @@
 
 
 package password.pwm.bean.pub;
 package password.pwm.bean.pub;
 
 
+import lombok.Getter;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
@@ -37,22 +38,23 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 
 
+@Getter
 public class PublicUserInfoBean implements Serializable {
 public class PublicUserInfoBean implements Serializable {
-    public String userDN;
-    public String ldapProfile;
-    public String userID;
-    public String userEmailAddress;
-    public Instant passwordExpirationTime;
-    public Instant passwordLastModifiedTime;
-    public boolean requiresNewPassword;
-    public boolean requiresResponseConfig;
-    public boolean requiresUpdateProfile;
-    public boolean requiresInteraction;
+    private String userDN;
+    private String ldapProfile;
+    private String userID;
+    private String userEmailAddress;
+    private Instant passwordExpirationTime;
+    private Instant passwordLastModifiedTime;
+    private boolean requiresNewPassword;
+    private boolean requiresResponseConfig;
+    private boolean requiresUpdateProfile;
+    private boolean requiresInteraction;
 
 
-    public PasswordStatus passwordStatus;
-    public Map<String, String> passwordPolicy;
-    public List<String> passwordRules;
-    public Map<String, String> attributes;
+    private PasswordStatus passwordStatus;
+    private Map<String, String> passwordPolicy;
+    private List<String> passwordRules;
+    private Map<String, String> attributes;
 
 
     public static PublicUserInfoBean fromUserInfoBean(final UserInfoBean userInfoBean, final Configuration config, final Locale locale, final MacroMachine macroMachine) {
     public static PublicUserInfoBean fromUserInfoBean(final UserInfoBean userInfoBean, final Configuration config, final Locale locale, final MacroMachine macroMachine) {
         final PublicUserInfoBean publicUserInfoBean = new PublicUserInfoBean();
         final PublicUserInfoBean publicUserInfoBean = new PublicUserInfoBean();
@@ -66,7 +68,7 @@ public class PublicUserInfoBean implements Serializable {
 
 
         publicUserInfoBean.requiresNewPassword = userInfoBean.isRequiresNewPassword();
         publicUserInfoBean.requiresNewPassword = userInfoBean.isRequiresNewPassword();
         publicUserInfoBean.requiresResponseConfig = userInfoBean.isRequiresResponseConfig();
         publicUserInfoBean.requiresResponseConfig = userInfoBean.isRequiresResponseConfig();
-        publicUserInfoBean.requiresUpdateProfile = userInfoBean.isRequiresResponseConfig();
+        publicUserInfoBean.requiresUpdateProfile = userInfoBean.isRequiresUpdateProfile();
         publicUserInfoBean.requiresInteraction = userInfoBean.isRequiresNewPassword()
         publicUserInfoBean.requiresInteraction = userInfoBean.isRequiresNewPassword()
                 || userInfoBean.isRequiresResponseConfig()
                 || userInfoBean.isRequiresResponseConfig()
                 || userInfoBean.isRequiresUpdateProfile()
                 || userInfoBean.isRequiresUpdateProfile()

+ 24 - 1
src/main/java/password/pwm/config/FormConfiguration.java

@@ -29,8 +29,10 @@ import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.java.JsonUtil;
+import password.pwm.i18n.Display;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.LocaleHelper;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.math.BigInteger;
 import java.math.BigInteger;
@@ -345,4 +347,25 @@ public class FormConfiguration implements Serializable {
         final Matcher matcher = pattern.matcher(address);
         final Matcher matcher = pattern.matcher(address);
         return matcher.matches();
         return matcher.matches();
     }
     }
+
+    public String displayValue(final String value, final Locale locale, final Configuration config) {
+        if (value == null) {
+            return LocaleHelper.getLocalizedMessage(locale, Display.Value_NotApplicable, config);
+        }
+
+        if (this.getType() == Type.select) {
+            if (this.getSelectOptions() != null) {
+                for (final String key : selectOptions.keySet()) {
+                    if (value.equals(key)) {
+                        final String displayValue = selectOptions.get(key);
+                        if (!StringUtil.isEmpty(displayValue)) {
+                            return displayValue;
+                        }
+                    }
+                }
+            }
+        }
+
+        return value;
+    }
 }
 }

+ 1 - 1
src/main/java/password/pwm/config/PwmSettingXml.java

@@ -74,7 +74,7 @@ public class PwmSettingXml {
                         xmlDocCache = null;
                         xmlDocCache = null;
                     }
                     }
                 };
                 };
-                t.setDaemon(false);
+                t.setDaemon(true);
                 t.start();
                 t.start();
 
 
                 return newDoc;
                 return newDoc;

+ 3 - 3
src/main/java/password/pwm/config/stored/NGStorageEngineImpl.java

@@ -25,7 +25,7 @@ package password.pwm.config.stored;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 
 
-import java.util.Date;
+import java.time.Instant;
 import java.util.Map;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.TreeMap;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -75,7 +75,7 @@ class NGStorageEngineImpl implements StorageEngine {
                 changeLog.updateChangeLog(reference, value);
                 changeLog.updateChangeLog(reference, value);
             }
             }
             values.put(reference, value);
             values.put(reference, value);
-            final ValueMetaData valueMetaData = new ValueMetaData(new Date(), userIdentity);
+            final ValueMetaData valueMetaData = new ValueMetaData(Instant.now(), userIdentity);
             metaValues.put(reference, valueMetaData);
             metaValues.put(reference, valueMetaData);
         } finally {
         } finally {
             bigLock.writeLock().unlock();
             bigLock.writeLock().unlock();
@@ -94,7 +94,7 @@ class NGStorageEngineImpl implements StorageEngine {
             }
             }
             values.remove(reference);
             values.remove(reference);
             if (metaValues.containsKey(reference)) {
             if (metaValues.containsKey(reference)) {
-                final ValueMetaData valueMetaData = new ValueMetaData(new Date(), userIdentity);
+                final ValueMetaData valueMetaData = new ValueMetaData(Instant.now(), userIdentity);
                 metaValues.put(reference, valueMetaData);
                 metaValues.put(reference, valueMetaData);
             }
             }
         } finally {
         } finally {

+ 5 - 6
src/main/java/password/pwm/config/stored/NGStoredConfiguration.java

@@ -22,18 +22,17 @@
 
 
 package password.pwm.config.stored;
 package password.pwm.config.stored;
 
 
-import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 
 
-import java.text.ParseException;
-import java.util.Date;
+import java.time.Instant;
 import java.util.Map;
 import java.util.Map;
 
 
 class NGStoredConfiguration implements StoredConfiguration {
 class NGStoredConfiguration implements StoredConfiguration {
@@ -164,12 +163,12 @@ class NGStoredConfiguration implements StoredConfiguration {
         return engine.readMetaData(storedConfigReference);
         return engine.readMetaData(storedConfigReference);
     }
     }
 
 
-    public Date modifyTime() {
+    public Instant modifyTime() {
         final String modifyTimeString = readConfigProperty(ConfigurationProperty.MODIFIFICATION_TIMESTAMP);
         final String modifyTimeString = readConfigProperty(ConfigurationProperty.MODIFIFICATION_TIMESTAMP);
         if (modifyTimeString != null) {
         if (modifyTimeString != null) {
             try {
             try {
-                return PwmConstants.DEFAULT_DATETIME_FORMAT.parse(modifyTimeString);
-            } catch (ParseException e) {
+                return JavaHelper.parseIsoToInstant((modifyTimeString));
+            } catch (Exception e) {
                 LOGGER.error("error parsing last modified timestamp property: " + e.getMessage());
                 LOGGER.error("error parsing last modified timestamp property: " + e.getMessage());
             }
             }
         }
         }

+ 5 - 6
src/main/java/password/pwm/config/stored/NGStoredConfigurationFactory.java

@@ -24,7 +24,6 @@ package password.pwm.config.stored;
 
 
 import org.jdom2.Document;
 import org.jdom2.Document;
 import org.jdom2.Element;
 import org.jdom2.Element;
-import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
@@ -32,14 +31,14 @@ import password.pwm.config.value.StringValue;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.XmlUtil;
 import password.pwm.util.java.XmlUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 
 
 import java.io.InputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStream;
-import java.text.ParseException;
-import java.util.Date;
+import java.time.Instant;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map;
 
 
@@ -204,12 +203,12 @@ public class NGStoredConfigurationFactory {
         static ValueMetaData readValueMetaData(final Element element)
         static ValueMetaData readValueMetaData(final Element element)
         {
         {
             final String modifyDateStr = element.getAttributeValue(StoredConfiguration.XML_ATTRIBUTE_MODIFY_TIME);
             final String modifyDateStr = element.getAttributeValue(StoredConfiguration.XML_ATTRIBUTE_MODIFY_TIME);
-            Date modifyDate = null;
+            Instant modifyDate = null;
             try {
             try {
                 modifyDate = modifyDateStr == null || modifyDateStr.isEmpty()
                 modifyDate = modifyDateStr == null || modifyDateStr.isEmpty()
                         ? null
                         ? null
-                        : PwmConstants.DEFAULT_DATETIME_FORMAT.parse(modifyDateStr);
-            } catch (ParseException e) {
+                        : JavaHelper.parseIsoToInstant(modifyDateStr);
+            } catch (Exception e) {
                 LOGGER.warn("error parsing stored date: " +  e.getMessage());
                 LOGGER.warn("error parsing stored date: " +  e.getMessage());
             }
             }
             final String modifyUser = element.getAttributeValue(StoredConfiguration.XML_ATTRIBUTE_MODIFY_USER);
             final String modifyUser = element.getAttributeValue(StoredConfiguration.XML_ATTRIBUTE_MODIFY_USER);

+ 2 - 2
src/main/java/password/pwm/config/stored/StoredConfiguration.java

@@ -29,7 +29,7 @@ import password.pwm.config.StoredValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 
 
-import java.util.Date;
+import java.time.Instant;
 
 
 public interface StoredConfiguration {
 public interface StoredConfiguration {
     String XML_ELEMENT_ROOT = "PwmConfiguration";
     String XML_ELEMENT_ROOT = "PwmConfiguration";
@@ -56,7 +56,7 @@ public interface StoredConfiguration {
 
 
     PwmSecurityKey getKey() throws PwmUnrecoverableException;
     PwmSecurityKey getKey() throws PwmUnrecoverableException;
 
 
-    Date modifyTime();
+    Instant modifyTime();
 
 
     boolean isLocked();
     boolean isLocked();
 
 

+ 15 - 14
src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -70,7 +70,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStream;
 import java.io.Serializable;
 import java.io.Serializable;
-import java.text.ParseException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
@@ -199,7 +199,7 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
 
 
     public StoredConfigurationImpl() throws PwmUnrecoverableException {
     public StoredConfigurationImpl() throws PwmUnrecoverableException {
         ConfigurationCleaner.cleanup(this);
         ConfigurationCleaner.cleanup(this);
-        final String createTime = PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date());
+        final String createTime = JavaHelper.toIsoDate(Instant.now());
         document.getRootElement().setAttribute(XML_ATTRIBUTE_CREATE_TIME,createTime);
         document.getRootElement().setAttribute(XML_ATTRIBUTE_CREATE_TIME,createTime);
     }
     }
 
 
@@ -237,8 +237,8 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
 
 
             final XPathExpression xp2 = XPathBuilder.xpathForConfigProperties();
             final XPathExpression xp2 = XPathBuilder.xpathForConfigProperties();
             final Element propertiesElement = (Element)xp2.evaluateFirst(document);
             final Element propertiesElement = (Element)xp2.evaluateFirst(document);
-            propertyElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-            propertiesElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+            propertyElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate(Instant.now()));
+            propertiesElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate(Instant.now()));
             propertiesElement.addContent(propertyElement);
             propertiesElement.addContent(propertyElement);
         } finally {
         } finally {
             domModifyLock.writeLock().unlock();
             domModifyLock.writeLock().unlock();
@@ -284,7 +284,7 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
             final ValueMetaData settingMetaData = readSettingMetadata(settingValueRecord.getSetting(), settingValueRecord.getProfile());
             final ValueMetaData settingMetaData = readSettingMetadata(settingValueRecord.getSetting(), settingValueRecord.getProfile());
             if (settingMetaData != null) {
             if (settingMetaData != null) {
                 if (settingMetaData.getModifyDate() != null) {
                 if (settingMetaData.getModifyDate() != null) {
-                    recordMap.put("modifyTime", PwmConstants.DEFAULT_DATETIME_FORMAT.format(settingMetaData.getModifyDate()));
+                    recordMap.put("modifyTime", JavaHelper.toIsoDate(settingMetaData.getModifyDate()));
                 }
                 }
                 if (settingMetaData.getUserIdentity() != null) {
                 if (settingMetaData.getUserIdentity() != null) {
                     recordMap.put("modifyUser",settingMetaData.getUserIdentity().toDisplayString());
                     recordMap.put("modifyUser",settingMetaData.getUserIdentity().toDisplayString());
@@ -532,10 +532,10 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
             return null;
             return null;
         }
         }
 
 
-        Date modifyDate = null;
+        Instant modifyDate = null;
         try {
         try {
             if (settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME) != null) {
             if (settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME) != null) {
-                modifyDate = PwmConstants.DEFAULT_DATETIME_FORMAT.parse(
+                modifyDate = JavaHelper.parseIsoToInstant(
                         settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME));
                         settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME));
             }
             }
         } catch (Exception e) {
         } catch (Exception e) {
@@ -724,7 +724,7 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
                 valueElement.setContent(new CDATA(localeMap.get(locale)));
                 valueElement.setContent(new CDATA(localeMap.get(locale)));
                 localeBundleElement.addContent(valueElement);
                 localeBundleElement.addContent(valueElement);
             }
             }
-            localeBundleElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+            localeBundleElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate(Instant.now()));
             document.getRootElement().addContent(localeBundleElement);
             document.getRootElement().addContent(localeBundleElement);
         } finally {
         } finally {
             domModifyLock.writeLock().unlock();
             domModifyLock.writeLock().unlock();
@@ -845,7 +845,7 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
         if (locked) {
         if (locked) {
             throw new UnsupportedOperationException("StoredConfiguration is locked and cannot be modified");
             throw new UnsupportedOperationException("StoredConfiguration is locked and cannot be modified");
         }
         }
-        document.getRootElement().setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+        document.getRootElement().setAttribute(XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate(Instant.now()));
     }
     }
 
 
 // -------------------------- INNER CLASSES --------------------------
 // -------------------------- INNER CLASSES --------------------------
@@ -1384,8 +1384,8 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
 
 
     private static void updateMetaData(final Element settingElement, final UserIdentity userIdentity) {
     private static void updateMetaData(final Element settingElement, final UserIdentity userIdentity) {
         final Element settingsElement = settingElement.getDocument().getRootElement().getChild(XML_ELEMENT_SETTINGS);
         final Element settingsElement = settingElement.getDocument().getRootElement().getChild(XML_ELEMENT_SETTINGS);
-        settingElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-        settingsElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+        settingElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate(Instant.now()));
+        settingsElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate(Instant.now()));
         settingElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
         settingElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
         settingsElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
         settingsElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
         if (userIdentity != null) {
         if (userIdentity != null) {
@@ -1517,13 +1517,13 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
     }
     }
 
 
     @Override
     @Override
-    public Date modifyTime() {
+    public Instant modifyTime() {
         final Element rootElement = document.getRootElement();
         final Element rootElement = document.getRootElement();
         final String modifyTimeString = rootElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME);
         final String modifyTimeString = rootElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME);
         if (modifyTimeString != null) {
         if (modifyTimeString != null) {
             try {
             try {
-                return PwmConstants.DEFAULT_DATETIME_FORMAT.parse(modifyTimeString);
-            } catch (ParseException e) {
+                return JavaHelper.parseIsoToInstant(modifyTimeString);
+            } catch (Exception e) {
                 LOGGER.error("error parsing root last modified timestamp: " + e.getMessage());
                 LOGGER.error("error parsing root last modified timestamp: " + e.getMessage());
             }
             }
         }
         }
@@ -1565,4 +1565,5 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
         }
         }
         return new ArrayList<>(loopResults);
         return new ArrayList<>(loopResults);
     }
     }
+
 }
 }

+ 4 - 4
src/main/java/password/pwm/config/stored/ValueMetaData.java

@@ -25,18 +25,18 @@ package password.pwm.config.stored;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
-import java.util.Date;
+import java.time.Instant;
 
 
 public class ValueMetaData implements Serializable {
 public class ValueMetaData implements Serializable {
-    private Date modifyDate;
+    private Instant modifyDate;
     private UserIdentity userIdentity;
     private UserIdentity userIdentity;
 
 
-    public ValueMetaData(final Date modifyDate, final UserIdentity userIdentity) {
+    public ValueMetaData(final Instant modifyDate, final UserIdentity userIdentity) {
         this.modifyDate = modifyDate;
         this.modifyDate = modifyDate;
         this.userIdentity = userIdentity;
         this.userIdentity = userIdentity;
     }
     }
 
 
-    public Date getModifyDate()
+    public Instant getModifyDate()
     {
     {
         return modifyDate;
         return modifyDate;
     }
     }

+ 7 - 1
src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -369,7 +369,13 @@ public class RequestInitializationFilter implements Filter {
         resp.setHeader(HttpHeader.Cache_Control, "no-cache, no-store, must-revalidate, proxy-revalidate");
         resp.setHeader(HttpHeader.Cache_Control, "no-cache, no-store, must-revalidate, proxy-revalidate");
 
 
         if (pwmSession != null) {
         if (pwmSession != null) {
-            final String contentPolicy = config.readSettingAsString(PwmSetting.SECURITY_CSP_HEADER);
+            final String contentPolicy;
+            if (pwmRequest.getURL().isConfigGuideURL() || pwmRequest.getURL().isConfigManagerURL()) {
+                contentPolicy = config.readAppProperty(AppProperty.SECURITY_HTTP_CONFIG_CSP_HEADER);
+            } else {
+                contentPolicy = config.readSettingAsString(PwmSetting.SECURITY_CSP_HEADER);
+            }
+
             if (contentPolicy != null && !contentPolicy.isEmpty()) {
             if (contentPolicy != null && !contentPolicy.isEmpty()) {
                 final String nonce = pwmRequest.getCspNonce();
                 final String nonce = pwmRequest.getCspNonce();
                 final String expandedPolicy = contentPolicy.replace("%NONCE%", nonce);
                 final String expandedPolicy = contentPolicy.replace("%NONCE%", nonce);

+ 3 - 2
src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -43,6 +43,7 @@ import password.pwm.config.profile.PwmPasswordRule;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
@@ -571,13 +572,13 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
                     userInfoBean.getPasswordLastModifiedTime(),
                     userInfoBean.getPasswordLastModifiedTime(),
                     userInfoBean.getPasswordState()
                     userInfoBean.getPasswordState()
             );
             );
-        } catch (PwmOperationalException e) {
+        } catch (PwmException e) {
             final boolean enforceFromForgotten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME);
             final boolean enforceFromForgotten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME);
             if (!enforceFromForgotten && userInfoBean.isRequiresNewPassword()) {
             if (!enforceFromForgotten && userInfoBean.isRequiresNewPassword()) {
                 LOGGER.debug(pwmSession, "current password is too young, but skipping enforcement of minimum lifetime check due to setting "
                 LOGGER.debug(pwmSession, "current password is too young, but skipping enforcement of minimum lifetime check due to setting "
                         + PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug(null, pwmSession.getSessionStateBean().getLocale()));
                         + PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug(null, pwmSession.getSessionStateBean().getLocale()));
             } else {
             } else {
-                throw e;
+                throw new PwmUnrecoverableException(e.getErrorInformation());
             }
             }
         }
         }
 
 

+ 2 - 2
src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -65,9 +65,9 @@ import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStream;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
@@ -175,7 +175,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         pwmRequest.setAttribute(PwmRequestAttribute.ApplicationPath, pwmRequest.getPwmApplication().getPwmEnvironment().getApplicationPath().getAbsolutePath());
         pwmRequest.setAttribute(PwmRequestAttribute.ApplicationPath, pwmRequest.getPwmApplication().getPwmEnvironment().getApplicationPath().getAbsolutePath());
         pwmRequest.setAttribute(PwmRequestAttribute.ConfigFilename, configurationReader.getConfigFile().getAbsolutePath());
         pwmRequest.setAttribute(PwmRequestAttribute.ConfigFilename, configurationReader.getConfigFile().getAbsolutePath());
         {
         {
-            final Date lastModifyTime = configurationReader.getStoredConfiguration().modifyTime();
+            final Instant lastModifyTime = configurationReader.getStoredConfiguration().modifyTime();
             final String output = lastModifyTime == null
             final String output = lastModifyTime == null
                     ? LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable,pwmRequest)
                     ? LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable,pwmRequest)
                     : JavaHelper.toIsoDate(lastModifyTime);
                     : JavaHelper.toIsoDate(lastModifyTime);

+ 12 - 8
src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java

@@ -344,7 +344,7 @@ public class DebugItemGenerator {
             final PrintWriter writer = new PrintWriter( new OutputStreamWriter(baos, PwmConstants.DEFAULT_CHARSET) );
             final PrintWriter writer = new PrintWriter( new OutputStreamWriter(baos, PwmConstants.DEFAULT_CHARSET) );
             final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true,true);
             final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true,true);
             for (final ThreadInfo threadInfo : threads) {
             for (final ThreadInfo threadInfo : threads) {
-                writer.write(threadInfo.toString());
+                writer.write(JavaHelper.threadInfoToString(threadInfo));
             }
             }
             writer.flush();
             writer.flush();
             outputStream.write(baos.toByteArray());
             outputStream.write(baos.toByteArray());
@@ -417,13 +417,17 @@ public class DebugItemGenerator {
                     csvPrinter.printComment(StringUtil.join(headerRow,","));
                     csvPrinter.printComment(StringUtil.join(headerRow,","));
                 }
                 }
                 for (final FileSystemUtility.FileSummaryInformation fileSummaryInformation : fileSummaryInformations) {
                 for (final FileSystemUtility.FileSummaryInformation fileSummaryInformation : fileSummaryInformations) {
-                    final List<String> dataRow = new ArrayList<>();
-                    dataRow.add(fileSummaryInformation.getFilepath());
-                    dataRow.add(fileSummaryInformation.getFilename());
-                    dataRow.add(JavaHelper.toIsoDate(fileSummaryInformation.getModified()));
-                    dataRow.add(String.valueOf(fileSummaryInformation.getSize()));
-                    dataRow.add(fileSummaryInformation.getSha1sum());
-                    csvPrinter.printRecord(dataRow);
+                    try {
+                        final List<String> dataRow = new ArrayList<>();
+                        dataRow.add(fileSummaryInformation.getFilepath());
+                        dataRow.add(fileSummaryInformation.getFilename());
+                        dataRow.add(JavaHelper.toIsoDate(fileSummaryInformation.getModified()));
+                        dataRow.add(String.valueOf(fileSummaryInformation.getSize()));
+                        dataRow.add(fileSummaryInformation.getSha1sum());
+                        csvPrinter.printRecord(dataRow);
+                    } catch (Exception e) {
+                        LOGGER.trace("error generating file summary info: " + e.getMessage());
+                    }
                 }
                 }
                 csvPrinter.flush();
                 csvPrinter.flush();
             }
             }

+ 9 - 0
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -202,6 +202,15 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
 
 
         checkForLocaleSwitch(pwmRequest, forgottenPasswordBean);
         checkForLocaleSwitch(pwmRequest, forgottenPasswordBean);
 
 
+        final ProcessAction action = this.readProcessAction(pwmRequest);
+
+        // convert a url command like /public/newuser/12321321 to redirect with a process action.
+        if (action == null) {
+            if (pwmRequest.convertURLtokenCommand()) {
+                return ProcessStatus.Halt;
+            }
+        }
+
         return ProcessStatus.Continue;
         return ProcessStatus.Continue;
     }
     }
 
 

+ 6 - 6
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -47,7 +47,6 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
@@ -115,23 +114,24 @@ class ForgottenPasswordUtil {
         return Collections.unmodifiableSet(result);
         return Collections.unmodifiableSet(result);
     }
     }
 
 
-    static UserInfoBean readUserInfoBean(final PwmRequest pwmRequest, final ForgottenPasswordBean forgottenPasswordBean) throws PwmUnrecoverableException
-    {
+    static UserInfoBean readUserInfoBean(final PwmRequest pwmRequest, final ForgottenPasswordBean forgottenPasswordBean) throws PwmUnrecoverableException {
         if (forgottenPasswordBean.getUserIdentity() == null) {
         if (forgottenPasswordBean.getUserIdentity() == null) {
             return null;
             return null;
         }
         }
 
 
+        final String CACHE_KEY = "ForgottenPassword-UserInfoCache";
+
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
 
 
         {
         {
-            final UserInfoBean beanInRequest = (UserInfoBean)pwmRequest.getAttribute(PwmRequestAttribute.ForgottenPasswordUserInfo);
+            final UserInfoBean beanInRequest = (UserInfoBean)pwmRequest.getHttpServletRequest().getSession().getAttribute(CACHE_KEY);
             if (beanInRequest != null) {
             if (beanInRequest != null) {
                 if (userIdentity.equals(beanInRequest.getUserIdentity())) {
                 if (userIdentity.equals(beanInRequest.getUserIdentity())) {
                     LOGGER.trace(pwmRequest, "using request cached UserInfoBean");
                     LOGGER.trace(pwmRequest, "using request cached UserInfoBean");
                     return beanInRequest;
                     return beanInRequest;
                 } else {
                 } else {
                     LOGGER.trace(pwmRequest, "request cached userInfoBean is not for current user, clearing.");
                     LOGGER.trace(pwmRequest, "request cached userInfoBean is not for current user, clearing.");
-                    pwmRequest.setAttribute(PwmRequestAttribute.ForgottenPasswordUserInfo, null);
+                    pwmRequest.getHttpServletRequest().getSession().setAttribute(CACHE_KEY, null);
                 }
                 }
             }
             }
         }
         }
@@ -146,7 +146,7 @@ class ForgottenPasswordUtil {
                 chaiProvider
                 chaiProvider
         );
         );
 
 
-        pwmRequest.setAttribute(PwmRequestAttribute.ForgottenPasswordUserInfo, userInfoBean);
+        pwmRequest.getHttpServletRequest().getSession().setAttribute(CACHE_KEY, userInfoBean);
 
 
         return userInfoBean;
         return userInfoBean;
     }
     }

+ 0 - 2
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -139,10 +139,8 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet {
         final String username = pwmRequest.readParameterAsString("username", PwmHttpRequestWrapper.Flag.BypassValidation);
         final String username = pwmRequest.readParameterAsString("username", PwmHttpRequestWrapper.Flag.BypassValidation);
         final boolean includeDisplayName = pwmRequest.readParameterAsBoolean("includeDisplayName");
         final boolean includeDisplayName = pwmRequest.readParameterAsBoolean("includeDisplayName");
 
 
-        // if not in cache, build results from ldap
         final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader(pwmRequest);
         final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader(pwmRequest);
         final SearchResultBean searchResultBean = peopleSearchDataReader.makeSearchResultBean(username, includeDisplayName);
         final SearchResultBean searchResultBean = peopleSearchDataReader.makeSearchResultBean(username, includeDisplayName);
-        searchResultBean.setFromCache(false);
         final RestResultBean restResultBean = new RestResultBean(searchResultBean);
         final RestResultBean restResultBean = new RestResultBean(searchResultBean);
 
 
         addExpiresHeadersToResponse(pwmRequest);
         addExpiresHeadersToResponse(pwmRequest);

+ 62 - 16
src/main/java/password/pwm/ldap/LdapConnectionService.java

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  * http://www.pwm-project.org
  *
  *
  * Copyright (c) 2006-2009 Novell, Inc.
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2016 The PWM Project
  *
  *
  * This program is free software; you can redistribute it and/or modify
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@ package password.pwm.ldap;
 
 
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.reflect.TypeToken;
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProvider;
+import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.LdapProfile;
@@ -41,14 +42,18 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 
 public class LdapConnectionService implements PwmService {
 public class LdapConnectionService implements PwmService {
     private static final PwmLogger LOGGER = PwmLogger.forClass(LdapConnectionService.class);
     private static final PwmLogger LOGGER = PwmLogger.forClass(LdapConnectionService.class);
 
 
-    private final Map<String,ChaiProvider> proxyChaiProviders = new HashMap<>();
-    private final Map<LdapProfile,ErrorInformation> lastLdapErrors = new HashMap<>();
+    private final Map<LdapProfile, Map<Integer,ChaiProvider>> proxyChaiProviders = new ConcurrentHashMap<>();
+    private final Map<LdapProfile, ErrorInformation> lastLdapErrors = new ConcurrentHashMap<>();
     private PwmApplication pwmApplication;
     private PwmApplication pwmApplication;
     private STATUS status = STATUS.NEW;
     private STATUS status = STATUS.NEW;
+    private int connectionsPerProfile = 1;
+    private final AtomicInteger slotIncrementer = new AtomicInteger(0);
 
 
     public STATUS status()
     public STATUS status()
     {
     {
@@ -63,6 +68,13 @@ public class LdapConnectionService implements PwmService {
         // read the lastLoginTime
         // read the lastLoginTime
         this.lastLdapErrors.putAll(readLastLdapFailure(pwmApplication));
         this.lastLdapErrors.putAll(readLastLdapFailure(pwmApplication));
 
 
+        connectionsPerProfile = maxSlotsPerProfile(pwmApplication);
+        LOGGER.trace("allocating " + connectionsPerProfile + " ldap proxy connections per profile");
+
+        for (final LdapProfile ldapProfile: pwmApplication.getConfig().getLdapProfiles().values()) {
+            proxyChaiProviders.put(ldapProfile, new ConcurrentHashMap<>());
+        }
+
         status = STATUS.OPEN;
         status = STATUS.OPEN;
     }
     }
 
 
@@ -70,13 +82,15 @@ public class LdapConnectionService implements PwmService {
     {
     {
         status = STATUS.CLOSED;
         status = STATUS.CLOSED;
         LOGGER.trace("closing ldap proxy connections");
         LOGGER.trace("closing ldap proxy connections");
-        for (final String id : proxyChaiProviders.keySet()) {
-            final ChaiProvider existingProvider = proxyChaiProviders.get(id);
-
-            try {
-                existingProvider.close();
-            } catch (Exception e) {
-                LOGGER.error("error closing ldap proxy connection: " + e.getMessage(), e);
+        for (final LdapProfile ldapProfile : proxyChaiProviders.keySet()) {
+            for (final int slot : proxyChaiProviders.get(ldapProfile).keySet()) {
+                final ChaiProvider existingProvider = proxyChaiProviders.get(ldapProfile).get(slot);
+
+                try {
+                    existingProvider.close();
+                } catch (Exception e) {
+                    LOGGER.error("error closing ldap proxy connection: " + e.getMessage(), e);
+                }
             }
             }
         }
         }
         proxyChaiProviders.clear();
         proxyChaiProviders.clear();
@@ -93,21 +107,28 @@ public class LdapConnectionService implements PwmService {
     }
     }
 
 
 
 
-    public ChaiProvider getProxyChaiProvider(final LdapProfile ldapProfile)
+    public ChaiProvider getProxyChaiProvider(final String identifier)
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        return getProxyChaiProvider(ldapProfile.getIdentifier());
+        final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(identifier);
+        return getProxyChaiProvider(ldapProfile);
     }
     }
 
 
-    public ChaiProvider getProxyChaiProvider(final String identifier)
+    public ChaiProvider getProxyChaiProvider(final LdapProfile identifier)
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final ChaiProvider proxyChaiProvider = proxyChaiProviders.get(identifier == null ? "" : identifier);
+        final int slot = nextSlot();
+
+        final ChaiProvider proxyChaiProvider = proxyChaiProviders.get(identifier).get(slot);
+
         if (proxyChaiProvider != null) {
         if (proxyChaiProvider != null) {
             return proxyChaiProvider;
             return proxyChaiProvider;
         }
         }
 
 
-        final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(identifier == null ? "" : identifier);
+        final LdapProfile ldapProfile = identifier == null
+                ? pwmApplication.getConfig().getDefaultLdapProfile()
+                : identifier;
+
         if (ldapProfile == null) {
         if (ldapProfile == null) {
             final String errorMsg = "unknown ldap profile requested connection: " + identifier;
             final String errorMsg = "unknown ldap profile requested connection: " + identifier;
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NO_LDAP_CONNECTION,errorMsg));
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NO_LDAP_CONNECTION,errorMsg));
@@ -120,7 +141,7 @@ public class LdapConnectionService implements PwmService {
                     pwmApplication.getConfig(),
                     pwmApplication.getConfig(),
                     pwmApplication.getStatisticsManager()
                     pwmApplication.getStatisticsManager()
             );
             );
-            proxyChaiProviders.put(identifier, newProvider);
+            proxyChaiProviders.get(identifier).put(slot, newProvider);
             return newProvider;
             return newProvider;
         } catch (PwmUnrecoverableException e) {
         } catch (PwmUnrecoverableException e) {
             setLastLdapFailure(ldapProfile,e.getErrorInformation());
             setLastLdapFailure(ldapProfile,e.getErrorInformation());
@@ -175,4 +196,29 @@ public class LdapConnectionService implements PwmService {
         }
         }
         return Collections.emptyMap();
         return Collections.emptyMap();
     }
     }
+
+    private int nextSlot() {
+        return slotIncrementer.getAndUpdate(operand -> {
+            operand++;
+            if (operand >= connectionsPerProfile) {
+                operand = 0;
+            }
+            return operand;
+        });
+    }
+
+    private int maxSlotsPerProfile(final PwmApplication pwmApplication) {
+        final int maxConnections = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PROXY_MAX_CONNECTIONS));
+        final int perProfile = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PROXY_CONNECTION_PER_PROFILE));
+        final int profileCount = pwmApplication.getConfig().getLdapProfiles().size();
+
+        if ((perProfile * profileCount) >= maxConnections) {
+            final int adjustedConnections = Math.min(1, (maxConnections / profileCount));
+            LOGGER.warn("connections per profile (" + perProfile + ") multiplied by number of profiles ("
+                    + profileCount + ") exceeds max connections (" + maxConnections  + "), will limit to " + adjustedConnections);
+            return adjustedConnections;
+        }
+
+        return perProfile;
+    }
 }
 }

+ 26 - 10
src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java

@@ -33,6 +33,7 @@ import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException;
 import com.novell.ldapchai.impl.oracleds.entry.OracleDSEntries;
 import com.novell.ldapchai.impl.oracleds.entry.OracleDSEntries;
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.provider.ChaiSetting;
+import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
@@ -429,13 +430,18 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
                 ORACLE_ATTR_PW_ALLOW_CHG_TIME);
                 ORACLE_ATTR_PW_ALLOW_CHG_TIME);
         log(PwmLogLevel.TRACE,"read OracleDS value of passwordAllowChangeTime value=" + oracleDS_PrePasswordAllowChangeTime);
         log(PwmLogLevel.TRACE,"read OracleDS value of passwordAllowChangeTime value=" + oracleDS_PrePasswordAllowChangeTime);
 
 
+
         if (oracleDS_PrePasswordAllowChangeTime != null && !oracleDS_PrePasswordAllowChangeTime.isEmpty()) {
         if (oracleDS_PrePasswordAllowChangeTime != null && !oracleDS_PrePasswordAllowChangeTime.isEmpty()) {
             final Date date = OracleDSEntries.convertZuluToDate(oracleDS_PrePasswordAllowChangeTime);
             final Date date = OracleDSEntries.convertZuluToDate(oracleDS_PrePasswordAllowChangeTime);
-            if (new Date().before(date)) {
-                final String errorMsg = "change not permitted until " + JavaHelper.toIsoDate(
-                        date);
-                throw new PwmUnrecoverableException(
-                        new ErrorInformation(PwmError.PASSWORD_TOO_SOON, errorMsg));
+
+            final boolean enforceFromForgotten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME);
+            if (enforceFromForgotten) {
+                if (new Date().before(date)) {
+                    final String errorMsg = "change not permitted until " + JavaHelper.toIsoDate(
+                            date);
+                    throw new PwmUnrecoverableException(
+                            new ErrorInformation(PwmError.PASSWORD_TOO_SOON, errorMsg));
+                }
             }
             }
         }
         }
 
 
@@ -471,11 +477,21 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
                     chaiUser.getEntryDN(),
                     chaiUser.getEntryDN(),
                     ORACLE_ATTR_PW_ALLOW_CHG_TIME);
                     ORACLE_ATTR_PW_ALLOW_CHG_TIME);
             if (oracleDS_PostPasswordAllowChangeTime != null && !oracleDS_PostPasswordAllowChangeTime.isEmpty()) {
             if (oracleDS_PostPasswordAllowChangeTime != null && !oracleDS_PostPasswordAllowChangeTime.isEmpty()) {
-                // password allow change time has appeared, but wasn't present previously, so delete it.
-                log(PwmLogLevel.TRACE, "a new value for passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN() + " has appeared, will remove");
-                chaiProvider.deleteStringAttributeValue(chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME,
-                        oracleDS_PostPasswordAllowChangeTime);
-                log(PwmLogLevel.TRACE, "deleted attribute value for passwordAllowChangeTime attribute on user " + chaiUser.getEntryDN());
+                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 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());
+                } else {
+                    // password allow change time has appeared, but wasn't present previously, so delete it.
+                    log(PwmLogLevel.TRACE, "a new value for passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN() + " has appeared, will remove");
+                    chaiProvider.deleteStringAttributeValue(chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME,
+                            oracleDS_PostPasswordAllowChangeTime);
+                    log(PwmLogLevel.TRACE, "deleted attribute value for passwordAllowChangeTime attribute on user " + chaiUser.getEntryDN());
+                }
+
             }
             }
         }
         }
     }
     }

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

@@ -24,7 +24,7 @@ package password.pwm.ldap.search;
 
 
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProvider;
 import lombok.Builder;
 import lombok.Builder;
-import lombok.Data;
+import lombok.Getter;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.FormConfiguration;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -32,7 +32,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
 @Builder
 @Builder
-@Data
+@Getter
 public class SearchConfiguration implements Serializable {
 public class SearchConfiguration implements Serializable {
 
 
     private String filter;
     private String filter;

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

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  * http://www.pwm-project.org
  *
  *
  * Copyright (c) 2006-2009 Novell, Inc.
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2016 The PWM Project
  *
  *
  * This program is free software; you can redistribute it and/or modify
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -25,9 +25,7 @@ package password.pwm.svc.token;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
-
-import java.util.Collections;
-import java.util.Iterator;
+import password.pwm.util.java.ClosableIterator;
 
 
 class CryptoTokenMachine implements TokenMachine {
 class CryptoTokenMachine implements TokenMachine {
 
 
@@ -53,27 +51,41 @@ class CryptoTokenMachine implements TokenMachine {
         return returnString.toString();
         return returnString.toString();
     }
     }
 
 
-    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
+    public TokenPayload retrieveToken(final TokenKey tokenKey)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        if (tokenKey == null || tokenKey.length() < 1) {
+        if (tokenKey == null || tokenKey.getStoredHash().length() < 1) {
             return null;
             return null;
         }
         }
-        return tokenService.fromEncryptedString(tokenKey);
+        return tokenService.fromEncryptedString(tokenKey.getStoredHash());
     }
     }
 
 
-    public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
+    public void storeToken(final TokenKey tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
     }
     }
 
 
-    public void removeToken(final String tokenKey, final SessionLabel sessionLabel) throws PwmOperationalException, PwmUnrecoverableException {
+    public void removeToken(final TokenKey tokenKey) throws PwmOperationalException, PwmUnrecoverableException {
     }
     }
 
 
     public int size() throws PwmOperationalException, PwmUnrecoverableException {
     public int size() throws PwmOperationalException, PwmUnrecoverableException {
         return 0;
         return 0;
     }
     }
 
 
-    public Iterator keyIterator() throws PwmOperationalException, PwmUnrecoverableException {
-        return Collections.<String>emptyList().iterator();
+    public ClosableIterator<TokenKey> keyIterator() throws PwmOperationalException, PwmUnrecoverableException {
+        return new ClosableIterator<TokenKey>() {
+            @Override
+            public void close() {
+            }
+
+            @Override
+            public boolean hasNext() {
+                return false;
+            }
+
+            @Override
+            public TokenKey next() {
+                return null;
+            }
+        };
     }
     }
 
 
     public void cleanup() {
     public void cleanup() {
@@ -83,4 +95,25 @@ class CryptoTokenMachine implements TokenMachine {
         return true;
         return true;
     }
     }
 
 
+    public TokenKey keyFromKey(final String key) throws PwmUnrecoverableException {
+        return new CryptoTokenKey(key);
+    }
+
+    @Override
+    public TokenKey keyFromStoredHash(final String storedHash) {
+        return new CryptoTokenKey(storedHash);
+    }
+
+    private static class CryptoTokenKey implements TokenKey {
+        private String value;
+
+        CryptoTokenKey(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String getStoredHash() {
+            return value;
+        }
+    }
 }
 }

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

@@ -1,91 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-package password.pwm.svc.token;
-
-import password.pwm.bean.SessionLabel;
-import password.pwm.error.PwmOperationalException;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.db.DatabaseAccessor;
-import password.pwm.util.db.DatabaseTable;
-
-import java.util.Iterator;
-
-class DBTokenMachine implements TokenMachine {
-    private DatabaseAccessor databaseAccessor;
-    private TokenService tokenService;
-
-    DBTokenMachine(final TokenService tokenService, final DatabaseAccessor databaseAccessor) {
-        this.tokenService = tokenService;
-        this.databaseAccessor = databaseAccessor;
-    }
-
-    public String generateToken(
-            final SessionLabel sessionLabel,
-            final TokenPayload tokenPayload
-    )
-            throws PwmUnrecoverableException, PwmOperationalException
-    {
-        return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
-    }
-
-    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
-            throws PwmOperationalException, PwmUnrecoverableException
-    {
-        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
-        final String storedRawValue = databaseAccessor.get(DatabaseTable.TOKENS,md5sumToken);
-
-        if (storedRawValue != null && storedRawValue.length() > 0 ) {
-            return tokenService.fromEncryptedString(storedRawValue);
-        }
-
-        return null;
-    }
-
-    public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
-        final String rawValue = tokenService.toEncryptedString(tokenPayload);
-        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
-        databaseAccessor.put(DatabaseTable.TOKENS, md5sumToken, rawValue);
-    }
-
-    public void removeToken(final String tokenKey, final SessionLabel sessionLabel) throws PwmOperationalException, PwmUnrecoverableException {
-        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
-        databaseAccessor.remove(DatabaseTable.TOKENS,tokenKey);
-        databaseAccessor.remove(DatabaseTable.TOKENS,md5sumToken);
-    }
-
-    public int size() throws PwmOperationalException, PwmUnrecoverableException {
-        return databaseAccessor.size(DatabaseTable.TOKENS);
-    }
-
-    public Iterator keyIterator() throws PwmOperationalException, PwmUnrecoverableException {
-        return databaseAccessor.iterator(DatabaseTable.TOKENS);
-    }
-
-    public void cleanup() throws PwmUnrecoverableException, PwmOperationalException {
-        tokenService.purgeOutdatedTokens();
-    }
-
-    public boolean supportsName() {
-        return true;
-    }
-}

+ 149 - 0
src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java

@@ -0,0 +1,149 @@
+package password.pwm.svc.token;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.SessionLabel;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.svc.PwmService;
+import password.pwm.util.DataStore;
+import password.pwm.util.java.ClosableIterator;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+
+public class DataStoreTokenMachine implements TokenMachine {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(DataStoreTokenMachine.class);
+    private final TokenService tokenService;
+    private final TimeDuration maxTokenPurgeAge;
+
+    private final DataStore dataStore;
+
+    private final PwmApplication pwmApplication;
+
+    DataStoreTokenMachine(
+            final PwmApplication pwmApplication,
+            final TokenService tokenService,
+            final DataStore dataStore
+    ) {
+        this.pwmApplication = pwmApplication;
+        this.tokenService = tokenService;
+        this.dataStore = dataStore;
+
+        final Configuration configuration = pwmApplication.getConfig();
+
+        final long maxTokenAgeMS = configuration.readSettingAsLong(PwmSetting.TOKEN_LIFETIME) * 1000;
+        this.maxTokenPurgeAge = new TimeDuration(maxTokenAgeMS, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public TokenKey keyFromKey(final String key) throws PwmUnrecoverableException {
+        return StoredTokenKey.fromKeyValue(pwmApplication, key);
+    }
+
+    @Override
+    public TokenKey keyFromStoredHash(final String storedHash) {
+        return StoredTokenKey.fromStoredHash(storedHash);
+    }
+
+    public void cleanup() throws PwmUnrecoverableException, PwmOperationalException {
+        if (size() < 1) {
+            return;
+        }
+
+        purgeOutdatedTokens();
+    }
+
+    private void purgeOutdatedTokens() throws
+            PwmUnrecoverableException, PwmOperationalException
+    {
+        final Instant startTime = Instant.now();
+        LOGGER.trace("beginning purge cycle; database size = " + size());
+        try (ClosableIterator<String> keyIterator = dataStore.iterator()) {
+            while (tokenService.status() == PwmService.STATUS.OPEN && keyIterator.hasNext()) {
+                final String storedHash = keyIterator.next();
+                final TokenKey loopKey = keyFromStoredHash(storedHash);
+                retrieveToken(loopKey); // retrieving token tests validity and causes purging
+            }
+        } catch (Exception e) {
+            LOGGER.error("unexpected error while cleaning expired stored tokens: " + e.getMessage());
+        }
+        LOGGER.trace("completed record purge cycle in " + TimeDuration.fromCurrent(startTime).asCompactString()
+                + "; database size = " + size());
+    }
+
+    private boolean testIfTokenNeedsPurging(final TokenPayload theToken) {
+        if (theToken == null) {
+            return false;
+        }
+        final Instant issueDate = theToken.getDate();
+        if (issueDate == null) {
+            LOGGER.error("retrieved token has no issueDate, marking as purgable: " + JsonUtil.serialize(theToken));
+            return true;
+        }
+        final TimeDuration duration = TimeDuration.fromCurrent(issueDate);
+        return duration.isLongerThan(maxTokenPurgeAge);
+    }
+
+    public String generateToken(
+            final SessionLabel sessionLabel,
+            final TokenPayload tokenPayload
+    )
+            throws PwmUnrecoverableException, PwmOperationalException
+    {
+        return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
+    }
+
+    public TokenPayload retrieveToken(final TokenKey tokenKey)
+            throws PwmOperationalException, PwmUnrecoverableException
+    {
+        final String storedHash = tokenKey.getStoredHash();
+        final String storedRawValue = dataStore.get(storedHash);
+
+        if (storedRawValue != null && storedRawValue.length() > 0 ) {
+            final TokenPayload tokenPayload;
+            try {
+                tokenPayload = tokenService.fromEncryptedString(storedRawValue);
+            } catch (PwmException e) {
+                LOGGER.trace("error while trying to decrypted stored token payload for key '" + storedHash + "', will purge record, error: " + e.getMessage());
+                dataStore.remove(storedHash);
+                return null;
+            }
+
+            if (testIfTokenNeedsPurging(tokenPayload)) {
+                LOGGER.trace("stored token key '" + storedHash + "', has an outdated issue date and will be purged");
+                dataStore.remove(storedHash);
+            } else {
+                return tokenPayload;
+            }
+        }
+
+        return null;
+    }
+
+    public void storeToken(final TokenKey tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
+        final String rawValue = tokenService.toEncryptedString(tokenPayload);
+        final String storedHash = tokenKey.getStoredHash();
+        dataStore.put(storedHash, rawValue);
+    }
+
+    public void removeToken(final TokenKey tokenKey)
+            throws PwmOperationalException, PwmUnrecoverableException
+    {
+        final String storedHash = tokenKey.getStoredHash();
+        dataStore.remove(storedHash);
+    }
+
+    public int size() throws PwmOperationalException {
+        return dataStore.size();
+    }
+
+    public boolean supportsName() {
+        return true;
+    }
+}

+ 23 - 19
src/main/java/password/pwm/svc/token/LdapTokenMachine.java

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  * http://www.pwm-project.org
  *
  *
  * Copyright (c) 2006-2009 Novell, Inc.
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2016 The PWM Project
  *
  *
  * This program is free software; you can redistribute it and/or modify
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -34,16 +34,14 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.LdapUserDataReader;
-import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
+import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchEngine;
 
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Map;
 
 
-class LdapTokenMachine implements TokenMachine {
+class LdapTokenMachine  implements TokenMachine {
     private PwmApplication pwmApplication;
     private PwmApplication pwmApplication;
     private String tokenAttribute;
     private String tokenAttribute;
     private final String KEY_VALUE_DELIMITER = " ";
     private final String KEY_VALUE_DELIMITER = " ";
@@ -66,29 +64,28 @@ class LdapTokenMachine implements TokenMachine {
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
         return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
     }
     }
 
 
-    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
+    public TokenPayload retrieveToken(final TokenKey tokenKey)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         final String searchFilter;
         final String searchFilter;
         {
         {
-            final String md5sumToken = tokenService.makeTokenHash(tokenKey);
+            final String storedHash = tokenKey.getStoredHash();
             final SearchHelper tempSearchHelper = new SearchHelper();
             final SearchHelper tempSearchHelper = new SearchHelper();
             final Map<String,String> filterAttributes = new HashMap<>();
             final Map<String,String> filterAttributes = new HashMap<>();
             for (final String loopStr : pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES)) {
             for (final String loopStr : pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES)) {
                 filterAttributes.put("objectClass", loopStr);
                 filterAttributes.put("objectClass", loopStr);
             }
             }
-            filterAttributes.put(tokenAttribute,md5sumToken + "*");
+            filterAttributes.put(tokenAttribute,storedHash + "*");
             tempSearchHelper.setFilterAnd(filterAttributes);
             tempSearchHelper.setFilterAnd(filterAttributes);
             searchFilter = tempSearchHelper.getFilter();
             searchFilter = tempSearchHelper.getFilter();
         }
         }
 
 
         try {
         try {
-            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            final UserSearchEngine userSearchEngine = new UserSearchEngine();
             final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
             final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                     .filter(searchFilter)
                     .filter(searchFilter)
                     .build();
                     .build();
-
-            final UserIdentity user = userSearchEngine.performSingleUserSearch(searchConfiguration, sessionLabel);
+            final UserIdentity user = userSearchEngine.performSingleUserSearch(searchConfiguration, null);
             if (user == null) {
             if (user == null) {
                 return null;
                 return null;
             }
             }
@@ -116,11 +113,11 @@ class LdapTokenMachine implements TokenMachine {
         return null;
         return null;
     }
     }
 
 
-    public void storeToken(final String tokenKey, final TokenPayload tokenPayload)
+    public void storeToken(final TokenKey tokenKey, final TokenPayload tokenPayload)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         try {
         try {
-            final String md5sumToken = tokenService.makeTokenHash(tokenKey);
+            final String md5sumToken = tokenKey.getStoredHash();
             final String encodedTokenPayload = tokenService.toEncryptedString(tokenPayload);
             final String encodedTokenPayload = tokenService.toEncryptedString(tokenPayload);
 
 
             final UserIdentity userIdentity = tokenPayload.getUserIdentity();
             final UserIdentity userIdentity = tokenPayload.getUserIdentity();
@@ -133,10 +130,10 @@ class LdapTokenMachine implements TokenMachine {
         }
         }
     }
     }
 
 
-    public void removeToken(final String tokenKey, final SessionLabel sessionLabel)
+    public void removeToken(final TokenKey tokenKey)
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final TokenPayload payload = retrieveToken(tokenKey, sessionLabel);
+        final TokenPayload payload = retrieveToken(tokenKey);
         if (payload != null) {
         if (payload != null) {
             final UserIdentity userIdentity = payload.getUserIdentity();
             final UserIdentity userIdentity = payload.getUserIdentity();
             try {
             try {
@@ -154,10 +151,6 @@ class LdapTokenMachine implements TokenMachine {
         return -1;
         return -1;
     }
     }
 
 
-    public Iterator keyIterator() throws PwmOperationalException {
-        return Collections.<String>emptyList().iterator();
-    }
-
     public void cleanup() throws PwmUnrecoverableException, PwmOperationalException {
     public void cleanup() throws PwmUnrecoverableException, PwmOperationalException {
     }
     }
 
 
@@ -165,4 +158,15 @@ class LdapTokenMachine implements TokenMachine {
     public boolean supportsName() {
     public boolean supportsName() {
         return false;
         return false;
     }
     }
+
+    @Override
+    public TokenKey keyFromKey(final String key) throws PwmUnrecoverableException {
+        return StoredTokenKey.fromKeyValue(pwmApplication, key);
+    }
+
+    @Override
+    public TokenKey keyFromStoredHash(final String storedHash) {
+        return StoredTokenKey.fromStoredHash(storedHash);
+    }
+
 }
 }

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

@@ -1,93 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-package password.pwm.svc.token;
-
-import password.pwm.bean.SessionLabel;
-import password.pwm.error.PwmOperationalException;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.localdb.LocalDB;
-
-class LocalDBTokenMachine implements TokenMachine {
-    private LocalDB localDB;
-    private TokenService tokenService;
-
-    LocalDBTokenMachine(
-            final TokenService tokenService,
-            final LocalDB localDB
-    ) {
-        this.tokenService = tokenService;
-        this.localDB = localDB;
-    }
-
-    public String generateToken(
-            final SessionLabel sessionLabel,
-            final TokenPayload tokenPayload
-    )
-            throws PwmUnrecoverableException, PwmOperationalException
-    {
-        return tokenService.makeUniqueTokenForMachine(sessionLabel, this);
-    }
-
-    public TokenPayload retrieveToken(final String tokenKey, final SessionLabel sessionLabel)
-            throws PwmOperationalException, PwmUnrecoverableException
-    {
-        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
-        final String storedRawValue = localDB.get(LocalDB.DB.TOKENS, md5sumToken);
-
-        if (storedRawValue != null && storedRawValue.length() > 0 ) {
-            return tokenService.fromEncryptedString(storedRawValue);
-        }
-
-        return null;
-    }
-
-    public void storeToken(final String tokenKey, final TokenPayload tokenPayload) throws PwmOperationalException, PwmUnrecoverableException {
-        final String rawValue = tokenService.toEncryptedString(tokenPayload);
-        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
-        localDB.put(LocalDB.DB.TOKENS, md5sumToken, rawValue);
-    }
-
-    public void removeToken(final String tokenKey, final SessionLabel sessionLabel)
-            throws PwmOperationalException, PwmUnrecoverableException
-    {
-        final String md5sumToken = tokenService.makeTokenHash(tokenKey);
-        localDB.remove(LocalDB.DB.TOKENS, tokenKey);
-        localDB.remove(LocalDB.DB.TOKENS, md5sumToken);
-    }
-
-    public int size() throws PwmOperationalException {
-        return localDB.size(LocalDB.DB.TOKENS);
-    }
-
-    public LocalDB.LocalDBIterator<String> keyIterator() throws PwmOperationalException {
-        return localDB.iterator(LocalDB.DB.TOKENS);
-    }
-
-    public void cleanup() throws PwmUnrecoverableException, PwmOperationalException {
-        tokenService.purgeOutdatedTokens();
-    }
-
-    public boolean supportsName() {
-        return true;
-    }
-}

+ 70 - 0
src/main/java/password/pwm/svc/token/StoredTokenKey.java

@@ -0,0 +1,70 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.svc.token;
+
+import password.pwm.PwmApplication;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.secure.SecureService;
+
+class StoredTokenKey implements TokenKey {
+    private static final String SUFFIX = "-hash";
+
+    private final String storedHash;
+
+    private StoredTokenKey(final String storedHash) {
+        this.storedHash = storedHash;
+    }
+
+    public String getStoredHash() {
+        return storedHash;
+    }
+    
+    static StoredTokenKey fromStoredHash(final String storedHash) {
+        if (storedHash == null) {
+            throw new NullPointerException();
+        }
+        
+        if (!storedHash.endsWith(SUFFIX)) {
+            throw new IllegalArgumentException("stored hash value has improper suffix");
+        }
+        
+        return new StoredTokenKey(storedHash);
+    }
+
+    static StoredTokenKey fromKeyValue(final PwmApplication pwmApplication, final String input)
+            throws PwmUnrecoverableException
+    {
+        if (input == null) {
+            throw new NullPointerException();
+        }
+
+        if (input.endsWith(SUFFIX)) {
+            throw new IllegalArgumentException("new key value has stored suffix");
+        }
+
+        final SecureService secureService = pwmApplication.getSecureService();
+        final String storedHash = secureService.hash(input) + SUFFIX;
+
+        return new StoredTokenKey(storedHash);
+    }
+}

+ 27 - 0
src/main/java/password/pwm/svc/token/TokenKey.java

@@ -0,0 +1,27 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.svc.token;
+
+interface TokenKey {
+    String getStoredHash();
+}

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

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  * http://www.pwm-project.org
  *
  *
  * Copyright (c) 2006-2009 Novell, Inc.
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2016 The PWM Project
  *
  *
  * This program is free software; you can redistribute it and/or modify
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -26,29 +26,28 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 
 
-import java.util.Iterator;
-
 interface TokenMachine {
 interface TokenMachine {
     String generateToken( SessionLabel sessionLabel,  TokenPayload tokenPayload)
     String generateToken( SessionLabel sessionLabel,  TokenPayload tokenPayload)
             throws PwmUnrecoverableException, PwmOperationalException;
             throws PwmUnrecoverableException, PwmOperationalException;
 
 
-    TokenPayload retrieveToken(String tokenKey, SessionLabel sessionLabel)
+    TokenPayload retrieveToken(TokenKey tokenKey)
             throws PwmOperationalException, PwmUnrecoverableException;
             throws PwmOperationalException, PwmUnrecoverableException;
 
 
-    void storeToken( String tokenKey,  TokenPayload tokenPayload)
+    void storeToken(TokenKey tokenKey, TokenPayload tokenPayload)
             throws PwmOperationalException, PwmUnrecoverableException;
             throws PwmOperationalException, PwmUnrecoverableException;
 
 
-    void removeToken(String tokenKey, SessionLabel sessionLabel)
+    void removeToken(TokenKey tokenKey)
             throws PwmOperationalException, PwmUnrecoverableException;
             throws PwmOperationalException, PwmUnrecoverableException;
 
 
     int size()
     int size()
             throws PwmOperationalException, PwmUnrecoverableException;
             throws PwmOperationalException, PwmUnrecoverableException;
 
 
-    Iterator<String> keyIterator()
-            throws PwmOperationalException, PwmUnrecoverableException;
-
     void cleanup()
     void cleanup()
             throws PwmUnrecoverableException, PwmOperationalException;
             throws PwmUnrecoverableException, PwmOperationalException;
 
 
     boolean supportsName();
     boolean supportsName();
+
+    TokenKey keyFromKey(String key) throws PwmUnrecoverableException;
+
+    TokenKey keyFromStoredHash(String storedHash);
 }
 }

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

@@ -22,19 +22,27 @@
 
 
 package password.pwm.svc.token;
 package password.pwm.svc.token;
 
 
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.JsonUtil;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
+@Getter
 public class TokenPayload implements Serializable {
 public class TokenPayload implements Serializable {
     private final Instant date;
     private final Instant date;
     private final String name;
     private final String name;
     private final Map<String,String> data;
     private final Map<String,String> data;
-    private final UserIdentity user;
+
+    @SerializedName("user")
+    private final UserIdentity userIdentity;
     private final Set<String> dest;
     private final Set<String> dest;
     private final String guid;
     private final String guid;
 
 
@@ -42,32 +50,18 @@ public class TokenPayload implements Serializable {
         this.date = Instant.now();
         this.date = Instant.now();
         this.data = data == null ? Collections.emptyMap() : Collections.unmodifiableMap(data);
         this.data = data == null ? Collections.emptyMap() : Collections.unmodifiableMap(data);
         this.name = name;
         this.name = name;
-        this.user = user;
+        this.userIdentity = user;
         this.dest = dest == null ? Collections.emptySet() : Collections.unmodifiableSet(dest);
         this.dest = dest == null ? Collections.emptySet() : Collections.unmodifiableSet(dest);
         this.guid = guid;
         this.guid = guid;
     }
     }
 
 
-    public Instant getDate() {
-        return date;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public Map<String, String> getData() {
-        return data;
-    }
-
-    public UserIdentity getUserIdentity() {
-        return user;
-    }
-
-    public Set<String> getDest() {
-        return dest;
-    }
 
 
-    public String getGuid() {
-        return guid;
+    public String toDebugString() {
+        final Map<String,String> debugMap = new HashMap<>();
+        debugMap.put("date", JavaHelper.toIsoDate(date));
+        debugMap.put("name", getName());
+        debugMap.put("user", getUserIdentity().toDisplayString());
+        debugMap.put("guid", getGuid());
+        return JsonUtil.serializeMap(debugMap);
     }
     }
 }
 }

+ 83 - 121
src/main/java/password/pwm/svc/token/TokenService.java

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  * http://www.pwm-project.org
  *
  *
  * Copyright (c) 2006-2009 Novell, Inc.
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2016 The PWM Project
  *
  *
  * This program is free software; you can redistribute it and/or modify
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * it under the terms of the GNU General Public License as published by
@@ -54,10 +54,14 @@ import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.util.DataStore;
+import password.pwm.util.db.DatabaseDataStore;
+import password.pwm.util.db.DatabaseTable;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDB;
+import password.pwm.util.localdb.LocalDBDataStore;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.PasswordUtility;
@@ -67,7 +71,7 @@ import password.pwm.util.secure.SecureService;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.Iterator;
+
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
@@ -87,8 +91,8 @@ public class TokenService implements PwmService {
 
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(TokenService.class);
     private static final PwmLogger LOGGER = PwmLogger.forClass(TokenService.class);
 
 
-    private static final long MAX_CLEANER_INTERVAL_MS = 24 * 60 * 60 * 1000; // one day
-    private static final long MIN_CLEANER_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
+    private static final TimeDuration MAX_CLEANER_INTERVAL_MS = new TimeDuration(1, TimeUnit.DAYS);
+    private static final TimeDuration MIN_CLEANER_INTERVAL_MS = new TimeDuration(5, TimeUnit.MINUTES);
 
 
     private ScheduledExecutorService executorService;
     private ScheduledExecutorService executorService;
 
 
@@ -96,7 +100,6 @@ public class TokenService implements PwmService {
     private Configuration configuration;
     private Configuration configuration;
     private TokenStorageMethod storageMethod;
     private TokenStorageMethod storageMethod;
     private long maxTokenAgeMS;
     private long maxTokenAgeMS;
-    private long maxTokenPurgeAgeMS;
     private TokenMachine tokenMachine;
     private TokenMachine tokenMachine;
     private long counter;
     private long counter;
 
 
@@ -105,12 +108,19 @@ public class TokenService implements PwmService {
 
 
     private ErrorInformation errorInformation = null;
     private ErrorInformation errorInformation = null;
 
 
+    private boolean verifyPwModifyTime = true;
+
     public TokenService()
     public TokenService()
             throws PwmOperationalException
             throws PwmOperationalException
     {
     {
     }
     }
 
 
-    public synchronized TokenPayload createTokenPayload(final TokenType name, final Map<String, String> data, final UserIdentity userIdentity, final Set<String> dest) {
+    public synchronized TokenPayload createTokenPayload(
+            final TokenType name,
+            final Map<String, String> data,
+            final UserIdentity userIdentity,
+            final Set<String> dest
+    ) {
         final long count = counter++;
         final long count = counter++;
         final StringBuilder guid = new StringBuilder();
         final StringBuilder guid = new StringBuilder();
         try {
         try {
@@ -142,7 +152,6 @@ public class TokenService implements PwmService {
         }
         }
         try {
         try {
             maxTokenAgeMS = configuration.readSettingAsLong(PwmSetting.TOKEN_LIFETIME) * 1000;
             maxTokenAgeMS = configuration.readSettingAsLong(PwmSetting.TOKEN_LIFETIME) * 1000;
-            maxTokenPurgeAgeMS = maxTokenAgeMS + Long.parseLong(configuration.readAppProperty(AppProperty.TOKEN_REMOVAL_DELAY_MS));
         } catch (Exception e) {
         } catch (Exception e) {
             final String errorMsg = "unable to parse max token age value: " + e.getMessage();
             final String errorMsg = "unable to parse max token age value: " + e.getMessage();
             errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,errorMsg);
             errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,errorMsg);
@@ -153,15 +162,19 @@ public class TokenService implements PwmService {
         try {
         try {
             DataStorageMethod usedStorageMethod = null;
             DataStorageMethod usedStorageMethod = null;
             switch (storageMethod) {
             switch (storageMethod) {
-                case STORE_LOCALDB:
-                    tokenMachine = new LocalDBTokenMachine(this, pwmApplication.getLocalDB());
+                case STORE_LOCALDB: {
+                    final DataStore dataStore = new LocalDBDataStore(pwmApplication.getLocalDB(), LocalDB.DB.TOKENS);
+                    tokenMachine = new DataStoreTokenMachine(pwmApplication, this, dataStore);
                     usedStorageMethod = DataStorageMethod.LOCALDB;
                     usedStorageMethod = DataStorageMethod.LOCALDB;
                     break;
                     break;
+                }
 
 
-                case STORE_DB:
-                    tokenMachine = new DBTokenMachine(this, pwmApplication.getDatabaseAccessor());
+                case STORE_DB: {
+                    final DataStore dataStore = new DatabaseDataStore(pwmApplication.getDatabaseAccessor(), DatabaseTable.TOKENS);
+                    tokenMachine = new DataStoreTokenMachine(pwmApplication, this, dataStore);
                     usedStorageMethod = DataStorageMethod.DB;
                     usedStorageMethod = DataStorageMethod.DB;
                     break;
                     break;
+                }
 
 
                 case STORE_CRYPTO:
                 case STORE_CRYPTO:
                     tokenMachine = new CryptoTokenMachine(this);
                     tokenMachine = new CryptoTokenMachine(this);
@@ -194,11 +207,13 @@ public class TokenService implements PwmService {
 
 
         final TimerTask cleanerTask = new CleanerTask();
         final TimerTask cleanerTask = new CleanerTask();
 
 
-        final long cleanerFrequency = (maxTokenAgeMS*0.5) > MAX_CLEANER_INTERVAL_MS
-                ? MAX_CLEANER_INTERVAL_MS
-                : (maxTokenAgeMS*0.5) < MIN_CLEANER_INTERVAL_MS
-                ? MIN_CLEANER_INTERVAL_MS
-                : (long)(maxTokenAgeMS*0.5);
+        final long cleanerFrequency = Math.max(
+                MIN_CLEANER_INTERVAL_MS.getTotalMilliseconds(),
+                Math.min(
+                        maxTokenAgeMS / 2,
+                        MAX_CLEANER_INTERVAL_MS.getTotalMilliseconds()
+                ));
+
 
 
         executorService.scheduleAtFixedRate(cleanerTask, 10 * 1000, cleanerFrequency, TimeUnit.MILLISECONDS);
         executorService.scheduleAtFixedRate(cleanerTask, 10 * 1000, cleanerFrequency, TimeUnit.MILLISECONDS);
         LOGGER.trace("token cleanup will occur every " + TimeDuration.asCompactString(cleanerFrequency));
         LOGGER.trace("token cleanup will occur every " + TimeDuration.asCompactString(cleanerFrequency));
@@ -210,6 +225,8 @@ public class TokenService implements PwmService {
             /* noop */
             /* noop */
         }
         }
 
 
+        verifyPwModifyTime = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.TOKEN_VERIFY_PW_MODIFY_TIME));
+
         status = STATUS.OPEN;
         status = STATUS.OPEN;
         LOGGER.debug("open");
         LOGGER.debug("open");
     }
     }
@@ -226,14 +243,14 @@ public class TokenService implements PwmService {
         final String tokenKey;
         final String tokenKey;
         try {
         try {
             tokenKey = tokenMachine.generateToken(sessionLabel, tokenPayload);
             tokenKey = tokenMachine.generateToken(sessionLabel, tokenPayload);
-            tokenMachine.storeToken(tokenKey, tokenPayload);
+            tokenMachine.storeToken(tokenMachine.keyFromKey(tokenKey), tokenPayload);
         } catch (PwmException e) {
         } catch (PwmException e) {
             final String errorMsg = "unexpected error trying to store token in datastore: " + e.getMessage();
             final String errorMsg = "unexpected error trying to store token in datastore: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation(e.getError(),errorMsg);
             final ErrorInformation errorInformation = new ErrorInformation(e.getError(),errorMsg);
             throw new PwmOperationalException(errorInformation);
             throw new PwmOperationalException(errorInformation);
         }
         }
 
 
-        LOGGER.trace(sessionLabel, "generated token type=" + tokenPayload.getName());
+        LOGGER.trace(sessionLabel, "generated toke with payload: "  + tokenPayload.toDebugString());
 
 
         final AuditRecord auditRecord = new AuditRecordFactory(pwmApplication).createUserAuditRecord(
         final AuditRecord auditRecord = new AuditRecordFactory(pwmApplication).createUserAuditRecord(
                 AuditEvent.TOKEN_ISSUED,
                 AuditEvent.TOKEN_ISSUED,
@@ -246,18 +263,28 @@ public class TokenService implements PwmService {
     }
     }
 
 
 
 
-    public void markTokenAsClaimed(final String tokenKey, final PwmSession pwmSession) throws PwmUnrecoverableException {
-
-        final TokenPayload tokenPayload;
-        try {
-            tokenPayload = retrieveTokenData(tokenKey, pwmSession.getLabel());
-        } catch (PwmOperationalException e) {
+    private void markTokenAsClaimed(
+            final TokenKey tokenKey,
+            final PwmSession pwmSession,
+            final TokenPayload tokenPayload
+    )
+            throws PwmUnrecoverableException
+    {
+        if (tokenPayload == null || tokenPayload.getUserIdentity() == null) {
             return;
             return;
         }
         }
 
 
-        if (tokenPayload == null || tokenPayload.getUserIdentity() == null) {
-            return;
+        final boolean removeOnClaim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.TOKEN_REMOVE_ON_CLAIM));
+
+        if (removeOnClaim) {
+            try {
+                LOGGER.trace(pwmSession, "removing claimed token: " + tokenPayload.toDebugString());
+                tokenMachine.removeToken(tokenKey);
+            } catch (PwmOperationalException e) {
+                LOGGER.error(pwmSession, "error clearing claimed token: " + e.getMessage());
+            }
         }
         }
+
         final AuditRecord auditRecord = new AuditRecordFactory(pwmApplication).createUserAuditRecord(
         final AuditRecord auditRecord = new AuditRecordFactory(pwmApplication).createUserAuditRecord(
                 AuditEvent.TOKEN_CLAIMED,
                 AuditEvent.TOKEN_CLAIMED,
                 tokenPayload.getUserIdentity(),
                 tokenPayload.getUserIdentity(),
@@ -266,27 +293,22 @@ public class TokenService implements PwmService {
         );
         );
         pwmApplication.getAuditManager().submit(auditRecord);
         pwmApplication.getAuditManager().submit(auditRecord);
 
 
-
         StatisticsManager.incrementStat(pwmApplication, Statistic.TOKENS_PASSSED);
         StatisticsManager.incrementStat(pwmApplication, Statistic.TOKENS_PASSSED);
     }
     }
 
 
-    public TokenPayload retrieveTokenData(final String tokenKey, final SessionLabel sessionLabel)
+    public TokenPayload retrieveTokenData(final SessionLabel sessionLabel, final String tokenKey)
             throws PwmOperationalException
             throws PwmOperationalException
     {
     {
         checkStatus();
         checkStatus();
 
 
         try {
         try {
-            final TokenPayload storedToken = tokenMachine.retrieveToken(tokenKey, sessionLabel);
+            final TokenPayload storedToken = tokenMachine.retrieveToken(tokenMachine.keyFromKey(tokenKey));
             if (storedToken != null) {
             if (storedToken != null) {
 
 
-                if (testIfTokenIsExpired(storedToken)) {
+                if (testIfTokenIsExpired(sessionLabel, storedToken)) {
                     throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_TOKEN_EXPIRED));
                     throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_TOKEN_EXPIRED));
                 }
                 }
 
 
-                if (testIfTokenIsPurgable(storedToken)) {
-                    tokenMachine.removeToken(tokenKey, sessionLabel);
-                }
-
                 return storedToken;
                 return storedToken;
             }
             }
         } catch (PwmException e) {
         } catch (PwmException e) {
@@ -340,88 +362,19 @@ public class TokenService implements PwmService {
         return returnRecords;
         return returnRecords;
     }
     }
 
 
-
-    private boolean testIfTokenIsExpired(final TokenPayload theToken) {
+    private boolean testIfTokenIsExpired(final SessionLabel sessionLabel, final TokenPayload theToken) {
         if (theToken == null) {
         if (theToken == null) {
             return false;
             return false;
         }
         }
         final Instant issueDate = theToken.getDate();
         final Instant issueDate = theToken.getDate();
         if (issueDate == null) {
         if (issueDate == null) {
-            LOGGER.error("retrieved token has no issueDate, marking as expired: " + JsonUtil.serialize(theToken));
+            LOGGER.error(sessionLabel, "retrieved token has no issueDate, marking as expired: " + theToken.toDebugString());
             return true;
             return true;
         }
         }
         final TimeDuration duration = TimeDuration.fromCurrent(issueDate);
         final TimeDuration duration = TimeDuration.fromCurrent(issueDate);
         return duration.isLongerThan(maxTokenAgeMS);
         return duration.isLongerThan(maxTokenAgeMS);
     }
     }
 
 
-    private boolean testIfTokenIsPurgable(final TokenPayload theToken) {
-        if (theToken == null) {
-            return false;
-        }
-        final Instant issueDate = theToken.getDate();
-        if (issueDate == null) {
-            LOGGER.error("retrieved token has no issueDate, marking as purgable: " + JsonUtil.serialize(theToken));
-            return true;
-        }
-        final TimeDuration duration = TimeDuration.fromCurrent(issueDate);
-        return duration.isLongerThan(maxTokenPurgeAgeMS);
-    }
-
-
-    void purgeOutdatedTokens() throws
-            PwmUnrecoverableException, PwmOperationalException
-    {
-        final long startTime = System.currentTimeMillis();
-        int cleanedTokens = 0;
-        final List<String> tempKeyList = new ArrayList<>();
-        final int purgeBatchSize = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.TOKEN_PURGE_BATCH_SIZE));
-        tempKeyList.addAll(discoverPurgeableTokenKeys(purgeBatchSize, PwmConstants.TOKEN_SESSION_LABEL));
-        while (status() == STATUS.OPEN && !tempKeyList.isEmpty()) {
-            for (final String loopKey : tempKeyList) {
-                tokenMachine.removeToken(loopKey, PwmConstants.HEALTH_SESSION_LABEL);
-            }
-            cleanedTokens = cleanedTokens + tempKeyList.size();
-            tempKeyList.clear();
-        }
-        if (cleanedTokens > 0) {
-            LOGGER.trace("cleaner thread removed " + cleanedTokens + " tokens in " + TimeDuration.fromCurrent(startTime).asCompactString());
-        }
-    }
-
-    private List<String> discoverPurgeableTokenKeys(final int maxCount, final SessionLabel sessionLabel)
-            throws PwmUnrecoverableException, PwmOperationalException
-    {
-        final List<String> returnList = new ArrayList<>();
-        Iterator<String> keyIterator = null;
-
-        try {
-            keyIterator = tokenMachine.keyIterator();
-
-            while (status() == STATUS.OPEN && returnList.size() < maxCount && keyIterator.hasNext()) {
-                final String loopKey = keyIterator.next();
-                final TokenPayload loopInfo = tokenMachine.retrieveToken(loopKey, sessionLabel);
-                if (loopInfo != null) {
-                    if (testIfTokenIsPurgable(loopInfo)) {
-                        returnList.add(loopKey);
-                    }
-                }
-            }
-        } catch (Exception e) {
-            LOGGER.error("unexpected error while cleaning expired stored tokens: " + e.getMessage());
-        } finally {
-            if (keyIterator != null && storageMethod == TokenStorageMethod.STORE_LOCALDB) {
-                try {
-                    ((LocalDB.LocalDBIterator)keyIterator).close();
-                } catch (Exception e) {
-                    LOGGER.error("unexpected error returning LocalDB token DB iterator: " + e.getMessage());
-                }
-            }
-        }
-
-        return returnList;
-    }
-
-
     private static String makeRandomCode(final Configuration config) {
     private static String makeRandomCode(final Configuration config) {
         final String RANDOM_CHARS = config.readSettingAsString(PwmSetting.TOKEN_CHARACTERS);
         final String RANDOM_CHARS = config.readSettingAsString(PwmSetting.TOKEN_CHARACTERS);
         final int CODE_LENGTH = (int) config.readSettingAsLong(PwmSetting.TOKEN_LENGTH);
         final int CODE_LENGTH = (int) config.readSettingAsLong(PwmSetting.TOKEN_LENGTH);
@@ -469,7 +422,8 @@ public class TokenService implements PwmService {
         while (tokenKey == null && attempts < maxUniqueCreateAttempts) {
         while (tokenKey == null && attempts < maxUniqueCreateAttempts) {
             tokenKey = makeRandomCode(configuration);
             tokenKey = makeRandomCode(configuration);
             LOGGER.trace(sessionLabel, "generated new token random code, checking for uniqueness");
             LOGGER.trace(sessionLabel, "generated new token random code, checking for uniqueness");
-            if (machine.retrieveToken(tokenKey, sessionLabel) != null) {
+            final TokenPayload existingPayload = machine.retrieveToken(tokenMachine.keyFromKey(tokenKey));
+            if (existingPayload != null) {
                 tokenKey = null;
                 tokenKey = null;
             }
             }
             attempts++;
             attempts++;
@@ -479,15 +433,10 @@ public class TokenService implements PwmService {
             throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unable to generate a unique token key after " + attempts + " attempts"));
             throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unable to generate a unique token key after " + attempts + " attempts"));
         }
         }
 
 
-        LOGGER.trace(sessionLabel, "found new, unique, random token value");
+        LOGGER.trace(sessionLabel, "created new unique random token value after " + attempts + " attempts");
         return tokenKey;
         return tokenKey;
     }
     }
 
 
-     String makeTokenHash(final String tokenKey) throws PwmUnrecoverableException {
-        final SecureService secureService = pwmApplication.getSecureService();
-        return secureService.hash(tokenKey) + "-hash";
-    }
-
     private static boolean tokensAreUsedInConfig(final Configuration configuration) {
     private static boolean tokensAreUsedInConfig(final Configuration configuration) {
         if (configuration.readSettingAsBoolean(PwmSetting.NEWUSER_ENABLE)) {
         if (configuration.readSettingAsBoolean(PwmSetting.NEWUSER_ENABLE)) {
             for (final NewUserProfile newUserProfile : configuration.getNewUserProfiles().values()) {
             for (final NewUserProfile newUserProfile : configuration.getNewUserProfiles().values()) {
@@ -529,7 +478,7 @@ public class TokenService implements PwmService {
             final String decryptedString = pwmApplication.getSecureService().decryptStringValue(deWhiteSpacedToken);
             final String decryptedString = pwmApplication.getSecureService().decryptStringValue(deWhiteSpacedToken);
             return JsonUtil.deserialize(decryptedString, TokenPayload.class);
             return JsonUtil.deserialize(decryptedString, TokenPayload.class);
         } catch (PwmUnrecoverableException e) {
         } catch (PwmUnrecoverableException e) {
-            final String errorMsg = "unable to decrypt user supplied token value: " + e.getErrorInformation().toDebugStr();
+            final String errorMsg = "unable to decrypt token payload: " + e.getErrorInformation().toDebugStr();
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
             throw new PwmOperationalException(errorInformation);
             throw new PwmOperationalException(errorInformation);
         }
         }
@@ -550,7 +499,6 @@ public class TokenService implements PwmService {
     {
     {
         try {
         try {
             final TokenPayload tokenPayload = processUserEnteredCodeImpl(
             final TokenPayload tokenPayload = processUserEnteredCodeImpl(
-                    pwmApplication,
                     pwmSession,
                     pwmSession,
                     sessionUserIdentity,
                     sessionUserIdentity,
                     tokenType,
                     tokenType,
@@ -561,7 +509,7 @@ public class TokenService implements PwmService {
                     pwmApplication.getIntruderManager().clear(RecordType.TOKEN_DEST, dest);
                     pwmApplication.getIntruderManager().clear(RecordType.TOKEN_DEST, dest);
                 }
                 }
             }
             }
-            pwmApplication.getTokenService().markTokenAsClaimed(userEnteredCode, pwmSession);
+            markTokenAsClaimed(tokenMachine.keyFromKey(userEnteredCode), pwmSession, tokenPayload);
             return tokenPayload;
             return tokenPayload;
         } catch (Exception e) {
         } catch (Exception e) {
             final ErrorInformation errorInformation;
             final ErrorInformation errorInformation;
@@ -584,8 +532,7 @@ public class TokenService implements PwmService {
         }
         }
     }
     }
 
 
-    private static TokenPayload processUserEnteredCodeImpl(
-            final PwmApplication pwmApplication,
+    private TokenPayload processUserEnteredCodeImpl(
             final PwmSession pwmSession,
             final PwmSession pwmSession,
             final UserIdentity sessionUserIdentity,
             final UserIdentity sessionUserIdentity,
             final TokenType tokenType,
             final TokenType tokenType,
@@ -595,7 +542,7 @@ public class TokenService implements PwmService {
     {
     {
         final TokenPayload tokenPayload;
         final TokenPayload tokenPayload;
         try {
         try {
-            tokenPayload = pwmApplication.getTokenService().retrieveTokenData(userEnteredCode, pwmSession.getLabel());
+            tokenPayload = pwmApplication.getTokenService().retrieveTokenData(pwmSession.getLabel(), userEnteredCode);
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             final String errorMsg = "unexpected error attempting to read token from storage: " + e.getErrorInformation().toDebugStr();
             final String errorMsg = "unexpected error attempting to read token from storage: " + e.getErrorInformation().toDebugStr();
             throw new PwmOperationalException(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
             throw new PwmOperationalException(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
@@ -606,7 +553,7 @@ public class TokenService implements PwmService {
             throw new PwmOperationalException(errorInformation);
             throw new PwmOperationalException(errorInformation);
         }
         }
 
 
-        LOGGER.trace(pwmSession, "retrieved tokenPayload: " + JsonUtil.serialize(tokenPayload));
+        LOGGER.trace(pwmSession, "retrieved tokenPayload: " + tokenPayload.toDebugString());
 
 
         if (tokenType != null && pwmApplication.getTokenService().supportsName()) {
         if (tokenType != null && pwmApplication.getTokenService().supportsName()) {
             if (!tokenType.matchesName(tokenPayload.getName()) ) {
             if (!tokenType.matchesName(tokenPayload.getName()) ) {
@@ -624,17 +571,32 @@ public class TokenService implements PwmService {
         }
         }
 
 
         // check if password-last-modified is same as when tried to read it before.
         // check if password-last-modified is same as when tried to read it before.
-        if (tokenPayload.getUserIdentity() != null && tokenPayload.getData() != null && tokenPayload.getData().containsKey(PwmConstants.TOKEN_KEY_PWD_CHG_DATE)) {
+        if (verifyPwModifyTime
+                && tokenPayload.getUserIdentity() != null
+                && tokenPayload.getData() != null
+                && tokenPayload.getData().containsKey(PwmConstants.TOKEN_KEY_PWD_CHG_DATE)
+                ) {
             try {
             try {
                 final Instant userLastPasswordChange = PasswordUtility.determinePwdLastModified(
                 final Instant userLastPasswordChange = PasswordUtility.determinePwdLastModified(
                         pwmApplication,
                         pwmApplication,
                         pwmSession.getLabel(),
                         pwmSession.getLabel(),
                         tokenPayload.getUserIdentity());
                         tokenPayload.getUserIdentity());
+
                 final String dateStringInToken = tokenPayload.getData().get(PwmConstants.TOKEN_KEY_PWD_CHG_DATE);
                 final String dateStringInToken = tokenPayload.getData().get(PwmConstants.TOKEN_KEY_PWD_CHG_DATE);
+
+                LOGGER.trace(pwmSession, "tokenPayload=" + tokenPayload.toDebugString()
+                        + ", sessionUser=" + (sessionUserIdentity == null ? "null" : sessionUserIdentity.toDisplayString())
+                        + ", payloadUserIdentity=" + tokenPayload.getUserIdentity().toDisplayString()
+                        + ", userLastPasswordChange=" + JavaHelper.toIsoDate(userLastPasswordChange)
+                        + ", dateStringInToken=" + dateStringInToken);
+
                 if (userLastPasswordChange != null && dateStringInToken != null) {
                 if (userLastPasswordChange != null && dateStringInToken != null) {
+
                     final String userChangeString = JavaHelper.toIsoDate(userLastPasswordChange);
                     final String userChangeString = JavaHelper.toIsoDate(userLastPasswordChange);
+
                     if (!dateStringInToken.equalsIgnoreCase(userChangeString)) {
                     if (!dateStringInToken.equalsIgnoreCase(userChangeString)) {
-                        final String errorString = "user password has changed since token issued, token rejected";
+                        final String errorString = "user password has changed since token issued, token rejected; currentValue=" + userChangeString + ", tokenValue=" + dateStringInToken;
+                        LOGGER.trace(pwmSession, errorString + "; token=" + tokenPayload.toDebugString());
                         final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_EXPIRED, errorString);
                         final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_EXPIRED, errorString);
                         throw new PwmOperationalException(errorInformation);
                         throw new PwmOperationalException(errorInformation);
                     }
                     }

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

@@ -44,7 +44,7 @@ public class TokenInfoCommand extends AbstractCliCommand {
         TokenPayload tokenPayload = null;
         TokenPayload tokenPayload = null;
         Exception lookupError = null;
         Exception lookupError = null;
         try {
         try {
-            tokenPayload = tokenService.retrieveTokenData(tokenKey, PwmConstants.TOKEN_SESSION_LABEL);
+            tokenPayload = tokenService.retrieveTokenData(PwmConstants.TOKEN_SESSION_LABEL, tokenKey);
         } catch (Exception e) {
         } catch (Exception e) {
             lookupError = e;
             lookupError = e;
         }
         }

+ 94 - 1
src/main/java/password/pwm/util/java/JavaHelper.java

@@ -34,7 +34,12 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.StringWriter;
+import java.lang.management.LockInfo;
+import java.lang.management.MonitorInfo;
+import java.lang.management.ThreadInfo;
 import java.lang.reflect.Method;
 import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
@@ -255,7 +260,22 @@ public class JavaHelper {
     }
     }
 
 
     public static String toIsoDate(final Date date) {
     public static String toIsoDate(final Date date) {
-        return date == null ? "" : PwmConstants.DEFAULT_DATETIME_FORMAT.format(date);
+        if (date == null) {
+            return "";
+        }
+
+        final DateFormat dateFormat = new SimpleDateFormat(
+                PwmConstants.DEFAULT_DATETIME_FORMAT_STR,
+                PwmConstants.DEFAULT_LOCALE
+        );
+
+        dateFormat.setTimeZone(PwmConstants.DEFAULT_TIMEZONE);
+
+        return dateFormat.format(date);
+    }
+
+    public static Instant parseIsoToInstant(final String input) {
+        return Instant.parse(input);
     }
     }
 
 
     public static boolean closeAndWaitExecutor(final ExecutorService executor, final TimeDuration timeDuration)
     public static boolean closeAndWaitExecutor(final ExecutorService executor, final TimeDuration timeDuration)
@@ -338,4 +358,77 @@ public class JavaHelper {
                         true
                         true
                 ));
                 ));
     }
     }
+
+
+    /**
+     * Copy of {@link ThreadInfo#toString()} but with the MAX_FRAMES changed from 8 to 256.
+     */
+    public static String threadInfoToString(final ThreadInfo threadInfo) {
+        final int MAX_FRAMES = 256;
+        final StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\"" +
+                " Id=" + threadInfo.getThreadId() + " " +
+                threadInfo.getThreadState());
+        if (threadInfo.getLockName() != null) {
+            sb.append(" on " + threadInfo.getLockName());
+        }
+        if (threadInfo.getLockOwnerName() != null) {
+            sb.append(" owned by \"" + threadInfo.getLockOwnerName() +
+                    "\" Id=" + threadInfo.getLockOwnerId());
+        }
+        if (threadInfo.isSuspended()) {
+            sb.append(" (suspended)");
+        }
+        if (threadInfo.isInNative()) {
+            sb.append(" (in native)");
+        }
+        sb.append('\n');
+
+        int counter = 0;
+        for (; counter < threadInfo.getStackTrace().length && counter < MAX_FRAMES; counter++) {
+            final StackTraceElement ste = threadInfo.getStackTrace()[counter];
+            sb.append("\tat ").append(ste.toString());
+            sb.append('\n');
+            if (counter == 0 && threadInfo.getLockInfo() != null) {
+                final Thread.State ts = threadInfo.getThreadState();
+                switch (ts) {
+                    case BLOCKED:
+                        sb.append("\t-  blocked on " + threadInfo.getLockInfo());
+                        sb.append('\n');
+                        break;
+                    case WAITING:
+                        sb.append("\t-  waiting on " + threadInfo.getLockInfo());
+                        sb.append('\n');
+                        break;
+                    case TIMED_WAITING:
+                        sb.append("\t-  waiting on " + threadInfo.getLockInfo());
+                        sb.append('\n');
+                        break;
+                    default:
+                }
+            }
+
+            for (MonitorInfo mi : threadInfo.getLockedMonitors()) {
+                if (mi.getLockedStackDepth() == counter) {
+                    sb.append("\t-  locked " + mi);
+                    sb.append('\n');
+                }
+            }
+        }
+        if (counter < threadInfo.getStackTrace().length) {
+            sb.append("\t...");
+            sb.append('\n');
+        }
+
+        final LockInfo[] locks = threadInfo.getLockedSynchronizers();
+        if (locks.length > 0) {
+            sb.append("\n\tNumber of locked synchronizers = " + locks.length);
+            sb.append('\n');
+            for (LockInfo li : locks) {
+                sb.append("\t- " + li);
+                sb.append('\n');
+            }
+        }
+        sb.append('\n');
+        return sb.toString();
+    }
 }
 }

+ 60 - 82
src/main/java/password/pwm/util/localdb/Xodus_LocalDB.java

@@ -24,6 +24,7 @@ package password.pwm.util.localdb;
 
 
 import jetbrains.exodus.ArrayByteIterable;
 import jetbrains.exodus.ArrayByteIterable;
 import jetbrains.exodus.ByteIterable;
 import jetbrains.exodus.ByteIterable;
+import jetbrains.exodus.InvalidSettingException;
 import jetbrains.exodus.bindings.StringBinding;
 import jetbrains.exodus.bindings.StringBinding;
 import jetbrains.exodus.env.Cursor;
 import jetbrains.exodus.env.Cursor;
 import jetbrains.exodus.env.Environment;
 import jetbrains.exodus.env.Environment;
@@ -33,11 +34,8 @@ import jetbrains.exodus.env.Environments;
 import jetbrains.exodus.env.Store;
 import jetbrains.exodus.env.Store;
 import jetbrains.exodus.env.StoreConfig;
 import jetbrains.exodus.env.StoreConfig;
 import jetbrains.exodus.env.Transaction;
 import jetbrains.exodus.env.Transaction;
-import jetbrains.exodus.env.TransactionalComputable;
-import jetbrains.exodus.env.TransactionalExecutable;
 import jetbrains.exodus.management.Statistics;
 import jetbrains.exodus.management.Statistics;
 import jetbrains.exodus.management.StatisticsItem;
 import jetbrains.exodus.management.StatisticsItem;
-import org.jetbrains.annotations.NotNull;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.util.java.ConditionalTaskExecutor;
 import password.pwm.util.java.ConditionalTaskExecutor;
@@ -94,12 +92,9 @@ public class Xodus_LocalDB implements LocalDBProvider {
 
 
     private final Map<LocalDB.DB,Store> cachedStoreObjects = new HashMap<>();
     private final Map<LocalDB.DB,Store> cachedStoreObjects = new HashMap<>();
 
 
-    private final ConditionalTaskExecutor outputLogExecutor = new ConditionalTaskExecutor(new Runnable() {
-        @Override
-        public void run() {
-            outputStats();
-        }
-    },new ConditionalTaskExecutor.TimeDurationPredicate(STATS_OUTPUT_INTERVAL).setNextTimeFromNow(1, TimeUnit.MINUTES));
+    private final ConditionalTaskExecutor outputLogExecutor = new ConditionalTaskExecutor(
+            () -> outputStats(),new ConditionalTaskExecutor.TimeDurationPredicate(STATS_OUTPUT_INTERVAL).setNextTimeFromNow(1, TimeUnit.MINUTES)
+    );
 
 
     private BindMachine bindMachine = new BindMachine(BindMachine.DEFAULT_ENABLE_COMPRESSION, BindMachine.DEFAULT_MIN_COMPRESSION_LENGTH);
     private BindMachine bindMachine = new BindMachine(BindMachine.DEFAULT_ENABLE_COMPRESSION, BindMachine.DEFAULT_MIN_COMPRESSION_LENGTH);
 
 
@@ -117,19 +112,7 @@ public class Xodus_LocalDB implements LocalDBProvider {
         LOGGER.trace("begin environment open");
         LOGGER.trace("begin environment open");
         final Instant startTime = Instant.now();
         final Instant startTime = Instant.now();
 
 
-        final EnvironmentConfig environmentConfig = new EnvironmentConfig();
-        environmentConfig.setLogDurableWrite(true);
-        environmentConfig.setGcEnabled(true);
-        environmentConfig.setEnvCloseForcedly(true);
-        environmentConfig.setFullFileReadonly(false);
-        environmentConfig.setGcStartIn(0);
-        environmentConfig.setGcUtilizationFromScratch(true);
-        environmentConfig.setMemoryUsage(50 * 1024 * 1024);
-
-        for (final String key : initParameters.keySet()) {
-            final String value = initParameters.get(key);
-            environmentConfig.setSetting(key,value);
-        }
+        final EnvironmentConfig environmentConfig = makeEnvironmentConfig(initParameters);
 
 
         {
         {
             final boolean compressionEnabled = initParameters.containsKey(Property.Compression_Enabled.getKeyName())
             final boolean compressionEnabled = initParameters.containsKey(Property.Compression_Enabled.getKeyName())
@@ -149,13 +132,10 @@ public class Xodus_LocalDB implements LocalDBProvider {
         environment = Environments.newInstance(dbDirectory.getAbsolutePath() + File.separator + "xodus", environmentConfig);
         environment = Environments.newInstance(dbDirectory.getAbsolutePath() + File.separator + "xodus", environmentConfig);
         LOGGER.trace("environment open (" + TimeDuration.fromCurrent(startTime).asCompactString() + ")");
         LOGGER.trace("environment open (" + TimeDuration.fromCurrent(startTime).asCompactString() + ")");
 
 
-        environment.executeInTransaction(new TransactionalExecutable() {
-            @Override
-            public void execute(@NotNull final Transaction txn) {
-                for (final LocalDB.DB db : LocalDB.DB.values()) {
-                    final Store store = initStore(db, txn);
-                    cachedStoreObjects.put(db,store);
-                }
+        environment.executeInTransaction(txn -> {
+            for (final LocalDB.DB db : LocalDB.DB.values()) {
+                final Store store = initStore(db, txn);
+                cachedStoreObjects.put(db,store);
             }
             }
         });
         });
 
 
@@ -175,15 +155,31 @@ public class Xodus_LocalDB implements LocalDBProvider {
         LOGGER.debug("closed");
         LOGGER.debug("closed");
     }
     }
 
 
+    private EnvironmentConfig makeEnvironmentConfig(final Map<String, String> initParameters) {
+        final EnvironmentConfig environmentConfig = new EnvironmentConfig();
+        environmentConfig.setEnvCloseForcedly(true);
+        environmentConfig.setMemoryUsage(50 * 1024 * 1024);
+
+        for (final String key : initParameters.keySet()) {
+            final String value = initParameters.get(key);
+            final Map<String,String> singleMap = Collections.singletonMap(key,value);
+            try {
+                environmentConfig.setSettings(singleMap);
+                LOGGER.trace("set env setting from appProperty: " + key + "=" + value);
+            } catch (InvalidSettingException e) {
+                LOGGER.warn("problem setting configured env settings: " + e.getMessage());
+            }
+        }
+
+        return environmentConfig;
+    }
+
     @Override
     @Override
     public int size(final LocalDB.DB db) throws LocalDBException {
     public int size(final LocalDB.DB db) throws LocalDBException {
         checkStatus(false);
         checkStatus(false);
-        return environment.computeInReadonlyTransaction(new TransactionalComputable<Integer>() {
-            @Override
-            public Integer compute(@NotNull final Transaction transaction) {
-                final Store store = getStore(db);
-                return (int) store.count(transaction);
-            }
+        return environment.computeInReadonlyTransaction(transaction -> {
+            final Store store = getStore(db);
+            return (int) store.count(transaction);
         });
         });
     }
     }
 
 
@@ -196,16 +192,13 @@ public class Xodus_LocalDB implements LocalDBProvider {
     @Override
     @Override
     public String get(final LocalDB.DB db, final String key) throws LocalDBException {
     public String get(final LocalDB.DB db, final String key) throws LocalDBException {
         checkStatus(false);
         checkStatus(false);
-        return environment.computeInReadonlyTransaction(new TransactionalComputable<String>() {
-            @Override
-            public String compute(@NotNull final Transaction transaction) {
-                final Store store = getStore(db);
-                final ByteIterable returnValue = store.get(transaction, bindMachine.keyToEntry(key));
-                if (returnValue != null) {
-                    return bindMachine.entryToValue(returnValue);
-                }
-                return null;
+        return environment.computeInReadonlyTransaction(transaction -> {
+            final Store store = getStore(db);
+            final ByteIterable returnValue = store.get(transaction, bindMachine.keyToEntry(key));
+            if (returnValue != null) {
+                return bindMachine.entryToValue(returnValue);
             }
             }
+            return null;
         });
         });
     }
     }
 
 
@@ -294,16 +287,13 @@ public class Xodus_LocalDB implements LocalDBProvider {
     @Override
     @Override
     public void putAll(final LocalDB.DB db, final Map<String, String> keyValueMap) throws LocalDBException {
     public void putAll(final LocalDB.DB db, final Map<String, String> keyValueMap) throws LocalDBException {
         checkStatus(true);
         checkStatus(true);
-        environment.executeInTransaction(new TransactionalExecutable() {
-            @Override
-            public void execute(@NotNull final Transaction transaction) {
-                final Store store = getStore(db);
-                for (final String key : keyValueMap.keySet()) {
-                    final String value = keyValueMap.get(key);
-                    final ByteIterable k = bindMachine.keyToEntry(key);
-                    final ByteIterable v = bindMachine.valueToEntry(value);
-                    store.put(transaction,k,v);
-                }
+        environment.executeInTransaction(transaction -> {
+            final Store store = getStore(db);
+            for (final String key : keyValueMap.keySet()) {
+                final String value = keyValueMap.get(key);
+                final ByteIterable k = bindMachine.keyToEntry(key);
+                final ByteIterable v = bindMachine.valueToEntry(value);
+                store.put(transaction,k,v);
             }
             }
         });
         });
         outputLogExecutor.conditionallyExecuteTask();
         outputLogExecutor.conditionallyExecuteTask();
@@ -313,39 +303,30 @@ public class Xodus_LocalDB implements LocalDBProvider {
     @Override
     @Override
     public boolean put(final LocalDB.DB db, final String key, final String value) throws LocalDBException {
     public boolean put(final LocalDB.DB db, final String key, final String value) throws LocalDBException {
         checkStatus(true);
         checkStatus(true);
-        return environment.computeInTransaction(new TransactionalComputable<Boolean>() {
-            @Override
-            public Boolean compute(@NotNull final Transaction transaction) {
-                final ByteIterable k = bindMachine.keyToEntry(key);
-                final ByteIterable v = bindMachine.valueToEntry(value);
-                final Store store = getStore(db);
-                return store.put(transaction,k,v);
-            }
+        return environment.computeInTransaction(transaction -> {
+            final ByteIterable k = bindMachine.keyToEntry(key);
+            final ByteIterable v = bindMachine.valueToEntry(value);
+            final Store store = getStore(db);
+            return store.put(transaction,k,v);
         });
         });
     }
     }
 
 
     @Override
     @Override
     public boolean remove(final LocalDB.DB db, final String key) throws LocalDBException {
     public boolean remove(final LocalDB.DB db, final String key) throws LocalDBException {
         checkStatus(true);
         checkStatus(true);
-        return environment.computeInTransaction(new TransactionalComputable<Boolean>() {
-            @Override
-            public Boolean compute(@NotNull final Transaction transaction) {
-                final Store store = getStore(db);
-                return store.delete(transaction, bindMachine.keyToEntry(key));
-            }
+        return environment.computeInTransaction(transaction -> {
+            final Store store = getStore(db);
+            return store.delete(transaction, bindMachine.keyToEntry(key));
         });
         });
     }
     }
 
 
     @Override
     @Override
     public void removeAll(final LocalDB.DB db, final Collection<String> keys) throws LocalDBException {
     public void removeAll(final LocalDB.DB db, final Collection<String> keys) throws LocalDBException {
         checkStatus(true);
         checkStatus(true);
-        environment.executeInTransaction(new TransactionalExecutable() {
-            @Override
-            public void execute(@NotNull final Transaction transaction) {
-                final Store store = getStore(db);
-                for (final String key : keys) {
-                    store.delete(transaction, bindMachine.keyToEntry(key));
-                }
+        environment.executeInTransaction(transaction -> {
+            final Store store = getStore(db);
+            for (final String key : keys) {
+                store.delete(transaction, bindMachine.keyToEntry(key));
             }
             }
         });
         });
     }
     }
@@ -358,13 +339,10 @@ public class Xodus_LocalDB implements LocalDBProvider {
         LOGGER.trace("begin truncate of " + db.toString() + ", size=" + this.size(db));
         LOGGER.trace("begin truncate of " + db.toString() + ", size=" + this.size(db));
         final Date startDate = new Date();
         final Date startDate = new Date();
 
 
-        environment.executeInTransaction(new TransactionalExecutable() {
-            @Override
-            public void execute(@NotNull final Transaction transaction) {
-                environment.truncateStore(db.toString(), transaction);
-                final Store newStoreReference = environment.openStore(db.toString(), StoreConfig.USE_EXISTING, transaction);
-                cachedStoreObjects.put(db, newStoreReference);
-            }
+        environment.executeInTransaction(transaction -> {
+            environment.truncateStore(db.toString(), transaction);
+            final Store newStoreReference = environment.openStore(db.toString(), StoreConfig.USE_EXISTING, transaction);
+            cachedStoreObjects.put(db, newStoreReference);
         });
         });
 
 
         LOGGER.trace("completed truncate of " + db.toString()
         LOGGER.trace("completed truncate of " + db.toString()

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

@@ -193,10 +193,20 @@ public abstract class RestServerHelper {
             }
             }
         }
         }
 
 
+        final String ldapProfileID;
+        final String effectiveUsername;
+        if (username.contains("|")) {
+            final int pipeIndex = username.indexOf("|");
+            ldapProfileID = username.substring(0, pipeIndex);
+            effectiveUsername = username.substring(pipeIndex + 1, username.length());
+        } else {
+            ldapProfileID = null;
+            effectiveUsername = username;
+        }
 
 
         try {
         try {
             final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
             final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
-            return userSearchEngine.resolveUsername(username, null, null, pwmSession.getLabel());
+            return userSearchEngine.resolveUsername(effectiveUsername, null, ldapProfileID, pwmSession.getLabel());
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             throw new PwmUnrecoverableException(e.getErrorInformation());
             throw new PwmUnrecoverableException(e.getErrorInformation());
         } catch (ChaiUnavailableException e) {
         } catch (ChaiUnavailableException e) {

+ 1 - 1
src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java

@@ -224,7 +224,7 @@ public class RestRandomPasswordServer extends AbstractRestServer {
 
 
         if (!jsonInput.noUser && restRequestBean.getPwmSession().isAuthenticated()) {
         if (!jsonInput.noUser && restRequestBean.getPwmSession().isAuthenticated()) {
             if (jsonInput.username != null && !jsonInput.username.isEmpty()) {
             if (jsonInput.username != null && !jsonInput.username.isEmpty()) {
-                final UserIdentity userIdentity = UserIdentity.fromKey(jsonInput.username, restRequestBean.getPwmApplication());
+                final UserIdentity userIdentity = restRequestBean.getUserIdentity();
                 final HelpdeskProfile helpdeskProfile = restRequestBean.getPwmSession().getSessionManager().getHelpdeskProfile(restRequestBean.getPwmApplication());
                 final HelpdeskProfile helpdeskProfile = restRequestBean.getPwmSession().getSessionManager().getHelpdeskProfile(restRequestBean.getPwmApplication());
                 final boolean useProxy = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY);
                 final boolean useProxy = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY);
 
 

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

@@ -151,6 +151,8 @@ ldap.cache.canonical.seconds=60
 ldap.cache.userGuid.enable=true
 ldap.cache.userGuid.enable=true
 ldap.cache.userGuid.seconds=3600
 ldap.cache.userGuid.seconds=3600
 ldap.chaiSettings=
 ldap.chaiSettings=
+ldap.proxy.connectionsPerProfile=10
+ldap.proxy.maxConnections=50
 ldap.extensions.nmas.enable=true
 ldap.extensions.nmas.enable=true
 ldap.connection.timeoutMS=30000
 ldap.connection.timeoutMS=30000
 ldap.profile.retryDelayMS=30000
 ldap.profile.retryDelayMS=30000
@@ -166,6 +168,7 @@ ldap.search.paging.size=500
 ldap.search.parallel.enable=true
 ldap.search.parallel.enable=true
 ldap.search.parallel.factor=5
 ldap.search.parallel.factor=5
 ldap.search.parallel.threadMax=50
 ldap.search.parallel.threadMax=50
+ldap.oracle.postTempPasswordUseCurrentTime=false
 localdb.aggressiveCompact.enabled=false
 localdb.aggressiveCompact.enabled=false
 localdb.implementation=password.pwm.util.localdb.Xodus_LocalDB
 localdb.implementation=password.pwm.util.localdb.Xodus_LocalDB
 localdb.initParameters=00
 localdb.initParameters=00
@@ -218,7 +221,7 @@ queue.sms.retryTimeoutMs=10000
 queue.sms.maxAgeMs=86400000
 queue.sms.maxAgeMs=86400000
 queue.sms.maxCount=100000
 queue.sms.maxCount=100000
 queue.syslog.retryTimeoutMs=30000
 queue.syslog.retryTimeoutMs=30000
-queue.syslog.maxAgeMs=86400000
+queue.syslog.maxAgeMs=2592000000
 queue.syslog.maxCount=100000
 queue.syslog.maxCount=100000
 reporting.ldap.searchTimeoutMs=1800000
 reporting.ldap.searchTimeoutMs=1800000
 reporting.ldap.searchThreads=8
 reporting.ldap.searchThreads=8
@@ -229,6 +232,7 @@ security.html.stripInlineJavascript=false
 security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
 security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
 security.http.performCsrfHeaderChecks=false
 security.http.performCsrfHeaderChecks=false
 security.http.promiscuousEnable=false
 security.http.promiscuousEnable=false
+security.http.config.cspHeader=default-src 'self'; object-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; report-uri /sspr/public/command/cspReport
 security.httpsServer.selfCert.futureSeconds=63113904
 security.httpsServer.selfCert.futureSeconds=63113904
 security.httpsServer.selfCert.alg=RSA
 security.httpsServer.selfCert.alg=RSA
 security.httpsServer.selfCert.keySize=2048
 security.httpsServer.selfCert.keySize=2048
@@ -249,11 +253,11 @@ security.defaultEphemeralHashAlg=SHA512
 security.config.minSecurityKeyLength=32
 security.config.minSecurityKeyLength=32
 seedlist.builtin.path=/WEB-INF/seedlist.zip
 seedlist.builtin.path=/WEB-INF/seedlist.zip
 smtp.subjectEncodingCharset=UTF8
 smtp.subjectEncodingCharset=UTF8
-token.removalDelayMS=86400000
-token.purgeBatchSize=1000
 token.maxUniqueCreateAttempts=100
 token.maxUniqueCreateAttempts=100
 token.resend.enabled=true
 token.resend.enabled=true
 token.resend.delayMS=3000
 token.resend.delayMS=3000
+token.verifyPwModifyTime=true
+token.removeOnClaim=true
 urlshortener.url.regex=(https?://([^:@]+(:[^@]+)?@)?([a-zA-Z0-9.]+|d{1,3}.d{1,3}.d{1,3}.d{1,3}|[[0-9a-fA-F:]+])(:d{1,5})?/*[a-zA-Z0-9/\%_.]*?*[a-zA-Z0-9/\%_.=&#]*)
 urlshortener.url.regex=(https?://([^:@]+(:[^@]+)?@)?([a-zA-Z0-9.]+|d{1,3}.d{1,3}.d{1,3}.d{1,3}|[[0-9a-fA-F:]+])(:d{1,5})?/*[a-zA-Z0-9/\%_.]*?*[a-zA-Z0-9/\%_.=&#]*)
 wordlist.builtin.path=/WEB-INF/wordlist.zip
 wordlist.builtin.path=/WEB-INF/wordlist.zip
 ws.restClient.pwRule.haltOnError=true
 ws.restClient.pwRule.haltOnError=true

+ 23 - 31
src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp

@@ -44,6 +44,8 @@
 <%@ page import="java.util.Locale" %>
 <%@ page import="java.util.Locale" %>
 <%@ page import="java.util.Map" %>
 <%@ page import="java.util.Map" %>
 <%@ page import="java.util.TreeMap" %>
 <%@ page import="java.util.TreeMap" %>
+<%@ page import="java.lang.management.ThreadInfo" %>
+<%@ page import="java.lang.management.ManagementFactory" %>
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true"
 <%@ page language="java" session="true" isThreadSafe="true"
          contentType="text/html" %>
          contentType="text/html" %>
@@ -51,7 +53,7 @@
 <%
 <%
     final Locale locale = JspUtility.locale(request);
     final Locale locale = JspUtility.locale(request);
     final NumberFormat numberFormat = NumberFormat.getInstance(locale);
     final NumberFormat numberFormat = NumberFormat.getInstance(locale);
-    final Map<Thread,StackTraceElement[]> threads = Thread.getAllStackTraces();
+    final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true,true);
     SessionTrackService sessionTrackService = null;
     SessionTrackService sessionTrackService = null;
 
 
     PwmRequest dashboard_pwmRequest = null;
     PwmRequest dashboard_pwmRequest = null;
@@ -638,7 +640,7 @@
                             Threads
                             Threads
                         </td>
                         </td>
                         <td>
                         <td>
-                            <%= threads.size() %>
+                            <%= threads.length %>
                         </td>
                         </td>
                     </tr>
                     </tr>
                 </table>
                 </table>
@@ -680,6 +682,7 @@
                 </table>
                 </table>
             </div>
             </div>
             <div id="ThreadsTab" data-dojo-type="dijit.layout.ContentPane" title="Threads" class="tabContent">
             <div id="ThreadsTab" data-dojo-type="dijit.layout.ContentPane" title="Threads" class="tabContent">
+                <% if (dashboard_pwmApplication.getLocalDB() != null && dashboard_pwmRequest.readParameterAsBoolean("showThreadDetails")) { %>
                 <div style="max-height: 400px; overflow: auto;">
                 <div style="max-height: 400px; overflow: auto;">
                     <table class="nomargin">
                     <table class="nomargin">
                         <tr>
                         <tr>
@@ -689,54 +692,33 @@
                             <td style="font-weight:bold;">
                             <td style="font-weight:bold;">
                                 Name
                                 Name
                             </td>
                             </td>
-                            <td style="font-weight:bold;">
-                                Priority
-                            </td>
                             <td style="font-weight:bold;">
                             <td style="font-weight:bold;">
                                 State
                                 State
                             </td>
                             </td>
-                            <td style="font-weight:bold;">
-                                Daemon
-                            </td>
                         </tr>
                         </tr>
                         <%
                         <%
                             try {
                             try {
-                                final TreeMap<Long,Thread> sortedThreads = new TreeMap<Long, Thread>();
-                                for (final Thread t : threads.keySet()) {
-                                    sortedThreads.put(t.getId(),t);
-                                }
-
-                                for (final Thread t : sortedThreads.values()) {
+                                for (final ThreadInfo t : threads) {
                         %>
                         %>
-                        <tr id="thread_<%=t.getId()%>">
-                            <td>
-                                <%= t.getId() %>
-                            </td>
-                            <td>
-                                <%= t.getName() != null ? t.getName() : JspUtility.getMessage(pageContext, Display.Value_NotApplicable) %>
-                            </td>
+                        <tr id="thread_<%=t.getThreadId()%>">
                             <td>
                             <td>
-                                <%= t.getPriority() %>
+                                <%= t.getThreadId() %>
                             </td>
                             </td>
                             <td>
                             <td>
-                                <%= t.getState().toString().toLowerCase() %>
+                                <%= t.getThreadName() != null ? t.getThreadName() : JspUtility.getMessage(pageContext, Display.Value_NotApplicable) %>
                             </td>
                             </td>
                             <td>
                             <td>
-                                <%= String.valueOf(t.isDaemon()) %>
+                                <%= t.getThreadState().toString().toLowerCase() %>
                             </td>
                             </td>
                         </tr>
                         </tr>
                         <%
                         <%
-                            final StringBuilder threadTrace = new StringBuilder();
-                            for (final StackTraceElement traceElement : threads.get(t)) {
-                                threadTrace.append(traceElement.toString());
-                                threadTrace.append("\n");
-                            }
+                            final String threadTrace = JavaHelper.threadInfoToString(t);
                         %>
                         %>
                         <pwm:script>
                         <pwm:script>
                             <script type="application/javascript">
                             <script type="application/javascript">
                                 PWM_GLOBAL['startupFunctions'].push(function(){
                                 PWM_GLOBAL['startupFunctions'].push(function(){
-                                    PWM_MAIN.addEventHandler('thread_<%=t.getId()%>','click',function(){
-                                        PWM_MAIN.showDialog({title:'Thread <%=t.getId()%>',text:'<pre>' +'<%=StringUtil.escapeJS(threadTrace.toString())%>' + '</pre>'})
+                                    PWM_MAIN.addEventHandler('thread_<%=t.getThreadId()%>','click',function(){
+                                        PWM_MAIN.showDialog({class:'wide',title:'Thread <%=t.getThreadId()%>',text:'<pre>' +'<%=StringUtil.escapeJS(threadTrace)%>' + '</pre>'})
                                     });
                                     });
                                 });
                                 });
                             </script>
                             </script>
@@ -745,6 +727,11 @@
                         <% } catch (Exception e) { /* */ } %>
                         <% } catch (Exception e) { /* */ } %>
                     </table>
                     </table>
                 </div>
                 </div>
+                <% } else { %>
+                <div class="noborder" style="text-align:center; width:100%;">
+                    <a style="cursor: pointer" id="button-showThreadDetails">Show thread details</a> (may be slow to load)
+                </div>
+                <% } %>
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
@@ -763,6 +750,11 @@
                         PWM_MAIN.goto('dashboard?showLocalDBCounts=true');
                         PWM_MAIN.goto('dashboard?showLocalDBCounts=true');
                     }})
                     }})
                 });
                 });
+                PWM_MAIN.addEventHandler('button-showThreadDetails','click',function(){
+                    PWM_MAIN.showWaitDialog({loadFunction:function(){
+                        PWM_MAIN.goto('dashboard?showThreadDetails=true');
+                    }})
+                });
             });
             });
         });
         });
     </script>
     </script>

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

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

+ 4 - 4
src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp

@@ -161,7 +161,7 @@
                 <td class="key">
                 <td class="key">
                     Password Readable From LDAP
                     Password Readable From LDAP
                 </td>
                 </td>
-                <td id="PasswordViolatesPolicy">
+                <td>
                     <%= JspUtility.freindlyWrite(pageContext, userDebugDataBean.isPasswordReadable()) %>
                     <%= JspUtility.freindlyWrite(pageContext, userDebugDataBean.isPasswordReadable()) %>
                 </td>
                 </td>
             </tr>
             </tr>
@@ -294,7 +294,7 @@
             <% if (responseInfoBean == null) { %>
             <% if (responseInfoBean == null) { %>
             <tr>
             <tr>
                 <td>Stored Responses</td>
                 <td>Stored Responses</td>
-                <td><pwm:display key="Display_NotApplicable"/></td>
+                <td><pwm:display key="<%=Display.Value_NotApplicable.toString()%>"/></td>
             </tr>
             </tr>
             <% } else { %>
             <% } else { %>
             <tr>
             <tr>
@@ -362,7 +362,7 @@
                 <% final Map<Challenge,String> helpdeskCrMap = responseInfoBean.getHelpdeskCrMap(); %>
                 <% final Map<Challenge,String> helpdeskCrMap = responseInfoBean.getHelpdeskCrMap(); %>
                 <% if (helpdeskCrMap == null) { %>
                 <% if (helpdeskCrMap == null) { %>
                 <td>
                 <td>
-                <pwm:display key="Display_NotApplicable"/>
+                <pwm:display key="<%=Display.Value_NotApplicable.toString()%>"/>
                 </td>
                 </td>
                 <% } else { %>
                 <% } else { %>
                 <td>
                 <td>
@@ -383,7 +383,7 @@
             <% if (challengeProfile == null) { %>
             <% if (challengeProfile == null) { %>
             <tr>
             <tr>
                 <td>Assigned Profile</td>
                 <td>Assigned Profile</td>
-                <td><pwm:display key="Display_NotApplicable"/></td>
+                <td><pwm:display key="<%=Display.Value_NotApplicable.toString()%>"/></td>
             </tr>
             </tr>
             <% } else { %>
             <% } else { %>
             <tr>
             <tr>

+ 4 - 4
src/main/webapp/WEB-INF/jsp/fragment/token-form-field.jsp

@@ -1,9 +1,12 @@
+<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
+<%@ page import="password.pwm.PwmConstants" %>
+
 <%--
 <%--
   ~ Password Management Servlets (PWM)
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
   ~ http://www.pwm-project.org
   ~
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2016 The PWM Project
   ~
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
   ~ it under the terms of the GNU General Public License as published by
@@ -20,9 +23,6 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   --%>
   --%>
 
 
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
-<%@ page import="password.pwm.PwmConstants" %>
-
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <h2><label for="<%=PwmConstants.PARAM_TOKEN%>"><pwm:display key="Field_Code"/></label></h2>
 <h2><label for="<%=PwmConstants.PARAM_TOKEN%>"><pwm:display key="Field_Code"/></label></h2>

+ 4 - 2
src/main/webapp/WEB-INF/jsp/updateprofile-confirm.jsp

@@ -55,9 +55,11 @@
                         final String value = formDataMap.get(formConfiguration);
                         final String value = formDataMap.get(formConfiguration);
                         if (formConfiguration.getType() == FormConfiguration.Type.checkbox) {
                         if (formConfiguration.getType() == FormConfiguration.Type.checkbox) {
                     %>
                     %>
-                    <%= LocaleHelper.booleanString(Boolean.parseBoolean(value),pwmRequest)%>
+                    <label class="checkboxWrapper">
+                        <input id="<%=formConfiguration.getName()%>" name="<%=formConfiguration.getName()%>" disabled type="checkbox" <%=(Boolean.parseBoolean(value))?"checked":""%>/>
+                    </label>
                     <% } else { %>
                     <% } else { %>
-                    <%=StringUtil.escapeHtml(value)%>
+                    <%=StringUtil.escapeHtml(formConfiguration.displayValue(value, JspUtility.locale(request), JspUtility.getPwmRequest(pageContext).getConfig()))%>
                     <% } %>
                     <% } %>
                 </td>
                 </td>
             </tr>
             </tr>

+ 0 - 1
src/main/webapp/public/resources/js/main.js

@@ -690,7 +690,6 @@ PWM_MAIN.initLocaleSelectorMenu = function(attachNode) {
 
 
 
 
 PWM_MAIN.showErrorDialog = function(error, options) {
 PWM_MAIN.showErrorDialog = function(error, options) {
-    debugger;
     options = options === undefined ? {} : options;
     options = options === undefined ? {} : options;
     var forceReload = false;
     var forceReload = false;
     var body = '';
     var body = '';